From 0572a3299a61831dbd0a6308a4326673c9334c0f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 25 Apr 2018 13:25:34 +0200 Subject: [PATCH 001/185] First experiment with the wxCollapsiblePane --- lib/Slic3r/GUI/Plater.pm | 2 +- xs/src/slic3r/GUI/GUI.cpp | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 53176ccfe1..7227d222cc 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -403,7 +403,7 @@ sub new { } } - my $frequently_changed_parameters_sizer = Wx::BoxSizer->new(wxHORIZONTAL); + my $frequently_changed_parameters_sizer = Wx::BoxSizer->new(wxVERTICAL);#(wxHORIZONTAL); Slic3r::GUI::add_frequently_changed_parameters($self, $frequently_changed_parameters_sizer, $presets); my $object_info_sizer; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index e85112008f..beae2d524a 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -40,6 +40,8 @@ #include #include #include +#include +#include #include "wxExtensions.hpp" @@ -638,8 +640,26 @@ wxString from_u8(const std::string &str) void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) { + // Experiments with new UI + wxCollapsiblePane *collpane = new wxCollapsiblePane(parent, wxID_ANY, "Print settings:"); + collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, collpane](wxCommandEvent e){ + wxWindowUpdateLocker noUpdates(parent); + + parent->Layout(); + collpane->Refresh(); + })); + + // add the pane with a zero proportion value to the sizer which contains it + sizer->Add(collpane, 0, wxGROW | wxALL, 5); + // now add a test label in the collapsible pane using a sizer to layout it: + wxWindow *win = collpane->GetPane(); +#ifdef __WXMSW__ + collpane->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + win->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif //__WXMSW__ + DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; - m_optgroup = std::make_shared(parent, "", config); + m_optgroup = std::make_shared(/*parent*/win, "", config); // const wxArrayInt& ar = preset_sizer->GetColWidths(); // m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front(); // doesn't work m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ @@ -755,7 +775,13 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl - sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); +// sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); + + + wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); + paneSz->Add(m_optgroup->sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); + win->SetSizer(paneSz); + paneSz->SetSizeHints(win); } ConfigOptionsGroup* get_optgroup() From 993294579d93ebda300c18e0d96b902c6b846a15 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 3 May 2018 13:27:20 +0200 Subject: [PATCH 002/185] Button's border and background aren't use on MSW. --- xs/src/slic3r/GUI/GUI.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 83d1e246e1..2542c04c6e 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -813,24 +813,24 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl wxCollapsiblePane *collpane = new wxCollapsiblePane(parent, wxID_ANY, "Print settings:"); collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, collpane](wxCommandEvent e){ wxWindowUpdateLocker noUpdates(parent); - parent->Layout(); collpane->Refresh(); })); // add the pane with a zero proportion value to the sizer which contains it - sizer->Add(collpane, 0, wxGROW | wxALL, 5); - // now add a test label in the collapsible pane using a sizer to layout it: + sizer->Add(collpane, 0, wxGROW | wxALL, 0); + wxWindow *win = collpane->GetPane(); #ifdef __WXMSW__ - collpane->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - win->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + wxColour& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + collpane->GetControlWidget()->SetWindowStyleFlag(wxNO_BORDER); + collpane->GetControlWidget()->SetBackgroundColour(clr); + collpane->SetBackgroundColour(clr); + win->SetBackgroundColour(clr); #endif //__WXMSW__ DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; - m_optgroup = std::make_shared(/*parent*/win, "", config); -// const wxArrayInt& ar = preset_sizer->GetColWidths(); -// m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front(); // doesn't work + m_optgroup = std::make_shared(win, "", config); m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ TabPrint* tab_print = nullptr; for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { @@ -943,10 +943,6 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl m_optgroup->append_line(line); - -// sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); - - wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); paneSz->Add(m_optgroup->sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); win->SetSizer(paneSz); From db549e86095eace3df2befe2ccca2248a58426b0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 May 2018 18:32:20 +0200 Subject: [PATCH 003/185] First experiments with ObjectsTreeList --- lib/Slic3r/GUI/Plater.pm | 4 +- xs/src/slic3r/GUI/GUI.cpp | 136 ++++++++- xs/src/slic3r/GUI/wxExtensions.cpp | 443 +++++++++++++++++++++++++++++ xs/src/slic3r/GUI/wxExtensions.hpp | 411 ++++++++++++++++++++++++++ 4 files changed, 992 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 603d97a2ff..83a14f21a6 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -72,7 +72,7 @@ sub new { }); # Initialize preview notebook - $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM); + $self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [-1,335], wxNB_BOTTOM); # Initialize handlers for canvases my $on_select_object = sub { @@ -485,6 +485,7 @@ sub new { $buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); + $right_sizer->SetMinSize([-1, 600]); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); @@ -522,6 +523,7 @@ sub new { } $self->update_ui_from_settings(); + $self->Layout; return $self; } diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 2542c04c6e..0be77b6bac 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -812,6 +812,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl // Experiments with new UI wxCollapsiblePane *collpane = new wxCollapsiblePane(parent, wxID_ANY, "Print settings:"); collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, collpane](wxCommandEvent e){ + wxWindowUpdateLocker noUpdates_cp(collpane); wxWindowUpdateLocker noUpdates(parent); parent->Layout(); collpane->Refresh(); @@ -942,9 +943,142 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl }; m_optgroup->append_line(line); + auto common_sizer = new wxBoxSizer(wxVERTICAL); + common_sizer->Add(m_optgroup->sizer); + +// auto listctrl = new wxDataViewListCtrl(win, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); +// listctrl->AppendToggleColumn("Toggle"); +// listctrl->AppendTextColumn("Text"); +// wxVector data; +// data.push_back(wxVariant(true)); +// data.push_back(wxVariant("row 1")); +// listctrl->AppendItem(data); +// data.clear(); +// data.push_back(wxVariant(false)); +// data.push_back(wxVariant("row 3")); +// listctrl->AppendItem(data); +// data.clear(); +// data.push_back(wxVariant(false)); +// data.push_back(wxVariant("row 2")); +// listctrl->AppendItem(data); +// common_sizer->Add(listctrl, 0, wxEXPAND | wxALL, 1); + + // ********************************************************************************************** + auto objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); + wxSizer *objects_sz = new wxBoxSizer(wxVERTICAL); + objects_ctrl->SetMinSize(wxSize(-1, 200)); + objects_sz->Add(objects_ctrl, 1, wxGROW | wxALL, 5); + + auto objects_model = new MyObjectTreeModel; + objects_ctrl->AssociateModel(objects_model); +#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + objects_ctrl->EnableDragSource(wxDF_UNICODETEXT); + objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); +#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + + // column 0 of the view control: + + wxDataViewTextRenderer *tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); + wxDataViewColumn *column00 = new wxDataViewColumn("Name", tr, 0, 150, wxALIGN_LEFT, + wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + objects_ctrl->AppendColumn(column00); + + // column 1 of the view control: + + tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); + wxDataViewColumn *column01 = new wxDataViewColumn("Copy", tr, 1, 95, wxALIGN_CENTER_HORIZONTAL, + wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + objects_ctrl->AppendColumn(column01); + + // column 2 of the view control: + + tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); + wxDataViewColumn *column02 = new wxDataViewColumn("Scale", tr, 2, 95, wxALIGN_CENTER_HORIZONTAL, + wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + objects_ctrl->AppendColumn(column02); + + common_sizer->Add(objects_sz, 0, wxEXPAND | wxALL, 1); + + // ********************************************************************************************** +/* auto view_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); + wxSizer *PanelSz = new wxBoxSizer(wxVERTICAL); + view_ctrl->SetMinSize(wxSize(-1, 200)); + PanelSz->Add(view_ctrl, 1, wxGROW | wxALL, 5); + PanelSz->Add( new wxStaticText(win, wxID_ANY, "Most of the cells above are editable!"), 0, wxGROW | wxALL, 5); + + auto m_music_model = new MyMusicTreeModel; + view_ctrl->AssociateModel(m_music_model); + +#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + view_ctrl->EnableDragSource(wxDF_UNICODETEXT); + view_ctrl->EnableDropTarget(wxDF_UNICODETEXT); +#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + + // column 0 of the view control: + + tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); + wxDataViewColumn *column0 = + new wxDataViewColumn("title", tr, 0, 150, wxALIGN_LEFT, + wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + view_ctrl->AppendColumn(column0); +#if 0 + // Call this and sorting is enabled + // immediately upon start up. + column0->SetAsSortKey(); +#endif + + // column 1 of the view control: + + tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE); + wxDataViewColumn *column1 = + new wxDataViewColumn("artist", tr, 1, 150, wxALIGN_LEFT, + wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE | + wxDATAVIEW_COL_RESIZABLE); + column1->SetMinWidth(100); // this column can't be resized to be smaller + view_ctrl->AppendColumn(column1); + + // column 2 of the view control: + + wxDataViewSpinRenderer *sr = + new wxDataViewSpinRenderer(0, 2010, wxDATAVIEW_CELL_EDITABLE, + wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); + wxDataViewColumn *column2 = + new wxDataViewColumn("year", sr, 2, 60, wxALIGN_LEFT, + wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE); + view_ctrl->AppendColumn(column2); + + // column 3 of the view control: + + wxArrayString choices; + choices.Add("good"); + choices.Add("bad"); + choices.Add("lousy"); + wxDataViewChoiceRenderer *c = + new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, + wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); + wxDataViewColumn *column3 = + new wxDataViewColumn("rating", c, 3, 100, wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE); + view_ctrl->AppendColumn(column3); + + // column 4 of the view control: + + view_ctrl->AppendProgressColumn("popularity", 4, wxDATAVIEW_CELL_INERT, 80); + + // column 5 of the view control: + + MyCustomRenderer *cr = new MyCustomRenderer(wxDATAVIEW_CELL_ACTIVATABLE); + wxDataViewColumn *column5 = + new wxDataViewColumn("custom", cr, 5, -1, wxALIGN_LEFT, + wxDATAVIEW_COL_RESIZABLE); + view_ctrl->AppendColumn(column5); + + // ********************************************************************************************** + common_sizer->Add(PanelSz, 0, wxEXPAND | wxALL, 1); +*/ wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); - paneSz->Add(m_optgroup->sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); + paneSz->Add(common_sizer/*m_optgroup->sizer*/, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); win->SetSizer(paneSz); paneSz->SetSizeHints(win); } diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 8bc282474e..9d691b1aa4 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -165,3 +165,446 @@ void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& e auto selected = GetItemText(GetSelection()); cmb->SetText(selected); } + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// MyObjectTreeModel +// ---------------------------------------------------------------------------- + +MyObjectTreeModel::MyObjectTreeModel() +{ + auto root1 = new MyObjectTreeModelNode("Object1"); + m_objects.emplace(root1); + + auto root2 = new MyObjectTreeModelNode("Object2"); + m_objects.emplace(root2); + root2->Append(new MyObjectTreeModelNode(root2, "SubObject1")); + root2->Append(new MyObjectTreeModelNode(root2, "SubObject2")); + root2->Append(new MyObjectTreeModelNode(root2, "SubObject3")); + + auto root3 = new MyObjectTreeModelNode("Object3"); + m_objects.emplace(root3); + auto root4 = new MyObjectTreeModelNode("Object4"); + m_objects.emplace(root4); + root4->Append(new MyObjectTreeModelNode(root2, "SubObject1")); + root4->Append(new MyObjectTreeModelNode(root2, "SubObject2")); + root4->Append(new MyObjectTreeModelNode(root2, "SubObject3")); +} + +wxString MyObjectTreeModel::GetName(const wxDataViewItem &item) const +{ + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_name; +} + +wxString MyObjectTreeModel::GetCopyCnt(const wxDataViewItem &item) const +{ + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_copy; +} + +wxString MyObjectTreeModel::GetScale(const wxDataViewItem &item) const +{ + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_scale; +} + +// void MyObjectTreeModel::Delete(const wxDataViewItem &item) +// { +// +// } + +void MyObjectTreeModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + switch (col) + { + case 0: + variant = node->m_name; + break; + case 1: + variant = node->m_copy; + break; + case 2: + variant = node->m_scale; + break; + default: + ;// wxLogError("MyMusicTreeModel::GetValue: wrong column %d", col); + } +} + +bool MyObjectTreeModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned int col) +{ + wxASSERT(item.IsOk()); + + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + switch (col) + { + case 0: + node->m_name = variant.GetString(); + return true; + case 1: + node->m_copy = variant.GetString(); + return true; + case 2: + node->m_scale = variant.GetString(); + return true; + + default:; + // wxLogError("MyObjectTreeModel::SetValue: wrong column"); + } + return false; +} + +// bool MyObjectTreeModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const +// { +// +// } + +wxDataViewItem MyObjectTreeModel::GetParent(const wxDataViewItem &item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(0); + + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + + // objects nodes also has no parent + if (m_objects.find(node) != m_objects.end()) + return wxDataViewItem(0); + + return wxDataViewItem((void*)node->GetParent()); +} + +bool MyObjectTreeModel::IsContainer(const wxDataViewItem &item) const +{ + // the invisble root node can have children + if (!item.IsOk()) + return true; + + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + return node->IsContainer(); +} + +unsigned int MyObjectTreeModel::GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const +{ + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)parent.GetID(); + if (!node) + { + for (auto object: m_objects) + array.Add(wxDataViewItem((void*)object)); + return m_objects.size(); + } + + if (node->GetChildCount() == 0) + { + return 0; + } + + unsigned int count = node->GetChildren().GetCount(); + for (unsigned int pos = 0; pos < count; pos++) + { + MyObjectTreeModelNode *child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + + return count; +} + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// MyMusicTreeModel +// ---------------------------------------------------------------------------- + +MyMusicTreeModel::MyMusicTreeModel() +{ + m_root = new MyMusicTreeModelNode(NULL, "");// , "My Music"); + + // setup pop music + m_pop = new MyMusicTreeModelNode(m_root, "Pop music"); + m_pop->Append( + new MyMusicTreeModelNode(m_pop, "You are not alone", "Michael Jackson", 1995)); + m_pop->Append( + new MyMusicTreeModelNode(m_pop, "Take a bow", "Madonna", 1994)); + m_root->Append(m_pop); + + // setup classical music + m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); + m_ninth = new MyMusicTreeModelNode(m_classical, "Ninth symphony", + "Ludwig van Beethoven", 1824); + m_classical->Append(m_ninth); + m_classical->Append(new MyMusicTreeModelNode(m_classical, "German Requiem", + "Johannes Brahms", 1868)); + m_root->Append(m_classical); + + m_classicalMusicIsKnownToControl = false; +} + +wxString MyMusicTreeModel::GetTitle(const wxDataViewItem &item) const +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_title; +} + +wxString MyMusicTreeModel::GetArtist(const wxDataViewItem &item) const +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_artist; +} + +int MyMusicTreeModel::GetYear(const wxDataViewItem &item) const +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return 2000; + + return node->m_year; +} + +void MyMusicTreeModel::AddToClassical(const wxString &title, const wxString &artist, + unsigned int year) +{ + if (!m_classical) + { + wxASSERT(m_root); + + // it was removed: restore it + m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); + m_root->Append(m_classical); + + // notify control + wxDataViewItem child((void*)m_classical); + wxDataViewItem parent((void*)m_root); + ItemAdded(parent, child); + } + + // add to the classical music node a new node: + MyMusicTreeModelNode *child_node = + new MyMusicTreeModelNode(m_classical, title, artist, year); + m_classical->Append(child_node); + + // FIXME: what's m_classicalMusicIsKnownToControl for? + if (m_classicalMusicIsKnownToControl) + { + // notify control + wxDataViewItem child((void*)child_node); + wxDataViewItem parent((void*)m_classical); + ItemAdded(parent, child); + } +} + +void MyMusicTreeModel::Delete(const wxDataViewItem &item) +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return; + + wxDataViewItem parent(node->GetParent()); + if (!parent.IsOk()) + { + wxASSERT(node == m_root); + + // don't make the control completely empty: + //wxLogError("Cannot remove the root item!"); + return; + } + + // is the node one of those we keep stored in special pointers? + if (node == m_pop) + m_pop = NULL; + else if (node == m_classical) + m_classical = NULL; + else if (node == m_ninth) + m_ninth = NULL; + + // first remove the node from the parent's array of children; + // NOTE: MyMusicTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + node->GetParent()->GetChildren().Remove(node); + + // free the node + delete node; + + // notify control + ItemDeleted(parent, item); +} + +int MyMusicTreeModel::Compare(const wxDataViewItem &item1, const wxDataViewItem &item2, + unsigned int column, bool ascending) const +{ + wxASSERT(item1.IsOk() && item2.IsOk()); + // should never happen + + if (IsContainer(item1) && IsContainer(item2)) + { + wxVariant value1, value2; + GetValue(value1, item1, 0); + GetValue(value2, item2, 0); + + wxString str1 = value1.GetString(); + wxString str2 = value2.GetString(); + int res = str1.Cmp(str2); + if (res) return res; + + // items must be different + wxUIntPtr litem1 = (wxUIntPtr)item1.GetID(); + wxUIntPtr litem2 = (wxUIntPtr)item2.GetID(); + + return litem1 - litem2; + } + + return wxDataViewModel::Compare(item1, item2, column, ascending); +} + +void MyMusicTreeModel::GetValue(wxVariant &variant, + const wxDataViewItem &item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + switch (col) + { + case 0: + variant = node->m_title; + break; + case 1: + variant = node->m_artist; + break; + case 2: + variant = (long)node->m_year; + break; + case 3: + variant = node->m_quality; + break; + case 4: + variant = 80L; // all music is very 80% popular + break; + case 5: + if (GetYear(item) < 1900) + variant = "old"; + else + variant = "new"; + break; + + default: + ;// wxLogError("MyMusicTreeModel::GetValue: wrong column %d", col); + } +} + +bool MyMusicTreeModel::SetValue(const wxVariant &variant, + const wxDataViewItem &item, unsigned int col) +{ + wxASSERT(item.IsOk()); + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + switch (col) + { + case 0: + node->m_title = variant.GetString(); + return true; + case 1: + node->m_artist = variant.GetString(); + return true; + case 2: + node->m_year = variant.GetLong(); + return true; + case 3: + node->m_quality = variant.GetString(); + return true; + + default:; +// wxLogError("MyMusicTreeModel::SetValue: wrong column"); + } + return false; +} + +bool MyMusicTreeModel::IsEnabled(const wxDataViewItem &item, + unsigned int col) const +{ + wxASSERT(item.IsOk()); + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + + // disable Beethoven's ratings, his pieces can only be good + return !(col == 3 && node->m_artist.EndsWith("Beethoven")); +} + +wxDataViewItem MyMusicTreeModel::GetParent(const wxDataViewItem &item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(0); + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + + // "MyMusic" also has no parent + if (node == m_root) + return wxDataViewItem(0); + + return wxDataViewItem((void*)node->GetParent()); +} + +bool MyMusicTreeModel::IsContainer(const wxDataViewItem &item) const +{ + // the invisble root node can have children + // (in our model always "MyMusic") + if (!item.IsOk()) + return true; + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + return node->IsContainer(); +} + +unsigned int MyMusicTreeModel::GetChildren(const wxDataViewItem &parent, + wxDataViewItemArray &array) const +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)parent.GetID(); + if (!node) + { + array.Add(wxDataViewItem((void*)m_root)); + return 1; + } + + if (node == m_classical) + { + MyMusicTreeModel *model = (MyMusicTreeModel*)(const MyMusicTreeModel*) this; + model->m_classicalMusicIsKnownToControl = true; + } + + if (node->GetChildCount() == 0) + { + return 0; + } + + unsigned int count = node->GetChildren().GetCount(); + for (unsigned int pos = 0; pos < count; pos++) + { + MyMusicTreeModelNode *child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + + return count; +} + + +// ***************************************************************************** + + + diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index ed8bb9276a..0ed27ab74f 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup { @@ -50,4 +53,412 @@ public: void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; } }; +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// MyObjectTreeModelNode: a node inside MyObjectTreeModel +// ---------------------------------------------------------------------------- + +class MyObjectTreeModelNode; +WX_DEFINE_ARRAY_PTR(MyObjectTreeModelNode*, MyObjectTreeModelNodePtrArray); + +class MyObjectTreeModelNode +{ + MyObjectTreeModelNode* m_parent; + MyObjectTreeModelNodePtrArray m_children; +public: + MyObjectTreeModelNode( const wxString &name) { + m_parent = NULL; + m_name = name; + m_copy = "1"; + m_scale = "100%"; + } + + MyObjectTreeModelNode( MyObjectTreeModelNode* parent, + const wxString& sub_obj) { + m_parent = parent; + m_name = sub_obj; + m_copy = wxEmptyString; + m_scale = wxEmptyString; + } + + ~MyObjectTreeModelNode() + { + // free all our children nodes + size_t count = m_children.GetCount(); + for (size_t i = 0; i < count; i++) + { + MyObjectTreeModelNode *child = m_children[i]; + delete child; + } + } + + wxString m_name; + wxString m_copy; + wxString m_scale; + bool m_container = false; + + bool IsContainer() const + { + return m_container; + } + + MyObjectTreeModelNode* GetParent() + { + return m_parent; + } + MyObjectTreeModelNodePtrArray& GetChildren() + { + return m_children; + } + MyObjectTreeModelNode* GetNthChild(unsigned int n) + { + return m_children.Item(n); + } + void Insert(MyObjectTreeModelNode* child, unsigned int n) + { + m_children.Insert(child, n); + } + void Append(MyObjectTreeModelNode* child) + { + if (!m_container) + m_container = true; + m_children.Add(child); + } + unsigned int GetChildCount() const + { + return m_children.GetCount(); + } +}; + +// ---------------------------------------------------------------------------- +// MyObjectTreeModel +// ---------------------------------------------------------------------------- + +class MyObjectTreeModel :public wxDataViewModel +{ + std::set m_objects; +public: + MyObjectTreeModel(); + ~MyObjectTreeModel() + { + for (auto object : m_objects) + delete object; + } + + // helper method for wxLog + + wxString GetName(const wxDataViewItem &item) const; + wxString GetCopyCnt(const wxDataViewItem &item) const; + wxString GetScale(const wxDataViewItem &item) const; + + // helper methods to change the model + +// void AddToClassical(const wxString &title, const wxString &artist, +// unsigned int year); +// void Delete(const wxDataViewItem &item); + + virtual unsigned int GetColumnCount() const override { return 3;} + virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } + + virtual void GetValue(wxVariant &variant, + const wxDataViewItem &item, unsigned int col) const override; + virtual bool SetValue(const wxVariant &variant, + const wxDataViewItem &item, unsigned int col) override; + +// virtual bool IsEnabled(const wxDataViewItem &item, +// unsigned int col) const override; + + virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; + virtual bool IsContainer(const wxDataViewItem &item) const override; + virtual unsigned int GetChildren(const wxDataViewItem &parent, + wxDataViewItemArray &array) const override; +}; + + + + +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// MyMusicTreeModelNode: a node inside MyMusicTreeModel +// ---------------------------------------------------------------------------- + +class MyMusicTreeModelNode; +WX_DEFINE_ARRAY_PTR(MyMusicTreeModelNode*, MyMusicTreeModelNodePtrArray); + +class MyMusicTreeModelNode +{ +public: + MyMusicTreeModelNode(MyMusicTreeModelNode* parent, + const wxString &title, const wxString &artist, + unsigned int year) + { + m_parent = parent; + + m_title = title; + m_artist = artist; + m_year = year; + m_quality = "good"; + + m_container = false; + } + + MyMusicTreeModelNode(MyMusicTreeModelNode* parent, + const wxString &branch) + { + m_parent = parent; + + m_title = branch; + m_year = -1; + + m_container = true; + } + + ~MyMusicTreeModelNode() + { + // free all our children nodes + size_t count = m_children.GetCount(); + for (size_t i = 0; i < count; i++) + { + MyMusicTreeModelNode *child = m_children[i]; + delete child; + } + } + + bool IsContainer() const + { + return m_container; + } + + MyMusicTreeModelNode* GetParent() + { + return m_parent; + } + MyMusicTreeModelNodePtrArray& GetChildren() + { + return m_children; + } + MyMusicTreeModelNode* GetNthChild(unsigned int n) + { + return m_children.Item(n); + } + void Insert(MyMusicTreeModelNode* child, unsigned int n) + { + m_children.Insert(child, n); + } + void Append(MyMusicTreeModelNode* child) + { + m_children.Add(child); + } + unsigned int GetChildCount() const + { + return m_children.GetCount(); + } + +public: // public to avoid getters/setters + wxString m_title; + wxString m_artist; + int m_year; + wxString m_quality; + + // TODO/FIXME: + // the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded) + // needs to know in advance if a node is or _will be_ a container. + // Thus implementing: + // bool IsContainer() const + // { return m_children.GetCount()>0; } + // doesn't work with wxGTK when MyMusicTreeModel::AddToClassical is called + // AND the classical node was removed (a new node temporary without children + // would be added to the control) + bool m_container; + +private: + MyMusicTreeModelNode *m_parent; + MyMusicTreeModelNodePtrArray m_children; +}; + + +// ---------------------------------------------------------------------------- +// MyMusicTreeModel +// ---------------------------------------------------------------------------- + +/* +Implement this data model +Title Artist Year Judgement +-------------------------------------------------------------------------- +1: My Music: +2: Pop music +3: You are not alone Michael Jackson 1995 good +4: Take a bow Madonna 1994 good +5: Classical music +6: Ninth Symphony Ludwig v. Beethoven 1824 good +7: German Requiem Johannes Brahms 1868 good +*/ + +class MyMusicTreeModel : public wxDataViewModel +{ +public: + MyMusicTreeModel(); + ~MyMusicTreeModel() + { + if (m_root) + delete m_root; + + } + + // helper method for wxLog + + wxString GetTitle(const wxDataViewItem &item) const; + wxString GetArtist(const wxDataViewItem &item) const; + int GetYear(const wxDataViewItem &item) const; + + // helper methods to change the model + + void AddToClassical(const wxString &title, const wxString &artist, + unsigned int year); + void Delete(const wxDataViewItem &item); + + wxDataViewItem GetNinthItem() const + { + return wxDataViewItem(m_ninth); + } + + // override sorting to always sort branches ascendingly + + int Compare(const wxDataViewItem &item1, const wxDataViewItem &item2, + unsigned int column, bool ascending) const override/*wxOVERRIDE*/; + + // implementation of base class virtuals to define model + + virtual unsigned int GetColumnCount() const override/*wxOVERRIDE*/ + { + return 6; + } + + virtual wxString GetColumnType(unsigned int col) const override/*wxOVERRIDE*/ + { + if (col == 2) + return wxT("long"); + + return wxT("string"); + } + + virtual void GetValue(wxVariant &variant, + const wxDataViewItem &item, unsigned int col) const override/*wxOVERRIDE*/; + virtual bool SetValue(const wxVariant &variant, + const wxDataViewItem &item, unsigned int col) override/*wxOVERRIDE*/; + + virtual bool IsEnabled(const wxDataViewItem &item, + unsigned int col) const override/*wxOVERRIDE*/; + + virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override/*wxOVERRIDE*/; + virtual bool IsContainer(const wxDataViewItem &item) const override/*wxOVERRIDE*/; + virtual unsigned int GetChildren(const wxDataViewItem &parent, + wxDataViewItemArray &array) const override/*wxOVERRIDE*/; + +private: + MyMusicTreeModelNode* m_root; + + // pointers to some "special" nodes of the tree: + MyMusicTreeModelNode* m_pop; + MyMusicTreeModelNode* m_classical; + MyMusicTreeModelNode* m_ninth; + + // ?? + bool m_classicalMusicIsKnownToControl; +}; + +// ---------------------------------------------------------------------------- +// MyCustomRenderer +// ---------------------------------------------------------------------------- + +class MyCustomRenderer : public wxDataViewCustomRenderer +{ +public: + // This renderer can be either activatable or editable, for demonstration + // purposes. In real programs, you should select whether the user should be + // able to activate or edit the cell and it doesn't make sense to switch + // between the two -- but this is just an example, so it doesn't stop us. + explicit MyCustomRenderer(wxDataViewCellMode mode) + : wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER) + { } + + virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/ + { + dc->SetBrush(*wxLIGHT_GREY_BRUSH); + dc->SetPen(*wxTRANSPARENT_PEN); + + rect.Deflate(2); + dc->DrawRoundedRectangle(rect, 5); + + RenderText(m_value, + 0, // no offset + wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), + dc, + state); + return true; + } + + virtual bool ActivateCell(const wxRect& WXUNUSED(cell), + wxDataViewModel *WXUNUSED(model), + const wxDataViewItem &WXUNUSED(item), + unsigned int WXUNUSED(col), + const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/ + { + wxString position; + if (mouseEvent) + position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y); + else + position = "from keyboard"; +// wxLogMessage("MyCustomRenderer ActivateCell() %s", position); + return false; + } + + virtual wxSize GetSize() const override/*wxOVERRIDE*/ + { + return wxSize(60, 20); + } + + virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/ + { + m_value = value.GetString(); + return true; + } + + virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; } + + virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; } + + virtual wxWindow* + CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) override/*wxOVERRIDE*/ + { + wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value, + labelRect.GetPosition(), + labelRect.GetSize(), + wxTE_PROCESS_ENTER); + text->SetInsertionPointEnd(); + + return text; + } + + virtual bool + GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/ + { + wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl); + if (!text) + return false; + + value = text->GetValue(); + + return true; + } + +private: + wxString m_value; +}; +// ***************************************************************************** + + + #endif // slic3r_GUI_wxExtensions_hpp_ From c07f347ff6cc952cf7cd8853cb89c6b5984d3272 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 9 May 2018 14:36:20 +0200 Subject: [PATCH 004/185] CollapsiblePanes are putted to ScrolledWindow --- xs/src/slic3r/GUI/GUI.cpp | 148 +++++++++++------------------ xs/src/slic3r/GUI/wxExtensions.cpp | 12 +-- xs/src/slic3r/GUI/wxExtensions.hpp | 6 +- 3 files changed, 65 insertions(+), 101 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 0be77b6bac..0e2893b574 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -809,17 +809,25 @@ wxString from_u8(const std::string &str) void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) { + sizer->SetMinSize(-1, 300); + auto main_sizer = new wxBoxSizer(wxVERTICAL); + auto main_page = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + main_page->SetSizer(main_sizer); + main_page->SetScrollbars(1, 1, 1, 1); + sizer->Add(main_page, 1, wxEXPAND | wxALL, 1); + // Experiments with new UI - wxCollapsiblePane *collpane = new wxCollapsiblePane(parent, wxID_ANY, "Print settings:"); - collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, collpane](wxCommandEvent e){ + wxCollapsiblePane *collpane = new wxCollapsiblePane(main_page, wxID_ANY, "Frequently Changing Parameters:"); + collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, main_page, collpane](wxCommandEvent e){ wxWindowUpdateLocker noUpdates_cp(collpane); - wxWindowUpdateLocker noUpdates(parent); - parent->Layout(); + wxWindowUpdateLocker noUpdates(main_page); + parent->Layout(); + main_page->Layout(); collpane->Refresh(); })); // add the pane with a zero proportion value to the sizer which contains it - sizer->Add(collpane, 0, wxGROW | wxALL, 0); + main_sizer->Add(collpane, 0, wxGROW | wxALL, 0); wxWindow *win = collpane->GetPane(); #ifdef __WXMSW__ @@ -943,8 +951,36 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl }; m_optgroup->append_line(line); - auto common_sizer = new wxBoxSizer(wxVERTICAL); - common_sizer->Add(m_optgroup->sizer); + wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); + paneSz->Add(m_optgroup->sizer, 1, wxGROW | wxEXPAND | /*wxBOTTOM*/wxALL, /*2*/5); + win->SetSizer(paneSz); + paneSz->SetSizeHints(win); + + + + + wxCollapsiblePane *collpane_objects = new wxCollapsiblePane(main_page, wxID_ANY, "Objects List:"); + collpane_objects->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, main_page, collpane_objects](wxCommandEvent e){ + wxWindowUpdateLocker noUpdates_cp(collpane_objects); + wxWindowUpdateLocker noUpdates(main_page); + parent->Layout(); + main_page->Layout(); + collpane_objects->Refresh(); + })); + + // add the pane with a zero proportion value to the sizer which contains it + main_sizer->Add(collpane_objects, 0, wxGROW | wxALL, 0); + + wxWindow *win_objects = collpane_objects->GetPane(); +#ifdef __WXMSW__ + collpane_objects->GetControlWidget()->SetWindowStyleFlag(wxNO_BORDER); + collpane_objects->GetControlWidget()->SetBackgroundColour(clr); + collpane_objects->SetBackgroundColour(clr); + win_objects->SetBackgroundColour(clr); +#endif //__WXMSW__ + +// auto common_sizer = new wxBoxSizer(wxVERTICAL); +// common_sizer->Add(m_optgroup->sizer); // auto listctrl = new wxDataViewListCtrl(win, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); // listctrl->AppendToggleColumn("Toggle"); @@ -964,9 +1000,10 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl // common_sizer->Add(listctrl, 0, wxEXPAND | wxALL, 1); // ********************************************************************************************** - auto objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); + auto objects_ctrl = new wxDataViewCtrl(win_objects, wxID_ANY, wxDefaultPosition, wxDefaultSize); wxSizer *objects_sz = new wxBoxSizer(wxVERTICAL); - objects_ctrl->SetMinSize(wxSize(-1, 200)); + objects_ctrl->SetBestFittingSize(wxSize(-1, 200)); +// objects_ctrl->SetMinSize(wxSize(-1, 200)); objects_sz->Add(objects_ctrl, 1, wxGROW | wxALL, 5); auto objects_model = new MyObjectTreeModel; @@ -979,108 +1016,31 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl // column 0 of the view control: wxDataViewTextRenderer *tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); - wxDataViewColumn *column00 = new wxDataViewColumn("Name", tr, 0, 150, wxALIGN_LEFT, + wxDataViewColumn *column00 = new wxDataViewColumn("Name", tr, 0, 140, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); objects_ctrl->AppendColumn(column00); // column 1 of the view control: tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); - wxDataViewColumn *column01 = new wxDataViewColumn("Copy", tr, 1, 95, wxALIGN_CENTER_HORIZONTAL, + wxDataViewColumn *column01 = new wxDataViewColumn("Copy", tr, 1, 75, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); objects_ctrl->AppendColumn(column01); // column 2 of the view control: tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); - wxDataViewColumn *column02 = new wxDataViewColumn("Scale", tr, 2, 95, wxALIGN_CENTER_HORIZONTAL, + wxDataViewColumn *column02 = new wxDataViewColumn("Scale", tr, 2, 80, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); objects_ctrl->AppendColumn(column02); - common_sizer->Add(objects_sz, 0, wxEXPAND | wxALL, 1); +// common_sizer->Add(objects_sz, 0, wxEXPAND | wxALL, 1); - // ********************************************************************************************** -/* auto view_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); - wxSizer *PanelSz = new wxBoxSizer(wxVERTICAL); - view_ctrl->SetMinSize(wxSize(-1, 200)); - PanelSz->Add(view_ctrl, 1, wxGROW | wxALL, 5); - PanelSz->Add( new wxStaticText(win, wxID_ANY, "Most of the cells above are editable!"), 0, wxGROW | wxALL, 5); - - auto m_music_model = new MyMusicTreeModel; - view_ctrl->AssociateModel(m_music_model); + wxSizer *paneSz_objects = new wxBoxSizer(wxVERTICAL); + paneSz_objects->Add(objects_sz, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); + win_objects->SetSizer(paneSz_objects); + paneSz_objects->SetSizeHints(win_objects); -#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - view_ctrl->EnableDragSource(wxDF_UNICODETEXT); - view_ctrl->EnableDropTarget(wxDF_UNICODETEXT); -#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - - // column 0 of the view control: - - tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); - wxDataViewColumn *column0 = - new wxDataViewColumn("title", tr, 0, 150, wxALIGN_LEFT, - wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - view_ctrl->AppendColumn(column0); -#if 0 - // Call this and sorting is enabled - // immediately upon start up. - column0->SetAsSortKey(); -#endif - - // column 1 of the view control: - - tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE); - wxDataViewColumn *column1 = - new wxDataViewColumn("artist", tr, 1, 150, wxALIGN_LEFT, - wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE | - wxDATAVIEW_COL_RESIZABLE); - column1->SetMinWidth(100); // this column can't be resized to be smaller - view_ctrl->AppendColumn(column1); - - // column 2 of the view control: - - wxDataViewSpinRenderer *sr = - new wxDataViewSpinRenderer(0, 2010, wxDATAVIEW_CELL_EDITABLE, - wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); - wxDataViewColumn *column2 = - new wxDataViewColumn("year", sr, 2, 60, wxALIGN_LEFT, - wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE); - view_ctrl->AppendColumn(column2); - - // column 3 of the view control: - - wxArrayString choices; - choices.Add("good"); - choices.Add("bad"); - choices.Add("lousy"); - wxDataViewChoiceRenderer *c = - new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, - wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); - wxDataViewColumn *column3 = - new wxDataViewColumn("rating", c, 3, 100, wxALIGN_LEFT, - wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE); - view_ctrl->AppendColumn(column3); - - // column 4 of the view control: - - view_ctrl->AppendProgressColumn("popularity", 4, wxDATAVIEW_CELL_INERT, 80); - - // column 5 of the view control: - - MyCustomRenderer *cr = new MyCustomRenderer(wxDATAVIEW_CELL_ACTIVATABLE); - wxDataViewColumn *column5 = - new wxDataViewColumn("custom", cr, 5, -1, wxALIGN_LEFT, - wxDATAVIEW_COL_RESIZABLE); - view_ctrl->AppendColumn(column5); - - // ********************************************************************************************** - common_sizer->Add(PanelSz, 0, wxEXPAND | wxALL, 1); -*/ - - wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); - paneSz->Add(common_sizer/*m_optgroup->sizer*/, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); - win->SetSizer(paneSz); - paneSz->SetSizeHints(win); } ConfigOptionsGroup* get_optgroup() diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 9d691b1aa4..0fb335118a 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -186,9 +186,9 @@ MyObjectTreeModel::MyObjectTreeModel() m_objects.emplace(root3); auto root4 = new MyObjectTreeModelNode("Object4"); m_objects.emplace(root4); - root4->Append(new MyObjectTreeModelNode(root2, "SubObject1")); - root4->Append(new MyObjectTreeModelNode(root2, "SubObject2")); - root4->Append(new MyObjectTreeModelNode(root2, "SubObject3")); + root4->Append(new MyObjectTreeModelNode(root4, "SubObject1")); + root4->Append(new MyObjectTreeModelNode(root4, "SubObject2")); + root4->Append(new MyObjectTreeModelNode(root4, "SubObject3")); } wxString MyObjectTreeModel::GetName(const wxDataViewItem &item) const @@ -200,7 +200,7 @@ wxString MyObjectTreeModel::GetName(const wxDataViewItem &item) const return node->m_name; } -wxString MyObjectTreeModel::GetCopyCnt(const wxDataViewItem &item) const +wxString MyObjectTreeModel::GetCopy(const wxDataViewItem &item) const { MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); if (!node) // happens if item.IsOk()==false @@ -302,8 +302,8 @@ unsigned int MyObjectTreeModel::GetChildren(const wxDataViewItem &parent, wxData MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)parent.GetID(); if (!node) { - for (auto object: m_objects) - array.Add(wxDataViewItem((void*)object)); + for (auto object : m_objects) + array.Add(wxDataViewItem((void*)object)); return m_objects.size(); } diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 0ed27ab74f..6ebdbe8b20 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -148,7 +148,7 @@ public: // helper method for wxLog wxString GetName(const wxDataViewItem &item) const; - wxString GetCopyCnt(const wxDataViewItem &item) const; + wxString GetCopy(const wxDataViewItem &item) const; wxString GetScale(const wxDataViewItem &item) const; // helper methods to change the model @@ -172,6 +172,10 @@ public: virtual bool IsContainer(const wxDataViewItem &item) const override; virtual unsigned int GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const override; + + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } }; From 2e7d623ee40489d437badee28c4b52ec62eaceed Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 May 2018 16:36:12 +0200 Subject: [PATCH 005/185] Created PrusaCollapsiblePane for CollapsiblePane view with disclosure triangles --- resources/icons/disclosure_triangle_close.png | Bin 0 -> 212 bytes resources/icons/disclosure_triangle_open.png | Bin 0 -> 210 bytes xs/src/slic3r/GUI/GUI.cpp | 83 ++++------- xs/src/slic3r/GUI/wxExtensions.cpp | 133 +++++++++++++++++- xs/src/slic3r/GUI/wxExtensions.hpp | 61 ++++++++ 5 files changed, 218 insertions(+), 59 deletions(-) create mode 100644 resources/icons/disclosure_triangle_close.png create mode 100644 resources/icons/disclosure_triangle_open.png diff --git a/resources/icons/disclosure_triangle_close.png b/resources/icons/disclosure_triangle_close.png new file mode 100644 index 0000000000000000000000000000000000000000..0660422c7f53ee6bc352d6ce790d653214b523e0 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!AwsV#}EtuWQjQo_U)UOd39CjnE<`Y1$*|` zaGp}(4+#s~$0~J+;prs42DTceNBSw!Or{U)Z?-8&GMKS+tWG;O=OouuHJ%lj2c~y* zbyck}_{fmOP&vzyVZ~3~o3k7n7PYucUE-sD-~ab%7 literal 0 HcmV?d00001 diff --git a/resources/icons/disclosure_triangle_open.png b/resources/icons/disclosure_triangle_open.png new file mode 100644 index 0000000000000000000000000000000000000000..81112f2a2610371930c4be6fcb173a2472c5e298 GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!E{d-#}EtuWC_*~makvG`UI#|CR|+=dgkfV zr=FssqVF4i2=nnw@@84tnsu1Pf%WUusZ;Ma{;-!~>%8hX^&xwJ^FD=4wvC;ad6+%g zCajZWYmi#qz!vJXb4l}-Ehb?CZAI>kVFwN{Fl?CZW##C;`XA6r22WQ%mvv4FO#qAdd(main_page, 1, wxEXPAND | wxALL, 1); // Experiments with new UI - wxCollapsiblePane *collpane = new wxCollapsiblePane(main_page, wxID_ANY, "Frequently Changing Parameters:"); - collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, main_page, collpane](wxCommandEvent e){ - wxWindowUpdateLocker noUpdates_cp(collpane); - wxWindowUpdateLocker noUpdates(main_page); - parent->Layout(); - main_page->Layout(); - collpane->Refresh(); - })); + // *** Frequently Changing Parameters *** + auto* collpane = new PrusaCollapsiblePane(main_page, wxID_ANY, "Frequently Changing Parameters:"); // add the pane with a zero proportion value to the sizer which contains it main_sizer->Add(collpane, 0, wxGROW | wxALL, 0); wxWindow *win = collpane->GetPane(); -#ifdef __WXMSW__ - wxColour& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - collpane->GetControlWidget()->SetWindowStyleFlag(wxNO_BORDER); - collpane->GetControlWidget()->SetBackgroundColour(clr); - collpane->SetBackgroundColour(clr); - win->SetBackgroundColour(clr); -#endif //__WXMSW__ DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; m_optgroup = std::make_shared(win, "", config); @@ -952,57 +939,24 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl m_optgroup->append_line(line); wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); - paneSz->Add(m_optgroup->sizer, 1, wxGROW | wxEXPAND | /*wxBOTTOM*/wxALL, /*2*/5); + paneSz->Add(m_optgroup->sizer, 1, wxGROW | wxEXPAND | wxLEFT | wxRIGHT, 5); win->SetSizer(paneSz); paneSz->SetSizeHints(win); - - wxCollapsiblePane *collpane_objects = new wxCollapsiblePane(main_page, wxID_ANY, "Objects List:"); - collpane_objects->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, main_page, collpane_objects](wxCommandEvent e){ - wxWindowUpdateLocker noUpdates_cp(collpane_objects); - wxWindowUpdateLocker noUpdates(main_page); - parent->Layout(); - main_page->Layout(); - collpane_objects->Refresh(); - })); - + // *** Objects List *** + auto *collpane_objects = new PrusaCollapsiblePane(main_page, wxID_ANY, "Objects List:"); // add the pane with a zero proportion value to the sizer which contains it main_sizer->Add(collpane_objects, 0, wxGROW | wxALL, 0); wxWindow *win_objects = collpane_objects->GetPane(); -#ifdef __WXMSW__ - collpane_objects->GetControlWidget()->SetWindowStyleFlag(wxNO_BORDER); - collpane_objects->GetControlWidget()->SetBackgroundColour(clr); - collpane_objects->SetBackgroundColour(clr); - win_objects->SetBackgroundColour(clr); -#endif //__WXMSW__ - -// auto common_sizer = new wxBoxSizer(wxVERTICAL); -// common_sizer->Add(m_optgroup->sizer); - -// auto listctrl = new wxDataViewListCtrl(win, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); -// listctrl->AppendToggleColumn("Toggle"); -// listctrl->AppendTextColumn("Text"); -// wxVector data; -// data.push_back(wxVariant(true)); -// data.push_back(wxVariant("row 1")); -// listctrl->AppendItem(data); -// data.clear(); -// data.push_back(wxVariant(false)); -// data.push_back(wxVariant("row 3")); -// listctrl->AppendItem(data); -// data.clear(); -// data.push_back(wxVariant(false)); -// data.push_back(wxVariant("row 2")); -// listctrl->AppendItem(data); -// common_sizer->Add(listctrl, 0, wxEXPAND | wxALL, 1); // ********************************************************************************************** auto objects_ctrl = new wxDataViewCtrl(win_objects, wxID_ANY, wxDefaultPosition, wxDefaultSize); wxSizer *objects_sz = new wxBoxSizer(wxVERTICAL); - objects_ctrl->SetBestFittingSize(wxSize(-1, 200)); + objects_ctrl->SetBestFittingSize(wxSize(-1, 200)); + // TODO - Set correct height according to the opened/closed objects // objects_ctrl->SetMinSize(wxSize(-1, 200)); objects_sz->Add(objects_ctrl, 1, wxGROW | wxALL, 5); @@ -1041,6 +995,29 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl win_objects->SetSizer(paneSz_objects); paneSz_objects->SetSizeHints(win_objects); + + +// auto common_sizer = new wxBoxSizer(wxVERTICAL); +// common_sizer->Add(m_optgroup->sizer); + +// auto listctrl = new wxDataViewListCtrl(win, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); +// listctrl->AppendToggleColumn("Toggle"); +// listctrl->AppendTextColumn("Text"); +// wxVector data; +// data.push_back(wxVariant(true)); +// data.push_back(wxVariant("row 1")); +// listctrl->AppendItem(data); +// data.clear(); +// data.push_back(wxVariant(false)); +// data.push_back(wxVariant("row 3")); +// listctrl->AppendItem(data); +// data.clear(); +// data.push_back(wxVariant(false)); +// data.push_back(wxVariant("row 2")); +// listctrl->AppendItem(data); +// common_sizer->Add(listctrl, 0, wxEXPAND | wxALL, 1); + + } ConfigOptionsGroup* get_optgroup() diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 0fb335118a..03d6bdaef9 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -1,5 +1,11 @@ #include "wxExtensions.hpp" +#include "GUI.hpp" +#include "../../libslic3r/Utils.hpp" + +#include +#include + const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200; const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200; const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18; @@ -166,6 +172,119 @@ void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& e cmb->SetText(selected); } +// *** PrusaCollapsiblePane *** +// ---------------------------------------------------------------------------- +bool PrusaCollapsiblePane::Create(wxWindow *parent, wxWindowID id, const wxString& label, + const wxPoint& pos, const wxSize& size, long style, const wxValidator& val, const wxString& name) +{ + if (!wxControl::Create(parent, id, pos, size, style, val, name)) + return false; + m_pStaticLine = NULL; + m_strLabel = label; + + // sizer containing the expand button and possibly a static line + m_sz = new wxBoxSizer(wxHORIZONTAL); + + m_bmp_close.LoadFile(Slic3r::GUI::from_u8(Slic3r::var("disclosure_triangle_close.png")), wxBITMAP_TYPE_PNG); + m_bmp_open.LoadFile(Slic3r::GUI::from_u8(Slic3r::var("disclosure_triangle_open.png")), wxBITMAP_TYPE_PNG); + + m_pDisclosureTriangleButton = new wxButton(this, wxID_ANY, m_strLabel, wxPoint(0, 0), + wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_pDisclosureTriangleButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) + { + if (event.GetEventObject() != m_pDisclosureTriangleButton) + { + event.Skip(); + return; + } + + Collapse(!IsCollapsed()); + + // this change was generated by the user - send the event + wxCollapsiblePaneEvent ev(this, GetId(), IsCollapsed()); + GetEventHandler()->ProcessEvent(ev); + }); + + UpdateBtnBmp(); + + m_sz->Add(m_pDisclosureTriangleButton, 0, wxLEFT | wxTOP | wxBOTTOM, GetBorder()); + + // do not set sz as our sizers since we handle the pane window without using sizers + m_pPane = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, + wxTAB_TRAVERSAL | wxNO_BORDER, wxT("wxCollapsiblePanePane")); + + wxColour& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + m_pDisclosureTriangleButton->SetBackgroundColour(clr); + this->SetBackgroundColour(clr); + m_pPane->SetBackgroundColour(clr); + + // start as collapsed: + m_pPane->Hide(); + + return true; +} + +void PrusaCollapsiblePane::UpdateBtnBmp() +{ + IsCollapsed() ? + m_pDisclosureTriangleButton->SetBitmap(m_bmp_close) : + m_pDisclosureTriangleButton->SetBitmap(m_bmp_open); + Layout(); +// m_pDisclosureTriangleButton->Refresh(); +} + +void PrusaCollapsiblePane::Collapse(bool collapse) +{ + // optimization + if (IsCollapsed() == collapse) + return; + + InvalidateBestSize(); + + // update our state + m_pPane->Show(!collapse); + + // update button label +// m_pDisclosureTriangleButton->SetLabel(m_strLabel); + UpdateBtnBmp(); + + OnStateChange(GetBestSize()); +} + +void PrusaCollapsiblePane::SetLabel(const wxString &label) +{ + m_strLabel = label; + + m_pDisclosureTriangleButton->SetLabel(m_strLabel); + + Layout(); +} + +bool PrusaCollapsiblePane::Layout() +{ + if (!m_pDisclosureTriangleButton || !m_pPane || !m_sz) + return false; // we need to complete the creation first! + + wxSize oursz(GetSize()); + + // move & resize the button and the static line + m_sz->SetDimension(0, 0, oursz.GetWidth(), m_sz->GetMinSize().GetHeight()); + m_sz->Layout(); + + if (IsExpanded()) + { + // move & resize the container window + int yoffset = m_sz->GetSize().GetHeight() + GetBorder(); + m_pPane->SetSize(0, yoffset, + oursz.x, oursz.y - yoffset); + + // this is very important to make the pane window layout show correctly + m_pPane->Layout(); + } + + return true; +} + // ***************************************************************************** // ---------------------------------------------------------------------------- // MyObjectTreeModel @@ -178,17 +297,19 @@ MyObjectTreeModel::MyObjectTreeModel() auto root2 = new MyObjectTreeModelNode("Object2"); m_objects.emplace(root2); - root2->Append(new MyObjectTreeModelNode(root2, "SubObject1")); - root2->Append(new MyObjectTreeModelNode(root2, "SubObject2")); - root2->Append(new MyObjectTreeModelNode(root2, "SubObject3")); + root2->Append(new MyObjectTreeModelNode(root2, "SubObject2_1")); + root2->Append(new MyObjectTreeModelNode(root2, "SubObject2_2")); + root2->Append(new MyObjectTreeModelNode(root2, "SubObject2_3")); auto root3 = new MyObjectTreeModelNode("Object3"); m_objects.emplace(root3); auto root4 = new MyObjectTreeModelNode("Object4"); m_objects.emplace(root4); - root4->Append(new MyObjectTreeModelNode(root4, "SubObject1")); - root4->Append(new MyObjectTreeModelNode(root4, "SubObject2")); - root4->Append(new MyObjectTreeModelNode(root4, "SubObject3")); + root4->Append(new MyObjectTreeModelNode(root4, "SubObject4_1")); + root4->Append(new MyObjectTreeModelNode(root4, "SubObject4_2")); + + auto root5 = new MyObjectTreeModelNode("Object5"); + m_objects.emplace(root5); } wxString MyObjectTreeModel::GetName(const wxDataViewItem &item) const diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 6ebdbe8b20..ec3b0d4790 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -5,6 +5,10 @@ #include #include #include +#include +#include +#include + #include #include @@ -53,6 +57,63 @@ public: void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; } }; + + +// *** PrusaCollapsiblePane *** +// ---------------------------------------------------------------------------- +class PrusaCollapsiblePane : public wxCollapsiblePane +{ +#ifdef __WXMSW__ + wxButton* m_pDisclosureTriangleButton = nullptr; + wxBitmap m_bmp_close; + wxBitmap m_bmp_open; +#endif //__WXMSW__ +public: + PrusaCollapsiblePane() {} + + + PrusaCollapsiblePane( wxWindow *parent, + wxWindowID winid, + const wxString& label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxCP_DEFAULT_STYLE, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxCollapsiblePaneNameStr) + { +#ifdef __WXMSW__ + Create(parent, winid, label, pos, size, style, val, name); +#else + this->Create(parent, winid, label); +#endif //__WXMSW__ + this->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, this](wxCommandEvent e){ + wxWindowUpdateLocker noUpdates_cp(this); + wxWindowUpdateLocker noUpdates(parent); + parent->GetParent()->Layout(); + parent->Layout(); + this->Refresh(); + })); + } + + bool Create(wxWindow *parent, + wxWindowID id, + const wxString& label, + const wxPoint& pos, + const wxSize& size, + long style, + const wxValidator& val, + const wxString& name); + +#ifdef __WXMSW__ + void UpdateBtnBmp(); + void Collapse(bool collapse) override; + void SetLabel(const wxString &label) override; + bool Layout() override; +#endif //__WXMSW__ + +}; + + // ***************************************************************************** // ---------------------------------------------------------------------------- // MyObjectTreeModelNode: a node inside MyObjectTreeModel From 27769edab288a5a99bcd598530a85215e7e43d90 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 11 May 2018 11:25:28 +0200 Subject: [PATCH 006/185] Fixed compilation bag on GTK and OSX --- xs/src/slic3r/GUI/wxExtensions.cpp | 3 +++ xs/src/slic3r/GUI/wxExtensions.hpp | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 03d6bdaef9..ace0ad53af 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -172,8 +172,10 @@ void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& e cmb->SetText(selected); } +// ---------------------------------------------------------------------------- // *** PrusaCollapsiblePane *** // ---------------------------------------------------------------------------- +#ifdef __WXMSW__ bool PrusaCollapsiblePane::Create(wxWindow *parent, wxWindowID id, const wxString& label, const wxPoint& pos, const wxSize& size, long style, const wxValidator& val, const wxString& name) { @@ -284,6 +286,7 @@ bool PrusaCollapsiblePane::Layout() return true; } +#endif //__WXMSW__ // ***************************************************************************** // ---------------------------------------------------------------------------- diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index ec3b0d4790..cea4ce32b5 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -70,8 +70,6 @@ class PrusaCollapsiblePane : public wxCollapsiblePane #endif //__WXMSW__ public: PrusaCollapsiblePane() {} - - PrusaCollapsiblePane( wxWindow *parent, wxWindowID winid, const wxString& label, @@ -84,7 +82,7 @@ public: #ifdef __WXMSW__ Create(parent, winid, label, pos, size, style, val, name); #else - this->Create(parent, winid, label); + Create(parent, winid, label); #endif //__WXMSW__ this->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, this](wxCommandEvent e){ wxWindowUpdateLocker noUpdates_cp(this); @@ -95,6 +93,9 @@ public: })); } + ~PrusaCollapsiblePane() {} + +#ifdef __WXMSW__ bool Create(wxWindow *parent, wxWindowID id, const wxString& label, @@ -104,7 +105,6 @@ public: const wxValidator& val, const wxString& name); -#ifdef __WXMSW__ void UpdateBtnBmp(); void Collapse(bool collapse) override; void SetLabel(const wxString &label) override; From 75a0dea93f0bb1f50b526dd52f2fd1a965609054 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 15 May 2018 12:01:33 +0200 Subject: [PATCH 007/185] PrusaCollapsiblePanel works correct on MSW now --- xs/src/slic3r/GUI/wxExtensions.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index ace0ad53af..2e0f7c90a5 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -192,6 +192,7 @@ bool PrusaCollapsiblePane::Create(wxWindow *parent, wxWindowID id, const wxStrin m_pDisclosureTriangleButton = new wxButton(this, wxID_ANY, m_strLabel, wxPoint(0, 0), wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + UpdateBtnBmp(); m_pDisclosureTriangleButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { if (event.GetEventObject() != m_pDisclosureTriangleButton) @@ -207,8 +208,6 @@ bool PrusaCollapsiblePane::Create(wxWindow *parent, wxWindowID id, const wxStrin GetEventHandler()->ProcessEvent(ev); }); - UpdateBtnBmp(); - m_sz->Add(m_pDisclosureTriangleButton, 0, wxLEFT | wxTOP | wxBOTTOM, GetBorder()); // do not set sz as our sizers since we handle the pane window without using sizers @@ -228,11 +227,17 @@ bool PrusaCollapsiblePane::Create(wxWindow *parent, wxWindowID id, const wxStrin void PrusaCollapsiblePane::UpdateBtnBmp() { - IsCollapsed() ? - m_pDisclosureTriangleButton->SetBitmap(m_bmp_close) : + if (IsCollapsed()) + m_pDisclosureTriangleButton->SetBitmap(m_bmp_close); + else{ m_pDisclosureTriangleButton->SetBitmap(m_bmp_open); + // To updating button bitmap it's needed to lost focus on this button, so + // we set focus to mainframe + //GetParent()->GetParent()->GetParent()->SetFocus(); + //or to pane + GetPane()->SetFocus(); + } Layout(); -// m_pDisclosureTriangleButton->Refresh(); } void PrusaCollapsiblePane::Collapse(bool collapse) @@ -246,8 +251,7 @@ void PrusaCollapsiblePane::Collapse(bool collapse) // update our state m_pPane->Show(!collapse); - // update button label -// m_pDisclosureTriangleButton->SetLabel(m_strLabel); + // update button bitmap UpdateBtnBmp(); OnStateChange(GetBestSize()); @@ -256,9 +260,7 @@ void PrusaCollapsiblePane::Collapse(bool collapse) void PrusaCollapsiblePane::SetLabel(const wxString &label) { m_strLabel = label; - m_pDisclosureTriangleButton->SetLabel(m_strLabel); - Layout(); } From 146a02a300f90562e353c294278dbd47879767d7 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 16 May 2018 14:38:37 +0200 Subject: [PATCH 008/185] Added view mode selection to the config_menu --- xs/src/slic3r/GUI/GUI.cpp | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 2d487edd58..f97f7e31e9 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -378,12 +378,20 @@ void get_installed_languages(wxArrayString & names, } } +std::string get_view_mode() +{ + return g_AppConfig->has("view_mode") ? + g_AppConfig->get("view_mode") : "simple"; +} + enum ConfigMenuIDs { ConfigMenuWizard, ConfigMenuSnapshots, ConfigMenuTakeSnapshot, ConfigMenuUpdate, ConfigMenuPreferences, + ConfigMenuModeSimple, + ConfigMenuModeExpert, ConfigMenuLanguage, ConfigMenuCnt, }; @@ -402,7 +410,14 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences"))+"\u2026\tCtrl+,", _(L("Application preferences"))); local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); + auto mode_menu = new wxMenu(); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("&Simple")), _(L("Simple View Mode"))); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("&Expert")), _(L("Expert View Mode"))); + if (get_view_mode() == "expert") + mode_menu->Check(config_id_base + ConfigMenuModeExpert, true); + local_menu->AppendSubMenu(mode_menu, _(L("&Mode")), _(L("Slic3r View Mode"))); + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ switch (event.GetId() - config_id_base) { case ConfigMenuWizard: @@ -457,8 +472,21 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l } } break; - } } + } + }); + mode_menu->Bind(wxEVT_MENU, [config_id_base](wxEvent& event) { + std::string mode = ""; + switch (event.GetId() - config_id_base){ + case ConfigMenuModeExpert: + mode = "expert"; + break; + case ConfigMenuModeSimple: + mode = "simple"; + break; + } + g_AppConfig->set("view_mode", mode); + g_AppConfig->save(); }); menu->Append(local_menu, _(L("&Configuration"))); } From d3106684620e8bac7f422daac25e6d959d0ba63b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 17 May 2018 10:46:32 +0200 Subject: [PATCH 009/185] Added Regular view mode to the menu. Right column objects send fron Perl to C++ --- lib/Slic3r/GUI/Plater.pm | 17 ++- xs/src/slic3r/GUI/GUI.cpp | 313 +++++++++++++++++++++----------------- xs/src/slic3r/GUI/GUI.hpp | 11 ++ xs/xsp/GUI.xsp | 23 +++ 4 files changed, 225 insertions(+), 139 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 21cbcca45f..93b41af1c8 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -409,8 +409,10 @@ sub new { } } - my $frequently_changed_parameters_sizer = Wx::BoxSizer->new(wxVERTICAL);#(wxHORIZONTAL); + my $frequently_changed_parameters_sizer = Wx::BoxSizer->new(wxVERTICAL); Slic3r::GUI::add_frequently_changed_parameters($self, $frequently_changed_parameters_sizer, $presets); + my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); + Slic3r::GUI::add_expert_mode_part($self, $expert_mode_part_sizer); my $object_info_sizer; { @@ -495,9 +497,10 @@ sub new { $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); - $right_sizer->SetMinSize([-1, 600]); + $right_sizer->SetMinSize([320, 600]); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; + $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 0) if defined $expert_mode_part_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); $right_sizer->Add($scrolled_window_panel, 1, wxEXPAND | wxALL, 1); # Callback for showing / hiding the print info box. @@ -525,6 +528,16 @@ sub new { $sizer->SetSizeHints($self); $self->SetSizer($sizer); + + # Send sizers/buttons to C++ + Slic3r::GUI::set_objects_from_perl( $frequently_changed_parameters_sizer, + $expert_mode_part_sizer, + $scrolled_window_sizer, + $self->{btn_export_stl}, + $self->{btn_reslice}, + $self->{btn_print}, + $self->{btn_send_gcode}, + $self->{btn_export_gcode}); } # Last correct selected item for each preset diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index f97f7e31e9..3cf3c8d0d0 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -201,6 +201,16 @@ std::shared_ptr m_optgroup; double m_brim_width = 0.0; wxButton* g_wiping_dialog_button = nullptr; +//showed/hided controls according to the view mode +wxBoxSizer *g_frequently_changed_parameters_sizer = nullptr; +wxBoxSizer *g_expert_mode_part_sizer = nullptr; +wxBoxSizer *g_scrolled_window_sizer = nullptr; +wxButton *g_btn_export_stl = nullptr; +wxButton *g_btn_reslice = nullptr; +wxButton *g_btn_print = nullptr; +wxButton *g_btn_send_gcode = nullptr; +wxButton *g_btn_export_gcode = nullptr; + static void init_label_colours() { auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -260,6 +270,21 @@ void set_preset_updater(PresetUpdater *updater) g_PresetUpdater = updater; } +void set_objects_from_perl( wxBoxSizer *frequently_changed_parameters_sizer, + wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer, + wxButton *btn_export_stl, wxButton *btn_reslice, wxButton *btn_print, + wxButton *btn_send_gcode, wxButton *btn_export_gcode) +{ + g_frequently_changed_parameters_sizer = frequently_changed_parameters_sizer; + g_expert_mode_part_sizer = expert_mode_part_sizer; + g_scrolled_window_sizer = scrolled_window_sizer; + g_btn_export_stl = btn_export_stl; + g_btn_reslice = btn_reslice; + g_btn_print = btn_print; + g_btn_send_gcode = btn_send_gcode; + g_btn_export_gcode = btn_export_gcode; +} + std::vector& get_tabs_list() { return g_tabs_list; @@ -378,12 +403,6 @@ void get_installed_languages(wxArrayString & names, } } -std::string get_view_mode() -{ - return g_AppConfig->has("view_mode") ? - g_AppConfig->get("view_mode") : "simple"; -} - enum ConfigMenuIDs { ConfigMenuWizard, ConfigMenuSnapshots, @@ -391,10 +410,24 @@ enum ConfigMenuIDs { ConfigMenuUpdate, ConfigMenuPreferences, ConfigMenuModeSimple, + ConfigMenuModeRegular, ConfigMenuModeExpert, ConfigMenuLanguage, ConfigMenuCnt, }; + +ConfigMenuIDs get_view_mode() +{ + if (!g_AppConfig->has("view_mode")) + return ConfigMenuModeSimple; + + const auto mode = g_AppConfig->get("view_mode"); + return mode == "expert" ? + ConfigMenuModeExpert : + mode == "regular" ? + ConfigMenuModeRegular : + ConfigMenuModeSimple; +} void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change) { @@ -412,9 +445,9 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l local_menu->AppendSeparator(); auto mode_menu = new wxMenu(); mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("&Simple")), _(L("Simple View Mode"))); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeRegular, _(L("&Regular")), _(L("Regular View Mode"))); mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("&Expert")), _(L("Expert View Mode"))); - if (get_view_mode() == "expert") - mode_menu->Check(config_id_base + ConfigMenuModeExpert, true); + mode_menu->Check(config_id_base + get_view_mode(), true); local_menu->AppendSubMenu(mode_menu, _(L("&Mode")), _(L("Slic3r View Mode"))); local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); @@ -481,12 +514,16 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l case ConfigMenuModeExpert: mode = "expert"; break; + case ConfigMenuModeRegular: + mode = "regular"; + break; case ConfigMenuModeSimple: mode = "simple"; break; } g_AppConfig->set("view_mode", mode); g_AppConfig->save(); + update_mode(); }); menu->Append(local_menu, _(L("&Configuration"))); } @@ -835,9 +872,9 @@ wxString from_u8(const std::string &str) } -void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) +void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) { - sizer->SetMinSize(-1, 300); + sizer->SetMinSize(-1, 150); auto main_sizer = new wxBoxSizer(wxVERTICAL); auto main_page = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); main_page->SetSizer(main_sizer); @@ -845,133 +882,10 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl sizer->Add(main_page, 1, wxEXPAND | wxALL, 1); // Experiments with new UI - - // *** Frequently Changing Parameters *** - auto* collpane = new PrusaCollapsiblePane(main_page, wxID_ANY, "Frequently Changing Parameters:"); - // add the pane with a zero proportion value to the sizer which contains it - main_sizer->Add(collpane, 0, wxGROW | wxALL, 0); - - wxWindow *win = collpane->GetPane(); - - DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; - m_optgroup = std::make_shared(win, "", config); - m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ - TabPrint* tab_print = nullptr; - for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { - Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (!tab) - continue; - if (tab->name() == "print"){ - tab_print = static_cast(tab); - break; - } - } - if (tab_print == nullptr) - return; - - if (opt_key == "fill_density"){ - value = m_optgroup->get_config_value(*config, opt_key); - tab_print->set_value(opt_key, value); - tab_print->update(); - } - else{ - DynamicPrintConfig new_conf = *config; - if (opt_key == "brim"){ - double new_val; - double brim_width = config->opt_float("brim_width"); - if (boost::any_cast(value) == true) - { - new_val = m_brim_width == 0.0 ? 10 : - m_brim_width < 0.0 ? m_brim_width * (-1) : - m_brim_width; - } - else{ - m_brim_width = brim_width * (-1); - new_val = 0; - } - new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val)); - } - else{ //(opt_key == "support") - const wxString& selection = boost::any_cast(value); - - auto support_material = selection == _("None") ? false : true; - new_conf.set_key_value("support_material", new ConfigOptionBool(support_material)); - - if (selection == _("Everywhere")) - new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false)); - else if (selection == _("Support on build plate only")) - new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true)); - } - tab_print->load_config(new_conf); - } - - tab_print->update_dirty(); - }; - - Option option = m_optgroup->get_option("fill_density"); - option.opt.sidetext = ""; - option.opt.full_width = true; - m_optgroup->append_single_option_line(option); - - ConfigOptionDef def; - - def.label = L("Support"); - def.type = coStrings; - def.gui_type = "select_open"; - def.tooltip = L("Select what kind of support do you need"); - def.enum_labels.push_back(L("None")); - def.enum_labels.push_back(L("Support on build plate only")); - def.enum_labels.push_back(L("Everywhere")); - std::string selection = !config->opt_bool("support_material") ? - "None" : - config->opt_bool("support_material_buildplate_only") ? - "Support on build plate only" : - "Everywhere"; - def.default_value = new ConfigOptionStrings { selection }; - option = Option(def, "support"); - option.opt.full_width = true; - m_optgroup->append_single_option_line(option); - - m_brim_width = config->opt_float("brim_width"); - def.label = L("Brim"); - def.type = coBool; - def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer."); - def.gui_type = ""; - def.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }; - option = Option(def, "brim"); - m_optgroup->append_single_option_line(option); - - - Line line = { "", "" }; - line.widget = [config](wxWindow* parent){ - g_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + "\u2026", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(g_wiping_dialog_button); - g_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) - { - auto &config = g_PresetBundle->project_config; - std::vector init_matrix = (config.option("wiping_volumes_matrix"))->values; - std::vector init_extruders = (config.option("wiping_volumes_extruders"))->values; - - WipingDialog dlg(parent,std::vector(init_matrix.begin(),init_matrix.end()),std::vector(init_extruders.begin(),init_extruders.end())); - - if (dlg.ShowModal() == wxID_OK) { - std::vector matrix = dlg.get_matrix(); - std::vector extruders = dlg.get_extruders(); - (config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(),matrix.end()); - (config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(),extruders.end()); - } - })); - return sizer; - }; - m_optgroup->append_line(line); - - wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); - paneSz->Add(m_optgroup->sizer, 1, wxGROW | wxEXPAND | wxLEFT | wxRIGHT, 5); - win->SetSizer(paneSz); - paneSz->SetSizeHints(win); - - +// wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); +// paneSz->Add(m_optgroup->sizer, 1, wxGROW | wxEXPAND | wxLEFT | wxRIGHT, 5); +// win->SetSizer(paneSz); +// paneSz->SetSizeHints(win); // *** Objects List *** auto *collpane_objects = new PrusaCollapsiblePane(main_page, wxID_ANY, "Objects List:"); @@ -1046,6 +960,131 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl // common_sizer->Add(listctrl, 0, wxEXPAND | wxALL, 1); +} + +void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) +{ + DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; + m_optgroup = std::make_shared(parent, "", config); + // const wxArrayInt& ar = preset_sizer->GetColWidths(); + // m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front(); // doesn't work + m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ + TabPrint* tab_print = nullptr; + for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { + Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); + if (!tab) + continue; + if (tab->name() == "print"){ + tab_print = static_cast(tab); + break; + } + } + if (tab_print == nullptr) + return; + + if (opt_key == "fill_density"){ + value = m_optgroup->get_config_value(*config, opt_key); + tab_print->set_value(opt_key, value); + tab_print->update(); + } + else{ + DynamicPrintConfig new_conf = *config; + if (opt_key == "brim"){ + double new_val; + double brim_width = config->opt_float("brim_width"); + if (boost::any_cast(value) == true) + { + new_val = m_brim_width == 0.0 ? 10 : + m_brim_width < 0.0 ? m_brim_width * (-1) : + m_brim_width; + } + else{ + m_brim_width = brim_width * (-1); + new_val = 0; + } + new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val)); + } + else{ //(opt_key == "support") + const wxString& selection = boost::any_cast(value); + + auto support_material = selection == _("None") ? false : true; + new_conf.set_key_value("support_material", new ConfigOptionBool(support_material)); + + if (selection == _("Everywhere")) + new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(false)); + else if (selection == _("Support on build plate only")) + new_conf.set_key_value("support_material_buildplate_only", new ConfigOptionBool(true)); + } + tab_print->load_config(new_conf); + } + + tab_print->update_dirty(); + }; + + Option option = m_optgroup->get_option("fill_density"); + option.opt.sidetext = ""; + option.opt.full_width = true; + m_optgroup->append_single_option_line(option); + + ConfigOptionDef def; + + def.label = L("Support"); + def.type = coStrings; + def.gui_type = "select_open"; + def.tooltip = L("Select what kind of support do you need"); + def.enum_labels.push_back(L("None")); + def.enum_labels.push_back(L("Support on build plate only")); + def.enum_labels.push_back(L("Everywhere")); + std::string selection = !config->opt_bool("support_material") ? + "None" : + config->opt_bool("support_material_buildplate_only") ? + "Support on build plate only" : + "Everywhere"; + def.default_value = new ConfigOptionStrings { selection }; + option = Option(def, "support"); + option.opt.full_width = true; + m_optgroup->append_single_option_line(option); + + m_brim_width = config->opt_float("brim_width"); + def.label = L("Brim"); + def.type = coBool; + def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer."); + def.gui_type = ""; + def.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }; + option = Option(def, "brim"); + m_optgroup->append_single_option_line(option); + + + Line line = { "", "" }; + line.widget = [config](wxWindow* parent){ + g_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + "\u2026", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(g_wiping_dialog_button); + g_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) + { + auto &config = g_PresetBundle->project_config; + std::vector init_matrix = (config.option("wiping_volumes_matrix"))->values; + std::vector init_extruders = (config.option("wiping_volumes_extruders"))->values; + + WipingDialog dlg(parent,std::vector(init_matrix.begin(),init_matrix.end()),std::vector(init_extruders.begin(),init_extruders.end())); + + if (dlg.ShowModal() == wxID_OK) { + std::vector matrix = dlg.get_matrix(); + std::vector extruders = dlg.get_extruders(); + (config.option("wiping_volumes_matrix"))->values = std::vector(matrix.begin(),matrix.end()); + (config.option("wiping_volumes_extruders"))->values = std::vector(extruders.begin(),extruders.end()); + } + })); + return sizer; + }; + m_optgroup->append_line(line); + + sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); +} + +void update_mode() +{ + } ConfigOptionsGroup* get_optgroup() diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 3752b5a7a4..28359decdf 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -79,6 +79,14 @@ void set_tab_panel(wxNotebook *tab_panel); void set_app_config(AppConfig *app_config); void set_preset_bundle(PresetBundle *preset_bundle); void set_preset_updater(PresetUpdater *updater); +void set_objects_from_perl( wxBoxSizer *frequently_changed_parameters_sizer, + wxBoxSizer *expert_mode_part_sizer, + wxBoxSizer *scrolled_window_sizer, + wxButton *btn_export_stl, + wxButton *btn_reslice, + wxButton *btn_print, + wxButton *btn_send_gcode, + wxButton *btn_export_gcode); AppConfig* get_app_config(); wxApp* get_app(); @@ -150,7 +158,10 @@ wxString L_str(const std::string &str); wxString from_u8(const std::string &str); +void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); +// Update view mode according to selected menu +void update_mode(); ConfigOptionsGroup* get_optgroup(); wxButton* get_wiping_dialog_button(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index aa95bd647d..8b173161bd 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -86,6 +86,29 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), (wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %}; +void add_expert_mode_part(SV *ui_parent, SV *ui_sizer) + %code%{ Slic3r::GUI::add_expert_mode_part((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), + (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer")); %}; + +void set_objects_from_perl( SV *frequently_changed_parameters_sizer, + SV *expert_mode_part_sizer, + SV *scrolled_window_sizer, + SV *btn_export_stl, + SV *btn_reslice, + SV *btn_print, + SV *btn_send_gcode, + SV *btn_export_gcode) + %code%{ Slic3r::GUI::set_objects_from_perl( + (wxBoxSizer *)wxPli_sv_2_object(aTHX_ frequently_changed_parameters_sizer, "Wx::BoxSizer"), + (wxBoxSizer *)wxPli_sv_2_object(aTHX_ expert_mode_part_sizer, "Wx::BoxSizer"), + (wxBoxSizer *)wxPli_sv_2_object(aTHX_ scrolled_window_sizer, "Wx::BoxSizer"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_stl, "Wx::Button"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_reslice, "Wx::Button"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_print, "Wx::Button"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_send_gcode, "Wx::Button"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_gcode, "Wx::Button")); %}; + + std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; From 4e47f4973cc7ac151ce6bc16f9a12ae2af48888d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 17 May 2018 14:07:50 +0200 Subject: [PATCH 010/185] Updating of the right column according selected view mode --- lib/Slic3r/GUI/Plater.pm | 13 +++++-- xs/src/slic3r/GUI/GUI.cpp | 59 +++++++++++++++++++++++++++--- xs/src/slic3r/GUI/GUI.hpp | 7 ++-- xs/src/slic3r/GUI/OptionsGroup.cpp | 27 ++++++++------ xs/src/slic3r/GUI/OptionsGroup.hpp | 1 + xs/src/slic3r/GUI/Tab.hpp | 2 +- xs/xsp/GUI.xsp | 14 ++++--- 7 files changed, 92 insertions(+), 31 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 93b41af1c8..ebb561a9e5 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -509,7 +509,12 @@ sub new { # $right_sizer->Show(5, $_[0]); # $self->Layout # } - if ($scrolled_window_sizer->IsShown(2) != $_[0]) { + if ($scrolled_window_sizer->IsShown(2) != $_[0]) { + Slic3r::GUI::set_show_print_info($_[0]); + my $mode = wxTheApp->{app_config}->get("view_mode"); + printf $mode."\n"; + return if ($mode eq "simple"); + print "non-simple\n"; $scrolled_window_sizer->Show(2, $_[0]); $scrolled_window_panel->Layout } @@ -530,14 +535,14 @@ sub new { $self->SetSizer($sizer); # Send sizers/buttons to C++ - Slic3r::GUI::set_objects_from_perl( $frequently_changed_parameters_sizer, + Slic3r::GUI::set_objects_from_perl( $self, + $frequently_changed_parameters_sizer, $expert_mode_part_sizer, $scrolled_window_sizer, $self->{btn_export_stl}, $self->{btn_reslice}, $self->{btn_print}, - $self->{btn_send_gcode}, - $self->{btn_export_gcode}); + $self->{btn_send_gcode} ); } # Last correct selected item for each preset diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 3cf3c8d0d0..09ede75beb 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -202,6 +202,7 @@ double m_brim_width = 0.0; wxButton* g_wiping_dialog_button = nullptr; //showed/hided controls according to the view mode +wxWindow *g_plater = nullptr; wxBoxSizer *g_frequently_changed_parameters_sizer = nullptr; wxBoxSizer *g_expert_mode_part_sizer = nullptr; wxBoxSizer *g_scrolled_window_sizer = nullptr; @@ -209,7 +210,7 @@ wxButton *g_btn_export_stl = nullptr; wxButton *g_btn_reslice = nullptr; wxButton *g_btn_print = nullptr; wxButton *g_btn_send_gcode = nullptr; -wxButton *g_btn_export_gcode = nullptr; +bool g_show_print_info = false; static void init_label_colours() { @@ -270,11 +271,12 @@ void set_preset_updater(PresetUpdater *updater) g_PresetUpdater = updater; } -void set_objects_from_perl( wxBoxSizer *frequently_changed_parameters_sizer, +void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_parameters_sizer, wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer, - wxButton *btn_export_stl, wxButton *btn_reslice, wxButton *btn_print, - wxButton *btn_send_gcode, wxButton *btn_export_gcode) + wxButton *btn_export_stl, wxButton *btn_reslice, + wxButton *btn_print, wxButton *btn_send_gcode) { + g_plater = parent; g_frequently_changed_parameters_sizer = frequently_changed_parameters_sizer; g_expert_mode_part_sizer = expert_mode_part_sizer; g_scrolled_window_sizer = scrolled_window_sizer; @@ -282,7 +284,11 @@ void set_objects_from_perl( wxBoxSizer *frequently_changed_parameters_sizer, g_btn_reslice = btn_reslice; g_btn_print = btn_print; g_btn_send_gcode = btn_send_gcode; - g_btn_export_gcode = btn_export_gcode; +} + +void set_show_print_info(bool show) +{ + g_show_print_info = show; } std::vector& get_tabs_list() @@ -601,6 +607,7 @@ void create_preset_tabs(bool no_controller, int event_value_change, int event_pr tab->set_event_value_change(wxEventType(event_value_change)); tab->set_event_presets_changed(wxEventType(event_presets_changed)); } + update_mode();// TODO change place of call this function } TabIface* get_preset_tab_iface(char *name) @@ -1082,9 +1089,51 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); } +void show_frequently_changed_parameters(bool show) +{ + g_frequently_changed_parameters_sizer->Show(show); + if (!show) return; + + for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { + Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); + if (!tab) + continue; + tab->update_wiping_button_visibility(); + break; + } +} + +void show_buttons(bool show) +{ + g_btn_export_stl->Show(show); + g_btn_reslice->Show(show); + for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { + TabPrinter *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); + if (!tab) + continue; + g_btn_print->Show(show && !tab->m_config->opt_string("serial_port").empty()); + g_btn_send_gcode->Show(show && !tab->m_config->opt_string("octoprint_host").empty()); + break; + } +} + +void show_scrolled_window_sizer(bool show) +{ + g_scrolled_window_sizer->Show(static_cast(0), show); + g_scrolled_window_sizer->Show(1, show); + g_scrolled_window_sizer->Show(2, show && g_show_print_info); +} + void update_mode() { + wxWindowUpdateLocker noUpdates(g_plater); + ConfigMenuIDs mode = get_view_mode(); + show_frequently_changed_parameters(mode >= ConfigMenuModeRegular); + g_expert_mode_part_sizer->Show(mode == ConfigMenuModeExpert); + show_scrolled_window_sizer(mode >= ConfigMenuModeRegular); + show_buttons(mode >= ConfigMenuModeRegular); + g_plater->Layout(); } ConfigOptionsGroup* get_optgroup() diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 28359decdf..1cca993197 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -79,14 +79,15 @@ void set_tab_panel(wxNotebook *tab_panel); void set_app_config(AppConfig *app_config); void set_preset_bundle(PresetBundle *preset_bundle); void set_preset_updater(PresetUpdater *updater); -void set_objects_from_perl( wxBoxSizer *frequently_changed_parameters_sizer, +void set_objects_from_perl( wxWindow* parent, + wxBoxSizer *frequently_changed_parameters_sizer, wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer, wxButton *btn_export_stl, wxButton *btn_reslice, wxButton *btn_print, - wxButton *btn_send_gcode, - wxButton *btn_export_gcode); + wxButton *btn_send_gcode); +void set_show_print_info(bool show); AppConfig* get_app_config(); wxApp* get_app(); diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 657ad03c03..40176a0007 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -86,17 +86,23 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co if (!this->m_disabled) this->back_to_sys_value(opt_id); }; - if (!m_is_tab_opt) { - field->m_Undo_btn->Hide(); - field->m_Undo_to_sys_btn->Hide(); - } -// if (nonsys_btn_icon != nullptr) -// field->set_nonsys_btn_icon(*nonsys_btn_icon); // assign function objects for callbacks, etc. return field; } +void OptionsGroup::add_undo_buttuns_to_sizer(wxBoxSizer* sizer, const t_field& field) +{ + if (!m_is_tab_opt) { + field->m_Undo_btn->Hide(); + field->m_Undo_to_sys_btn->Hide(); + return; + } + + sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); +} + void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* = nullptr*/) { //! if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)){ if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width){ @@ -131,8 +137,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* const auto& field = build_field(option); auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); - btn_sizer->Add(field->m_Undo_to_sys_btn); - btn_sizer->Add(field->m_Undo_btn); + add_undo_buttuns_to_sizer(btn_sizer, field); tmp_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0); if (is_window_field(field)) tmp_sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); @@ -176,8 +181,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* const auto& option = option_set.front(); const auto& field = build_field(option, label); - sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); - sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); + add_undo_buttuns_to_sizer(sizer, field); if (is_window_field(field)) sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, (option.opt.full_width ? wxEXPAND : 0) | wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, wxOSX ? 0 : 2); @@ -205,8 +209,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // add field const Option& opt_ref = opt; auto& field = build_field(opt_ref, label); - sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); - sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL, 0); + add_undo_buttuns_to_sizer(sizer, field); is_sizer_field(field) ? sizer->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) : sizer->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0); diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 83b5b1233f..55a8dc70b9 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -171,6 +171,7 @@ protected: const t_field& build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label = nullptr); const t_field& build_field(const t_config_option_key& id, wxStaticText* label = nullptr); const t_field& build_field(const Option& opt, wxStaticText* label = nullptr); + void add_undo_buttuns_to_sizer(wxBoxSizer* sizer, const t_field& field); virtual void on_kill_focus (){}; virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 62030bce34..8f540b1977 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -264,11 +264,11 @@ public: void on_value_change(const std::string& opt_key, const boost::any& value); + void update_wiping_button_visibility(); protected: void on_presets_changed(); void update_preset_description_line(); void update_frequently_changed_parameters(); - void update_wiping_button_visibility(); void update_tab_presets(wxComboCtrl* ui, bool show_incompatible); void fill_icon_descriptions(); void set_tooltips_text(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 8b173161bd..b402ab656e 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -90,24 +90,26 @@ void add_expert_mode_part(SV *ui_parent, SV *ui_sizer) %code%{ Slic3r::GUI::add_expert_mode_part((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer")); %}; -void set_objects_from_perl( SV *frequently_changed_parameters_sizer, +void set_objects_from_perl( SV *ui_parent, + SV *frequently_changed_parameters_sizer, SV *expert_mode_part_sizer, SV *scrolled_window_sizer, SV *btn_export_stl, SV *btn_reslice, SV *btn_print, - SV *btn_send_gcode, - SV *btn_export_gcode) - %code%{ Slic3r::GUI::set_objects_from_perl( + SV *btn_send_gcode) + %code%{ Slic3r::GUI::set_objects_from_perl( + (wxWindow *)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), (wxBoxSizer *)wxPli_sv_2_object(aTHX_ frequently_changed_parameters_sizer, "Wx::BoxSizer"), (wxBoxSizer *)wxPli_sv_2_object(aTHX_ expert_mode_part_sizer, "Wx::BoxSizer"), (wxBoxSizer *)wxPli_sv_2_object(aTHX_ scrolled_window_sizer, "Wx::BoxSizer"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_stl, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_reslice, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_print, "Wx::Button"), - (wxButton *)wxPli_sv_2_object(aTHX_ btn_send_gcode, "Wx::Button"), - (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_gcode, "Wx::Button")); %}; + (wxButton *)wxPli_sv_2_object(aTHX_ btn_send_gcode, "Wx::Button")); %}; +void set_show_print_info(bool show) + %code%{ Slic3r::GUI::set_show_print_info(show); %}; std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; From 876cf9aa8bb89ce977d6d4a6463a783fcb79d079 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 18 May 2018 11:39:49 +0200 Subject: [PATCH 011/185] Show/hide warning icon according to the view mode --- lib/Slic3r/GUI/Plater.pm | 34 +++++++++++++++++++++++++--------- xs/src/slic3r/GUI/GUI.cpp | 12 +++++++++++- xs/src/slic3r/GUI/GUI.hpp | 5 ++++- xs/xsp/GUI.xsp | 9 +++++++-- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ebb561a9e5..5a3c1bb9f1 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -442,7 +442,16 @@ sub new { $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); if ($field eq 'manifold') { $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($scrolled_window_panel, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); - $self->{object_info_manifold_warning_icon}->Hide; + #$self->{object_info_manifold_warning_icon}->Hide; + $self->{"object_info_manifold_warning_icon_show"} = sub { + if ($self->{object_info_manifold_warning_icon}->IsShown() != $_[0]) { + Slic3r::GUI::set_show_manifold_warning_icon($_[0]); + return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); + $self->{object_info_manifold_warning_icon}->Show($_[0]); + $self->Layout + } + }; + $self->{"object_info_manifold_warning_icon_show"}->(0); my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $h_sizer->Add($self->{object_info_manifold_warning_icon}, 0); @@ -511,10 +520,7 @@ sub new { # } if ($scrolled_window_sizer->IsShown(2) != $_[0]) { Slic3r::GUI::set_show_print_info($_[0]); - my $mode = wxTheApp->{app_config}->get("view_mode"); - printf $mode."\n"; - return if ($mode eq "simple"); - print "non-simple\n"; + return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); $scrolled_window_sizer->Show(2, $_[0]); $scrolled_window_panel->Layout } @@ -542,7 +548,8 @@ sub new { $self->{btn_export_stl}, $self->{btn_reslice}, $self->{btn_print}, - $self->{btn_send_gcode} ); + $self->{btn_send_gcode}, + $self->{object_info_manifold_warning_icon} ); } # Last correct selected item for each preset @@ -881,6 +888,9 @@ sub remove { $self->select_object(undef); $self->update; $self->schedule_background_process; + + # Hide the slicing results if the current slicing status is no more valid. + $self->{"print_info_box_show"}->(0); } sub reset { @@ -900,6 +910,9 @@ sub reset { $self->select_object(undef); $self->update; + + # Hide the slicing results if the current slicing status is no more valid. + $self->{"print_info_box_show"}->(0); } sub increase { @@ -2017,7 +2030,8 @@ sub selection_changed { $self->{object_info_facets}->SetLabel(sprintf(L('%d (%d shells)'), $model_object->facets_count, $stats->{number_of_parts})); if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) { $self->{object_info_manifold}->SetLabel(sprintf(L("Auto-repaired (%d errors)"), $errors)); - $self->{object_info_manifold_warning_icon}->Show; + #$self->{object_info_manifold_warning_icon}->Show; + $self->{"object_info_manifold_warning_icon_show"}->(1); # we don't show normals_fixed because we never provide normals # to admesh, so it generates normals for all facets @@ -2027,14 +2041,16 @@ sub selection_changed { $self->{object_info_manifold_warning_icon}->SetToolTipString($message); } else { $self->{object_info_manifold}->SetLabel(L("Yes")); - $self->{object_info_manifold_warning_icon}->Hide; + #$self->{object_info_manifold_warning_icon}->Hide; + $self->{"object_info_manifold_warning_icon_show"}->(0); } } else { $self->{object_info_facets}->SetLabel($object->facets); } } else { $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold); - $self->{object_info_manifold_warning_icon}->Hide; + #$self->{object_info_manifold_warning_icon}->Hide; + $self->{"object_info_manifold_warning_icon_show"}->(0); $self->{object_info_manifold}->SetToolTipString(""); } $self->Layout; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 09ede75beb..abdeb6c72e 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -210,7 +210,9 @@ wxButton *g_btn_export_stl = nullptr; wxButton *g_btn_reslice = nullptr; wxButton *g_btn_print = nullptr; wxButton *g_btn_send_gcode = nullptr; +wxStaticBitmap *g_manifold_warning_icon = nullptr; bool g_show_print_info = false; +bool g_show_manifold_warning_icon = false; static void init_label_colours() { @@ -274,7 +276,8 @@ void set_preset_updater(PresetUpdater *updater) void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_parameters_sizer, wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer, wxButton *btn_export_stl, wxButton *btn_reslice, - wxButton *btn_print, wxButton *btn_send_gcode) + wxButton *btn_print, wxButton *btn_send_gcode, + wxStaticBitmap *manifold_warning_icon) { g_plater = parent; g_frequently_changed_parameters_sizer = frequently_changed_parameters_sizer; @@ -284,6 +287,7 @@ void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_par g_btn_reslice = btn_reslice; g_btn_print = btn_print; g_btn_send_gcode = btn_send_gcode; + g_manifold_warning_icon = manifold_warning_icon; } void set_show_print_info(bool show) @@ -291,6 +295,11 @@ void set_show_print_info(bool show) g_show_print_info = show; } +void set_show_manifold_warning_icon(bool show) +{ + g_show_manifold_warning_icon = show; +} + std::vector& get_tabs_list() { return g_tabs_list; @@ -1122,6 +1131,7 @@ void show_scrolled_window_sizer(bool show) g_scrolled_window_sizer->Show(static_cast(0), show); g_scrolled_window_sizer->Show(1, show); g_scrolled_window_sizer->Show(2, show && g_show_print_info); + g_manifold_warning_icon->Show(show && g_show_manifold_warning_icon); } void update_mode() diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 1cca993197..4fd8b800ab 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -20,6 +20,7 @@ class wxBoxSizer; class wxFlexGridSizer; class wxButton; class wxFileDialog; +class wxStaticBitmap; namespace Slic3r { @@ -86,8 +87,10 @@ void set_objects_from_perl( wxWindow* parent, wxButton *btn_export_stl, wxButton *btn_reslice, wxButton *btn_print, - wxButton *btn_send_gcode); + wxButton *btn_send_gcode, + wxStaticBitmap *manifold_warning_icon); void set_show_print_info(bool show); +void set_show_manifold_warning_icon(bool show); AppConfig* get_app_config(); wxApp* get_app(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index b402ab656e..6dcb4a6ab3 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -97,7 +97,8 @@ void set_objects_from_perl( SV *ui_parent, SV *btn_export_stl, SV *btn_reslice, SV *btn_print, - SV *btn_send_gcode) + SV *btn_send_gcode, + SV *manifold_warning_icon) %code%{ Slic3r::GUI::set_objects_from_perl( (wxWindow *)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), (wxBoxSizer *)wxPli_sv_2_object(aTHX_ frequently_changed_parameters_sizer, "Wx::BoxSizer"), @@ -106,11 +107,15 @@ void set_objects_from_perl( SV *ui_parent, (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_stl, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_reslice, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_print, "Wx::Button"), - (wxButton *)wxPli_sv_2_object(aTHX_ btn_send_gcode, "Wx::Button")); %}; + (wxButton *)wxPli_sv_2_object(aTHX_ btn_send_gcode, "Wx::Button"), + (wxStaticBitmap *)wxPli_sv_2_object(aTHX_ manifold_warning_icon, "Wx::StaticBitmap")); %}; void set_show_print_info(bool show) %code%{ Slic3r::GUI::set_show_print_info(show); %}; +void set_show_manifold_warning_icon(bool show) + %code%{ Slic3r::GUI::set_show_manifold_warning_icon(show); %}; + std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; From bd2371cb03ff69a0d2fc87e9c9653d91c07caf5f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 18 May 2018 12:16:15 +0200 Subject: [PATCH 012/185] Right column of the Plater is passed to own panel to be able be updated separately from whole Plater panel --- lib/Slic3r/GUI/Plater.pm | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5a3c1bb9f1..e57c2267aa 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -225,9 +225,12 @@ sub new { $self->{btoolbar}->Add($self->{"btn_layer_editing"}); } + ### Panel for right column + $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + ### Scrolled Window for info boxes my $scrolled_window_sizer = Wx::BoxSizer->new(wxVERTICAL); - my $scrolled_window_panel = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + my $scrolled_window_panel = Wx::ScrolledWindow->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $scrolled_window_panel->SetSizer($scrolled_window_sizer); $scrolled_window_panel->SetScrollbars(1, 1, 1, 1); @@ -249,11 +252,11 @@ sub new { }); # right pane buttons - $self->{btn_export_gcode} = Wx::Button->new($self, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_reslice} = Wx::Button->new($self, -1, L("Slice now"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_print} = Wx::Button->new($self, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_send_gcode} = Wx::Button->new($self, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_export_stl} = Wx::Button->new($self, -1, L("Export STL…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_reslice} = Wx::Button->new($self->{right_panel}, -1, L("Slice now"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_print} = Wx::Button->new($self->{right_panel}, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_send_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_export_stl} = Wx::Button->new($self->{right_panel}, -1, L("Export STL…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); #$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); #$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); $self->{btn_print}->Hide; @@ -390,9 +393,9 @@ sub new { # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; for my $group (qw(print filament printer)) { - my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + my $text = Wx::StaticText->new($self->{right_panel}, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); - my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); + my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); if ($group eq 'filament') { EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } ); } @@ -410,9 +413,9 @@ sub new { } my $frequently_changed_parameters_sizer = Wx::BoxSizer->new(wxVERTICAL); - Slic3r::GUI::add_frequently_changed_parameters($self, $frequently_changed_parameters_sizer, $presets); + Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); - Slic3r::GUI::add_expert_mode_part($self, $expert_mode_part_sizer); + Slic3r::GUI::add_expert_mode_part($self->{right_panel}, $expert_mode_part_sizer); my $object_info_sizer; { @@ -528,9 +531,13 @@ sub new { # Show the box initially, let it be shown after the slicing is finished. $self->{"print_info_box_show"}->(0); + $right_sizer->SetSizeHints($self->{right_panel}); + $self->{right_panel}->SetSizer($right_sizer); + my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); - $hsizer->Add($right_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 3); + $hsizer->Add($self->{right_panel}, 0, wxEXPAND | wxLEFT | wxRIGHT, 3); + #$hsizer->Add($right_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 3); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar}; @@ -2004,7 +2011,7 @@ sub selection_changed { my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; - $self->Freeze; + $self->{right_panel}->Freeze; if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_sel) @@ -2058,7 +2065,7 @@ sub selection_changed { # prepagate the event to the frame (a custom Wx event would be cleaner) $self->GetFrame->on_plater_selection_changed($have_sel); - $self->Thaw; + $self->{right_panel}->Thaw; } sub select_object { From 622c613b410fd0f8ac82b78ae577194f9e1c3607 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 21 May 2018 14:49:31 +0200 Subject: [PATCH 013/185] Update of the view mode works correctly --- lib/Slic3r/GUI/MainFrame.pm | 2 ++ lib/Slic3r/GUI/Plater.pm | 7 ++++--- lib/Slic3r/GUI/Plater/3D.pm | 2 +- xs/src/slic3r/GUI/GUI.cpp | 9 ++++----- xs/src/slic3r/GUI/GUI.hpp | 2 ++ xs/src/slic3r/GUI/Tab.cpp | 16 ++++++++++------ xs/xsp/GUI.xsp | 3 +++ 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index fbcd34a3f6..51f5911f01 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -96,6 +96,8 @@ sub new { $self->update_ui_from_settings; + Slic3r::GUI::update_mode(); + return $self; } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e57c2267aa..5bd4587bbe 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -449,7 +449,8 @@ sub new { $self->{"object_info_manifold_warning_icon_show"} = sub { if ($self->{object_info_manifold_warning_icon}->IsShown() != $_[0]) { Slic3r::GUI::set_show_manifold_warning_icon($_[0]); - return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); + my $mode = wxTheApp->{app_config}->get("view_mode"); + return if ($mode eq "" || $mode eq "simple"); $self->{object_info_manifold_warning_icon}->Show($_[0]); $self->Layout } @@ -548,7 +549,7 @@ sub new { $self->SetSizer($sizer); # Send sizers/buttons to C++ - Slic3r::GUI::set_objects_from_perl( $self, + Slic3r::GUI::set_objects_from_perl( $self->{right_panel}, $frequently_changed_parameters_sizer, $expert_mode_part_sizer, $scrolled_window_sizer, @@ -1767,7 +1768,7 @@ sub on_extruders_change { my @presets = $choices->[0]->GetStrings; # initialize new choice - my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); + my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); my $extruder_idx = scalar @$choices; EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } ); push @$choices, $choice; diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index c9c9542762..c3521a4dad 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -245,7 +245,7 @@ sub reload_scene { $self->set_warning_enabled(0); $self->volumes->update_outside_state($self->{config}, 1); Slic3r::GUI::_3DScene::reset_warning_texture(); - $self->on_enable_action_buttons->(1) if ($self->on_enable_action_buttons); + $self->on_enable_action_buttons->(scalar @{$self->{model}->objects} > 0) if ($self->on_enable_action_buttons); } } else { $self->set_warning_enabled(0); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index abdeb6c72e..1fc02ed1f5 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -202,7 +202,7 @@ double m_brim_width = 0.0; wxButton* g_wiping_dialog_button = nullptr; //showed/hided controls according to the view mode -wxWindow *g_plater = nullptr; +wxWindow *g_right_panel = nullptr; wxBoxSizer *g_frequently_changed_parameters_sizer = nullptr; wxBoxSizer *g_expert_mode_part_sizer = nullptr; wxBoxSizer *g_scrolled_window_sizer = nullptr; @@ -279,7 +279,7 @@ void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_par wxButton *btn_print, wxButton *btn_send_gcode, wxStaticBitmap *manifold_warning_icon) { - g_plater = parent; + g_right_panel = parent; g_frequently_changed_parameters_sizer = frequently_changed_parameters_sizer; g_expert_mode_part_sizer = expert_mode_part_sizer; g_scrolled_window_sizer = scrolled_window_sizer; @@ -616,7 +616,6 @@ void create_preset_tabs(bool no_controller, int event_value_change, int event_pr tab->set_event_value_change(wxEventType(event_value_change)); tab->set_event_presets_changed(wxEventType(event_presets_changed)); } - update_mode();// TODO change place of call this function } TabIface* get_preset_tab_iface(char *name) @@ -1136,14 +1135,14 @@ void show_scrolled_window_sizer(bool show) void update_mode() { - wxWindowUpdateLocker noUpdates(g_plater); + wxWindowUpdateLocker noUpdates(g_right_panel); ConfigMenuIDs mode = get_view_mode(); show_frequently_changed_parameters(mode >= ConfigMenuModeRegular); g_expert_mode_part_sizer->Show(mode == ConfigMenuModeExpert); show_scrolled_window_sizer(mode >= ConfigMenuModeRegular); show_buttons(mode >= ConfigMenuModeRegular); - g_plater->Layout(); + g_right_panel->Layout(); } ConfigOptionsGroup* get_optgroup() diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 4fd8b800ab..5f89d8b661 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -142,6 +142,8 @@ void save_language(); void get_installed_languages(wxArrayString & names, wxArrayLong & identifiers); // select language from the list of installed languages bool select_language(wxArrayString & names, wxArrayLong & identifiers); +// update right panel of the Plater according to view mode +void update_mode(); std::vector& get_tabs_list(); bool checked_tab(Tab* tab); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 5a47dd1e7b..21c85925d8 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -705,13 +705,17 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) // Show/hide the 'purging volumes' button void Tab::update_wiping_button_visibility() { - bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; - bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; - bool single_extruder_mm = dynamic_cast( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; + if (!get_app_config()->has("view_mode") || get_app_config()->get("view_mode") == "simple") + get_wiping_dialog_button()->Hide(); + else { + bool wipe_tower_enabled = dynamic_cast((m_preset_bundle->prints.get_edited_preset().config).option("wipe_tower"))->value; + bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; + bool single_extruder_mm = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; - if (wipe_tower_enabled && multiple_extruders && single_extruder_mm) - get_wiping_dialog_button()->Show(); - else get_wiping_dialog_button()->Hide(); + if (wipe_tower_enabled && multiple_extruders && single_extruder_mm) + get_wiping_dialog_button()->Show(); + else get_wiping_dialog_button()->Hide(); + } (get_wiping_dialog_button()->GetParent())->Layout(); } diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 6dcb4a6ab3..aca60d0f87 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -116,6 +116,9 @@ void set_show_print_info(bool show) void set_show_manifold_warning_icon(bool show) %code%{ Slic3r::GUI::set_show_manifold_warning_icon(show); %}; +void update_mode() + %code%{ Slic3r::GUI::update_mode(); %}; + std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; From 6e2d72f35cb10a8af3532d3af43af4d9b96d2b48 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 22 May 2018 08:41:33 +0200 Subject: [PATCH 014/185] Billet for the part of the expert view mode --- xs/src/slic3r/GUI/GUI.cpp | 92 ++++++++++++++++++------------ xs/src/slic3r/GUI/GUI.hpp | 5 ++ xs/src/slic3r/GUI/OptionsGroup.cpp | 8 +-- xs/src/slic3r/GUI/OptionsGroup.hpp | 6 +- 4 files changed, 67 insertions(+), 44 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 1fc02ed1f5..c49cccc3d4 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -886,35 +886,28 @@ wxString from_u8(const std::string &str) return wxString::FromUTF8(str.c_str()); } - -void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) +// add PrusaCollapsiblePane to sizer +void add_prusa_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) { - sizer->SetMinSize(-1, 150); - auto main_sizer = new wxBoxSizer(wxVERTICAL); - auto main_page = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - main_page->SetSizer(main_sizer); - main_page->SetScrollbars(1, 1, 1, 1); - sizer->Add(main_page, 1, wxEXPAND | wxALL, 1); - - // Experiments with new UI -// wxSizer *paneSz = new wxBoxSizer(wxVERTICAL); -// paneSz->Add(m_optgroup->sizer, 1, wxGROW | wxEXPAND | wxLEFT | wxRIGHT, 5); -// win->SetSizer(paneSz); -// paneSz->SetSizeHints(win); - - // *** Objects List *** - auto *collpane_objects = new PrusaCollapsiblePane(main_page, wxID_ANY, "Objects List:"); + auto *collpane = new PrusaCollapsiblePane(parent, wxID_ANY, name); // add the pane with a zero proportion value to the sizer which contains it - main_sizer->Add(collpane_objects, 0, wxGROW | wxALL, 0); + sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0); - wxWindow *win_objects = collpane_objects->GetPane(); + wxWindow *win = collpane->GetPane(); - // ********************************************************************************************** - auto objects_ctrl = new wxDataViewCtrl(win_objects, wxID_ANY, wxDefaultPosition, wxDefaultSize); - wxSizer *objects_sz = new wxBoxSizer(wxVERTICAL); - objects_ctrl->SetBestFittingSize(wxSize(-1, 200)); - // TODO - Set correct height according to the opened/closed objects -// objects_ctrl->SetMinSize(wxSize(-1, 200)); + wxSizer *sizer = content_function(win); + + wxSizer *sizer_pane = new wxBoxSizer(wxVERTICAL); + sizer_pane->Add(sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); + win->SetSizer(sizer_pane); + sizer_pane->SetSizeHints(win); +} + +wxBoxSizer* content_objects_list(wxWindow *win) +{ + auto objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); + objects_ctrl->SetBestFittingSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects + auto objects_sz = new wxBoxSizer(wxVERTICAL); objects_sz->Add(objects_ctrl, 1, wxGROW | wxALL, 5); auto objects_model = new MyObjectTreeModel; @@ -945,19 +938,44 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); objects_ctrl->AppendColumn(column02); -// common_sizer->Add(objects_sz, 0, wxEXPAND | wxALL, 1); + return objects_sz; +} - wxSizer *paneSz_objects = new wxBoxSizer(wxVERTICAL); - paneSz_objects->Add(objects_sz, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); - win_objects->SetSizer(paneSz_objects); - paneSz_objects->SetSizeHints(win_objects); +wxBoxSizer* content_object_settings(wxWindow *win) +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(new wxStaticText(win, wxID_ANY, "Some object text")); + return sizer; +} + +wxBoxSizer* content_part_settings(wxWindow *win) +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(new wxStaticText(win, wxID_ANY, "Some part text")); + return sizer; +} + +void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) +{ + sizer->SetMinSize(-1, 150); + auto main_sizer = new wxBoxSizer(wxVERTICAL); + auto main_page = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + main_page->SetSizer(main_sizer); + main_page->SetScrollbars(1, 1, 1, 1); + sizer->Add(main_page, 1, wxEXPAND | wxALL, 1); + + // Experiments with new UI + + // *** Objects List *** + add_prusa_collapsible_pane(main_page, main_sizer, "Objects List:", content_objects_list); + // *** Object Settings *** + add_prusa_collapsible_pane(main_page, main_sizer, "Object Settings:", content_object_settings); + // *** Part Settings *** + add_prusa_collapsible_pane(main_page, main_sizer, "Part Settings:", content_part_settings); - -// auto common_sizer = new wxBoxSizer(wxVERTICAL); -// common_sizer->Add(m_optgroup->sizer); - -// auto listctrl = new wxDataViewListCtrl(win, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); + // More experiments with UI +// auto listctrl = new wxDataViewListCtrl(main_page, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); // listctrl->AppendToggleColumn("Toggle"); // listctrl->AppendTextColumn("Text"); // wxVector data; @@ -972,9 +990,7 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) // data.push_back(wxVariant(false)); // data.push_back(wxVariant("row 2")); // listctrl->AppendItem(data); -// common_sizer->Add(listctrl, 0, wxEXPAND | wxALL, 1); - - +// main_sizer->Add(listctrl, 0, wxEXPAND | wxALL, 1); } void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 5f89d8b661..2b3c968210 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -47,6 +47,11 @@ class TabIface; namespace GUI { +enum ogDrawFlag{ + ogDEFAULT, + ogSIDE_OPTIONS_TO_GRID +}; + class Tab; class ConfigOptionsGroup; // Map from an file_type name to full file wildcard name. diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 40176a0007..dcb73f5cf1 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -173,7 +173,9 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* return; } - // if we have a single option with no sidetext just add it directly to the grid sizer + // If we're here, we have more than one option or a single option with sidetext + // so we need a horizontal sizer to arrange these things + // If we have a single option with no sidetext just add it directly to the grid sizer auto sizer = new wxBoxSizer(wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | wxALL, 0); if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 && @@ -190,9 +192,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* return; } - // if we're here, we have more than one option or a single option with sidetext - // so we need a horizontal sizer to arrange these things - for (auto opt : option_set) { + for (auto opt : option_set) { ConfigOptionDef option = opt.opt; // add label if any if (option.label != "") { diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 55a8dc70b9..7da39dbf9e 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -127,8 +127,8 @@ public: inline void enable() { for (auto& field : m_fields) field.second->enable(); } inline void disable() { for (auto& field : m_fields) field.second->disable(); } - OptionsGroup(wxWindow* _parent, const wxString& title, bool is_tab_opt=false) : - m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), staticbox(title!="") { + OptionsGroup(wxWindow* _parent, const wxString& title, bool is_tab_opt=false, ogDrawFlag flag = ogDEFAULT) : + m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), staticbox(title!=""), m_flag(flag) { sizer = (staticbox ? new wxStaticBoxSizer(new wxStaticBox(_parent, wxID_ANY, title), wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; if (label_width != 0) num_columns++; @@ -158,6 +158,8 @@ protected: // "true" if option is created in preset tabs bool m_is_tab_opt{ false }; + ogDrawFlag m_flag{ ogDEFAULT }; + // This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox // Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel // inside it before you insert the other controls. From ec5b98477d2773a24d237491c9bec63befe9f005 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 22 May 2018 16:14:41 +0200 Subject: [PATCH 015/185] Filled content_object_settings --- xs/src/slic3r/GUI/GUI.cpp | 68 ++++++++++++++++++++++-------- xs/src/slic3r/GUI/GUI.hpp | 9 ++-- xs/src/slic3r/GUI/OptionsGroup.cpp | 4 +- xs/src/slic3r/GUI/OptionsGroup.hpp | 9 +++- xs/src/slic3r/GUI/Tab.cpp | 16 +++---- 5 files changed, 73 insertions(+), 33 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 1af1ce5412..511e7edd13 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -118,8 +118,8 @@ std::vector g_tabs_list; wxLocale* g_wxLocale; -std::shared_ptr m_optgroup; -double m_brim_width = 0.0; +std::vector > m_optgroups; +double m_brim_width = 0.0; wxButton* g_wiping_dialog_button = nullptr; //showed/hided controls according to the view mode @@ -379,8 +379,7 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l // local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates"))); local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences"))+"\u2026\tCtrl+,", _(L("Application preferences"))); - local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); - local_menu->AppendSeparator(); + local_menu->AppendSeparator(); auto mode_menu = new wxMenu(); mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("&Simple")), _(L("Simple View Mode"))); mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeRegular, _(L("&Regular")), _(L("Regular View Mode"))); @@ -389,6 +388,11 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l local_menu->AppendSubMenu(mode_menu, _(L("&Mode")), _(L("Slic3r View Mode"))); local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language"))); + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _(L("Flash printer firmware")), _(L("Upload a firmware image into an Arduino based printer"))); + // TODO: for when we're able to flash dictionaries + // local_menu->Append(config_id_base + FirmwareMenuDict, _(L("Flash language file")), _(L("Upload a language dictionary file into a Prusa printer"))); + local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){ switch (event.GetId() - config_id_base) { case ConfigMenuWizard: @@ -876,8 +880,36 @@ wxBoxSizer* content_objects_list(wxWindow *win) wxBoxSizer* content_object_settings(wxWindow *win) { + DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; + std::shared_ptr optgroup = std::make_shared(win, "", config, false, ogSIDE_OPTIONS_VERTICAL); + optgroup->label_width = 100; + + Line line = { _(L("Position")), "" }; + ConfigOptionDef def; + + def.label = L("X"); + def.type = coInt; + def.default_value = new ConfigOptionInt(1); + def.sidetext = L("mm"); + + Option option = Option(def, "position_X"); + option.opt.full_width = true; + line.append_option(option); + + def.label = L("Y"); + option = Option(def, "position_Y"); + line.append_option(option); + + def.label = L("Z"); + option = Option(def, "position_Z"); + line.append_option(option); + + optgroup->append_line(line); + + m_optgroups.push_back(optgroup); // ogObjectSettings + auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(new wxStaticText(win, wxID_ANY, "Some object text")); + sizer->Add(optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); return sizer; } @@ -929,10 +961,10 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) { DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; - m_optgroup = std::make_shared(parent, "", config); + std::shared_ptr optgroup = std::make_shared(parent, "", config); const wxArrayInt& ar = preset_sizer->GetColWidths(); - m_optgroup->label_width = ar.IsEmpty() ? 100 : ar.front()-4; // doesn't work - m_optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ + optgroup->label_width = ar.IsEmpty() ? 100 : ar.front()-4; // doesn't work + optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ TabPrint* tab_print = nullptr; for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); @@ -947,7 +979,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl return; if (opt_key == "fill_density"){ - value = m_optgroup->get_config_value(*config, opt_key); + value = m_optgroups[ogFrequentlyChangingParameters]->get_config_value(*config, opt_key); tab_print->set_value(opt_key, value); tab_print->update(); } @@ -985,10 +1017,10 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl tab_print->update_dirty(); }; - Option option = m_optgroup->get_option("fill_density"); + Option option = optgroup->get_option("fill_density"); option.opt.sidetext = ""; option.opt.full_width = true; - m_optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); ConfigOptionDef def; @@ -1007,7 +1039,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl def.default_value = new ConfigOptionStrings { selection }; option = Option(def, "support"); option.opt.full_width = true; - m_optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); m_brim_width = config->opt_float("brim_width"); def.label = L("Brim"); @@ -1016,7 +1048,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl def.gui_type = ""; def.default_value = new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }; option = Option(def, "brim"); - m_optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); Line line = { "", "" }; @@ -1041,9 +1073,11 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl })); return sizer; }; - m_optgroup->append_line(line); + optgroup->append_line(line); - sizer->Add(m_optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); + sizer->Add(optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); + + m_optgroups.push_back(optgroup);// ogFrequentlyChangingParameters } void show_frequently_changed_parameters(bool show) @@ -1094,9 +1128,9 @@ void update_mode() g_right_panel->Layout(); } -ConfigOptionsGroup* get_optgroup() +ConfigOptionsGroup* get_optgroup(size_t i) { - return m_optgroup.get(); + return m_optgroups[i].get(); } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 3681827b6d..243c63cc3e 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -47,9 +47,10 @@ class TabIface; namespace GUI { -enum ogDrawFlag{ - ogDEFAULT, - ogSIDE_OPTIONS_TO_GRID +enum ogGroup{ + ogFrequentlyChangingParameters, + ogObjectSettings, + ogPartSettings }; class Tab; @@ -173,7 +174,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl // Update view mode according to selected menu void update_mode(); -ConfigOptionsGroup* get_optgroup(); +ConfigOptionsGroup* get_optgroup(size_t i); wxButton* get_wiping_dialog_button(); void add_export_option(wxFileDialog* dlg, const std::string& format); diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index e17315802e..052bf6e6ca 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -175,9 +175,9 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // If we're here, we have more than one option or a single option with sidetext // so we need a horizontal sizer to arrange these things - // If we have a single option with no sidetext just add it directly to the grid sizer - auto sizer = new wxBoxSizer(wxHORIZONTAL); + auto sizer = new wxBoxSizer((m_flag & ogSIDE_OPTIONS_VERTICAL) != 0 ? wxVERTICAL :wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM|wxTOP|wxLEFT), staticbox ? 0 : 1); + // If we have a single option with no sidetext just add it directly to the grid sizer if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { const auto& option = option_set.front(); diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 7da39dbf9e..d302fa1ece 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -26,6 +26,11 @@ namespace Slic3r { namespace GUI { +enum ogDrawFlag{ + ogDEFAULT, + ogSIDE_OPTIONS_VERTICAL +}; + /// Widget type describes a function object that returns a wxWindow (our widget) and accepts a wxWidget (parent window). using widget_t = std::function;//!std::function; using column_t = std::function; @@ -183,8 +188,8 @@ protected: class ConfigOptionsGroup: public OptionsGroup { public: - ConfigOptionsGroup(wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, bool is_tab_opt = false) : - OptionsGroup(parent, title, is_tab_opt), m_config(_config) {} + ConfigOptionsGroup(wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, bool is_tab_opt = false, ogDrawFlag flag = ogDEFAULT) : + OptionsGroup(parent, title, is_tab_opt, flag), m_config(_config) {} /// reference to libslic3r config, non-owning pointer (?). DynamicPrintConfig* m_config {nullptr}; diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 52fc499d52..88a97c5c85 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -679,8 +679,8 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) } if (opt_key == "fill_density") { - boost::any val = get_optgroup()->get_config_value(*m_config, opt_key); - get_optgroup()->set_value(opt_key, val); + boost::any val = get_optgroup(ogFrequentlyChangingParameters)->get_config_value(*m_config, opt_key); + get_optgroup(ogFrequentlyChangingParameters)->set_value(opt_key, val); } if (opt_key == "support_material" || opt_key == "support_material_buildplate_only") { @@ -689,12 +689,12 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) m_config->opt_bool("support_material_buildplate_only") ? _("Support on build plate only") : _("Everywhere"); - get_optgroup()->set_value("support", new_selection); + get_optgroup(ogFrequentlyChangingParameters)->set_value("support", new_selection); } if (opt_key == "brim_width") { bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; - get_optgroup()->set_value("brim", val); + get_optgroup(ogFrequentlyChangingParameters)->set_value("brim", val); } if (opt_key == "wipe_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) @@ -784,18 +784,18 @@ void Tab::update_preset_description_line() void Tab::update_frequently_changed_parameters() { - boost::any value = get_optgroup()->get_config_value(*m_config, "fill_density"); - get_optgroup()->set_value("fill_density", value); + boost::any value = get_optgroup(ogFrequentlyChangingParameters)->get_config_value(*m_config, "fill_density"); + get_optgroup(ogFrequentlyChangingParameters)->set_value("fill_density", value); wxString new_selection = !m_config->opt_bool("support_material") ? _("None") : m_config->opt_bool("support_material_buildplate_only") ? _("Support on build plate only") : _("Everywhere"); - get_optgroup()->set_value("support", new_selection); + get_optgroup(ogFrequentlyChangingParameters)->set_value("support", new_selection); bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; - get_optgroup()->set_value("brim", val); + get_optgroup(ogFrequentlyChangingParameters)->set_value("brim", val); update_wiping_button_visibility(); } From a877846699efa4b4b25144d3c53cb49935580022 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 23 May 2018 16:21:42 +0200 Subject: [PATCH 016/185] Filling object settings (continue) --- lib/Slic3r/GUI/Plater.pm | 4 +- xs/src/slic3r/GUI/GUI.cpp | 62 +++++++++++++++++++++++------- xs/src/slic3r/GUI/OptionsGroup.cpp | 24 +++++++----- xs/src/slic3r/GUI/OptionsGroup.hpp | 3 +- xs/src/slic3r/GUI/wxExtensions.hpp | 6 +-- 5 files changed, 69 insertions(+), 30 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 619af05101..eec9b304a7 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -512,10 +512,10 @@ sub new { $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); - $right_sizer->SetMinSize([320, 600]); + $right_sizer->SetMinSize([320, -1]); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; - $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 0) if defined $expert_mode_part_sizer; + $right_sizer->Add($expert_mode_part_sizer, 1, wxEXPAND | wxTOP, 0) if defined $expert_mode_part_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); $right_sizer->Add($scrolled_window_panel, 1, wxEXPAND | wxALL, 1); # Callback for showing / hiding the print info box. diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index adb5eaf18f..22554bba56 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -120,6 +120,7 @@ wxLocale* g_wxLocale; std::vector > m_optgroups; double m_brim_width = 0.0; +size_t m_label_width = 100; wxButton* g_wiping_dialog_button = nullptr; //showed/hided controls according to the view mode @@ -878,33 +879,66 @@ wxBoxSizer* content_objects_list(wxWindow *win) return objects_sz; } -wxBoxSizer* content_object_settings(wxWindow *win) +Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value=0) { - DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; - std::shared_ptr optgroup = std::make_shared(win, "", config, false, ogSIDE_OPTIONS_VERTICAL); - optgroup->label_width = 100; - - Line line = { _(L("Position")), "" }; + Line line = { _(option_name), "" }; ConfigOptionDef def; def.label = L("X"); def.type = coInt; - def.default_value = new ConfigOptionInt(1); - def.sidetext = L("mm"); + def.default_value = new ConfigOptionInt(def_value); + def.sidetext = sidetext; + def.width = 50; - Option option = Option(def, "position_X"); + const std::string lower_name = boost::algorithm::to_lower_copy(option_name); + + Option option = Option(def, lower_name + "_X"); option.opt.full_width = true; line.append_option(option); def.label = L("Y"); - option = Option(def, "position_Y"); + option = Option(def, lower_name + "_Y"); line.append_option(option); def.label = L("Z"); - option = Option(def, "position_Z"); + option = Option(def, lower_name + "_Z"); line.append_option(option); + return line; +} - optgroup->append_line(line); +wxBoxSizer* content_object_settings(wxWindow *win) +{ + DynamicPrintConfig* config = /*&g_PresetBundle->full_config();*/&g_PresetBundle->prints.get_edited_preset().config; + std::shared_ptr optgroup = std::make_shared(win, "", config); + optgroup->label_width = m_label_width; + + ConfigOptionDef def; + def.label = L("Name"); + def.type = coString; + def.tooltip = L("Object name"); + def.full_width = true; + def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; + optgroup->append_single_option_line(Option(def, "object_name")); + + optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL); + + optgroup->append_line(add_og_to_object_settings(L("Position"), L("mm"))); + optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°"/*"\u00b0"*/, 1)); + optgroup->append_line(add_og_to_object_settings(L("Scale"), "%", 2)); + + optgroup->set_flag(ogDEFAULT); + + def.label = L("Place on bed"); + def.type = coBool; + def.tooltip = L("Automatic placing of models on printing bed in Y axis"); + def.gui_type = ""; + def.sidetext = ""; + def.default_value = new ConfigOptionBool{ false }; + optgroup->append_single_option_line(Option(def, "place_on_bed")); + + Option option = optgroup->get_option("extruder"); + option.opt.default_value = new ConfigOptionInt(1); + optgroup->append_single_option_line(option); m_optgroups.push_back(optgroup); // ogObjectSettings @@ -922,7 +956,6 @@ wxBoxSizer* content_part_settings(wxWindow *win) void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) { - sizer->SetMinSize(-1, 150); auto main_sizer = new wxBoxSizer(wxVERTICAL); auto main_page = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); main_page->SetSizer(main_sizer); @@ -963,7 +996,8 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; std::shared_ptr optgroup = std::make_shared(parent, "", config); const wxArrayInt& ar = preset_sizer->GetColWidths(); - optgroup->label_width = ar.IsEmpty() ? 100 : ar.front()-4; // doesn't work + m_label_width = ar.IsEmpty() ? 100 : ar.front()-4; + optgroup->label_width = m_label_width; optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ TabPrint* tab_print = nullptr; for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 052bf6e6ca..55887da91a 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -159,7 +159,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* wxDefaultPosition, wxSize(label_width, -1), staticbox ? 0 : wxALIGN_RIGHT); label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug - grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); + grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | + (m_flag == ogSIDE_OPTIONS_VERTICAL ? wxTOP : wxALIGN_CENTER_VERTICAL), 5); if (line.label_tooltip.compare("") != 0) label->SetToolTip(line.label_tooltip); } @@ -175,7 +176,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // If we're here, we have more than one option or a single option with sidetext // so we need a horizontal sizer to arrange these things - auto sizer = new wxBoxSizer((m_flag & ogSIDE_OPTIONS_VERTICAL) != 0 ? wxVERTICAL :wxHORIZONTAL); + auto sizer = new wxBoxSizer(m_flag == ogSIDE_OPTIONS_VERTICAL ? wxVERTICAL : wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM|wxTOP|wxLEFT), staticbox ? 0 : 1); // If we have a single option with no sidetext just add it directly to the grid sizer if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 && @@ -194,6 +195,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* for (auto opt : option_set) { ConfigOptionDef option = opt.opt; + wxBoxSizer* sizer_tmp = m_flag == ogSIDE_OPTIONS_VERTICAL ? new wxBoxSizer(wxHORIZONTAL) : sizer; // add label if any if (option.label != "") { wxString str_label = L_str(option.label); @@ -203,33 +205,35 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // L_str(option.label); label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize); label->SetFont(label_font); - sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); } // add field const Option& opt_ref = opt; auto& field = build_field(opt_ref, label); - add_undo_buttuns_to_sizer(sizer, field); + add_undo_buttuns_to_sizer(sizer_tmp, field); is_sizer_field(field) ? - sizer->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) : - sizer->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_tmp->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) : + sizer_tmp->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0); // add sidetext if any if (option.sidetext != "") { auto sidetext = new wxStaticText(parent(), wxID_ANY, L_str(option.sidetext), wxDefaultPosition, wxDefaultSize); sidetext->SetFont(sidetext_font); - sizer->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); + sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); } // add side widget if any if (opt.side_widget != nullptr) { - sizer->Add(opt.side_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification + sizer_tmp->Add(opt.side_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification } - if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back()) + if (opt.opt_id != option_set.back().opt_id && m_flag != ogSIDE_OPTIONS_VERTICAL) //! istead of (opt != option_set.back()) { - sizer->AddSpacer(6); + sizer_tmp->AddSpacer(6); } + if (m_flag == ogSIDE_OPTIONS_VERTICAL) + sizer->Add(sizer_tmp, 1, wxEXPAND|wxALIGN_RIGHT|wxALL, 0); } // add extra sizers if any for (auto extra_widget : line.get_extra_widgets()) { diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index d302fa1ece..8285dade6b 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -131,6 +131,7 @@ public: inline void enable() { for (auto& field : m_fields) field.second->enable(); } inline void disable() { for (auto& field : m_fields) field.second->disable(); } + void set_flag(ogDrawFlag flag) { m_flag = flag; } OptionsGroup(wxWindow* _parent, const wxString& title, bool is_tab_opt=false, ogDrawFlag flag = ogDEFAULT) : m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), staticbox(title!=""), m_flag(flag) { @@ -139,7 +140,7 @@ public: if (label_width != 0) num_columns++; if (extra_column != nullptr) num_columns++; m_grid_sizer = new wxFlexGridSizer(0, num_columns, 0,0); - static_cast(m_grid_sizer)->SetFlexibleDirection(wxHORIZONTAL); + static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/); static_cast(m_grid_sizer)->AddGrowableCol(label_width != 0); #ifdef __WXGTK__ m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index d4fdcb752b..1a34282c17 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -102,9 +102,9 @@ public: this->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, this](wxCommandEvent e){ wxWindowUpdateLocker noUpdates_cp(this); wxWindowUpdateLocker noUpdates(parent); - parent->GetParent()->Layout(); - parent->Layout(); - this->Refresh(); + parent->GetParent() ? parent->GetParent()->Layout() : //; + parent->Layout(); +// this->Refresh(); })); } From 46f71661b2d68f5805a3e9dc19dcc79efa78dd49 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 24 May 2018 16:57:35 +0200 Subject: [PATCH 017/185] Some changes in the concept of the new right column --- lib/Slic3r/GUI/Plater.pm | 11 ++- xs/src/slic3r/GUI/GUI.cpp | 151 +++++++++++++++++++++++------ xs/src/slic3r/GUI/GUI.hpp | 5 + xs/src/slic3r/GUI/OptionsGroup.cpp | 23 +++-- xs/src/slic3r/GUI/OptionsGroup.hpp | 12 ++- xs/src/slic3r/GUI/Tab.cpp | 4 +- 6 files changed, 158 insertions(+), 48 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5e568d2792..b446bf1248 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -226,14 +226,17 @@ sub new { } ### Panel for right column - $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); +# $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + $self->{right_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + $self->{right_panel}->SetScrollbars(0, 1, 1, 1); ### Scrolled Window for info boxes my $scrolled_window_sizer = Wx::BoxSizer->new(wxVERTICAL); $scrolled_window_sizer->SetMinSize([310, -1]); - my $scrolled_window_panel = Wx::ScrolledWindow->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); +# my $scrolled_window_panel = Wx::ScrolledWindow->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + my $scrolled_window_panel = Wx::Panel->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $scrolled_window_panel->SetSizer($scrolled_window_sizer); - $scrolled_window_panel->SetScrollbars(1, 1, 1, 1); +# $scrolled_window_panel->SetScrollbars(1, 1, 1, 1); $self->{list} = Wx::ListView->new($scrolled_window_panel, -1, wxDefaultPosition, wxDefaultSize, wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); @@ -517,7 +520,7 @@ sub new { $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; $right_sizer->Add($expert_mode_part_sizer, 1, wxEXPAND | wxTOP, 0) if defined $expert_mode_part_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); - $right_sizer->Add($scrolled_window_panel, 1, wxEXPAND | wxALL, 1); + $right_sizer->Add($scrolled_window_panel, 0, wxEXPAND | wxALL, 1); # Callback for showing / hiding the print info box. $self->{"print_info_box_show"} = sub { # if ($right_sizer->IsShown(5) != $_[0]) { diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 22554bba56..33572bd04d 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -136,6 +136,12 @@ wxStaticBitmap *g_manifold_warning_icon = nullptr; bool g_show_print_info = false; bool g_show_manifold_warning_icon = false; +wxFont g_small_font{ wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; +#ifdef __WXMAC__ +g_small_font->SetPointSize(11); +#endif /*__WXMAC__*/ +wxFont g_bold_font{ wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold() }; + static void init_label_colours() { auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -739,6 +745,14 @@ void set_label_clr_sys(const wxColour& clr) { g_AppConfig->save(); } +const wxFont& small_font(){ + return g_small_font; +} + +const wxFont& bold_font(){ + return g_bold_font; +} + const wxColour& get_label_clr_default() { return g_color_label_default; } @@ -879,6 +893,58 @@ wxBoxSizer* content_objects_list(wxWindow *win) return objects_sz; } +wxBoxSizer* content_edit_object_buttons(wxWindow* win) +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + + auto btn_load_part = new wxButton(win, wxID_ANY, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + auto btn_load_modifier = new wxButton(win, wxID_ANY, "Load modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, "Load generic…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + auto btn_delete = new wxButton(win, wxID_ANY, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + auto btn_split = new wxButton(win, wxID_ANY, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + auto btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); + auto btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); + btn_move_up->SetMinSize(wxSize(20, -1)); + btn_move_down->SetMinSize(wxSize(20, -1)); + btn_load_part->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); + btn_load_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); + btn_load_lambda_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); + btn_delete->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG)); + btn_split->SetBitmap(wxBitmap(from_u8(Slic3r::var("shape_ungroup.png")), wxBITMAP_TYPE_PNG)); + btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG)); + btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG)); + + auto buttons_object_sizer = new wxFlexGridSizer(1, 3, 0, 1); + buttons_object_sizer->SetFlexibleDirection(wxBOTH); + buttons_object_sizer->Add(btn_load_part, 0, wxEXPAND); + buttons_object_sizer->Add(btn_load_modifier, 0, wxEXPAND); + buttons_object_sizer->Add(btn_load_lambda_modifier, 0, wxEXPAND); + + auto buttons_part_sizer = new wxFlexGridSizer(1, 3, 0, 1); + buttons_part_sizer->SetFlexibleDirection(wxBOTH); + buttons_part_sizer->Add(btn_delete, 0, wxEXPAND); + buttons_part_sizer->Add(btn_split, 0, wxEXPAND); + { + auto up_down_sizer = new wxGridSizer(1, 2, 0, 1); + up_down_sizer->Add(btn_move_up, 1, wxEXPAND); + up_down_sizer->Add(btn_move_down, 1, wxEXPAND); + buttons_part_sizer->Add(up_down_sizer, 0, wxEXPAND); + } + buttons_part_sizer->Show(false); + + btn_load_part->SetFont(Slic3r::GUI::small_font()); + btn_load_modifier->SetFont(Slic3r::GUI::small_font()); + btn_load_lambda_modifier->SetFont(Slic3r::GUI::small_font()); + btn_delete->SetFont(Slic3r::GUI::small_font()); + btn_split->SetFont(Slic3r::GUI::small_font()); + btn_move_up->SetFont(Slic3r::GUI::small_font()); + btn_move_down->SetFont(Slic3r::GUI::small_font()); + + sizer->Add(buttons_object_sizer, 0, wxALIGN_CENTER_HORIZONTAL); + sizer->Add(buttons_part_sizer, 0, wxALIGN_CENTER_HORIZONTAL); + return sizer; +} + Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value=0) { Line line = { _(option_name), "" }; @@ -888,7 +954,7 @@ Line add_og_to_object_settings(const std::string& option_name, const std::string def.type = coInt; def.default_value = new ConfigOptionInt(def_value); def.sidetext = sidetext; - def.width = 50; + def.width = 70; const std::string lower_name = boost::algorithm::to_lower_copy(option_name); @@ -903,39 +969,29 @@ Line add_og_to_object_settings(const std::string& option_name, const std::string def.label = L("Z"); option = Option(def, lower_name + "_Z"); line.append_option(option); + + if (option_name == "Scale") + { + def.label = L("Units"); + def.type = coStrings; + def.gui_type = "select_open"; + def.enum_labels.push_back(L("%")); + def.enum_labels.push_back(L("mm")); + def.default_value = new ConfigOptionStrings{ "%" }; + def.sidetext = " "; + + option = Option(def, lower_name + "_unit"); + line.append_option(option); + } return line; } wxBoxSizer* content_object_settings(wxWindow *win) { - DynamicPrintConfig* config = /*&g_PresetBundle->full_config();*/&g_PresetBundle->prints.get_edited_preset().config; - std::shared_ptr optgroup = std::make_shared(win, "", config); + DynamicPrintConfig* config = &g_PresetBundle->printers.get_edited_preset().config; // TODO get config from Model_volume + std::shared_ptr optgroup = std::make_shared(win, "Extruders", config); optgroup->label_width = m_label_width; - ConfigOptionDef def; - def.label = L("Name"); - def.type = coString; - def.tooltip = L("Object name"); - def.full_width = true; - def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; - optgroup->append_single_option_line(Option(def, "object_name")); - - optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL); - - optgroup->append_line(add_og_to_object_settings(L("Position"), L("mm"))); - optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°"/*"\u00b0"*/, 1)); - optgroup->append_line(add_og_to_object_settings(L("Scale"), "%", 2)); - - optgroup->set_flag(ogDEFAULT); - - def.label = L("Place on bed"); - def.type = coBool; - def.tooltip = L("Automatic placing of models on printing bed in Y axis"); - def.gui_type = ""; - def.sidetext = ""; - def.default_value = new ConfigOptionBool{ false }; - optgroup->append_single_option_line(Option(def, "place_on_bed")); - Option option = optgroup->get_option("extruder"); option.opt.default_value = new ConfigOptionInt(1); optgroup->append_single_option_line(option); @@ -957,15 +1013,17 @@ wxBoxSizer* content_part_settings(wxWindow *win) void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) { auto main_sizer = new wxBoxSizer(wxVERTICAL); - auto main_page = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto main_page = new wxPanel/*ScrolledWindow*/(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); main_page->SetSizer(main_sizer); - main_page->SetScrollbars(1, 1, 1, 1); +// main_page->SetScrollbars(0, 1, 1, 1); sizer->Add(main_page, 1, wxEXPAND | wxALL, 1); // Experiments with new UI // *** Objects List *** add_prusa_collapsible_pane(main_page, main_sizer, "Objects List:", content_objects_list); + // *** Edit Object Buttons*** + main_sizer->Add(content_edit_object_buttons(main_page), 0, wxEXPAND, 0); // *** Object Settings *** add_prusa_collapsible_pane(main_page, main_sizer, "Object Settings:", content_object_settings); // *** Part Settings *** @@ -1109,9 +1167,42 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl }; optgroup->append_line(line); - sizer->Add(optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 2); m_optgroups.push_back(optgroup);// ogFrequentlyChangingParameters + + // Frequently Object Settings + optgroup = std::make_shared(parent, _(L("Object Settings")), config); + optgroup->label_width = 100; + optgroup->set_grid_vgap(5); + + def.label = L("Name"); + def.type = coString; + def.tooltip = L("Object name"); + def.full_width = true; + def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; + optgroup->append_single_option_line(Option(def, "object_name")); + + optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL); + optgroup->sidetext_width = 25; + + optgroup->append_line(add_og_to_object_settings(L("Position"), L("mm"))); + optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°", 1)); + optgroup->append_line(add_og_to_object_settings(L("Scale"), "%", 2)); + + optgroup->set_flag(ogDEFAULT); + + def.label = L("Place on bed"); + def.type = coBool; + def.tooltip = L("Automatic placing of models on printing bed in Y axis"); + def.gui_type = ""; + def.sidetext = ""; + def.default_value = new ConfigOptionBool{ false }; + optgroup->append_single_option_line(Option(def, "place_on_bed")); + + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxLEFT, 20); + + m_optgroups.push_back(optgroup); // ogFrequentlyObjectSettings } void show_frequently_changed_parameters(bool show) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 243c63cc3e..a7314541ec 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -21,6 +21,7 @@ class wxFlexGridSizer; class wxButton; class wxFileDialog; class wxStaticBitmap; +class wxFont; namespace Slic3r { @@ -49,6 +50,7 @@ namespace GUI { enum ogGroup{ ogFrequentlyChangingParameters, + ogFrequentlyObjectSettings, ogObjectSettings, ogPartSettings }; @@ -108,6 +110,9 @@ unsigned get_colour_approx_luma(const wxColour &colour); void set_label_clr_modified(const wxColour& clr); void set_label_clr_sys(const wxColour& clr); +const wxFont& small_font(); +const wxFont& bold_font(); + extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); // This is called when closing the application, when loading a config file or when starting the config wizard diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 55887da91a..f59fffd7f0 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -91,7 +91,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co return field; } -void OptionsGroup::add_undo_buttuns_to_sizer(wxBoxSizer* sizer, const t_field& field) +void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field) { if (!m_is_tab_opt) { field->m_Undo_btn->Hide(); @@ -177,7 +177,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // If we're here, we have more than one option or a single option with sidetext // so we need a horizontal sizer to arrange these things auto sizer = new wxBoxSizer(m_flag == ogSIDE_OPTIONS_VERTICAL ? wxVERTICAL : wxHORIZONTAL); - grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM|wxTOP|wxLEFT), staticbox ? 0 : 1); + grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); // If we have a single option with no sidetext just add it directly to the grid sizer if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { @@ -195,7 +195,14 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* for (auto opt : option_set) { ConfigOptionDef option = opt.opt; - wxBoxSizer* sizer_tmp = m_flag == ogSIDE_OPTIONS_VERTICAL ? new wxBoxSizer(wxHORIZONTAL) : sizer; + wxSizer* sizer_tmp; + if (m_flag == ogSIDE_OPTIONS_VERTICAL){ + auto sz = new wxFlexGridSizer(1, 3, 2, 2); + sz->RemoveGrowableCol(2); + sizer_tmp = sz; + } + else + sizer_tmp = sizer; // add label if any if (option.label != "") { wxString str_label = L_str(option.label); @@ -205,7 +212,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // L_str(option.label); label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize); label->SetFont(label_font); - sizer_tmp->Add(label, 0, wxALIGN_CENTER_VERTICAL, 0); + sizer_tmp->Add(label, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 0); } // add field @@ -218,9 +225,10 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // add sidetext if any if (option.sidetext != "") { - auto sidetext = new wxStaticText(parent(), wxID_ANY, L_str(option.sidetext), wxDefaultPosition, wxDefaultSize); + auto sidetext = new wxStaticText( parent(), wxID_ANY, L_str(option.sidetext), wxDefaultPosition, + wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT); sidetext->SetFont(sidetext_font); - sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); + sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, m_flag == ogSIDE_OPTIONS_VERTICAL ? 0 : 4); } // add side widget if any @@ -232,8 +240,9 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* { sizer_tmp->AddSpacer(6); } + if (m_flag == ogSIDE_OPTIONS_VERTICAL) - sizer->Add(sizer_tmp, 1, wxEXPAND|wxALIGN_RIGHT|wxALL, 0); + sizer->Add(sizer_tmp, 0, wxALIGN_RIGHT|wxALL, 0); } // add extra sizers if any for (auto extra_widget : line.get_extra_widgets()) { diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 8285dade6b..cd604fc8ed 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -93,8 +93,7 @@ public: wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; - -// std::function nonsys_btn_icon{ nullptr }; + int sidetext_width{ -1 }; /// Returns a copy of the pointer of the parent wxWindow. /// Accessor function is because users are not allowed to change the parent @@ -132,14 +131,17 @@ public: inline void enable() { for (auto& field : m_fields) field.second->enable(); } inline void disable() { for (auto& field : m_fields) field.second->disable(); } void set_flag(ogDrawFlag flag) { m_flag = flag; } + void set_grid_vgap(int gap) { m_grid_sizer->SetVGap(gap); } OptionsGroup(wxWindow* _parent, const wxString& title, bool is_tab_opt=false, ogDrawFlag flag = ogDEFAULT) : m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), staticbox(title!=""), m_flag(flag) { - sizer = (staticbox ? new wxStaticBoxSizer(new wxStaticBox(_parent, wxID_ANY, title), wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); + auto stb = new wxStaticBox(_parent, wxID_ANY, title); + stb->SetFont(bold_font()); + sizer = (staticbox ? new wxStaticBoxSizer(stb/*new wxStaticBox(_parent, wxID_ANY, title)*/, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; if (label_width != 0) num_columns++; if (extra_column != nullptr) num_columns++; - m_grid_sizer = new wxFlexGridSizer(0, num_columns, 0,0); + m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0); static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/); static_cast(m_grid_sizer)->AddGrowableCol(label_width != 0); #ifdef __WXGTK__ @@ -179,7 +181,7 @@ protected: const t_field& build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label = nullptr); const t_field& build_field(const t_config_option_key& id, wxStaticText* label = nullptr); const t_field& build_field(const Option& opt, wxStaticText* label = nullptr); - void add_undo_buttuns_to_sizer(wxBoxSizer* sizer, const t_field& field); + void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field); virtual void on_kill_focus (){}; virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 88a97c5c85..6b45abc926 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1446,7 +1446,7 @@ void TabPrinter::build() Line line{ _(L("Bed shape")), "" }; line.widget = [this](wxWindow* parent){ auto btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - // btn->SetFont(Slic3r::GUI::small_font); + btn->SetFont(Slic3r::GUI::small_font()); btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); auto sizer = new wxBoxSizer(wxHORIZONTAL); @@ -1514,7 +1514,7 @@ void TabPrinter::build() auto serial_test = [this](wxWindow* parent){ auto btn = m_serial_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); -// btn->SetFont($Slic3r::GUI::small_font); + btn->SetFont(Slic3r::GUI::small_font()); btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); From d93a8aec3df2ed60c4c6644e37a0423f09e248d5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Sun, 27 May 2018 22:12:01 +0200 Subject: [PATCH 018/185] New concept of the main IU. * only 2 mode - Regular & Expert * "Export Gcode" moved to bottom of the window (analogue to the PrusaControll) * Experiments with layout of collapsible_pane [! not successful] --- lib/Slic3r/GUI/Plater.pm | 12 ++- xs/src/slic3r/GUI/GUI.cpp | 148 ++++++++++++++++++----------- xs/src/slic3r/GUI/GUI.hpp | 1 + xs/src/slic3r/GUI/OptionsGroup.cpp | 2 +- xs/src/slic3r/GUI/Tab.cpp | 16 +--- xs/src/slic3r/GUI/wxExtensions.hpp | 26 ++++- xs/xsp/GUI.xsp | 2 + 7 files changed, 131 insertions(+), 76 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index b446bf1248..64dab1d810 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -256,7 +256,7 @@ sub new { }); # right pane buttons - $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxNO_BORDER);#, wxBU_LEFT); $self->{btn_reslice} = Wx::Button->new($self->{right_panel}, -1, L("Slice now"), wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_print} = Wx::Button->new($self->{right_panel}, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_send_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); @@ -266,12 +266,12 @@ sub new { $self->{btn_print}->Hide; $self->{btn_send_gcode}->Hide; + # export_gcode cog_go.png my %icons = qw( add brick_add.png remove brick_delete.png reset cross.png arrange bricks.png - export_gcode cog_go.png print arrow_up.png send_gcode arrow_up.png reslice reslice.png @@ -508,7 +508,7 @@ sub new { $buttons_sizer->Add($self->{btn_reslice}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_print}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT, 0); - $buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); + #$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5); $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); @@ -517,10 +517,11 @@ sub new { my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); $right_sizer->SetMinSize([320, -1]); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; - $right_sizer->Add($frequently_changed_parameters_sizer, 0, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; - $right_sizer->Add($expert_mode_part_sizer, 1, wxEXPAND | wxTOP, 0) if defined $expert_mode_part_sizer; + $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; + $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 0) if defined $expert_mode_part_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); $right_sizer->Add($scrolled_window_panel, 0, wxEXPAND | wxALL, 1); + $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20); # Callback for showing / hiding the print info box. $self->{"print_info_box_show"} = sub { # if ($right_sizer->IsShown(5) != $_[0]) { @@ -556,6 +557,7 @@ sub new { $frequently_changed_parameters_sizer, $expert_mode_part_sizer, $scrolled_window_sizer, + $self->{btn_export_gcode}, $self->{btn_export_stl}, $self->{btn_reslice}, $self->{btn_print}, diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 33572bd04d..ccdfcb6d5d 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -128,6 +128,7 @@ wxWindow *g_right_panel = nullptr; wxBoxSizer *g_frequently_changed_parameters_sizer = nullptr; wxBoxSizer *g_expert_mode_part_sizer = nullptr; wxBoxSizer *g_scrolled_window_sizer = nullptr; +wxButton *g_btn_export_gcode = nullptr; wxButton *g_btn_export_stl = nullptr; wxButton *g_btn_reslice = nullptr; wxButton *g_btn_print = nullptr; @@ -135,6 +136,9 @@ wxButton *g_btn_send_gcode = nullptr; wxStaticBitmap *g_manifold_warning_icon = nullptr; bool g_show_print_info = false; bool g_show_manifold_warning_icon = false; +wxSizer *m_sizer_object_buttons = nullptr; +wxSizer *m_sizer_part_buttons = nullptr; +wxDataViewCtrl *m_objects_ctrl = nullptr; wxFont g_small_font{ wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; #ifdef __WXMAC__ @@ -203,6 +207,7 @@ void set_preset_updater(PresetUpdater *updater) void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_parameters_sizer, wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer, + wxButton *btn_export_gcode, wxButton *btn_export_stl, wxButton *btn_reslice, wxButton *btn_print, wxButton *btn_send_gcode, wxStaticBitmap *manifold_warning_icon) @@ -211,6 +216,7 @@ void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_par g_frequently_changed_parameters_sizer = frequently_changed_parameters_sizer; g_expert_mode_part_sizer = expert_mode_part_sizer; g_scrolled_window_sizer = scrolled_window_sizer; + g_btn_export_gcode = btn_export_gcode; g_btn_export_stl = btn_export_stl; g_btn_reslice = btn_reslice; g_btn_print = btn_print; @@ -353,7 +359,6 @@ enum ConfigMenuIDs { ConfigMenuUpdate, ConfigMenuPreferences, ConfigMenuModeSimple, - ConfigMenuModeRegular, ConfigMenuModeExpert, ConfigMenuLanguage, ConfigMenuFlashFirmware, @@ -366,11 +371,7 @@ ConfigMenuIDs get_view_mode() return ConfigMenuModeSimple; const auto mode = g_AppConfig->get("view_mode"); - return mode == "expert" ? - ConfigMenuModeExpert : - mode == "regular" ? - ConfigMenuModeRegular : - ConfigMenuModeSimple; + return mode == "expert" ? ConfigMenuModeExpert : ConfigMenuModeSimple; } void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change) @@ -389,7 +390,6 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l local_menu->AppendSeparator(); auto mode_menu = new wxMenu(); mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("&Simple")), _(L("Simple View Mode"))); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeRegular, _(L("&Regular")), _(L("Regular View Mode"))); mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("&Expert")), _(L("Expert View Mode"))); mode_menu->Check(config_id_base + get_view_mode(), true); local_menu->AppendSubMenu(mode_menu, _(L("&Mode")), _(L("Slic3r View Mode"))); @@ -463,18 +463,8 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l } }); mode_menu->Bind(wxEVT_MENU, [config_id_base](wxEvent& event) { - std::string mode = ""; - switch (event.GetId() - config_id_base){ - case ConfigMenuModeExpert: - mode = "expert"; - break; - case ConfigMenuModeRegular: - mode = "regular"; - break; - case ConfigMenuModeSimple: - mode = "simple"; - break; - } + std::string mode = event.GetId() - config_id_base == ConfigMenuModeExpert ? + "expert" : "simple"; g_AppConfig->set("view_mode", mode); g_AppConfig->save(); update_mode(); @@ -839,9 +829,10 @@ wxString from_u8(const std::string &str) } // add PrusaCollapsiblePane to sizer -void add_prusa_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) +PrusaCollapsiblePane* add_prusa_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) { auto *collpane = new PrusaCollapsiblePane(parent, wxID_ANY, name); + collpane->SetTopParent(g_right_panel); // add the pane with a zero proportion value to the sizer which contains it sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0); @@ -853,42 +844,54 @@ void add_prusa_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, cons sizer_pane->Add(sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); win->SetSizer(sizer_pane); sizer_pane->SetSizeHints(win); + return collpane; } wxBoxSizer* content_objects_list(wxWindow *win) { - auto objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); - objects_ctrl->SetBestFittingSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects + m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); + m_objects_ctrl->SetBestFittingSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects auto objects_sz = new wxBoxSizer(wxVERTICAL); - objects_sz->Add(objects_ctrl, 1, wxGROW | wxALL, 5); + objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT/*ALL*/, 20/*5*/); auto objects_model = new MyObjectTreeModel; - objects_ctrl->AssociateModel(objects_model); + m_objects_ctrl->AssociateModel(objects_model); #if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - objects_ctrl->EnableDragSource(wxDF_UNICODETEXT); - objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); + m_objects_ctrl->EnableDragSource(wxDF_UNICODETEXT); + m_objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); #endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE // column 0 of the view control: wxDataViewTextRenderer *tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); - wxDataViewColumn *column00 = new wxDataViewColumn("Name", tr, 0, 140, wxALIGN_LEFT, + wxDataViewColumn *column00 = new wxDataViewColumn("Name", tr, 0, 110, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - objects_ctrl->AppendColumn(column00); + m_objects_ctrl->AppendColumn(column00); // column 1 of the view control: tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); wxDataViewColumn *column01 = new wxDataViewColumn("Copy", tr, 1, 75, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - objects_ctrl->AppendColumn(column01); + m_objects_ctrl->AppendColumn(column01); // column 2 of the view control: tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); wxDataViewColumn *column02 = new wxDataViewColumn("Scale", tr, 2, 80, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - objects_ctrl->AppendColumn(column02); + m_objects_ctrl->AppendColumn(column02); + + m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [objects_model](wxCommandEvent& evt) + { + wxWindowUpdateLocker noUpdates(g_right_panel); + auto item = m_objects_ctrl->GetSelection(); + auto show_obj_sizer = objects_model->GetParent(item) == wxDataViewItem(0); + m_sizer_object_buttons->Show(show_obj_sizer); + m_sizer_part_buttons->Show(!show_obj_sizer); + + g_right_panel->Layout(); + }); return objects_sz; } @@ -897,11 +900,11 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) { auto sizer = new wxBoxSizer(wxVERTICAL); - auto btn_load_part = new wxButton(win, wxID_ANY, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - auto btn_load_modifier = new wxButton(win, wxID_ANY, "Load modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, "Load generic…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - auto btn_delete = new wxButton(win, wxID_ANY, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - auto btn_split = new wxButton(win, wxID_ANY, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + auto btn_load_part = new wxButton(win, wxID_ANY, /*Load */"part…", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_load_modifier = new wxButton(win, wxID_ANY, /*Load */"modifier…", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, /*Load */"generic…", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_delete = new wxButton(win, wxID_ANY, "Delete"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_split = new wxButton(win, wxID_ANY, "Split"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); auto btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); auto btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); btn_move_up->SetMinSize(wxSize(20, -1)); @@ -914,23 +917,24 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG)); btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG)); - auto buttons_object_sizer = new wxFlexGridSizer(1, 3, 0, 1); - buttons_object_sizer->SetFlexibleDirection(wxBOTH); - buttons_object_sizer->Add(btn_load_part, 0, wxEXPAND); - buttons_object_sizer->Add(btn_load_modifier, 0, wxEXPAND); - buttons_object_sizer->Add(btn_load_lambda_modifier, 0, wxEXPAND); + m_sizer_object_buttons = new /*wxFlex*/wxGridSizer(1, 3, 0, 0); +// static_cast(m_sizer_object_buttons)->SetFlexibleDirection(wxBOTH); + m_sizer_object_buttons->Add(btn_load_part, 0, wxEXPAND); + m_sizer_object_buttons->Add(btn_load_modifier, 0, wxEXPAND); + m_sizer_object_buttons->Add(btn_load_lambda_modifier, 0, wxEXPAND); + m_sizer_object_buttons->Show(false); - auto buttons_part_sizer = new wxFlexGridSizer(1, 3, 0, 1); - buttons_part_sizer->SetFlexibleDirection(wxBOTH); - buttons_part_sizer->Add(btn_delete, 0, wxEXPAND); - buttons_part_sizer->Add(btn_split, 0, wxEXPAND); + m_sizer_part_buttons = new /*wxFlex*/wxGridSizer(1, 3, 0, 0); +// m_sizer_part_buttons->SetFlexibleDirection(wxBOTH); + m_sizer_part_buttons->Add(btn_delete, 0, wxEXPAND); + m_sizer_part_buttons->Add(btn_split, 0, wxEXPAND); { - auto up_down_sizer = new wxGridSizer(1, 2, 0, 1); + auto up_down_sizer = new wxGridSizer(1, 2, 0, 0); up_down_sizer->Add(btn_move_up, 1, wxEXPAND); up_down_sizer->Add(btn_move_down, 1, wxEXPAND); - buttons_part_sizer->Add(up_down_sizer, 0, wxEXPAND); + m_sizer_part_buttons->Add(up_down_sizer, 0, wxEXPAND); } - buttons_part_sizer->Show(false); + m_sizer_part_buttons->Show(false); btn_load_part->SetFont(Slic3r::GUI::small_font()); btn_load_modifier->SetFont(Slic3r::GUI::small_font()); @@ -940,8 +944,8 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) btn_move_up->SetFont(Slic3r::GUI::small_font()); btn_move_down->SetFont(Slic3r::GUI::small_font()); - sizer->Add(buttons_object_sizer, 0, wxALIGN_CENTER_HORIZONTAL); - sizer->Add(buttons_part_sizer, 0, wxALIGN_CENTER_HORIZONTAL); + sizer->Add(m_sizer_object_buttons, 0, wxEXPAND|wxLEFT, 20/*wxALIGN_CENTER_HORIZONTAL*/); + sizer->Add(m_sizer_part_buttons, 0, wxEXPAND|wxLEFT, 20/*wxALIGN_CENTER_HORIZONTAL*/); return sizer; } @@ -999,14 +1003,27 @@ wxBoxSizer* content_object_settings(wxWindow *win) m_optgroups.push_back(optgroup); // ogObjectSettings auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(optgroup->sizer, 1, wxEXPAND | wxBOTTOM, 2); + sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); + + return sizer; } wxBoxSizer* content_part_settings(wxWindow *win) { + DynamicPrintConfig* config = &g_PresetBundle->printers.get_edited_preset().config; // TODO get config from Model_volume + std::shared_ptr optgroup = std::make_shared(win, "Extruders", config); + optgroup->label_width = m_label_width; + + Option option = optgroup->get_option("extruder"); + option.opt.default_value = new ConfigOptionInt(1); + optgroup->append_single_option_line(option); + + m_optgroups.push_back(optgroup); // ogPartSettings + auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(new wxStaticText(win, wxID_ANY, "Some part text")); + sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); + return sizer; } @@ -1021,7 +1038,17 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) // Experiments with new UI // *** Objects List *** - add_prusa_collapsible_pane(main_page, main_sizer, "Objects List:", content_objects_list); + auto collpane = add_prusa_collapsible_pane(main_page, main_sizer, "Objects List:", content_objects_list); + collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent e){ + wxWindowUpdateLocker noUpdates(g_right_panel); + if (collpane->IsCollapsed()) { + m_sizer_object_buttons->Show(false); + m_sizer_part_buttons->Show(false); + } + else + m_objects_ctrl->UnselectAll(); + g_right_panel->Layout(); + })); // *** Edit Object Buttons*** main_sizer->Add(content_edit_object_buttons(main_page), 0, wxEXPAND, 0); // *** Object Settings *** @@ -1235,7 +1262,7 @@ void show_buttons(bool show) void show_scrolled_window_sizer(bool show) { - g_scrolled_window_sizer->Show(static_cast(0), show); + g_scrolled_window_sizer->Show(static_cast(0), false/*show*/); //don't used now g_scrolled_window_sizer->Show(1, show); g_scrolled_window_sizer->Show(2, show && g_show_print_info); g_manifold_warning_icon->Show(show && g_show_manifold_warning_icon); @@ -1243,13 +1270,22 @@ void show_scrolled_window_sizer(bool show) void update_mode() { + //TODO There is a not the best place of it! + //*** Update style of the "Export G-code" button**** + if (g_btn_export_gcode->GetFont() != bold_font()){ + g_btn_export_gcode->SetBackgroundColour(wxColour(252, 77, 1)); + g_btn_export_gcode->SetFont(bold_font()); + } + //************************************ + wxWindowUpdateLocker noUpdates(g_right_panel); ConfigMenuIDs mode = get_view_mode(); - show_frequently_changed_parameters(mode >= ConfigMenuModeRegular); +// show_frequently_changed_parameters(mode >= ConfigMenuModeRegular); g_expert_mode_part_sizer->Show(mode == ConfigMenuModeExpert); - show_scrolled_window_sizer(mode >= ConfigMenuModeRegular); - show_buttons(mode >= ConfigMenuModeRegular); + show_scrolled_window_sizer(mode == ConfigMenuModeExpert); + show_buttons(mode == ConfigMenuModeExpert); + g_right_panel->GetParent()->Layout(); g_right_panel->Layout(); } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index a7314541ec..19ee545337 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -91,6 +91,7 @@ void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_parameters_sizer, wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer, + wxButton *btn_export_gcode, wxButton *btn_export_stl, wxButton *btn_reslice, wxButton *btn_print, diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index f59fffd7f0..52882afdaf 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -266,7 +266,7 @@ void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost:: Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/) { if (!m_config->has(opt_key)) { - std::cerr << "No " << opt_key << " in ConfigOptionsGroup config."; + std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.\n"; } std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 6b45abc926..cecbb9daff 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -706,18 +706,12 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) // Show/hide the 'purging volumes' button void Tab::update_wiping_button_visibility() { - if (!get_app_config()->has("view_mode") || get_app_config()->get("view_mode") == "simple") - get_wiping_dialog_button()->Hide(); - else { - bool wipe_tower_enabled = dynamic_cast((m_preset_bundle->prints.get_edited_preset().config).option("wipe_tower"))->value; - bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; - bool single_extruder_mm = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; - - if (wipe_tower_enabled && multiple_extruders && single_extruder_mm) - get_wiping_dialog_button()->Show(); - else get_wiping_dialog_button()->Hide(); - } + bool wipe_tower_enabled = dynamic_cast((m_preset_bundle->prints.get_edited_preset().config).option("wipe_tower"))->value; + bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; + bool single_extruder_mm = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; + get_wiping_dialog_button()->Show(wipe_tower_enabled && multiple_extruders && single_extruder_mm); + (get_wiping_dialog_button()->GetParent())->Layout(); } diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 1a34282c17..b256ac8bbe 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -83,6 +84,7 @@ class PrusaCollapsiblePane : public wxCollapsiblePane wxBitmap m_bmp_close; wxBitmap m_bmp_open; #endif //__WXMSW__ + wxWindow* m_top_parent = nullptr; public: PrusaCollapsiblePane() {} PrusaCollapsiblePane( wxWindow *parent, @@ -102,14 +104,32 @@ public: this->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, this](wxCommandEvent e){ wxWindowUpdateLocker noUpdates_cp(this); wxWindowUpdateLocker noUpdates(parent); - parent->GetParent() ? parent->GetParent()->Layout() : //; - parent->Layout(); -// this->Refresh(); + + parent->Layout(); + this->Refresh(); + + if (m_top_parent) + { + m_top_parent->GetSizer()->Layout(); + } + else{ + wxGetTopLevelParent(this)->Layout(); + } + +// if (parent->GetParent()){ +// parent->GetParent()->Layout(); +// parent->Refresh(); +// } +// else{ +// parent->Layout(); +// this->Refresh();} })); } ~PrusaCollapsiblePane() {} + void SetTopParent(wxWindow *parent) { m_top_parent = parent; } + #ifdef __WXMSW__ bool Create(wxWindow *parent, wxWindowID id, diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 64ca4f28a3..f7d84d87c2 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -95,6 +95,7 @@ void set_objects_from_perl( SV *ui_parent, SV *frequently_changed_parameters_sizer, SV *expert_mode_part_sizer, SV *scrolled_window_sizer, + SV *btn_export_gcode, SV *btn_export_stl, SV *btn_reslice, SV *btn_print, @@ -105,6 +106,7 @@ void set_objects_from_perl( SV *ui_parent, (wxBoxSizer *)wxPli_sv_2_object(aTHX_ frequently_changed_parameters_sizer, "Wx::BoxSizer"), (wxBoxSizer *)wxPli_sv_2_object(aTHX_ expert_mode_part_sizer, "Wx::BoxSizer"), (wxBoxSizer *)wxPli_sv_2_object(aTHX_ scrolled_window_sizer, "Wx::BoxSizer"), + (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_gcode, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_stl, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_reslice, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_print, "Wx::Button"), From 3fb567d2860495ba99213b379b174fdeaa605b40 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 28 May 2018 11:10:09 +0200 Subject: [PATCH 019/185] Final prototype --- lib/Slic3r/GUI/Plater.pm | 6 ++++-- xs/src/slic3r/GUI/GUI.cpp | 41 +++++++++++++++------------------------ 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 64dab1d810..68a06b01dc 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -425,6 +425,7 @@ sub new { my $object_info_sizer; { my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info")); + $box->SetFont($Slic3r::GUI::small_bold_font); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $object_info_sizer->SetMinSize([300,-1]); my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); @@ -475,6 +476,7 @@ sub new { my $print_info_sizer; { my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info")); + $box->SetFont($Slic3r::GUI::small_bold_font); $print_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $print_info_sizer->SetMinSize([300,-1]); my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5); @@ -511,8 +513,8 @@ sub new { #$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5); - $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); - $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); + $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND | wxLEFT, 20); + $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND | wxLEFT, 20); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); $right_sizer->SetMinSize([320, -1]); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index ccdfcb6d5d..166fc9c267 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -139,6 +139,7 @@ bool g_show_manifold_warning_icon = false; wxSizer *m_sizer_object_buttons = nullptr; wxSizer *m_sizer_part_buttons = nullptr; wxDataViewCtrl *m_objects_ctrl = nullptr; +PrusaCollapsiblePane *m_collpane_settings = nullptr; wxFont g_small_font{ wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; #ifdef __WXMAC__ @@ -886,9 +887,12 @@ wxBoxSizer* content_objects_list(wxWindow *win) { wxWindowUpdateLocker noUpdates(g_right_panel); auto item = m_objects_ctrl->GetSelection(); + if (!item) return; auto show_obj_sizer = objects_model->GetParent(item) == wxDataViewItem(0); m_sizer_object_buttons->Show(show_obj_sizer); m_sizer_part_buttons->Show(!show_obj_sizer); + m_collpane_settings->SetLabelText((show_obj_sizer ? _(L("Object Settings")) : _(L("Part Settings"))) + ":"); + m_collpane_settings->Show(true); g_right_panel->Layout(); }); @@ -990,7 +994,7 @@ Line add_og_to_object_settings(const std::string& option_name, const std::string return line; } -wxBoxSizer* content_object_settings(wxWindow *win) +wxBoxSizer* content_settings(wxWindow *win) { DynamicPrintConfig* config = &g_PresetBundle->printers.get_edited_preset().config; // TODO get config from Model_volume std::shared_ptr optgroup = std::make_shared(win, "Extruders", config); @@ -1005,24 +1009,10 @@ wxBoxSizer* content_object_settings(wxWindow *win) auto sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); - - return sizer; -} - -wxBoxSizer* content_part_settings(wxWindow *win) -{ - DynamicPrintConfig* config = &g_PresetBundle->printers.get_edited_preset().config; // TODO get config from Model_volume - std::shared_ptr optgroup = std::make_shared(win, "Extruders", config); - optgroup->label_width = m_label_width; - - Option option = optgroup->get_option("extruder"); - option.opt.default_value = new ConfigOptionInt(1); - optgroup->append_single_option_line(option); - - m_optgroups.push_back(optgroup); // ogPartSettings - - auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); + auto add_btn = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); + sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT, 20); return sizer; } @@ -1030,9 +1020,8 @@ wxBoxSizer* content_part_settings(wxWindow *win) void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) { auto main_sizer = new wxBoxSizer(wxVERTICAL); - auto main_page = new wxPanel/*ScrolledWindow*/(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto main_page = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); main_page->SetSizer(main_sizer); -// main_page->SetScrollbars(0, 1, 1, 1); sizer->Add(main_page, 1, wxEXPAND | wxALL, 1); // Experiments with new UI @@ -1044,17 +1033,19 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) if (collpane->IsCollapsed()) { m_sizer_object_buttons->Show(false); m_sizer_part_buttons->Show(false); + m_collpane_settings->Show(false); } else m_objects_ctrl->UnselectAll(); g_right_panel->Layout(); })); + // *** Edit Object Buttons*** main_sizer->Add(content_edit_object_buttons(main_page), 0, wxEXPAND, 0); - // *** Object Settings *** - add_prusa_collapsible_pane(main_page, main_sizer, "Object Settings:", content_object_settings); - // *** Part Settings *** - add_prusa_collapsible_pane(main_page, main_sizer, "Part Settings:", content_part_settings); + + // *** Object/Part Settings *** + m_collpane_settings = add_prusa_collapsible_pane(main_page, main_sizer, "Settings:", content_settings); + m_collpane_settings->Show(false); // More experiments with UI From d7d0edf4dc35a2c2909a2ade506e75f628d4ab9a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 28 May 2018 12:04:39 +0200 Subject: [PATCH 020/185] edit_object_buttons moved to Object/Part Settings --- xs/src/slic3r/GUI/GUI.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 166fc9c267..bbb113105f 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1007,6 +1007,8 @@ wxBoxSizer* content_settings(wxWindow *win) m_optgroups.push_back(optgroup); // ogObjectSettings auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(content_edit_object_buttons(win), 0, wxEXPAND, 0); // *** Edit Object Buttons*** + sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); auto add_btn = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); @@ -1040,9 +1042,6 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) g_right_panel->Layout(); })); - // *** Edit Object Buttons*** - main_sizer->Add(content_edit_object_buttons(main_page), 0, wxEXPAND, 0); - // *** Object/Part Settings *** m_collpane_settings = add_prusa_collapsible_pane(main_page, main_sizer, "Settings:", content_settings); m_collpane_settings->Show(false); From c7d7da452e28eac4c3b62ecfd670793dc7dce296 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 28 May 2018 17:08:48 +0200 Subject: [PATCH 021/185] Updated Collapsed/Layout for PrusaCollapsiblePane. Cleaned right_panel --- lib/Slic3r/GUI/Plater.pm | 48 +++++++++++++----------------- xs/src/slic3r/GUI/GUI.cpp | 47 +++++++++++++---------------- xs/src/slic3r/GUI/wxExtensions.cpp | 42 +++++++++++++++++++++++++- xs/src/slic3r/GUI/wxExtensions.hpp | 31 ++----------------- 4 files changed, 86 insertions(+), 82 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 68a06b01dc..6649e491a6 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -230,15 +230,7 @@ sub new { $self->{right_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{right_panel}->SetScrollbars(0, 1, 1, 1); - ### Scrolled Window for info boxes - my $scrolled_window_sizer = Wx::BoxSizer->new(wxVERTICAL); - $scrolled_window_sizer->SetMinSize([310, -1]); -# my $scrolled_window_panel = Wx::ScrolledWindow->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - my $scrolled_window_panel = Wx::Panel->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $scrolled_window_panel->SetSizer($scrolled_window_sizer); -# $scrolled_window_panel->SetScrollbars(1, 1, 1, 1); - - $self->{list} = Wx::ListView->new($scrolled_window_panel, -1, wxDefaultPosition, wxDefaultSize, + $self->{list} = Wx::ListView->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); $self->{list}->InsertColumn(0, L("Name"), wxLIST_FORMAT_LEFT, 145); $self->{list}->InsertColumn(1, L("Copies"), wxLIST_FORMAT_CENTER, 45); @@ -424,7 +416,7 @@ sub new { my $object_info_sizer; { - my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info")); + my $box = Wx::StaticBox->new($self->{right_panel}, -1, L("Info")); $box->SetFont($Slic3r::GUI::small_bold_font); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $object_info_sizer->SetMinSize([300,-1]); @@ -443,14 +435,14 @@ sub new { ); while (my $field = shift @info) { my $label = shift @info; - my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + my $text = Wx::StaticText->new($self->{right_panel}, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $text->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($text, 0); - $self->{"object_info_$field"} = Wx::StaticText->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + $self->{"object_info_$field"} = Wx::StaticText->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); if ($field eq 'manifold') { - $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($scrolled_window_panel, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); + $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self->{right_panel}, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); #$self->{object_info_manifold_warning_icon}->Hide; $self->{"object_info_manifold_warning_icon_show"} = sub { if ($self->{object_info_manifold_warning_icon}->IsShown() != $_[0]) { @@ -475,7 +467,7 @@ sub new { my $print_info_sizer; { - my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info")); + my $box = Wx::StaticBox->new($self->{right_panel}, -1, L("Sliced Info")); $box->SetFont($Slic3r::GUI::small_bold_font); $print_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $print_info_sizer->SetMinSize([300,-1]); @@ -493,11 +485,11 @@ sub new { ); while (my $field = shift @info) { my $label = shift @info; - my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + my $text = Wx::StaticText->new($self->{right_panel}, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($text, 0); - $self->{"print_info_$field"} = Wx::StaticText->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + $self->{"print_info_$field"} = Wx::StaticText->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"print_info_$field"}->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($self->{"print_info_$field"}, 0); } @@ -511,18 +503,22 @@ sub new { $buttons_sizer->Add($self->{btn_print}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT, 0); #$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); - - $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5); - $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND | wxLEFT, 20); - $scrolled_window_sizer->Add($print_info_sizer, 0, wxEXPAND | wxLEFT, 20); + + ### Sizer for info boxes + my $info_sizer = Wx::BoxSizer->new(wxVERTICAL); + $info_sizer->SetMinSize([310, -1]); + $info_sizer->Add($self->{list}, 1, wxEXPAND, 5); + $info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); + $info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); + $self->{right_panel}->SetSizer($right_sizer); $right_sizer->SetMinSize([320, -1]); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 0) if defined $expert_mode_part_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); - $right_sizer->Add($scrolled_window_panel, 0, wxEXPAND | wxALL, 1); + $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20); # Callback for showing / hiding the print info box. $self->{"print_info_box_show"} = sub { @@ -530,18 +526,16 @@ sub new { # $right_sizer->Show(5, $_[0]); # $self->Layout # } - if ($scrolled_window_sizer->IsShown(2) != $_[0]) { + if ($info_sizer->IsShown(2) != $_[0]) { Slic3r::GUI::set_show_print_info($_[0]); return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); - $scrolled_window_sizer->Show(2, $_[0]); - $scrolled_window_panel->Layout + $info_sizer->Show(2, $_[0]); + $self->{right_panel}->Layout } }; # Show the box initially, let it be shown after the slicing is finished. $self->{"print_info_box_show"}->(0); - $self->{right_panel}->SetSizer($right_sizer); - my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); $hsizer->Add($self->{right_panel}, 0, wxEXPAND | wxLEFT | wxRIGHT, 3); @@ -558,7 +552,7 @@ sub new { Slic3r::GUI::set_objects_from_perl( $self->{right_panel}, $frequently_changed_parameters_sizer, $expert_mode_part_sizer, - $scrolled_window_sizer, + $info_sizer, $self->{btn_export_gcode}, $self->{btn_export_stl}, $self->{btn_reslice}, diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index bbb113105f..1700512079 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -142,10 +142,11 @@ wxDataViewCtrl *m_objects_ctrl = nullptr; PrusaCollapsiblePane *m_collpane_settings = nullptr; wxFont g_small_font{ wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; -#ifdef __WXMAC__ -g_small_font->SetPointSize(11); -#endif /*__WXMAC__*/ wxFont g_bold_font{ wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold() }; +#ifdef __WXMAC__ +g_small_font.SetPointSize(11); +g_bold_font.SetPointSize(11); +#endif /*__WXMAC__*/ static void init_label_colours() { @@ -833,7 +834,6 @@ wxString from_u8(const std::string &str) PrusaCollapsiblePane* add_prusa_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) { auto *collpane = new PrusaCollapsiblePane(parent, wxID_ANY, name); - collpane->SetTopParent(g_right_panel); // add the pane with a zero proportion value to the sizer which contains it sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0); @@ -844,14 +844,14 @@ PrusaCollapsiblePane* add_prusa_collapsible_pane(wxWindow* parent, wxBoxSizer* s wxSizer *sizer_pane = new wxBoxSizer(wxVERTICAL); sizer_pane->Add(sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); win->SetSizer(sizer_pane); - sizer_pane->SetSizeHints(win); +// sizer_pane->SetSizeHints(win); return collpane; } wxBoxSizer* content_objects_list(wxWindow *win) { m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); - m_objects_ctrl->SetBestFittingSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects + m_objects_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects auto objects_sz = new wxBoxSizer(wxVERTICAL); objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT/*ALL*/, 20/*5*/); @@ -883,18 +883,17 @@ wxBoxSizer* content_objects_list(wxWindow *win) wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); m_objects_ctrl->AppendColumn(column02); - m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [objects_model](wxCommandEvent& evt) + m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [objects_model](wxEvent& evt) { wxWindowUpdateLocker noUpdates(g_right_panel); auto item = m_objects_ctrl->GetSelection(); if (!item) return; +// m_objects_ctrl->SetSize(m_objects_ctrl->GetBestSize()); // TODO override GetBestSize(), than use it auto show_obj_sizer = objects_model->GetParent(item) == wxDataViewItem(0); m_sizer_object_buttons->Show(show_obj_sizer); m_sizer_part_buttons->Show(!show_obj_sizer); m_collpane_settings->SetLabelText((show_obj_sizer ? _(L("Object Settings")) : _(L("Part Settings"))) + ":"); - m_collpane_settings->Show(true); - - g_right_panel->Layout(); + m_collpane_settings->show_it(true); }); return objects_sz; @@ -921,15 +920,13 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG)); btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG)); - m_sizer_object_buttons = new /*wxFlex*/wxGridSizer(1, 3, 0, 0); -// static_cast(m_sizer_object_buttons)->SetFlexibleDirection(wxBOTH); + m_sizer_object_buttons = new wxGridSizer(1, 3, 0, 0); m_sizer_object_buttons->Add(btn_load_part, 0, wxEXPAND); m_sizer_object_buttons->Add(btn_load_modifier, 0, wxEXPAND); m_sizer_object_buttons->Add(btn_load_lambda_modifier, 0, wxEXPAND); m_sizer_object_buttons->Show(false); - m_sizer_part_buttons = new /*wxFlex*/wxGridSizer(1, 3, 0, 0); -// m_sizer_part_buttons->SetFlexibleDirection(wxBOTH); + m_sizer_part_buttons = new wxGridSizer(1, 3, 0, 0); m_sizer_part_buttons->Add(btn_delete, 0, wxEXPAND); m_sizer_part_buttons->Add(btn_split, 0, wxEXPAND); { @@ -948,8 +945,8 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) btn_move_up->SetFont(Slic3r::GUI::small_font()); btn_move_down->SetFont(Slic3r::GUI::small_font()); - sizer->Add(m_sizer_object_buttons, 0, wxEXPAND|wxLEFT, 20/*wxALIGN_CENTER_HORIZONTAL*/); - sizer->Add(m_sizer_part_buttons, 0, wxEXPAND|wxLEFT, 20/*wxALIGN_CENTER_HORIZONTAL*/); + sizer->Add(m_sizer_object_buttons, 0, wxEXPAND|wxLEFT, 20); + sizer->Add(m_sizer_part_buttons, 0, wxEXPAND|wxLEFT, 20); return sizer; } @@ -1021,31 +1018,29 @@ wxBoxSizer* content_settings(wxWindow *win) void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) { - auto main_sizer = new wxBoxSizer(wxVERTICAL); - auto main_page = new wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - main_page->SetSizer(main_sizer); - sizer->Add(main_page, 1, wxEXPAND | wxALL, 1); + wxWindowUpdateLocker noUpdates(parent); // Experiments with new UI // *** Objects List *** - auto collpane = add_prusa_collapsible_pane(main_page, main_sizer, "Objects List:", content_objects_list); - collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent e){ + auto collpane = add_prusa_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); + collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){ + e.Skip(); wxWindowUpdateLocker noUpdates(g_right_panel); if (collpane->IsCollapsed()) { m_sizer_object_buttons->Show(false); m_sizer_part_buttons->Show(false); - m_collpane_settings->Show(false); + m_collpane_settings->show_it(false); } else m_objects_ctrl->UnselectAll(); + g_right_panel->Layout(); })); // *** Object/Part Settings *** - m_collpane_settings = add_prusa_collapsible_pane(main_page, main_sizer, "Settings:", content_settings); - m_collpane_settings->Show(false); - + m_collpane_settings = add_prusa_collapsible_pane(parent, sizer, "Settings:", content_settings); + m_collpane_settings->Hide(); // ? TODO why doesn't work? // More experiments with UI // auto listctrl = new wxDataViewListCtrl(main_page, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index d993b22403..eb851ffd47 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -257,6 +257,36 @@ void PrusaCollapsiblePane::UpdateBtnBmp() Layout(); } +void PrusaCollapsiblePane::OnStateChange_(const wxSize& sz) +{ + SetSize(sz); + + if (this->HasFlag(wxCP_NO_TLW_RESIZE)) + { + // the user asked to explicitly handle the resizing itself... + return; + } + + auto top = GetParent(); //right_panel + if (!top) + return; + + wxSizer *sizer = top->GetSizer(); + if (!sizer) + return; + + const wxSize newBestSize = sizer->ComputeFittingClientSize(top); + top->SetMinClientSize(newBestSize); + + wxWindowUpdateLocker noUpdates_p(top->GetParent()); + // we shouldn't attempt to resize a maximized window, whatever happens +// if (!top->IsMaximized()) +// top->SetClientSize(newBestSize); + top->GetParent()->Layout(); + top->Refresh(); +} + + void PrusaCollapsiblePane::Collapse(bool collapse) { // optimization @@ -268,10 +298,20 @@ void PrusaCollapsiblePane::Collapse(bool collapse) // update our state m_pPane->Show(!collapse); + // update button label +#if defined( __WXMAC__ ) && !defined(__WXUNIVERSAL__) + m_pButton->SetOpen(!collapse); +#else +#ifdef __WXMSW__ // update button bitmap UpdateBtnBmp(); +#else + // NB: this must be done after updating our "state" + m_pButton->SetLabel(GetBtnLabel()); +#endif //__WXMSW__ +#endif - OnStateChange(GetBestSize()); + OnStateChange_(GetBestSize()); } void PrusaCollapsiblePane::SetLabel(const wxString &label) diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index b256ac8bbe..51c77210e4 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -84,7 +84,6 @@ class PrusaCollapsiblePane : public wxCollapsiblePane wxBitmap m_bmp_close; wxBitmap m_bmp_open; #endif //__WXMSW__ - wxWindow* m_top_parent = nullptr; public: PrusaCollapsiblePane() {} PrusaCollapsiblePane( wxWindow *parent, @@ -101,35 +100,10 @@ public: #else Create(parent, winid, label); #endif //__WXMSW__ - this->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([parent, this](wxCommandEvent e){ - wxWindowUpdateLocker noUpdates_cp(this); - wxWindowUpdateLocker noUpdates(parent); - - parent->Layout(); - this->Refresh(); - - if (m_top_parent) - { - m_top_parent->GetSizer()->Layout(); - } - else{ - wxGetTopLevelParent(this)->Layout(); - } - -// if (parent->GetParent()){ -// parent->GetParent()->Layout(); -// parent->Refresh(); -// } -// else{ -// parent->Layout(); -// this->Refresh();} - })); } ~PrusaCollapsiblePane() {} - void SetTopParent(wxWindow *parent) { m_top_parent = parent; } - #ifdef __WXMSW__ bool Create(wxWindow *parent, wxWindowID id, @@ -141,11 +115,12 @@ public: const wxString& name); void UpdateBtnBmp(); - void Collapse(bool collapse) override; void SetLabel(const wxString &label) override; bool Layout() override; #endif //__WXMSW__ - + void Collapse(bool collapse) override; + void OnStateChange_(const wxSize& sz); //override of OnStateChange + void show_it(bool show) { Show(show); OnStateChange_(GetBestSize()); } }; From 45b6c99353c062558d04a3d453008cd0501dfd8d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 29 May 2018 20:55:43 +0200 Subject: [PATCH 022/185] print_info_box is correct placed on right_panel --- lib/Slic3r/GUI/Plater.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 6649e491a6..98c9a9466a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -529,8 +529,9 @@ sub new { if ($info_sizer->IsShown(2) != $_[0]) { Slic3r::GUI::set_show_print_info($_[0]); return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); - $info_sizer->Show(2, $_[0]); - $self->{right_panel}->Layout + $info_sizer->Show(2, $_[0]); + $self->Layout; + $self->{right_panel}->Refresh; } }; # Show the box initially, let it be shown after the slicing is finished. From db7c58009c1ea375f72c7feac9cbb5305b2ec55b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 29 May 2018 22:45:35 +0200 Subject: [PATCH 023/185] Added "Add/Delete" functions to the MyObjectTreeModel --- xs/src/slic3r/GUI/GUI.cpp | 34 ++++++++++++++--- xs/src/slic3r/GUI/wxExtensions.cpp | 60 +++++++++++++++++++++--------- xs/src/slic3r/GUI/wxExtensions.hpp | 10 ++--- 3 files changed, 76 insertions(+), 28 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 1700512079..991402a51f 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -138,7 +138,8 @@ bool g_show_print_info = false; bool g_show_manifold_warning_icon = false; wxSizer *m_sizer_object_buttons = nullptr; wxSizer *m_sizer_part_buttons = nullptr; -wxDataViewCtrl *m_objects_ctrl = nullptr; +wxDataViewCtrl *m_objects_ctrl = nullptr; +MyObjectTreeModel *m_objects_model = nullptr; PrusaCollapsiblePane *m_collpane_settings = nullptr; wxFont g_small_font{ wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; @@ -853,10 +854,10 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_objects_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects auto objects_sz = new wxBoxSizer(wxVERTICAL); - objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT/*ALL*/, 20/*5*/); + objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT, 20); - auto objects_model = new MyObjectTreeModel; - m_objects_ctrl->AssociateModel(objects_model); + m_objects_model = new MyObjectTreeModel; + m_objects_ctrl->AssociateModel(m_objects_model); #if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE m_objects_ctrl->EnableDragSource(wxDF_UNICODETEXT); m_objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); @@ -883,13 +884,13 @@ wxBoxSizer* content_objects_list(wxWindow *win) wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); m_objects_ctrl->AppendColumn(column02); - m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [objects_model](wxEvent& evt) + m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& evt) { wxWindowUpdateLocker noUpdates(g_right_panel); auto item = m_objects_ctrl->GetSelection(); if (!item) return; // m_objects_ctrl->SetSize(m_objects_ctrl->GetBestSize()); // TODO override GetBestSize(), than use it - auto show_obj_sizer = objects_model->GetParent(item) == wxDataViewItem(0); + auto show_obj_sizer = m_objects_model->GetParent(item) == wxDataViewItem(0); m_sizer_object_buttons->Show(show_obj_sizer); m_sizer_part_buttons->Show(!show_obj_sizer); m_collpane_settings->SetLabelText((show_obj_sizer ? _(L("Object Settings")) : _(L("Part Settings"))) + ":"); @@ -910,6 +911,17 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) auto btn_split = new wxButton(win, wxID_ANY, "Split"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); auto btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); auto btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); + + //*** button's functions + btn_load_part->Bind(wxEVT_BUTTON, [](wxEvent&) + { + auto item = m_objects_ctrl->GetSelection(); + if (!item) return; + wxString name = "Part"; + m_objects_model->AddChild(item, name); + }); + //*** + btn_move_up->SetMinSize(wxSize(20, -1)); btn_move_down->SetMinSize(wxSize(20, -1)); btn_load_part->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); @@ -1021,6 +1033,10 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) wxWindowUpdateLocker noUpdates(parent); // Experiments with new UI + auto add_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); + sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT, 20); // *** Objects List *** auto collpane = add_prusa_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); @@ -1042,6 +1058,12 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) m_collpane_settings = add_prusa_collapsible_pane(parent, sizer, "Settings:", content_settings); m_collpane_settings->Hide(); // ? TODO why doesn't work? + add_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) + { + wxString name = "Object"; + m_objects_model->Add(name); + }); + // More experiments with UI // auto listctrl = new wxDataViewListCtrl(main_page, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); // listctrl->AppendToggleColumn("Toggle"); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index eb851ffd47..7e7792ee49 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -352,26 +352,52 @@ bool PrusaCollapsiblePane::Layout() // MyObjectTreeModel // ---------------------------------------------------------------------------- -MyObjectTreeModel::MyObjectTreeModel() +void MyObjectTreeModel::Add(wxString &name) { - auto root1 = new MyObjectTreeModelNode("Object1"); - m_objects.emplace(root1); + auto root = new MyObjectTreeModelNode(name); + m_objects.emplace(root); + // notify control + wxDataViewItem child((void*)root); + wxDataViewItem parent((void*)NULL); + ItemAdded(parent, child); +} - auto root2 = new MyObjectTreeModelNode("Object2"); - m_objects.emplace(root2); - root2->Append(new MyObjectTreeModelNode(root2, "SubObject2_1")); - root2->Append(new MyObjectTreeModelNode(root2, "SubObject2_2")); - root2->Append(new MyObjectTreeModelNode(root2, "SubObject2_3")); +void MyObjectTreeModel::AddChild(const wxDataViewItem &parent_item, wxString &name) +{ + MyObjectTreeModelNode *root = (MyObjectTreeModelNode*)parent_item.GetID(); + if (!root) return; - auto root3 = new MyObjectTreeModelNode("Object3"); - m_objects.emplace(root3); - auto root4 = new MyObjectTreeModelNode("Object4"); - m_objects.emplace(root4); - root4->Append(new MyObjectTreeModelNode(root4, "SubObject4_1")); - root4->Append(new MyObjectTreeModelNode(root4, "SubObject4_2")); + auto node = new MyObjectTreeModelNode(root, name); + root->Append(node); + // notify control + wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); +} - auto root5 = new MyObjectTreeModelNode("Object5"); - m_objects.emplace(root5); +void MyObjectTreeModel::Delete(const wxDataViewItem &item) +{ + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return; + + wxDataViewItem parent(node->GetParent()); + if (!parent.IsOk()) + { +// wxASSERT(node == m_root); + // don't make the control completely empty: + //wxLogError("Cannot remove the root item!"); + return; + } + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + node->GetParent()->GetChildren().Remove(node); + // free the node + delete node; + + // notify control + ItemDeleted(parent, item); } wxString MyObjectTreeModel::GetName(const wxDataViewItem &item) const @@ -463,7 +489,7 @@ wxDataViewItem MyObjectTreeModel::GetParent(const wxDataViewItem &item) const MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); - // objects nodes also has no parent + // objects nodes has no parent too if (m_objects.find(node) != m_objects.end()) return wxDataViewItem(0); diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 51c77210e4..02bcfe148b 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -209,13 +209,17 @@ class MyObjectTreeModel :public wxDataViewModel { std::set m_objects; public: - MyObjectTreeModel(); + MyObjectTreeModel(){} ~MyObjectTreeModel() { for (auto object : m_objects) delete object; } + void Add(wxString &name); + void AddChild(const wxDataViewItem &parent_item, wxString &name); + void Delete(const wxDataViewItem &item); + // helper method for wxLog wxString GetName(const wxDataViewItem &item) const; @@ -224,10 +228,6 @@ public: // helper methods to change the model -// void AddToClassical(const wxString &title, const wxString &artist, -// unsigned int year); -// void Delete(const wxDataViewItem &item); - virtual unsigned int GetColumnCount() const override { return 3;} virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } From 5c4c9121323b5f5d04db737fc046c7f1ada861b2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 30 May 2018 00:36:44 +0200 Subject: [PATCH 024/185] Extended "Delete" functions --- xs/src/slic3r/GUI/GUI.cpp | 23 +++++++++++++++++++- xs/src/slic3r/GUI/wxExtensions.cpp | 34 +++++++++++++++++++++--------- xs/src/slic3r/GUI/wxExtensions.hpp | 3 ++- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 991402a51f..296dd76915 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -920,6 +920,13 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) wxString name = "Part"; m_objects_model->AddChild(item, name); }); + + btn_delete->Bind(wxEVT_BUTTON, [](wxEvent&) + { + auto item = m_objects_ctrl->GetSelection(); + if (!item) return; + m_objects_model->Delete(item); + }); //*** btn_move_up->SetMinSize(wxSize(20, -1)); @@ -1036,7 +1043,12 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) auto add_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); - sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT, 20); + sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT | wxTOP, 20); + + auto del_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) del_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + del_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG)); + sizer->Add(del_btn, 0, wxALIGN_LEFT | wxLEFT, 20); // *** Objects List *** auto collpane = add_prusa_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); @@ -1064,6 +1076,15 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) m_objects_model->Add(name); }); + del_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) + { + auto item = m_objects_ctrl->GetSelection(); + if (!item) return; + m_objects_model->Delete(item); + if (m_objects_model->IsEmpty()) + m_collpane_settings->show_it(false); + }); + // More experiments with UI // auto listctrl = new wxDataViewListCtrl(main_page, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); // listctrl->AppendToggleColumn("Toggle"); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 7e7792ee49..84953ebaaa 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -367,6 +367,15 @@ void MyObjectTreeModel::AddChild(const wxDataViewItem &parent_item, wxString &na MyObjectTreeModelNode *root = (MyObjectTreeModelNode*)parent_item.GetID(); if (!root) return; + if (root->GetChildren().Count() == 0) + { + auto node = new MyObjectTreeModelNode(root, root->m_name); + root->Append(node); + // notify control + wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + } + auto node = new MyObjectTreeModelNode(root, name); root->Append(node); // notify control @@ -380,22 +389,27 @@ void MyObjectTreeModel::Delete(const wxDataViewItem &item) if (!node) // happens if item.IsOk()==false return; - wxDataViewItem parent(node->GetParent()); - if (!parent.IsOk()) - { -// wxASSERT(node == m_root); - // don't make the control completely empty: - //wxLogError("Cannot remove the root item!"); - return; - } + auto node_parent = node->GetParent(); + wxDataViewItem parent(node_parent); // first remove the node from the parent's array of children; // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ // thus removing the node from it doesn't result in freeing it - node->GetParent()->GetChildren().Remove(node); + if (node_parent) + node_parent->GetChildren().Remove(node); + else + { + auto it = m_objects.find(node); + if (it != m_objects.end()) + m_objects.erase(it); + } // free the node delete node; + // set m_containet to FALSE if parent has no child + if (node_parent && node_parent->GetChildCount() == 0) + node_parent->m_container = false; + // notify control ItemDeleted(parent, item); } @@ -498,7 +512,7 @@ wxDataViewItem MyObjectTreeModel::GetParent(const wxDataViewItem &item) const bool MyObjectTreeModel::IsContainer(const wxDataViewItem &item) const { - // the invisble root node can have children + // the invisible root node can have children if (!item.IsOk()) return true; diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 02bcfe148b..cabd1e7caa 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -195,7 +195,7 @@ public: m_container = true; m_children.Add(child); } - unsigned int GetChildCount() const + size_t GetChildCount() const { return m_children.GetCount(); } @@ -219,6 +219,7 @@ public: void Add(wxString &name); void AddChild(const wxDataViewItem &parent_item, wxString &name); void Delete(const wxDataViewItem &item); + bool IsEmpty() { return m_objects.empty(); } // helper method for wxLog From c857b68fbebec2745ea7f10654585c16eba14e54 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 30 May 2018 17:52:28 +0200 Subject: [PATCH 025/185] Functions "Add/Delete" objects to/from list works correct now --- xs/src/slic3r/GUI/GUI.cpp | 34 ++++++++++++++++-------- xs/src/slic3r/GUI/GUI.hpp | 4 +++ xs/src/slic3r/GUI/wxExtensions.cpp | 42 +++++++++++++++++++----------- xs/src/slic3r/GUI/wxExtensions.hpp | 8 +++--- xs/xsp/GUI.xsp | 6 +++++ 5 files changed, 64 insertions(+), 30 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 296dd76915..77a1a54b8a 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -917,15 +917,18 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) { auto item = m_objects_ctrl->GetSelection(); if (!item) return; + if (m_objects_model->GetParent(item) != wxDataViewItem(0)) + item = m_objects_model->GetParent(item); + if (!item) return; wxString name = "Part"; - m_objects_model->AddChild(item, name); + m_objects_ctrl->Select(m_objects_model->AddChild(item, name)); }); btn_delete->Bind(wxEVT_BUTTON, [](wxEvent&) { auto item = m_objects_ctrl->GetSelection(); if (!item) return; - m_objects_model->Delete(item); + m_objects_ctrl->Select(m_objects_model->Delete(item)); }); //*** @@ -1035,6 +1038,22 @@ wxBoxSizer* content_settings(wxWindow *win) return sizer; } +void add_object(const std::string &name) +{ + wxString item = name; + m_objects_ctrl->Select(m_objects_model->Add(item)); +} + +void del_object() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item) return; + m_objects_ctrl->Select(m_objects_model->Delete(item)); + + if (m_objects_model->IsEmpty()) + m_collpane_settings->show_it(false); +} + void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) { wxWindowUpdateLocker noUpdates(parent); @@ -1073,17 +1092,10 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) add_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) { wxString name = "Object"; - m_objects_model->Add(name); + m_objects_ctrl->Select(m_objects_model->Add(name)); }); - del_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) - { - auto item = m_objects_ctrl->GetSelection(); - if (!item) return; - m_objects_model->Delete(item); - if (m_objects_model->IsEmpty()) - m_collpane_settings->show_it(false); - }); + del_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) { del_object(); }); // More experiments with UI // auto listctrl = new wxDataViewListCtrl(main_page, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 19ee545337..79dc2c5954 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -174,6 +174,10 @@ wxString L_str(const std::string &str); // Return wxString from std::string in UTF8 wxString from_u8(const std::string &str); +// Add object to the list +void add_object(const std::string &name); +// Delete object from the list +void del_object(); void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 84953ebaaa..ecb79caaf9 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -352,20 +352,21 @@ bool PrusaCollapsiblePane::Layout() // MyObjectTreeModel // ---------------------------------------------------------------------------- -void MyObjectTreeModel::Add(wxString &name) +wxDataViewItem MyObjectTreeModel::Add(wxString &name) { auto root = new MyObjectTreeModelNode(name); - m_objects.emplace(root); + m_objects.push_back(root); // notify control wxDataViewItem child((void*)root); wxDataViewItem parent((void*)NULL); ItemAdded(parent, child); + return child; } -void MyObjectTreeModel::AddChild(const wxDataViewItem &parent_item, wxString &name) +wxDataViewItem MyObjectTreeModel::AddChild(const wxDataViewItem &parent_item, wxString &name) { MyObjectTreeModelNode *root = (MyObjectTreeModelNode*)parent_item.GetID(); - if (!root) return; + if (!root) return wxDataViewItem(0); if (root->GetChildren().Count() == 0) { @@ -381,13 +382,15 @@ void MyObjectTreeModel::AddChild(const wxDataViewItem &parent_item, wxString &na // notify control wxDataViewItem child((void*)node); ItemAdded(parent_item, child); + return child; } -void MyObjectTreeModel::Delete(const wxDataViewItem &item) +wxDataViewItem MyObjectTreeModel::Delete(const wxDataViewItem &item) { + auto ret_item = wxDataViewItem(0); MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); if (!node) // happens if item.IsOk()==false - return; + return ret_item; auto node_parent = node->GetParent(); wxDataViewItem parent(node_parent); @@ -395,23 +398,37 @@ void MyObjectTreeModel::Delete(const wxDataViewItem &item) // first remove the node from the parent's array of children; // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ // thus removing the node from it doesn't result in freeing it - if (node_parent) + if (node_parent){ + auto id = node_parent->GetChildren().Index(node); node_parent->GetChildren().Remove(node); + if (id > 0){ + if(id == node_parent->GetChildCount()) id--; + ret_item = wxDataViewItem(node_parent->GetChildren().Item(id)); + } + } else { - auto it = m_objects.find(node); + auto it = find(m_objects.begin(), m_objects.end(), node); + auto id = it - m_objects.begin(); if (it != m_objects.end()) m_objects.erase(it); + if (id > 0){ + if(id == m_objects.size()) id--; + ret_item = wxDataViewItem(m_objects[id]); + } } // free the node delete node; // set m_containet to FALSE if parent has no child - if (node_parent && node_parent->GetChildCount() == 0) + if (node_parent && node_parent->GetChildCount() == 0){ node_parent->m_container = false; + ret_item = parent; + } // notify control ItemDeleted(parent, item); + return ret_item; } wxString MyObjectTreeModel::GetName(const wxDataViewItem &item) const @@ -441,11 +458,6 @@ wxString MyObjectTreeModel::GetScale(const wxDataViewItem &item) const return node->m_scale; } -// void MyObjectTreeModel::Delete(const wxDataViewItem &item) -// { -// -// } - void MyObjectTreeModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const { wxASSERT(item.IsOk()); @@ -504,7 +516,7 @@ wxDataViewItem MyObjectTreeModel::GetParent(const wxDataViewItem &item) const MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); // objects nodes has no parent too - if (m_objects.find(node) != m_objects.end()) + if (find(m_objects.begin(), m_objects.end(),node) != m_objects.end()) return wxDataViewItem(0); return wxDataViewItem((void*)node->GetParent()); diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index cabd1e7caa..329eecd598 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -207,7 +207,7 @@ public: class MyObjectTreeModel :public wxDataViewModel { - std::set m_objects; + std::vector m_objects; public: MyObjectTreeModel(){} ~MyObjectTreeModel() @@ -216,9 +216,9 @@ public: delete object; } - void Add(wxString &name); - void AddChild(const wxDataViewItem &parent_item, wxString &name); - void Delete(const wxDataViewItem &item); + wxDataViewItem Add(wxString &name); + wxDataViewItem AddChild(const wxDataViewItem &parent_item, wxString &name); + wxDataViewItem Delete(const wxDataViewItem &item); bool IsEmpty() { return m_objects.empty(); } // helper method for wxLog diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index f7d84d87c2..9059541721 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -122,6 +122,12 @@ void set_show_manifold_warning_icon(bool show) void update_mode() %code%{ Slic3r::GUI::update_mode(); %}; +void add_object(const char *name) + %code%{ Slic3r::GUI::add_object(name); %}; + +void del_object() + %code%{ Slic3r::GUI::del_object(); %}; + std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; From 04dc50cec4d20079319f98588e86303ef2484645 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 4 Jun 2018 15:59:55 +0200 Subject: [PATCH 026/185] Add, Delete and DeleteAll are working for new list now --- lib/Slic3r/GUI/Plater.pm | 11 +++++++-- xs/src/slic3r/GUI/GUI.cpp | 38 ++++++++++++++++++++++-------- xs/src/slic3r/GUI/GUI.hpp | 7 ++++-- xs/src/slic3r/GUI/wxExtensions.cpp | 34 +++++++++++++++++++++++++- xs/src/slic3r/GUI/wxExtensions.hpp | 23 +++++++++++++++--- xs/xsp/GUI.xsp | 11 +++++---- 6 files changed, 102 insertions(+), 22 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 98c9a9466a..a3f381e5c2 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -515,10 +515,10 @@ sub new { $self->{right_panel}->SetSizer($right_sizer); $right_sizer->SetMinSize([320, -1]); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; - $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; + $right_sizer->Add($frequently_changed_parameters_sizer, 2, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 0) if defined $expert_mode_part_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); - $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); + $right_sizer->Add($info_sizer, 1, wxEXPAND | wxLEFT, 20); $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20); # Callback for showing / hiding the print info box. $self->{"print_info_box_show"} = sub { @@ -847,6 +847,9 @@ sub load_model_objects { $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); + + # Add object to list on c++ side + Slic3r::GUI::add_object_to_list($object->name, $model_object->instances_count, ($model_object->instances->[0]->scaling_factor * 100)); $self->reset_thumbnail($obj_idx); } @@ -894,6 +897,8 @@ sub remove { $self->{model}->delete_object($obj_idx); $self->{print}->delete_object($obj_idx); $self->{list}->DeleteItem($obj_idx); + # Delete object from list on c++ side + Slic3r::GUI::delete_object_from_list(); $self->object_list_changed; $self->select_object(undef); @@ -917,6 +922,8 @@ sub reset { $self->{model}->clear_objects; $self->{print}->clear_objects; $self->{list}->DeleteAllItems; + # Delete all objects from list on c++ side + Slic3r::GUI::delete_all_objects_from_list(); $self->object_list_changed; $self->select_object(undef); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 77a1a54b8a..7f26816f0e 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -477,7 +477,7 @@ void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_l void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change) { - add_config_menu(menu, event_language_change, event_language_change); + add_config_menu(menu, event_preferences_changed, event_language_change); } // This is called when closing the application, when loading a config file or when starting the config wizard @@ -1038,36 +1038,52 @@ wxBoxSizer* content_settings(wxWindow *win) return sizer; } -void add_object(const std::string &name) +void add_object_to_list(const std::string &name, int instances_count, int scale) { wxString item = name; - m_objects_ctrl->Select(m_objects_model->Add(item)); + m_objects_ctrl->Select(m_objects_model->Add(item, instances_count, scale)); } -void del_object() +void delete_object_from_list() { auto item = m_objects_ctrl->GetSelection(); - if (!item) return; - m_objects_ctrl->Select(m_objects_model->Delete(item)); + if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) + return; +// m_objects_ctrl->Select(m_objects_model->Delete(item)); + m_objects_model->Delete(item); if (m_objects_model->IsEmpty()) m_collpane_settings->show_it(false); } +void delete_all_objects_from_list() +{ + m_objects_model->DeleteAll(); + m_collpane_settings->show_it(false); +} + void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) { wxWindowUpdateLocker noUpdates(parent); + auto btn_grid_sizer = new wxGridSizer(1, 3, 2, 2); + sizer->Add(btn_grid_sizer, 0, wxALIGN_LEFT | wxLEFT, 20); + // Experiments with new UI auto add_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); - sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT | wxTOP, 20); + btn_grid_sizer->Add(add_btn, 0, wxEXPAND, 0); auto del_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); if (wxMSW) del_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); del_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG)); - sizer->Add(del_btn, 0, wxALIGN_LEFT | wxLEFT, 20); + btn_grid_sizer->Add(del_btn, 0, wxEXPAND, 0); + + auto del_all_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) del_all_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + del_all_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("delete.png")), wxBITMAP_TYPE_PNG)); + btn_grid_sizer->Add(del_all_btn, 0, wxEXPAND, 0); // *** Objects List *** auto collpane = add_prusa_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); @@ -1095,7 +1111,9 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) m_objects_ctrl->Select(m_objects_model->Add(name)); }); - del_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) { del_object(); }); + del_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) { delete_object_from_list(); }); + + del_all_btn->Bind(wxEVT_BUTTON, [](wxEvent&) { delete_all_objects_from_list(); }); // More experiments with UI // auto listctrl = new wxDataViewListCtrl(main_page, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); @@ -1302,7 +1320,7 @@ void show_buttons(bool show) void show_scrolled_window_sizer(bool show) { - g_scrolled_window_sizer->Show(static_cast(0), false/*show*/); //don't used now + g_scrolled_window_sizer->Show(static_cast(0), /*false*/show); //don't used now g_scrolled_window_sizer->Show(1, show); g_scrolled_window_sizer->Show(2, show && g_show_print_info); g_manifold_warning_icon->Show(show && g_show_manifold_warning_icon); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 79dc2c5954..293183e7d4 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -175,9 +175,12 @@ wxString L_str(const std::string &str); wxString from_u8(const std::string &str); // Add object to the list -void add_object(const std::string &name); +//void add_object(const std::string &name); +void add_object_to_list(const std::string &name, int instances_count=1, int scale=100); // Delete object from the list -void del_object(); +void delete_object_from_list(); +// Delete all objects from the list +void delete_all_objects_from_list(); void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index ecb79caaf9..268267cd28 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -363,6 +363,17 @@ wxDataViewItem MyObjectTreeModel::Add(wxString &name) return child; } +wxDataViewItem MyObjectTreeModel::Add(wxString &name, int instances_count, int scale) +{ + auto root = new MyObjectTreeModelNode(name, instances_count, scale); + m_objects.push_back(root); + // notify control + wxDataViewItem child((void*)root); + wxDataViewItem parent((void*)NULL); + ItemAdded(parent, child); + return child; +} + wxDataViewItem MyObjectTreeModel::AddChild(const wxDataViewItem &parent_item, wxString &name) { MyObjectTreeModelNode *root = (MyObjectTreeModelNode*)parent_item.GetID(); @@ -431,6 +442,27 @@ wxDataViewItem MyObjectTreeModel::Delete(const wxDataViewItem &item) return ret_item; } +void MyObjectTreeModel::DeleteAll() +{ + while (!m_objects.empty()) + { + auto object = m_objects.back(); +// object->RemoveAllChildren(); + Delete(wxDataViewItem(object)); + } +} + +wxDataViewItem MyObjectTreeModel::GetItemById(int obj_idx) +{ + if (obj_idx >= m_objects.size()) + { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + return wxDataViewItem(m_objects[obj_idx]); +} + + wxString MyObjectTreeModel::GetName(const wxDataViewItem &item) const { MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); @@ -475,7 +507,7 @@ void MyObjectTreeModel::GetValue(wxVariant &variant, const wxDataViewItem &item, variant = node->m_scale; break; default: - ;// wxLogError("MyMusicTreeModel::GetValue: wrong column %d", col); + ; } } diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 329eecd598..189637de53 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -137,11 +137,11 @@ class MyObjectTreeModelNode MyObjectTreeModelNode* m_parent; MyObjectTreeModelNodePtrArray m_children; public: - MyObjectTreeModelNode( const wxString &name) { + MyObjectTreeModelNode(const wxString &name, int instances_count=1, int scale=100) { m_parent = NULL; m_name = name; - m_copy = "1"; - m_scale = "100%"; + m_copy = wxString::Format("%d", instances_count); + m_scale = wxString::Format("%d%%", scale); } MyObjectTreeModelNode( MyObjectTreeModelNode* parent, @@ -195,6 +195,20 @@ public: m_container = true; m_children.Add(child); } + void RemoveAllChildren() + { + if (GetChildCount() == 0) + return; + for (size_t id = GetChildCount() - 1; id >= 0; --id) + { + if (m_children.Item(id)->GetChildCount() > 0) + m_children[id]->RemoveAllChildren(); + auto node = m_children[id]; + m_children.RemoveAt(id); + delete node; + } + } + size_t GetChildCount() const { return m_children.GetCount(); @@ -217,8 +231,11 @@ public: } wxDataViewItem Add(wxString &name); + wxDataViewItem Add(wxString &name, int instances_count, int scale); wxDataViewItem AddChild(const wxDataViewItem &parent_item, wxString &name); wxDataViewItem Delete(const wxDataViewItem &item); + void DeleteAll(); + wxDataViewItem GetItemById(int obj_idx); bool IsEmpty() { return m_objects.empty(); } // helper method for wxLog diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 9059541721..b38970c099 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -122,11 +122,14 @@ void set_show_manifold_warning_icon(bool show) void update_mode() %code%{ Slic3r::GUI::update_mode(); %}; -void add_object(const char *name) - %code%{ Slic3r::GUI::add_object(name); %}; +void add_object_to_list(const char *name, int instances_count, int scale) + %code%{ Slic3r::GUI::add_object_to_list(name, instances_count, scale); %}; -void del_object() - %code%{ Slic3r::GUI::del_object(); %}; +void delete_object_from_list() + %code%{ Slic3r::GUI::delete_object_from_list(); %}; + +void delete_all_objects_from_list() + %code%{ Slic3r::GUI::delete_all_objects_from_list(); %}; std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; From bb27d62ba8029dc8ca81bd820d89fc289d354f3d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 5 Jun 2018 09:13:03 +0200 Subject: [PATCH 027/185] Try to fix compilation bug --- xs/src/slic3r/GUI/GUI.cpp | 17 +++-- xs/src/slic3r/GUI/wxExtensions.cpp | 113 ++++++++++++++--------------- 2 files changed, 67 insertions(+), 63 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 7f26816f0e..9473dd23c8 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -142,12 +142,8 @@ wxDataViewCtrl *m_objects_ctrl = nullptr; MyObjectTreeModel *m_objects_model = nullptr; PrusaCollapsiblePane *m_collpane_settings = nullptr; -wxFont g_small_font{ wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; -wxFont g_bold_font{ wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold() }; -#ifdef __WXMAC__ -g_small_font.SetPointSize(11); -g_bold_font.SetPointSize(11); -#endif /*__WXMAC__*/ +wxFont g_small_font { wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; +wxFont g_bold_font { wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold() }; static void init_label_colours() { @@ -177,10 +173,19 @@ void update_label_colours_from_appconfig() } } +static void init_fonts() +{ +#ifdef __WXMAC__ + g_small_font.SetPointSize(11); + g_bold_font.SetPointSize(11); +#endif /*__WXMAC__*/ +} + void set_wxapp(wxApp *app) { g_wxApp = app; init_label_colours(); + init_fonts(); } void set_main_frame(wxFrame *main_frame) diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 268267cd28..970102ec0c 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -257,63 +257,6 @@ void PrusaCollapsiblePane::UpdateBtnBmp() Layout(); } -void PrusaCollapsiblePane::OnStateChange_(const wxSize& sz) -{ - SetSize(sz); - - if (this->HasFlag(wxCP_NO_TLW_RESIZE)) - { - // the user asked to explicitly handle the resizing itself... - return; - } - - auto top = GetParent(); //right_panel - if (!top) - return; - - wxSizer *sizer = top->GetSizer(); - if (!sizer) - return; - - const wxSize newBestSize = sizer->ComputeFittingClientSize(top); - top->SetMinClientSize(newBestSize); - - wxWindowUpdateLocker noUpdates_p(top->GetParent()); - // we shouldn't attempt to resize a maximized window, whatever happens -// if (!top->IsMaximized()) -// top->SetClientSize(newBestSize); - top->GetParent()->Layout(); - top->Refresh(); -} - - -void PrusaCollapsiblePane::Collapse(bool collapse) -{ - // optimization - if (IsCollapsed() == collapse) - return; - - InvalidateBestSize(); - - // update our state - m_pPane->Show(!collapse); - - // update button label -#if defined( __WXMAC__ ) && !defined(__WXUNIVERSAL__) - m_pButton->SetOpen(!collapse); -#else -#ifdef __WXMSW__ - // update button bitmap - UpdateBtnBmp(); -#else - // NB: this must be done after updating our "state" - m_pButton->SetLabel(GetBtnLabel()); -#endif //__WXMSW__ -#endif - - OnStateChange_(GetBestSize()); -} - void PrusaCollapsiblePane::SetLabel(const wxString &label) { m_strLabel = label; @@ -347,6 +290,62 @@ bool PrusaCollapsiblePane::Layout() } #endif //__WXMSW__ +void PrusaCollapsiblePane::OnStateChange_(const wxSize& sz) +{ + SetSize(sz); + + if (this->HasFlag(wxCP_NO_TLW_RESIZE)) + { + // the user asked to explicitly handle the resizing itself... + return; + } + + auto top = GetParent(); //right_panel + if (!top) + return; + + wxSizer *sizer = top->GetSizer(); + if (!sizer) + return; + + const wxSize newBestSize = sizer->ComputeFittingClientSize(top); + top->SetMinClientSize(newBestSize); + + wxWindowUpdateLocker noUpdates_p(top->GetParent()); + // we shouldn't attempt to resize a maximized window, whatever happens +// if (!top->IsMaximized()) +// top->SetClientSize(newBestSize); + top->GetParent()->Layout(); + top->Refresh(); +} + +void PrusaCollapsiblePane::Collapse(bool collapse) +{ + // optimization + if (IsCollapsed() == collapse) + return; + + InvalidateBestSize(); + + // update our state + m_pPane->Show(!collapse); + + // update button label +#if defined( __WXMAC__ ) && !defined(__WXUNIVERSAL__) + m_pButton->SetOpen(!collapse); +#else +#ifdef __WXMSW__ + // update button bitmap + UpdateBtnBmp(); +#else + // NB: this must be done after updating our "state" + m_pButton->SetLabel(GetBtnLabel()); +#endif //__WXMSW__ +#endif + + OnStateChange_(GetBestSize()); +} + // ***************************************************************************** // ---------------------------------------------------------------------------- // MyObjectTreeModel From 72541ad13e3c8580a57512d02a5c2a90d584837e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 5 Jun 2018 10:41:20 +0200 Subject: [PATCH 028/185] Next try to fix OSX/Linux compilation bug --- xs/src/slic3r/GUI/GUI.cpp | 22 +++++++++++++--------- xs/src/slic3r/GUI/wxExtensions.cpp | 8 ++++++++ xs/src/slic3r/GUI/wxExtensions.hpp | 27 ++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 9473dd23c8..b0a7751a5b 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -140,7 +140,7 @@ wxSizer *m_sizer_object_buttons = nullptr; wxSizer *m_sizer_part_buttons = nullptr; wxDataViewCtrl *m_objects_ctrl = nullptr; MyObjectTreeModel *m_objects_model = nullptr; -PrusaCollapsiblePane *m_collpane_settings = nullptr; +wxCollapsiblePane *m_collpane_settings = nullptr; wxFont g_small_font { wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; wxFont g_bold_font { wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold() }; @@ -836,10 +836,14 @@ wxString from_u8(const std::string &str) return wxString::FromUTF8(str.c_str()); } -// add PrusaCollapsiblePane to sizer -PrusaCollapsiblePane* add_prusa_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) +// add Collapsible Pane to sizer +wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) { +#ifdef __WXMSW__ auto *collpane = new PrusaCollapsiblePane(parent, wxID_ANY, name); +#else + auto *collpane = new wxCollapsiblePane(parent, wxID_ANY, name); +#endif // __WXMSW__ // add the pane with a zero proportion value to the sizer which contains it sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0); @@ -899,7 +903,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_sizer_object_buttons->Show(show_obj_sizer); m_sizer_part_buttons->Show(!show_obj_sizer); m_collpane_settings->SetLabelText((show_obj_sizer ? _(L("Object Settings")) : _(L("Part Settings"))) + ":"); - m_collpane_settings->show_it(true); + m_collpane_settings->Show(true); }); return objects_sz; @@ -1058,13 +1062,13 @@ void delete_object_from_list() m_objects_model->Delete(item); if (m_objects_model->IsEmpty()) - m_collpane_settings->show_it(false); + m_collpane_settings->Show(false); } void delete_all_objects_from_list() { m_objects_model->DeleteAll(); - m_collpane_settings->show_it(false); + m_collpane_settings->Show(false); } void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) @@ -1091,14 +1095,14 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) btn_grid_sizer->Add(del_all_btn, 0, wxEXPAND, 0); // *** Objects List *** - auto collpane = add_prusa_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); + auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){ e.Skip(); wxWindowUpdateLocker noUpdates(g_right_panel); if (collpane->IsCollapsed()) { m_sizer_object_buttons->Show(false); m_sizer_part_buttons->Show(false); - m_collpane_settings->show_it(false); + m_collpane_settings->Show(false); } else m_objects_ctrl->UnselectAll(); @@ -1107,7 +1111,7 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) })); // *** Object/Part Settings *** - m_collpane_settings = add_prusa_collapsible_pane(parent, sizer, "Settings:", content_settings); + m_collpane_settings = add_collapsible_pane(parent, sizer, "Settings:", content_settings); m_collpane_settings->Hide(); // ? TODO why doesn't work? add_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 970102ec0c..b7bc58ed6c 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -533,6 +533,14 @@ bool MyObjectTreeModel::SetValue(const wxVariant &variant, const wxDataViewItem return false; } +bool MyObjectTreeModel::SetValue(const wxVariant &variant, const int item_idx, unsigned int col) +{ + if (item_idx < 0 || item_idx >= m_objects.size()) + return false; + + return m_objects[item_idx]->SetValue(variant, col); +} + // bool MyObjectTreeModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const // { // diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 189637de53..c677db80df 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -120,7 +120,11 @@ public: #endif //__WXMSW__ void Collapse(bool collapse) override; void OnStateChange_(const wxSize& sz); //override of OnStateChange - void show_it(bool show) { Show(show); OnStateChange_(GetBestSize()); } + virtual bool Show(bool show=true) override { + wxCollapsiblePane::Show(show); + OnStateChange_(GetBestSize()); + return true; + } }; @@ -213,6 +217,26 @@ public: { return m_children.GetCount(); } + + bool SetValue(const wxVariant &variant, unsigned int col) + { + switch (col) + { + case 0: + m_name = variant.GetString(); + return true; + case 1: + m_copy = variant.GetString(); + return true; + case 2: + m_scale = variant.GetString(); + return true; + + default: + printf("MyObjectTreeModel::SetValue: wrong column"); + } + return false; + } }; // ---------------------------------------------------------------------------- @@ -253,6 +277,7 @@ public: const wxDataViewItem &item, unsigned int col) const override; virtual bool SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned int col) override; + bool SetValue(const wxVariant &variant, const int item_idx, unsigned int col); // virtual bool IsEnabled(const wxDataViewItem &item, // unsigned int col) const override; From dcf0b432cbce5cc5418f552a3aae2ca5c87d627b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 5 Jun 2018 11:17:37 +0200 Subject: [PATCH 029/185] PrusaCollapsiblePane is used only on MSW --- xs/src/slic3r/GUI/wxExtensions.cpp | 14 ++------------ xs/src/slic3r/GUI/wxExtensions.hpp | 13 +++---------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index b7bc58ed6c..662369e054 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -190,7 +190,7 @@ void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& e } // ---------------------------------------------------------------------------- -// *** PrusaCollapsiblePane *** +// *** PrusaCollapsiblePane *** used only #ifdef __WXMSW__ // ---------------------------------------------------------------------------- #ifdef __WXMSW__ bool PrusaCollapsiblePane::Create(wxWindow *parent, wxWindowID id, const wxString& label, @@ -288,7 +288,6 @@ bool PrusaCollapsiblePane::Layout() return true; } -#endif //__WXMSW__ void PrusaCollapsiblePane::OnStateChange_(const wxSize& sz) { @@ -330,21 +329,12 @@ void PrusaCollapsiblePane::Collapse(bool collapse) // update our state m_pPane->Show(!collapse); - // update button label -#if defined( __WXMAC__ ) && !defined(__WXUNIVERSAL__) - m_pButton->SetOpen(!collapse); -#else -#ifdef __WXMSW__ // update button bitmap UpdateBtnBmp(); -#else - // NB: this must be done after updating our "state" - m_pButton->SetLabel(GetBtnLabel()); -#endif //__WXMSW__ -#endif OnStateChange_(GetBestSize()); } +#endif //__WXMSW__ // ***************************************************************************** // ---------------------------------------------------------------------------- diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index c677db80df..078ea48ed5 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -75,15 +75,14 @@ public: -// *** PrusaCollapsiblePane *** +// *** PrusaCollapsiblePane *** used only #ifdef __WXMSW__ // ---------------------------------------------------------------------------- +#ifdef __WXMSW__ class PrusaCollapsiblePane : public wxCollapsiblePane { -#ifdef __WXMSW__ wxButton* m_pDisclosureTriangleButton = nullptr; wxBitmap m_bmp_close; wxBitmap m_bmp_open; -#endif //__WXMSW__ public: PrusaCollapsiblePane() {} PrusaCollapsiblePane( wxWindow *parent, @@ -95,16 +94,11 @@ public: const wxValidator& val = wxDefaultValidator, const wxString& name = wxCollapsiblePaneNameStr) { -#ifdef __WXMSW__ Create(parent, winid, label, pos, size, style, val, name); -#else - Create(parent, winid, label); -#endif //__WXMSW__ } ~PrusaCollapsiblePane() {} -#ifdef __WXMSW__ bool Create(wxWindow *parent, wxWindowID id, const wxString& label, @@ -117,7 +111,6 @@ public: void UpdateBtnBmp(); void SetLabel(const wxString &label) override; bool Layout() override; -#endif //__WXMSW__ void Collapse(bool collapse) override; void OnStateChange_(const wxSize& sz); //override of OnStateChange virtual bool Show(bool show=true) override { @@ -126,7 +119,7 @@ public: return true; } }; - +#endif //__WXMSW__ // ***************************************************************************** // ---------------------------------------------------------------------------- From 5f82d01f19e3fde3dcc300492492b1b8c0e3032b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 5 Jun 2018 13:17:24 +0200 Subject: [PATCH 030/185] Porting object list manipulation from Perl to c++: * Set count and scale to the objects on c++ side * Select/unselect object --- lib/Slic3r/GUI/Plater.pm | 11 +++++++++++ xs/src/slic3r/GUI/GUI.cpp | 22 ++++++++++++++++++++++ xs/src/slic3r/GUI/GUI.hpp | 8 ++++++++ xs/src/slic3r/GUI/wxExtensions.cpp | 17 +---------------- xs/xsp/GUI.xsp | 12 ++++++++++++ 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a3f381e5c2..7f0dbedb07 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -949,6 +949,8 @@ sub increase { $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); } $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); + # Set conut of object on c++ side + Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); # only autoarrange if user has autocentering enabled $self->stop_background_process; @@ -975,6 +977,8 @@ sub decrease { $self->{print}->objects->[$obj_idx]->delete_last_copy; } $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); + # Set conut of object on c++ side + Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); } elsif (defined $copies_asked) { # The "decrease" came from the "set number of copies" dialog. $self->remove; @@ -1168,6 +1172,8 @@ sub changescale { } $self->{list}->SetItem($obj_idx, 2, "$scale%"); + # Set object scale on c++ side + Slic3r::GUI::set_object_scale($obj_idx, $scale); $scale /= 100; # turn percent into factor my $variation = $scale / $model_instance->scaling_factor; @@ -2096,6 +2102,9 @@ sub select_object { $self->{list}->Select($o, 0); $PreventListEvents = 0; } + + # Unselect all objects in the list on c++ side + Slic3r::GUI::unselect_objects(); if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); @@ -2105,6 +2114,8 @@ sub select_object { $PreventListEvents = 1; $self->{list}->Select($obj_idx, 1); $PreventListEvents = 0; + # Select current object in the list on c++ side + Slic3r::GUI::select_current_object($obj_idx); } else { # TODO: deselect all in list } diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index b0a7751a5b..9179710fd9 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1071,6 +1071,28 @@ void delete_all_objects_from_list() m_collpane_settings->Show(false); } +void set_object_count(int idx, int count) +{ + m_objects_model->SetValue(wxString::Format("%d", count), idx, 1); + m_objects_ctrl->Refresh(); +} + +void set_object_scale(int idx, int scale) +{ + m_objects_model->SetValue(wxString::Format("%d%%", scale), idx, 2); + m_objects_ctrl->Refresh(); +} + +void unselect_objects() +{ + m_objects_ctrl->UnselectAll(); +} + +void select_current_object(int idx) +{ + m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); +} + void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) { wxWindowUpdateLocker noUpdates(parent); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 293183e7d4..6cb8ac1f07 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -181,6 +181,14 @@ void add_object_to_list(const std::string &name, int instances_count=1, int scal void delete_object_from_list(); // Delete all objects from the list void delete_all_objects_from_list(); +// Set count of object on c++ side +void set_object_count(int idx, int count); +// Set object scale on c++ side +void set_object_scale(int idx, int scale); +// Unselect all objects in the list on c++ side +void unselect_objects(); +// Select current object in the list on c++ side +void select_current_object(int idx); void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 662369e054..351c631db1 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -505,22 +505,7 @@ bool MyObjectTreeModel::SetValue(const wxVariant &variant, const wxDataViewItem wxASSERT(item.IsOk()); MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); - switch (col) - { - case 0: - node->m_name = variant.GetString(); - return true; - case 1: - node->m_copy = variant.GetString(); - return true; - case 2: - node->m_scale = variant.GetString(); - return true; - - default:; - // wxLogError("MyObjectTreeModel::SetValue: wrong column"); - } - return false; + return node->SetValue(variant, col); } bool MyObjectTreeModel::SetValue(const wxVariant &variant, const int item_idx, unsigned int col) diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index b38970c099..51eb36fa88 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -131,6 +131,18 @@ void delete_object_from_list() void delete_all_objects_from_list() %code%{ Slic3r::GUI::delete_all_objects_from_list(); %}; +void set_object_count(int idx, int count) + %code%{ Slic3r::GUI::set_object_count(idx, count); %}; + +void set_object_scale(int idx, int scale) + %code%{ Slic3r::GUI::set_object_scale(idx, scale); %}; + +void unselect_objects() + %code%{ Slic3r::GUI::unselect_objects(); %}; + +void select_current_object(int idx) + %code%{ Slic3r::GUI::select_current_object(idx); %}; + std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; From bc6e6492af91b2162752dbfc73c8e5a8e60bdf5c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 5 Jun 2018 14:38:22 +0200 Subject: [PATCH 031/185] Move font initialization to init_fonts --- xs/src/slic3r/GUI/GUI.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 9179710fd9..4a8fbb6e70 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -142,8 +142,8 @@ wxDataViewCtrl *m_objects_ctrl = nullptr; MyObjectTreeModel *m_objects_model = nullptr; wxCollapsiblePane *m_collpane_settings = nullptr; -wxFont g_small_font { wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; -wxFont g_bold_font { wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold() }; +wxFont g_small_font; +wxFont g_bold_font; static void init_label_colours() { @@ -175,6 +175,8 @@ void update_label_colours_from_appconfig() static void init_fonts() { + g_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + g_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); #ifdef __WXMAC__ g_small_font.SetPointSize(11); g_bold_font.SetPointSize(11); From 9e0d2793cbc4c0808ad9f293ee0f36ca87d74d3a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 7 Jun 2018 00:55:09 +0200 Subject: [PATCH 032/185] CallBack from C++ to Perl to manipulations with object list --- lib/Slic3r/GUI/MainFrame.pm | 20 ++++++++++++- lib/Slic3r/GUI/Plater.pm | 23 ++++++++++++--- xs/src/slic3r/GUI/GUI.cpp | 45 ++++++++++++++++++++++++++---- xs/src/slic3r/GUI/GUI.hpp | 2 +- xs/src/slic3r/GUI/wxExtensions.cpp | 12 ++++++++ xs/src/slic3r/GUI/wxExtensions.hpp | 1 + xs/xsp/GUI.xsp | 4 +-- 7 files changed, 94 insertions(+), 13 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 074609659f..df2812102f 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -25,6 +25,8 @@ our $last_config; our $VALUE_CHANGE_EVENT = Wx::NewEventType; # 2) To inform about a preset selection change or a "modified" status change. our $PRESETS_CHANGED_EVENT = Wx::NewEventType; +# 3) To inform about a change of object selection +our $OBJECT_SELECTION_CHANGED_EVENT = Wx::NewEventType; sub new { my ($class, %params) = @_; @@ -113,7 +115,9 @@ sub _init_tabpanel { }); if (!$self->{no_plater}) { - $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), L("Plater")); + $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel, + event_object_selection_changed => $OBJECT_SELECTION_CHANGED_EVENT, + ), L("Plater")); if (!$self->{no_controller}) { $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), L("Controller")); } @@ -168,6 +172,20 @@ sub _init_tabpanel { } } }); + + # The following event is emited by the C++ Tab implementation on config value change. + EVT_COMMAND($self, -1, $OBJECT_SELECTION_CHANGED_EVENT, sub { + my ($self, $event) = @_; + my $obj_idx = $event->GetInt; + print "obj_idx = $obj_idx\n"; + $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx); + + $self->{plater}->{canvas}->Refresh; + $self->{plater}->{canvas3D}->deselect_volumes if $self->{canvas3D}; + $self->{plater}->{canvas3D}->Render if $self->{canvas3D}; + }); + + Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT); $self->{options_tabs} = {}; for my $tab_name (qw(print filament printer)) { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 7f0dbedb07..94975e60b7 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -47,7 +47,7 @@ use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds my $PreventListEvents = 0; sub new { - my ($class, $parent) = @_; + my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height @@ -55,6 +55,10 @@ sub new { nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_rotation_angle extruder_colour filament_colour max_print_height )]); + + # store input params + $self->{event_object_selection_changed} = $params{event_object_selection_changed}; + # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; # C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm @@ -411,8 +415,16 @@ sub new { my $frequently_changed_parameters_sizer = Wx::BoxSizer->new(wxVERTICAL); Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); + my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); - Slic3r::GUI::add_expert_mode_part($self->{right_panel}, $expert_mode_part_sizer); + print "Plater event = ".$self->{event_object_selection_changed}."\n"; + Slic3r::GUI::add_expert_mode_part($self->{right_panel}, $expert_mode_part_sizer, $self->{event_object_selection_changed}); + if ($expert_mode_part_sizer->IsShown(2)==1) + { + $expert_mode_part_sizer->Layout; + $expert_mode_part_sizer->Show(2, 0); # ? Why doesn't work + $self->{right_panel}->Layout; + } my $object_info_sizer; { @@ -535,7 +547,7 @@ sub new { } }; # Show the box initially, let it be shown after the slicing is finished. - $self->{"print_info_box_show"}->(0); + #$self->{"print_info_box_show"}->(0); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); @@ -2095,6 +2107,7 @@ sub selection_changed { sub select_object { my ($self, $obj_idx) = @_; + print "obj_idx = $obj_idx\n"; # remove current selection foreach my $o (0..$#{$self->{objects}}) { $PreventListEvents = 1; @@ -2104,7 +2117,7 @@ sub select_object { } # Unselect all objects in the list on c++ side - Slic3r::GUI::unselect_objects(); + #Slic3r::GUI::unselect_objects(); if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); @@ -2118,6 +2131,7 @@ sub select_object { Slic3r::GUI::select_current_object($obj_idx); } else { # TODO: deselect all in list + Slic3r::GUI::unselect_objects(); } $self->selection_changed(1); } @@ -2125,6 +2139,7 @@ sub select_object { sub selected_object { my ($self) = @_; my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; + print "selected obj_idx = $obj_idx\n"; return defined $obj_idx ? ($obj_idx, $self->{objects}[$obj_idx]) : undef; } diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 4a8fbb6e70..9f119a956a 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -141,6 +141,7 @@ wxSizer *m_sizer_part_buttons = nullptr; wxDataViewCtrl *m_objects_ctrl = nullptr; MyObjectTreeModel *m_objects_model = nullptr; wxCollapsiblePane *m_collpane_settings = nullptr; +int m_event_object_selection_changed = 0; wxFont g_small_font; wxFont g_bold_font; @@ -895,11 +896,24 @@ wxBoxSizer* content_objects_list(wxWindow *win) wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); m_objects_ctrl->AppendColumn(column02); - m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& evt) + m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { wxWindowUpdateLocker noUpdates(g_right_panel); auto item = m_objects_ctrl->GetSelection(); - if (!item) return; + int obj_idx = -1; + if (!item) + unselect_objects(); + else + obj_idx = m_objects_model->GetIdByItem(item); + + if (m_event_object_selection_changed > 0) { + wxCommandEvent event(m_event_object_selection_changed); + event.SetInt(obj_idx); + g_wxMainFrame->ProcessWindowEvent(event); + } + + if (obj_idx < 0) return; + // m_objects_ctrl->SetSize(m_objects_ctrl->GetBestSize()); // TODO override GetBestSize(), than use it auto show_obj_sizer = m_objects_model->GetParent(item) == wxDataViewItem(0); m_sizer_object_buttons->Show(show_obj_sizer); @@ -908,6 +922,14 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_collpane_settings->Show(true); }); + m_objects_ctrl->Bind(wxEVT_KEY_DOWN, [](wxKeyEvent& event) + { + if (event.GetKeyCode() == WXK_TAB) + m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); + else + event.Skip(); + }); + return objects_sz; } @@ -1088,15 +1110,29 @@ void set_object_scale(int idx, int scale) void unselect_objects() { m_objects_ctrl->UnselectAll(); + if (m_sizer_object_buttons->IsShown(1)) + m_sizer_object_buttons->Show(false); + if (m_sizer_part_buttons->IsShown(1)) + m_sizer_part_buttons->Show(false); + if (m_collpane_settings->IsShown()) + m_collpane_settings->Show(false); } void select_current_object(int idx) { + m_objects_ctrl->UnselectAll(); + if (idx < 0) return; m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); + + if (!m_sizer_object_buttons->IsShown(1)) + m_sizer_object_buttons->Show(true); + if (!m_collpane_settings->IsShown()) + m_collpane_settings->Show(true); } -void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) +void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed) { + m_event_object_selection_changed = event_object_selection_changed; wxWindowUpdateLocker noUpdates(parent); auto btn_grid_sizer = new wxGridSizer(1, 3, 2, 2); @@ -1135,8 +1171,7 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer) })); // *** Object/Part Settings *** - m_collpane_settings = add_collapsible_pane(parent, sizer, "Settings:", content_settings); - m_collpane_settings->Hide(); // ? TODO why doesn't work? + m_collpane_settings = add_collapsible_pane(parent, sizer, "Settings", content_settings); add_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) { diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 6cb8ac1f07..4c01622f0f 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -190,7 +190,7 @@ void unselect_objects(); // Select current object in the list on c++ side void select_current_object(int idx); -void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer); +void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); // Update view mode according to selected menu void update_mode(); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 351c631db1..cf232847bb 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -452,6 +452,18 @@ wxDataViewItem MyObjectTreeModel::GetItemById(int obj_idx) } +int MyObjectTreeModel::GetIdByItem(wxDataViewItem& item) +{ + wxASSERT(item.IsOk()); + + MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + auto it = find(m_objects.begin(), m_objects.end(), node); + if (it == m_objects.end()) + return -1; + + return it - m_objects.begin(); +} + wxString MyObjectTreeModel::GetName(const wxDataViewItem &item) const { MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 078ea48ed5..2ae5cf6d1d 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -253,6 +253,7 @@ public: wxDataViewItem Delete(const wxDataViewItem &item); void DeleteAll(); wxDataViewItem GetItemById(int obj_idx); + int GetIdByItem(wxDataViewItem& item); bool IsEmpty() { return m_objects.empty(); } // helper method for wxLog diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 51eb36fa88..a744e6dcd2 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -87,9 +87,9 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), (wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %}; -void add_expert_mode_part(SV *ui_parent, SV *ui_sizer) +void add_expert_mode_part(SV *ui_parent, SV *ui_sizer, int event) %code%{ Slic3r::GUI::add_expert_mode_part((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), - (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer")); %}; + (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), event); %}; void set_objects_from_perl( SV *ui_parent, SV *frequently_changed_parameters_sizer, From b31e696edcdcbddd673f21da7f85ada7b8f15e90 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 7 Jun 2018 11:12:09 +0200 Subject: [PATCH 033/185] ListView doesn't used now. (Whole logic of the object list manipulation is on c++ side) Remove experimental add/del buttons --- lib/Slic3r/GUI/MainFrame.pm | 7 +-- lib/Slic3r/GUI/Plater.pm | 104 +++++++----------------------------- xs/src/slic3r/GUI/GUI.cpp | 60 ++++++++------------- 3 files changed, 41 insertions(+), 130 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index df2812102f..c48186c764 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -177,12 +177,9 @@ sub _init_tabpanel { EVT_COMMAND($self, -1, $OBJECT_SELECTION_CHANGED_EVENT, sub { my ($self, $event) = @_; my $obj_idx = $event->GetInt; - print "obj_idx = $obj_idx\n"; + $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx); - - $self->{plater}->{canvas}->Refresh; - $self->{plater}->{canvas3D}->deselect_volumes if $self->{canvas3D}; - $self->{plater}->{canvas3D}->Render if $self->{canvas3D}; + $self->{plater}->item_changed_selection($obj_idx); }); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 94975e60b7..58673937e3 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -44,8 +44,6 @@ our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType; use constant FILAMENT_CHOOSERS_SPACING => 0; use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds -my $PreventListEvents = 0; - sub new { my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); @@ -234,23 +232,6 @@ sub new { $self->{right_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{right_panel}->SetScrollbars(0, 1, 1, 1); - $self->{list} = Wx::ListView->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, - wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); - $self->{list}->InsertColumn(0, L("Name"), wxLIST_FORMAT_LEFT, 145); - $self->{list}->InsertColumn(1, L("Copies"), wxLIST_FORMAT_CENTER, 45); - $self->{list}->InsertColumn(2, L("Scale"), wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER); - EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected); - EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected); - EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated); - EVT_KEY_DOWN($self->{list}, sub { - my ($list, $event) = @_; - if ($event->GetKeyCode == WXK_TAB) { - $list->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward); - } else { - $event->Skip; - } - }); - # right pane buttons $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxNO_BORDER);#, wxBU_LEFT); $self->{btn_reslice} = Wx::Button->new($self->{right_panel}, -1, L("Slice now"), wxDefaultPosition, [-1, 30], wxBU_LEFT); @@ -335,7 +316,7 @@ sub new { $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) for grep defined($_), - $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; + $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}; EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub { my ($self, $event) = @_; @@ -417,7 +398,6 @@ sub new { Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); - print "Plater event = ".$self->{event_object_selection_changed}."\n"; Slic3r::GUI::add_expert_mode_part($self->{right_panel}, $expert_mode_part_sizer, $self->{event_object_selection_changed}); if ($expert_mode_part_sizer->IsShown(2)==1) { @@ -519,7 +499,6 @@ sub new { ### Sizer for info boxes my $info_sizer = Wx::BoxSizer->new(wxVERTICAL); $info_sizer->SetMinSize([310, -1]); - $info_sizer->Add($self->{list}, 1, wxEXPAND, 5); $info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); $info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); @@ -527,21 +506,17 @@ sub new { $self->{right_panel}->SetSizer($right_sizer); $right_sizer->SetMinSize([320, -1]); $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; - $right_sizer->Add($frequently_changed_parameters_sizer, 2, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; - $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 0) if defined $expert_mode_part_sizer; - $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5); - $right_sizer->Add($info_sizer, 1, wxEXPAND | wxLEFT, 20); + $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; + $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 10) if defined $expert_mode_part_sizer; + $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, 10); + $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20); # Callback for showing / hiding the print info box. $self->{"print_info_box_show"} = sub { -# if ($right_sizer->IsShown(5) != $_[0]) { -# $right_sizer->Show(5, $_[0]); -# $self->Layout -# } - if ($info_sizer->IsShown(2) != $_[0]) { + if ($info_sizer->IsShown(1) != $_[0]) { Slic3r::GUI::set_show_print_info($_[0]); return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); - $info_sizer->Show(2, $_[0]); + $info_sizer->Show(1, $_[0]); $self->Layout; $self->{right_panel}->Refresh; } @@ -853,12 +828,6 @@ sub load_model_objects { foreach my $obj_idx (@obj_idx) { my $object = $self->{objects}[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx]; - $self->{list}->InsertStringItem($obj_idx, $object->name); - $self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)) - if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont() - - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); - $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); # Add object to list on c++ side Slic3r::GUI::add_object_to_list($object->name, $model_object->instances_count, ($model_object->instances->[0]->scaling_factor * 100)); @@ -872,8 +841,6 @@ sub load_model_objects { $self->{canvas3D}->zoom_to_volumes if $self->{canvas3D}; - $self->{list}->Update; - $self->{list}->Select($obj_idx[-1], 1); $self->object_list_changed; $self->schedule_background_process; @@ -908,7 +875,6 @@ sub remove { splice @{$self->{objects}}, $obj_idx, 1; $self->{model}->delete_object($obj_idx); $self->{print}->delete_object($obj_idx); - $self->{list}->DeleteItem($obj_idx); # Delete object from list on c++ side Slic3r::GUI::delete_object_from_list(); $self->object_list_changed; @@ -933,7 +899,6 @@ sub reset { @{$self->{objects}} = (); $self->{model}->clear_objects; $self->{print}->clear_objects; - $self->{list}->DeleteAllItems; # Delete all objects from list on c++ side Slic3r::GUI::delete_all_objects_from_list(); $self->object_list_changed; @@ -960,7 +925,6 @@ sub increase { ); $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); # Set conut of object on c++ side Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); @@ -988,7 +952,6 @@ sub decrease { $model_object->delete_last_instance; $self->{print}->objects->[$obj_idx]->delete_last_copy; } - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); # Set conut of object on c++ side Slic3r::GUI::set_object_count($obj_idx, $model_object->instances_count); } elsif (defined $copies_asked) { @@ -999,11 +962,7 @@ sub decrease { $self->resume_background_process; return; } - - if ($self->{objects}[$obj_idx]) { - $self->{list}->Select($obj_idx, 0); - $self->{list}->Select($obj_idx, 1); - } + $self->update; $self->schedule_background_process; } @@ -1182,8 +1141,7 @@ sub changescale { $scale = $self->_get_number_from_user(L('Enter the scale % for the selected object:'), L('Scale'), L('Invalid scaling value entered'), $model_instance->scaling_factor*100, 1); return if ! defined($scale) || $scale eq ''; } - - $self->{list}->SetItem($obj_idx, 2, "$scale%"); + # Set object scale on c++ side Slic3r::GUI::set_object_scale($obj_idx, $scale); $scale /= 100; # turn percent into factor @@ -1887,31 +1845,19 @@ sub on_config_change { $self->schedule_background_process; } -sub list_item_deselected { - my ($self, $event) = @_; - return if $PreventListEvents; - $self->{_lecursor} = Wx::BusyCursor->new(); - if ($self->{list}->GetFirstSelected == -1) { - $self->select_object(undef); - $self->{canvas}->Refresh; - $self->{canvas3D}->deselect_volumes if $self->{canvas3D}; - $self->{canvas3D}->Render if $self->{canvas3D}; - } - undef $self->{_lecursor}; -} +sub item_changed_selection{ + my ($self, $obj_idx) = @_; -sub list_item_selected { - my ($self, $event) = @_; - return if $PreventListEvents; - $self->{_lecursor} = Wx::BusyCursor->new(); - my $obj_idx = $event->GetIndex; - $self->select_object($obj_idx); $self->{canvas}->Refresh; - $self->{canvas3D}->update_volumes_selection if $self->{canvas3D}; + if ($self->{canvas3D}){ + $self->{canvas3D}->deselect_volumes; + if ($obj_idx >= 0) { + $self->{canvas3D}->update_volumes_selection}; + } $self->{canvas3D}->Render if $self->{canvas3D}; - undef $self->{_lecursor}; } +# doesn't used now sub list_item_activated { my ($self, $event, $obj_idx) = @_; @@ -2107,30 +2053,17 @@ sub selection_changed { sub select_object { my ($self, $obj_idx) = @_; - print "obj_idx = $obj_idx\n"; # remove current selection foreach my $o (0..$#{$self->{objects}}) { - $PreventListEvents = 1; $self->{objects}->[$o]->selected(0); - $self->{list}->Select($o, 0); - $PreventListEvents = 0; } - # Unselect all objects in the list on c++ side - #Slic3r::GUI::unselect_objects(); - if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); - # We use this flag to avoid circular event handling - # Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows, - # whose event handler calls this method again and again and again - $PreventListEvents = 1; - $self->{list}->Select($obj_idx, 1); - $PreventListEvents = 0; # Select current object in the list on c++ side Slic3r::GUI::select_current_object($obj_idx); } else { - # TODO: deselect all in list + # Unselect all objects in the list on c++ side Slic3r::GUI::unselect_objects(); } $self->selection_changed(1); @@ -2139,7 +2072,6 @@ sub select_object { sub selected_object { my ($self) = @_; my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; - print "selected obj_idx = $obj_idx\n"; return defined $obj_idx ? ($obj_idx, $self->{objects}[$obj_idx]) : undef; } diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 9f119a956a..2e27efcddc 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -904,7 +904,12 @@ wxBoxSizer* content_objects_list(wxWindow *win) if (!item) unselect_objects(); else - obj_idx = m_objects_model->GetIdByItem(item); + { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + obj_idx = m_objects_model->GetIdByItem(item); + else + obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); // TODO Temporary decision for sub-objects selection + } if (m_event_object_selection_changed > 0) { wxCommandEvent event(m_event_object_selection_changed); @@ -915,6 +920,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) if (obj_idx < 0) return; // m_objects_ctrl->SetSize(m_objects_ctrl->GetBestSize()); // TODO override GetBestSize(), than use it + auto show_obj_sizer = m_objects_model->GetParent(item) == wxDataViewItem(0); m_sizer_object_buttons->Show(show_obj_sizer); m_sizer_part_buttons->Show(!show_obj_sizer); @@ -1135,25 +1141,6 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_ m_event_object_selection_changed = event_object_selection_changed; wxWindowUpdateLocker noUpdates(parent); - auto btn_grid_sizer = new wxGridSizer(1, 3, 2, 2); - sizer->Add(btn_grid_sizer, 0, wxALIGN_LEFT | wxLEFT, 20); - - // Experiments with new UI - auto add_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); - btn_grid_sizer->Add(add_btn, 0, wxEXPAND, 0); - - auto del_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - if (wxMSW) del_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - del_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG)); - btn_grid_sizer->Add(del_btn, 0, wxEXPAND, 0); - - auto del_all_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - if (wxMSW) del_all_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - del_all_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("delete.png")), wxBITMAP_TYPE_PNG)); - btn_grid_sizer->Add(del_all_btn, 0, wxEXPAND, 0); - // *** Objects List *** auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){ @@ -1171,17 +1158,7 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_ })); // *** Object/Part Settings *** - m_collpane_settings = add_collapsible_pane(parent, sizer, "Settings", content_settings); - - add_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) - { - wxString name = "Object"; - m_objects_ctrl->Select(m_objects_model->Add(name)); - }); - - del_btn->Bind(wxEVT_BUTTON, [](wxEvent& ) { delete_object_from_list(); }); - - del_all_btn->Bind(wxEVT_BUTTON, [](wxEvent&) { delete_all_objects_from_list(); }); + m_collpane_settings = add_collapsible_pane(parent, sizer, "Object Settings", content_settings); // More experiments with UI // auto listctrl = new wxDataViewListCtrl(main_page, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); @@ -1386,31 +1363,36 @@ void show_buttons(bool show) } } -void show_scrolled_window_sizer(bool show) +void show_info_sizer(bool show) { - g_scrolled_window_sizer->Show(static_cast(0), /*false*/show); //don't used now - g_scrolled_window_sizer->Show(1, show); - g_scrolled_window_sizer->Show(2, show && g_show_print_info); + g_scrolled_window_sizer->Show(static_cast(0), show); + g_scrolled_window_sizer->Show(1, show && g_show_print_info); g_manifold_warning_icon->Show(show && g_show_manifold_warning_icon); } void update_mode() { - //TODO There is a not the best place of it! + wxWindowUpdateLocker noUpdates(g_right_panel); + + // TODO There is a not the best place of it! //*** Update style of the "Export G-code" button**** if (g_btn_export_gcode->GetFont() != bold_font()){ g_btn_export_gcode->SetBackgroundColour(wxColour(252, 77, 1)); g_btn_export_gcode->SetFont(bold_font()); } - //************************************ + // *********************************** - wxWindowUpdateLocker noUpdates(g_right_panel); ConfigMenuIDs mode = get_view_mode(); // show_frequently_changed_parameters(mode >= ConfigMenuModeRegular); g_expert_mode_part_sizer->Show(mode == ConfigMenuModeExpert); - show_scrolled_window_sizer(mode == ConfigMenuModeExpert); + show_info_sizer(mode == ConfigMenuModeExpert); show_buttons(mode == ConfigMenuModeExpert); + + // TODO There is a not the best place of it! + // *** Update showing of the collpane_settings + m_collpane_settings->Show(mode == ConfigMenuModeExpert && !m_objects_model->IsEmpty()); + // ************************* g_right_panel->GetParent()->Layout(); g_right_panel->Layout(); } From 49f0a1a824173b9615e9f98053b35f95528e42e0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 7 Jun 2018 11:54:42 +0200 Subject: [PATCH 034/185] Fixed OSX compilation bug Changed info_box (more place to the manifold information) --- lib/Slic3r/GUI/Plater.pm | 14 +++++++++----- xs/src/slic3r/GUI/GUI.cpp | 6 ++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 58673937e3..689ccff284 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -412,7 +412,8 @@ sub new { $box->SetFont($Slic3r::GUI::small_bold_font); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $object_info_sizer->SetMinSize([300,-1]); - my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); + #!my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); + my $grid_sizer = Wx::FlexGridSizer->new(2, 4, 5, 5); $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); $grid_sizer->AddGrowableCol(1, 1); $grid_sizer->AddGrowableCol(3, 1); @@ -429,7 +430,7 @@ sub new { my $label = shift @info; my $text = Wx::StaticText->new($self->{right_panel}, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $text->SetFont($Slic3r::GUI::small_font); - $grid_sizer->Add($text, 0); + #!$grid_sizer->Add($text, 0); $self->{"object_info_$field"} = Wx::StaticText->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); @@ -448,10 +449,13 @@ sub new { $self->{"object_info_manifold_warning_icon_show"}->(0); my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); - $h_sizer->Add($self->{object_info_manifold_warning_icon}, 0); - $h_sizer->Add($self->{"object_info_$field"}, 0); - $grid_sizer->Add($h_sizer, 0, wxEXPAND); + $h_sizer->Add($text, 0); + $h_sizer->Add($self->{object_info_manifold_warning_icon}, 0, wxLEFT, 2); + $h_sizer->Add($self->{"object_info_$field"}, 0, wxLEFT, 2); + #!$grid_sizer->Add($h_sizer, 0, wxEXPAND); + $object_info_sizer->Add($h_sizer, 0, wxEXPAND|wxTOP, 4); } else { + $grid_sizer->Add($text, 0); $grid_sizer->Add($self->{"object_info_$field"}, 0); } } diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 2e27efcddc..d2bcc7b58b 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -907,8 +907,10 @@ wxBoxSizer* content_objects_list(wxWindow *win) { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) obj_idx = m_objects_model->GetIdByItem(item); - else - obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); // TODO Temporary decision for sub-objects selection + else { + auto parent = m_objects_model->GetParent(item); + obj_idx = m_objects_model->GetIdByItem(parent); // TODO Temporary decision for sub-objects selection + } } if (m_event_object_selection_changed > 0) { From ebe5ee3b1f41ecac8c5e13d80280acde9befe3bb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 7 Jun 2018 14:57:45 +0200 Subject: [PATCH 035/185] Fixed uncorrected Settings sizer showing --- xs/src/slic3r/GUI/GUI.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index d2bcc7b58b..898edc3659 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1132,10 +1132,12 @@ void select_current_object(int idx) if (idx < 0) return; m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); - if (!m_sizer_object_buttons->IsShown(1)) - m_sizer_object_buttons->Show(true); - if (!m_collpane_settings->IsShown()) - m_collpane_settings->Show(true); + if (get_view_mode() == ConfigMenuModeExpert){ + if (!m_sizer_object_buttons->IsShown(1)) + m_sizer_object_buttons->Show(true); + if (!m_collpane_settings->IsShown()) + m_collpane_settings->Show(true); + } } void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed) From e2a7bd4a149f5f20969dee6d6c171ae7bdc04b53 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 7 Jun 2018 15:52:35 +0200 Subject: [PATCH 036/185] Added MessageBoxes for experimenting on OSX --- lib/Slic3r/GUI/MainFrame.pm | 1 + lib/Slic3r/GUI/Plater.pm | 3 +++ xs/src/slic3r/GUI/GUI.cpp | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index c48186c764..e36bf4a5ee 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -179,6 +179,7 @@ sub _init_tabpanel { my $obj_idx = $event->GetInt; $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx); + Wx::MessageBox("Before item_changed_selection", "Slic3r Info", wxOK | wxICON_INFORMATION, $self); $self->{plater}->item_changed_selection($obj_idx); }); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 689ccff284..a12b2aa8ea 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1852,6 +1852,8 @@ sub on_config_change { sub item_changed_selection{ my ($self, $obj_idx) = @_; + Wx::MessageBox("Inside item_changed_selection", "Slic3r Info", wxOK | wxICON_INFORMATION, $self); + $self->{canvas}->Refresh; if ($self->{canvas3D}){ $self->{canvas3D}->deselect_volumes; @@ -2065,6 +2067,7 @@ sub select_object { if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); # Select current object in the list on c++ side + Wx::MessageBox("Before select_current_object", "Slic3r Info", wxOK | wxICON_INFORMATION, $self); Slic3r::GUI::select_current_object($obj_idx); } else { # Unselect all objects in the list on c++ side diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 898edc3659..11c8683269 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1128,9 +1128,12 @@ void unselect_objects() void select_current_object(int idx) { + wxMessageBox("Inside select_current_object", "Info"); m_objects_ctrl->UnselectAll(); + wxMessageBox("UnselectAll", "Info"); if (idx < 0) return; m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); + wxMessageBox("Item is selected", "Info"); if (get_view_mode() == ConfigMenuModeExpert){ if (!m_sizer_object_buttons->IsShown(1)) @@ -1138,6 +1141,7 @@ void select_current_object(int idx) if (!m_collpane_settings->IsShown()) m_collpane_settings->Show(true); } + wxMessageBox("Updated sizer showing", "Info"); } void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed) From 7ba2093a9bbdf2ad58961748ec6ee07f81c8c9c4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 8 Jun 2018 09:03:46 +0200 Subject: [PATCH 037/185] Try to fix the circular event handling Select() on OSX --- xs/src/slic3r/GUI/GUI.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 11c8683269..d0064d0a2d 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -142,6 +142,9 @@ wxDataViewCtrl *m_objects_ctrl = nullptr; MyObjectTreeModel *m_objects_model = nullptr; wxCollapsiblePane *m_collpane_settings = nullptr; int m_event_object_selection_changed = 0; +bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() + // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler + // calls this method again and again and again wxFont g_small_font; wxFont g_bold_font; @@ -898,6 +901,8 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { + if (g_prevent_list_events) return; + wxWindowUpdateLocker noUpdates(g_right_panel); auto item = m_objects_ctrl->GetSelection(); int obj_idx = -1; @@ -1129,10 +1134,15 @@ void unselect_objects() void select_current_object(int idx) { wxMessageBox("Inside select_current_object", "Info"); + g_prevent_list_events = true; m_objects_ctrl->UnselectAll(); wxMessageBox("UnselectAll", "Info"); - if (idx < 0) return; + if (idx < 0) { + g_prevent_list_events = false; + return; + } m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); + g_prevent_list_events = false; wxMessageBox("Item is selected", "Info"); if (get_view_mode() == ConfigMenuModeExpert){ From da9b0a9b7da08224b53de76570b0d35567b8d9a1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 8 Jun 2018 09:55:27 +0200 Subject: [PATCH 038/185] Deleted debugging messages --- lib/Slic3r/GUI/Plater.pm | 3 --- xs/src/slic3r/GUI/GUI.cpp | 4 ---- 2 files changed, 7 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a12b2aa8ea..689ccff284 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1852,8 +1852,6 @@ sub on_config_change { sub item_changed_selection{ my ($self, $obj_idx) = @_; - Wx::MessageBox("Inside item_changed_selection", "Slic3r Info", wxOK | wxICON_INFORMATION, $self); - $self->{canvas}->Refresh; if ($self->{canvas3D}){ $self->{canvas3D}->deselect_volumes; @@ -2067,7 +2065,6 @@ sub select_object { if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); # Select current object in the list on c++ side - Wx::MessageBox("Before select_current_object", "Slic3r Info", wxOK | wxICON_INFORMATION, $self); Slic3r::GUI::select_current_object($obj_idx); } else { # Unselect all objects in the list on c++ side diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index d0064d0a2d..442acd3e4a 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1133,17 +1133,14 @@ void unselect_objects() void select_current_object(int idx) { - wxMessageBox("Inside select_current_object", "Info"); g_prevent_list_events = true; m_objects_ctrl->UnselectAll(); - wxMessageBox("UnselectAll", "Info"); if (idx < 0) { g_prevent_list_events = false; return; } m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); g_prevent_list_events = false; - wxMessageBox("Item is selected", "Info"); if (get_view_mode() == ConfigMenuModeExpert){ if (!m_sizer_object_buttons->IsShown(1)) @@ -1151,7 +1148,6 @@ void select_current_object(int idx) if (!m_collpane_settings->IsShown()) m_collpane_settings->Show(true); } - wxMessageBox("Updated sizer showing", "Info"); } void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed) From 08ccf85a616f12577dab96fde9bc73ef2f1fed41 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 8 Jun 2018 12:43:39 +0200 Subject: [PATCH 039/185] Overrided OnStateChange() function to CollapsiblePane --- lib/Slic3r/GUI/MainFrame.pm | 1 - xs/src/slic3r/GUI/GUI.cpp | 20 ++++---- xs/src/slic3r/GUI/wxExtensions.cpp | 75 ++++++++++++++++-------------- xs/src/slic3r/GUI/wxExtensions.hpp | 44 +++++++++++++----- 4 files changed, 82 insertions(+), 58 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index e36bf4a5ee..c48186c764 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -179,7 +179,6 @@ sub _init_tabpanel { my $obj_idx = $event->GetInt; $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx); - Wx::MessageBox("Before item_changed_selection", "Slic3r Info", wxOK | wxICON_INFORMATION, $self); $self->{plater}->item_changed_selection($obj_idx); }); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 22a0803556..3114622fb6 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -858,9 +858,9 @@ wxString from_u8(const std::string &str) wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) { #ifdef __WXMSW__ - auto *collpane = new PrusaCollapsiblePane(parent, wxID_ANY, name); + auto *collpane = new PrusaCollapsiblePaneMSW(parent, wxID_ANY, name); #else - auto *collpane = new wxCollapsiblePane(parent, wxID_ANY, name); + auto *collpane = new PrusaCollapsiblePane/*wxCollapsiblePane*/(parent, wxID_ANY, name); #endif // __WXMSW__ // add the pane with a zero proportion value to the sizer which contains it sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0); @@ -962,9 +962,9 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) { auto sizer = new wxBoxSizer(wxVERTICAL); - auto btn_load_part = new wxButton(win, wxID_ANY, /*Load */"part…", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_load_modifier = new wxButton(win, wxID_ANY, /*Load */"modifier…", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, /*Load */"generic…", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_load_part = new wxButton(win, wxID_ANY, /*Load */"part"+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_load_modifier = new wxButton(win, wxID_ANY, /*Load */"modifier" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, /*Load */"generic" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); auto btn_delete = new wxButton(win, wxID_ANY, "Delete"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); auto btn_split = new wxButton(win, wxID_ANY, "Split"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); auto btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); @@ -1170,17 +1170,17 @@ void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_ // *** Objects List *** auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){ - e.Skip(); - wxWindowUpdateLocker noUpdates(g_right_panel); +// wxWindowUpdateLocker noUpdates(g_right_panel); if (collpane->IsCollapsed()) { m_sizer_object_buttons->Show(false); m_sizer_part_buttons->Show(false); m_collpane_settings->Show(false); } - else - m_objects_ctrl->UnselectAll(); +// else +// m_objects_ctrl->UnselectAll(); - g_right_panel->Layout(); +// e.Skip(); +// g_right_panel->Layout(); })); // *** Object/Part Settings *** diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index cf232847bb..b0c60d4c15 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -190,10 +190,42 @@ void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& e } // ---------------------------------------------------------------------------- -// *** PrusaCollapsiblePane *** used only #ifdef __WXMSW__ +// *** PrusaCollapsiblePane *** +// ---------------------------------------------------------------------------- +void PrusaCollapsiblePane::OnStateChange(const wxSize& sz) +{ + SetSize(sz); + + if (this->HasFlag(wxCP_NO_TLW_RESIZE)) + { + // the user asked to explicitly handle the resizing itself... + return; + } + + auto top = GetParent(); //right_panel + if (!top) + return; + + wxSizer *sizer = top->GetSizer(); + if (!sizer) + return; + + const wxSize newBestSize = sizer->ComputeFittingClientSize(top); + top->SetMinClientSize(newBestSize); + + wxWindowUpdateLocker noUpdates_p(top->GetParent()); + // we shouldn't attempt to resize a maximized window, whatever happens + // if (!top->IsMaximized()) + // top->SetClientSize(newBestSize); + top->GetParent()->Layout(); + top->Refresh(); +} + +// ---------------------------------------------------------------------------- +// *** PrusaCollapsiblePaneMSW *** used only #ifdef __WXMSW__ // ---------------------------------------------------------------------------- #ifdef __WXMSW__ -bool PrusaCollapsiblePane::Create(wxWindow *parent, wxWindowID id, const wxString& label, +bool PrusaCollapsiblePaneMSW::Create(wxWindow *parent, wxWindowID id, const wxString& label, const wxPoint& pos, const wxSize& size, long style, const wxValidator& val, const wxString& name) { if (!wxControl::Create(parent, id, pos, size, style, val, name)) @@ -242,7 +274,7 @@ bool PrusaCollapsiblePane::Create(wxWindow *parent, wxWindowID id, const wxStrin return true; } -void PrusaCollapsiblePane::UpdateBtnBmp() +void PrusaCollapsiblePaneMSW::UpdateBtnBmp() { if (IsCollapsed()) m_pDisclosureTriangleButton->SetBitmap(m_bmp_close); @@ -257,14 +289,14 @@ void PrusaCollapsiblePane::UpdateBtnBmp() Layout(); } -void PrusaCollapsiblePane::SetLabel(const wxString &label) +void PrusaCollapsiblePaneMSW::SetLabel(const wxString &label) { m_strLabel = label; m_pDisclosureTriangleButton->SetLabel(m_strLabel); Layout(); } -bool PrusaCollapsiblePane::Layout() +bool PrusaCollapsiblePaneMSW::Layout() { if (!m_pDisclosureTriangleButton || !m_pPane || !m_sz) return false; // we need to complete the creation first! @@ -289,36 +321,7 @@ bool PrusaCollapsiblePane::Layout() return true; } -void PrusaCollapsiblePane::OnStateChange_(const wxSize& sz) -{ - SetSize(sz); - - if (this->HasFlag(wxCP_NO_TLW_RESIZE)) - { - // the user asked to explicitly handle the resizing itself... - return; - } - - auto top = GetParent(); //right_panel - if (!top) - return; - - wxSizer *sizer = top->GetSizer(); - if (!sizer) - return; - - const wxSize newBestSize = sizer->ComputeFittingClientSize(top); - top->SetMinClientSize(newBestSize); - - wxWindowUpdateLocker noUpdates_p(top->GetParent()); - // we shouldn't attempt to resize a maximized window, whatever happens -// if (!top->IsMaximized()) -// top->SetClientSize(newBestSize); - top->GetParent()->Layout(); - top->Refresh(); -} - -void PrusaCollapsiblePane::Collapse(bool collapse) +void PrusaCollapsiblePaneMSW::Collapse(bool collapse) { // optimization if (IsCollapsed() == collapse) @@ -332,7 +335,7 @@ void PrusaCollapsiblePane::Collapse(bool collapse) // update button bitmap UpdateBtnBmp(); - OnStateChange_(GetBestSize()); + OnStateChange(GetBestSize()); } #endif //__WXMSW__ diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 2ae5cf6d1d..d3330a8275 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -75,17 +75,45 @@ public: -// *** PrusaCollapsiblePane *** used only #ifdef __WXMSW__ +// *** PrusaCollapsiblePane *** +// ---------------------------------------------------------------------------- +class PrusaCollapsiblePane : public wxCollapsiblePane +{ +public: + PrusaCollapsiblePane() {} + PrusaCollapsiblePane(wxWindow *parent, + wxWindowID winid, + const wxString& label, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxCP_DEFAULT_STYLE, + const wxValidator& val = wxDefaultValidator, + const wxString& name = wxCollapsiblePaneNameStr) + { + Create(parent, winid, label, pos, size, style, val, name); + } + ~PrusaCollapsiblePane() {} + + void OnStateChange(const wxSize& sz); //override/hide of OnStateChange from wxCollapsiblePane + virtual bool Show(bool show = true) override { + wxCollapsiblePane::Show(show); + OnStateChange(GetBestSize()); + return true; + } +}; + + +// *** PrusaCollapsiblePaneMSW *** used only #ifdef __WXMSW__ // ---------------------------------------------------------------------------- #ifdef __WXMSW__ -class PrusaCollapsiblePane : public wxCollapsiblePane +class PrusaCollapsiblePaneMSW : public PrusaCollapsiblePane//wxCollapsiblePane { wxButton* m_pDisclosureTriangleButton = nullptr; wxBitmap m_bmp_close; wxBitmap m_bmp_open; public: - PrusaCollapsiblePane() {} - PrusaCollapsiblePane( wxWindow *parent, + PrusaCollapsiblePaneMSW() {} + PrusaCollapsiblePaneMSW( wxWindow *parent, wxWindowID winid, const wxString& label, const wxPoint& pos = wxDefaultPosition, @@ -97,7 +125,7 @@ public: Create(parent, winid, label, pos, size, style, val, name); } - ~PrusaCollapsiblePane() {} + ~PrusaCollapsiblePaneMSW() {} bool Create(wxWindow *parent, wxWindowID id, @@ -112,12 +140,6 @@ public: void SetLabel(const wxString &label) override; bool Layout() override; void Collapse(bool collapse) override; - void OnStateChange_(const wxSize& sz); //override of OnStateChange - virtual bool Show(bool show=true) override { - wxCollapsiblePane::Show(show); - OnStateChange_(GetBestSize()); - return true; - } }; #endif //__WXMSW__ From f5ef6728158c0f7051c61678a5473a207f20d129 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 12 Jun 2018 19:15:03 +0200 Subject: [PATCH 040/185] Prepared callback from c++ to Perl to inform about a change of object settings --- lib/Slic3r/GUI/MainFrame.pm | 18 +++++++++++++++-- lib/Slic3r/GUI/Plater.pm | 40 ++++++++++++++++++++++++++++++------- xs/src/slic3r/GUI/GUI.cpp | 6 +++++- xs/src/slic3r/GUI/GUI.hpp | 4 +++- xs/xsp/GUI.xsp | 10 +++++++--- 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index c48186c764..626bdef81e 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -27,6 +27,8 @@ our $VALUE_CHANGE_EVENT = Wx::NewEventType; our $PRESETS_CHANGED_EVENT = Wx::NewEventType; # 3) To inform about a change of object selection our $OBJECT_SELECTION_CHANGED_EVENT = Wx::NewEventType; +# 4) To inform about a change of object settings +our $OBJECT_SETTINGS_CHANGED_EVENT = Wx::NewEventType; sub new { my ($class, %params) = @_; @@ -117,6 +119,7 @@ sub _init_tabpanel { if (!$self->{no_plater}) { $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel, event_object_selection_changed => $OBJECT_SELECTION_CHANGED_EVENT, + event_object_settings_changed => $OBJECT_SETTINGS_CHANGED_EVENT, ), L("Plater")); if (!$self->{no_controller}) { $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), L("Controller")); @@ -173,14 +176,25 @@ sub _init_tabpanel { } }); - # The following event is emited by the C++ Tab implementation on config value change. + # The following event is emited by the C++ Tab implementation on object selection change. EVT_COMMAND($self, -1, $OBJECT_SELECTION_CHANGED_EVENT, sub { my ($self, $event) = @_; my $obj_idx = $event->GetInt; $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx); $self->{plater}->item_changed_selection($obj_idx); - }); + }); + + # The following event is emited by the C++ Tab implementation on object settings change. + EVT_COMMAND($self, -1, $OBJECT_SETTINGS_CHANGED_EVENT, sub { + my ($self, $event) = @_; + my $obj_idx = $event->GetInt; + + #my $line = $event->GetString; + #my ($parts_changed, $part_settings_changed) = split('',$line); + + #$self->{plater}->changed_object_settings($obj_idx, $parts_changed, $part_settings_changed); + }); Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 689ccff284..bc149a8288 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -56,6 +56,7 @@ sub new { # store input params $self->{event_object_selection_changed} = $params{event_object_selection_changed}; + $self->{event_object_settings_changed} = $params{event_object_settings_changed}; # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; @@ -398,13 +399,15 @@ sub new { Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); - Slic3r::GUI::add_expert_mode_part($self->{right_panel}, $expert_mode_part_sizer, $self->{event_object_selection_changed}); - if ($expert_mode_part_sizer->IsShown(2)==1) - { - $expert_mode_part_sizer->Layout; - $expert_mode_part_sizer->Show(2, 0); # ? Why doesn't work - $self->{right_panel}->Layout; - } + Slic3r::GUI::add_expert_mode_part( $self->{right_panel}, $expert_mode_part_sizer, + $self->{event_object_selection_changed}, + $self->{event_object_settings_changed}); +# if ($expert_mode_part_sizer->IsShown(2)==1) +# { +# $expert_mode_part_sizer->Layout; +# $expert_mode_part_sizer->Show(2, 0); # ? Why doesn't work +# $self->{right_panel}->Layout; +# } my $object_info_sizer; { @@ -1960,6 +1963,29 @@ sub object_settings_dialog { } } +sub changed_object_settings { + my ($self, $obj_idx, $parts_changed, $part_settings_changed) = @_; + + # update thumbnail since parts may have changed + if ($parts_changed) { + # recenter and re-align to Z = 0 + my $model_object = $self->{model}->objects->[$obj_idx]; + $model_object->center_around_origin; + $self->reset_thumbnail($obj_idx); + } + + # update print + if ($parts_changed || $part_settings_changed) { + $self->stop_background_process; + $self->{print}->reload_object($obj_idx); + $self->schedule_background_process; + $self->{canvas}->reload_scene if $self->{canvas}; + $self->{canvas3D}->reload_scene if $self->{canvas3D}; + } else { + $self->resume_background_process; + } +} + # Called to update various buttons depending on whether there are any objects or # whether background processing (export of a G-code, sending to Octoprint, forced background re-slicing) is active. sub object_list_changed { diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index a34b6f1d75..01a61ff517 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -143,6 +143,7 @@ wxDataViewCtrl *m_objects_ctrl = nullptr; MyObjectTreeModel *m_objects_model = nullptr; wxCollapsiblePane *m_collpane_settings = nullptr; int m_event_object_selection_changed = 0; +int m_event_object_settings_changed = 0; bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler // calls this method again and again and again @@ -1163,9 +1164,12 @@ void select_current_object(int idx) } } -void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed) +void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, + int event_object_selection_changed, + int event_object_settings_changed) { m_event_object_selection_changed = event_object_selection_changed; + m_event_object_settings_changed = event_object_settings_changed; wxWindowUpdateLocker noUpdates(parent); // *** Objects List *** diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 894751f611..277729ab07 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -202,7 +202,9 @@ void unselect_objects(); // Select current object in the list on c++ side void select_current_object(int idx); -void add_expert_mode_part(wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed); +void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, + int event_object_selection_changed, + int event_object_settings_changed); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); // Update view mode according to selected menu void update_mode(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index ba4c2e7fd2..ba800f38ac 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -83,9 +83,13 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), (wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %}; -void add_expert_mode_part(SV *ui_parent, SV *ui_sizer, int event) - %code%{ Slic3r::GUI::add_expert_mode_part((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), - (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), event); %}; +void add_expert_mode_part( SV *ui_parent, SV *ui_sizer, + int event_object_selection_changed, + int event_object_settings_changed) + %code%{ Slic3r::GUI::add_expert_mode_part( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), + (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), + event_object_selection_changed, + event_object_settings_changed); %}; void set_objects_from_perl( SV *ui_parent, SV *frequently_changed_parameters_sizer, From 1c695fd97eb4d987bdf2914ed97ee491f4ab3a80 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 12 Jun 2018 23:42:01 +0200 Subject: [PATCH 041/185] Added object movers to the object settings. Added SliderControl to Field. --- xs/src/slic3r/GUI/Field.cpp | 64 ++++++++++++++++++++++++++++++ xs/src/slic3r/GUI/Field.hpp | 32 +++++++++++++++ xs/src/slic3r/GUI/GUI.cpp | 46 ++++++++++++++++++++- xs/src/slic3r/GUI/GUI.hpp | 1 + xs/src/slic3r/GUI/OptionsGroup.cpp | 3 +- 5 files changed, 144 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 43c9e7db9e..acab4f4b6d 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -665,6 +665,70 @@ boost::any& PointCtrl::get_value() return m_value = ret_point; } +void SliderCtrl::BUILD() +{ + auto size = wxSize(wxDefaultSize); + if (m_opt.height >= 0) size.SetHeight(m_opt.height); + if (m_opt.width >= 0) size.SetWidth(m_opt.width); + + auto temp = new wxBoxSizer(wxHORIZONTAL); + + auto default = static_cast(m_opt.default_value)->value; + auto min = m_opt.min == INT_MIN ? 0 : m_opt.min; + auto max = m_opt.max == INT_MAX ? 100 : m_opt.max; + + m_slider = new wxSlider(m_parent, wxID_ANY, default * m_scale, + min * m_scale, max * m_scale, + wxDefaultPosition, size); + wxSize field_size(40, -1); + + m_textctrl = new wxTextCtrl(m_parent, wxID_ANY, wxString::Format("%d", m_slider->GetValue()/m_scale), + wxDefaultPosition, field_size); + + temp->Add(m_slider, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0); + temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0); + + m_slider->Bind(wxEVT_SLIDER, ([this](wxCommandEvent e) { + if (!m_disable_change_event){ + int val = boost::any_cast(get_value()); + m_textctrl->SetLabel(wxString::Format("%d", val)); + on_change_field(); + } + }), m_slider->GetId()); + + m_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { + std::string value = e.GetString().utf8_str().data(); + if (is_matched(value, "^-?\\d+(\\.\\d*)?$")){ + m_disable_change_event = true; + m_slider->SetValue(stoi(value)*m_scale); + m_disable_change_event = false; + on_change_field(); + } + }), m_textctrl->GetId()); + + // // recast as a wxWindow to fit the calling convention + m_sizer = dynamic_cast(temp); +} + +void SliderCtrl::set_value(const boost::any& value, bool change_event) +{ + m_disable_change_event = !change_event; + + m_slider->SetValue(boost::any_cast(value)*m_scale); + int val = boost::any_cast(get_value()); + m_textctrl->SetLabel(wxString::Format("%d", val)); + + m_disable_change_event = false; +} + +boost::any& SliderCtrl::get_value() +{ +// int ret_val; +// x_textctrl->GetValue().ToDouble(&val); + return m_value = int(m_slider->GetValue()/m_scale); +} + + } // GUI } // Slic3r diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 948178d3ee..40f56c0d36 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -384,6 +384,38 @@ public: wxSizer* getSizer() override { return sizer; } }; +class SliderCtrl : public Field { + using Field::Field; +public: + SliderCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} + SliderCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} + ~SliderCtrl() {} + + wxSizer* m_sizer{ nullptr }; + wxTextCtrl* m_textctrl{ nullptr }; + wxSlider* m_slider{ nullptr }; + + int m_scale = 10; + + void BUILD() override; + + void set_value(const int value, bool change_event = false); + void set_value(const boost::any& value, bool change_event = false); + boost::any& get_value() override; + + void enable() override { + m_slider->Enable(); + m_textctrl->Enable(); + m_textctrl->SetEditable(true); + } + void disable() override{ + m_slider->Disable(); + m_textctrl->Disable(); + m_textctrl->SetEditable(false); + } + wxSizer* getSizer() override { return m_sizer; } +}; + } // GUI } // Slic3r diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 01a61ff517..d897b6d298 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -139,6 +139,7 @@ bool g_show_print_info = false; bool g_show_manifold_warning_icon = false; wxSizer *m_sizer_object_buttons = nullptr; wxSizer *m_sizer_part_buttons = nullptr; +wxSizer *m_sizer_object_movers = nullptr; wxDataViewCtrl *m_objects_ctrl = nullptr; MyObjectTreeModel *m_objects_model = nullptr; wxCollapsiblePane *m_collpane_settings = nullptr; @@ -1032,6 +1033,42 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) return sizer; } +wxSizer* object_movers(wxWindow *win) +{ + DynamicPrintConfig* config = &g_PresetBundle->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume + std::shared_ptr optgroup = std::make_shared(win, "Move", config); + optgroup->label_width = 20; + + ConfigOptionDef def; + def.label = L("X"); + def.type = coInt; + def.gui_type = "slider"; + def.default_value = new ConfigOptionInt(0); +// def.min = -(model_object->bounding_box->size->x) * 4; +// def.max = model_object->bounding_box->size->x * 4; + + Option option = Option(def, "x"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + def.label = L("Y"); +// def.min = -(model_object->bounding_box->size->y) * 4; +// def.max = model_object->bounding_box->size->y * 4; + option = Option(def, "y"); + optgroup->append_single_option_line(option); + + def.label = L("Z"); +// def.min = -(model_object->bounding_box->size->z) * 4; +// def.max = model_object->bounding_box->size->z * 4; + option = Option(def, "z"); + optgroup->append_single_option_line(option); + + m_optgroups.push_back(optgroup); // ogObjectMovers + m_sizer_object_movers = optgroup->sizer; + m_sizer_object_movers->Show(false); + return optgroup->sizer; +} + Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value=0) { Line line = { _(option_name), "" }; @@ -1075,7 +1112,7 @@ Line add_og_to_object_settings(const std::string& option_name, const std::string wxBoxSizer* content_settings(wxWindow *win) { - DynamicPrintConfig* config = &g_PresetBundle->printers.get_edited_preset().config; // TODO get config from Model_volume + DynamicPrintConfig* config = &g_PresetBundle->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume std::shared_ptr optgroup = std::make_shared(win, "Extruders", config); optgroup->label_width = m_label_width; @@ -1095,6 +1132,8 @@ wxBoxSizer* content_settings(wxWindow *win) add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT, 20); + sizer->Add(object_movers(win), 0, wxEXPAND | wxLEFT, 20); + return sizer; } @@ -1141,6 +1180,8 @@ void unselect_objects() m_sizer_object_buttons->Show(false); if (m_sizer_part_buttons->IsShown(1)) m_sizer_part_buttons->Show(false); + if (m_sizer_object_movers->IsShown(1)) + m_sizer_object_movers->Show(false); if (m_collpane_settings->IsShown()) m_collpane_settings->Show(false); } @@ -1159,6 +1200,8 @@ void select_current_object(int idx) if (get_view_mode() == ConfigMenuModeExpert){ if (!m_sizer_object_buttons->IsShown(1)) m_sizer_object_buttons->Show(true); + if (!m_sizer_object_movers->IsShown(1)) + m_sizer_object_movers->Show(true); if (!m_collpane_settings->IsShown()) m_collpane_settings->Show(true); } @@ -1179,6 +1222,7 @@ void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, if (collpane->IsCollapsed()) { m_sizer_object_buttons->Show(false); m_sizer_part_buttons->Show(false); + m_sizer_object_movers->Show(false); m_collpane_settings->Show(false); } // else diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 277729ab07..6e975f953a 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -61,6 +61,7 @@ enum ogGroup{ ogFrequentlyChangingParameters, ogFrequentlyObjectSettings, ogObjectSettings, + ogObjectMovers, ogPartSettings }; diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 52882afdaf..4778047be2 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -30,6 +30,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co opt.gui_type.compare("i_enum_closed") == 0) { m_fields.emplace(id, STDMOVE(Choice::Create(parent(), opt, id))); } else if (opt.gui_type.compare("slider") == 0) { + m_fields.emplace(id, STDMOVE(SliderCtrl::Create(parent(), opt, id))); } else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl } else { switch (opt.type) { @@ -189,7 +190,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, (option.opt.full_width ? wxEXPAND : 0) | wxBOTTOM | wxTOP | wxALIGN_CENTER_VERTICAL, (wxOSX||!staticbox) ? 0 : 2); if (is_sizer_field(field)) - sizer->Add(field->getSizer(), 0, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0); + sizer->Add(field->getSizer(), 1, (option.opt.full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0); return; } From 8899be8cca7d2528babb19ee7f9a540df23b755f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 13 Jun 2018 16:39:33 +0200 Subject: [PATCH 042/185] Started porting of the functions for object settings editing --- xs/CMakeLists.txt | 2 ++ xs/src/slic3r/GUI/GUI.cpp | 34 +++++++++++++++++++++ xs/src/slic3r/GUI/GUI.hpp | 2 ++ xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 43 +++++++++++++++++++++++++++ xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 12 ++++++++ 5 files changed, 93 insertions(+) create mode 100644 xs/src/slic3r/GUI/GUI_ObjectParts.cpp create mode 100644 xs/src/slic3r/GUI/GUI_ObjectParts.hpp diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 9c61fa7294..8555fb0155 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -192,6 +192,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/PresetHints.hpp ${LIBDIR}/slic3r/GUI/GUI.cpp ${LIBDIR}/slic3r/GUI/GUI.hpp + ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.cpp + ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.hpp ${LIBDIR}/slic3r/GUI/Tab.cpp ${LIBDIR}/slic3r/GUI/Tab.hpp ${LIBDIR}/slic3r/GUI/TabIface.cpp diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index d897b6d298..dec2c7d7cd 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -55,6 +55,7 @@ #include "PresetBundle.hpp" #include "UpdateDialogs.hpp" #include "FirmwareDialog.hpp" +#include "GUI_ObjectParts.hpp" #include "../Utils/PresetUpdater.hpp" #include "../Config/Snapshot.hpp" @@ -495,6 +496,27 @@ void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_languag add_config_menu(menu, event_preferences_changed, event_language_change); } +wxArrayString* open_model(wxWindow *parent){ + t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card(); + std::vector file_types = { "known", "stl", "obj", "amf", "3mf", "prusa" }; + wxString MODEL_WILDCARD; + for (auto file_type : file_types) + MODEL_WILDCARD += vec_FILE_WILDCARDS.at(file_type) + "|"; + + auto dlg_title = _(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")); + auto dialog = new wxFileDialog(parent /*? parent : GetTopWindow(g_wxMainFrame)*/, dlg_title, + g_AppConfig->get_last_dir(), "", + MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + if (dialog->ShowModal() != wxID_OK) { + dialog->Destroy(); + return nullptr; + } + wxArrayString input_files; + dialog->GetPaths(input_files); + dialog->Destroy(); + return &input_files; +} + // This is called when closing the application, when loading a config file or when starting the config wizard // to notify the user whether he is aware that some preset changes will be lost. bool check_unsaved_changes() @@ -985,6 +1007,18 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) m_objects_ctrl->Select(m_objects_model->AddChild(item, name)); }); + btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) + { + on_btn_load(win, true); +// auto item = m_objects_ctrl->GetSelection(); +// if (!item) return; +// if (m_objects_model->GetParent(item) != wxDataViewItem(0)) +// item = m_objects_model->GetParent(item); +// if (!item) return; +// wxString name = "Part"; +// m_objects_ctrl->Select(m_objects_model->AddChild(item, name)); + }); + btn_delete->Bind(wxEVT_BUTTON, [](wxEvent&) { auto item = m_objects_ctrl->GetSelection(); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 6e975f953a..22b41fb201 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -124,6 +124,8 @@ void set_label_clr_sys(const wxColour& clr); const wxFont& small_font(); const wxFont& bold_font(); +wxArrayString* open_model(wxWindow *parent); + extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); // This is called when closing the application, when loading a config file or when starting the config wizard diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp new file mode 100644 index 0000000000..a5abf2af60 --- /dev/null +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -0,0 +1,43 @@ +#include "GUI.hpp" +#include "GUI_ObjectParts.hpp" + +#include + +namespace Slic3r +{ +namespace GUI +{ +void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/) +{ + auto input_files = open_model(parent); +// for(auto input_file : input_files) { +// my $model = eval{ Slic3r::Model->read_from_file($input_file) }; +// if ($@) { +// Slic3r::GUI::show_error($self, $@); +// next; +// } + +// foreach my $object(@{$model->objects}) { +// foreach my $volume(@{$object->volumes}) { +// my $new_volume = $self->{model_object}->add_volume($volume); +// $new_volume->set_modifier($is_modifier); +// $new_volume->set_name(basename($input_file)); +// +// # apply the same translation we applied to the object +// $new_volume->mesh->translate(@{$self->{model_object}->origin_translation}); +// +// # set a default extruder value, since user can't add it manually +// $new_volume->config->set_ifndef('extruder', 0); +// +// $self->{parts_changed} = 1; +// } +// } +// } + + parts_changed(); +} + +void parts_changed(){ ; } + +} //namespace GUI +} //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp new file mode 100644 index 0000000000..ffbda35bfa --- /dev/null +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -0,0 +1,12 @@ +#ifndef slic3r_GUI_ObjectParts_hpp_ +#define slic3r_GUI_ObjectParts_hpp_ + +namespace Slic3r +{ +namespace GUI +{ +void on_btn_load(wxWindow* parent, bool is_modifier = false); +void parts_changed(); +} //namespace GUI +} //namespace Slic3r +#endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file From 9504780ef4155d2fe15c5b56945e171c13227911 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 14 Jun 2018 15:33:42 +0200 Subject: [PATCH 043/185] Buttons "Add Part" and "Add Modifier" (in the Object Settings) works now --- lib/Slic3r/GUI/MainFrame.pm | 7 +- lib/Slic3r/GUI/Plater.pm | 2 +- xs/src/slic3r/GUI/GUI.cpp | 50 +++++++------ xs/src/slic3r/GUI/GUI.hpp | 14 +++- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 104 +++++++++++++++++++------- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 5 ++ xs/xsp/GUI.xsp | 6 +- 7 files changed, 133 insertions(+), 55 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 626bdef81e..fd2844783e 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -188,12 +188,11 @@ sub _init_tabpanel { # The following event is emited by the C++ Tab implementation on object settings change. EVT_COMMAND($self, -1, $OBJECT_SETTINGS_CHANGED_EVENT, sub { my ($self, $event) = @_; - my $obj_idx = $event->GetInt; - #my $line = $event->GetString; - #my ($parts_changed, $part_settings_changed) = split('',$line); + my $line = $event->GetString; + my ($obj_idx, $parts_changed, $part_settings_changed) = split('',$line); - #$self->{plater}->changed_object_settings($obj_idx, $parts_changed, $part_settings_changed); + $self->{plater}->changed_object_settings($obj_idx, $parts_changed, $part_settings_changed); }); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index bc149a8288..1d2168fe89 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -837,7 +837,7 @@ sub load_model_objects { my $model_object = $self->{model}->objects->[$obj_idx]; # Add object to list on c++ side - Slic3r::GUI::add_object_to_list($object->name, $model_object->instances_count, ($model_object->instances->[0]->scaling_factor * 100)); + Slic3r::GUI::add_object_to_list($object->name, $model_object); $self->reset_thumbnail($obj_idx); } diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index dec2c7d7cd..46e1a96d6f 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -59,6 +59,7 @@ #include "../Utils/PresetUpdater.hpp" #include "../Config/Snapshot.hpp" +#include "Model.hpp" namespace Slic3r { namespace GUI { @@ -149,6 +150,7 @@ int m_event_object_settings_changed = 0; bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler // calls this method again and again and again +ModelObjectPtrs m_objects; wxFont g_small_font; wxFont g_bold_font; @@ -496,7 +498,7 @@ void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_languag add_config_menu(menu, event_preferences_changed, event_language_change); } -wxArrayString* open_model(wxWindow *parent){ +void open_model(wxWindow *parent, wxArrayString& input_files){ t_file_wild_card vec_FILE_WILDCARDS = get_file_wild_card(); std::vector file_types = { "known", "stl", "obj", "amf", "3mf", "prusa" }; wxString MODEL_WILDCARD; @@ -509,12 +511,11 @@ wxArrayString* open_model(wxWindow *parent){ MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if (dialog->ShowModal() != wxID_OK) { dialog->Destroy(); - return nullptr; + return ; } - wxArrayString input_files; + dialog->GetPaths(input_files); dialog->Destroy(); - return &input_files; } // This is called when closing the application, when loading a config file or when starting the config wizard @@ -810,6 +811,24 @@ unsigned get_colour_approx_luma(const wxColour &colour) b * b * .068 )); } +wxDataViewCtrl* get_objects_ctrl() { + return m_objects_ctrl; +} +MyObjectTreeModel* get_objects_model() { + return m_objects_model; +} + +ModelObjectPtrs& get_objects() { + return m_objects; +} + +const int& get_event_object_settings_changed() { + return m_event_object_settings_changed; +} + +wxFrame* get_main_frame() { + return g_wxMainFrame; +} void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value) { @@ -996,27 +1015,14 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) auto btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); //*** button's functions - btn_load_part->Bind(wxEVT_BUTTON, [](wxEvent&) + btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) { - auto item = m_objects_ctrl->GetSelection(); - if (!item) return; - if (m_objects_model->GetParent(item) != wxDataViewItem(0)) - item = m_objects_model->GetParent(item); - if (!item) return; - wxString name = "Part"; - m_objects_ctrl->Select(m_objects_model->AddChild(item, name)); + on_btn_load(win); }); btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { on_btn_load(win, true); -// auto item = m_objects_ctrl->GetSelection(); -// if (!item) return; -// if (m_objects_model->GetParent(item) != wxDataViewItem(0)) -// item = m_objects_model->GetParent(item); -// if (!item) return; -// wxString name = "Part"; -// m_objects_ctrl->Select(m_objects_model->AddChild(item, name)); }); btn_delete->Bind(wxEVT_BUTTON, [](wxEvent&) @@ -1171,10 +1177,12 @@ wxBoxSizer* content_settings(wxWindow *win) return sizer; } -void add_object_to_list(const std::string &name, int instances_count, int scale) +void add_object_to_list(const std::string &name, ModelObject* model_object) { wxString item = name; - m_objects_ctrl->Select(m_objects_model->Add(item, instances_count, scale)); + int scale = model_object->instances[0]->scaling_factor * 100; + m_objects_ctrl->Select(m_objects_model->Add(item, model_object->instances.size(), scale)); + m_objects.push_back(model_object); } void delete_object_from_list() diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 22b41fb201..5c5a1a6a90 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -25,6 +25,8 @@ class wxButton; class wxFileDialog; class wxStaticBitmap; class wxFont; +class wxDataViewCtrl; +class MyObjectTreeModel; namespace Slic3r { @@ -34,6 +36,7 @@ class AppConfig; class PresetUpdater; class DynamicPrintConfig; class TabIface; +class ModelObject; #define _(s) Slic3r::translate((s)) inline wxString translate(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)); } @@ -67,6 +70,7 @@ enum ogGroup{ class Tab; class ConfigOptionsGroup; +typedef std::vector ModelObjectPtrs; // Map from an file_type name to full file wildcard name. typedef std::map t_file_wild_card; inline t_file_wild_card& get_file_wild_card() { @@ -124,7 +128,13 @@ void set_label_clr_sys(const wxColour& clr); const wxFont& small_font(); const wxFont& bold_font(); -wxArrayString* open_model(wxWindow *parent); +void open_model(wxWindow *parent, wxArrayString& input_files); + +wxDataViewCtrl* get_objects_ctrl (); +MyObjectTreeModel* get_objects_model(); +ModelObjectPtrs& get_objects(); +const int& get_event_object_settings_changed(); +wxFrame* get_main_frame(); extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); @@ -191,7 +201,7 @@ wxString from_u8(const std::string &str); // Add object to the list //void add_object(const std::string &name); -void add_object_to_list(const std::string &name, int instances_count=1, int scale=100); +void add_object_to_list(const std::string &name, ModelObject* model_object); // Delete object from the list void delete_object_from_list(); // Delete all objects from the list diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index a5abf2af60..57d46c977d 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -1,42 +1,96 @@ #include "GUI.hpp" #include "GUI_ObjectParts.hpp" +#include "Model.hpp" +#include "wxExtensions.hpp" #include +#include +#include namespace Slic3r { namespace GUI { -void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/) -{ - auto input_files = open_model(parent); -// for(auto input_file : input_files) { -// my $model = eval{ Slic3r::Model->read_from_file($input_file) }; -// if ($@) { -// Slic3r::GUI::show_error($self, $@); -// next; -// } +bool m_parts_changed = false; +bool m_part_settings_changed = false; -// foreach my $object(@{$model->objects}) { -// foreach my $volume(@{$object->volumes}) { -// my $new_volume = $self->{model_object}->add_volume($volume); -// $new_volume->set_modifier($is_modifier); -// $new_volume->set_name(basename($input_file)); -// -// # apply the same translation we applied to the object -// $new_volume->mesh->translate(@{$self->{model_object}->origin_translation}); -// -// # set a default extruder value, since user can't add it manually -// $new_volume->config->set_ifndef('extruder', 0); -// -// $self->{parts_changed} = 1; -// } -// } -// } +bool is_parts_changed(){return m_parts_changed;} +bool is_part_settings_changed(){ return m_part_settings_changed; } + +void load_part(wxWindow* parent, ModelObject* model_object, wxArrayString& part_names, bool is_modifier) +{ + wxArrayString input_files; + open_model(parent, input_files); + for (int i = 0; i < input_files.size(); ++i) { + std::string input_file = input_files.Item(i).ToStdString(); + + Model model; + try { + model = Model::read_from_file(input_file); + } + catch (std::exception &e) { + auto msg = _(L("Error! ")) + input_file + " : " + e.what() + "."; + show_error(parent, msg); + exit(1); + } + + for ( auto object : model.objects) { + for (auto volume : object->volumes) { + auto new_volume = model_object->add_volume(*volume); + new_volume->modifier = is_modifier; + boost::filesystem::path(input_file).filename().string(); + new_volume->name = boost::filesystem::path(input_file).filename().string(); + + part_names.Add(new_volume->name); + + // apply the same translation we applied to the object + new_volume->mesh.translate( model_object->origin_translation.x, + model_object->origin_translation.y, + model_object->origin_translation.y ); + + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + + m_parts_changed = true; + } + } + } parts_changed(); } +void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/) +{ + auto objects_ctrl = get_objects_ctrl(); + auto item = objects_ctrl->GetSelection(); + if (!item) + return; + int obj_idx = -1; + auto objects_model = get_objects_model(); + if (objects_model->GetParent(item) == wxDataViewItem(0)) + obj_idx = objects_model->GetIdByItem(item); + else + return; + + if (obj_idx < 0) return; + wxArrayString part_names; + ModelObjectPtrs& objects = get_objects(); + load_part(parent, objects[obj_idx], part_names, is_modifier); + + auto event = get_event_object_settings_changed(); + if (event > 0) { + wxCommandEvent e(event); + auto event_str = wxString::Format("%d %d %d", obj_idx, + is_parts_changed() ? 1 : 0, + is_part_settings_changed() ? 1 : 0); + e.SetString(event_str); + get_main_frame()->ProcessWindowEvent(e); + } + + for (int i = 0; i < part_names.size(); ++i) + objects_ctrl->Select(objects_model->AddChild(item, part_names.Item(i))); +} + void parts_changed(){ ; } } //namespace GUI diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index ffbda35bfa..9ee982b96b 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -5,6 +5,11 @@ namespace Slic3r { namespace GUI { +bool is_parts_changed(); +bool is_part_settings_changed(); + +void load_part( wxWindow* parent, ModelObject* model_object, + wxArrayString& part_names, bool is_modifier); void on_btn_load(wxWindow* parent, bool is_modifier = false); void parts_changed(); } //namespace GUI diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index ba800f38ac..2c5e7fccd8 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -122,8 +122,10 @@ void set_show_manifold_warning_icon(bool show) void update_mode() %code%{ Slic3r::GUI::update_mode(); %}; -void add_object_to_list(const char *name, int instances_count, int scale) - %code%{ Slic3r::GUI::add_object_to_list(name, instances_count, scale); %}; +void add_object_to_list(const char *name, SV *object_model) + %code%{ Slic3r::GUI::add_object_to_list( + name, + (ModelObject *)wxPli_sv_2_object(aTHX_ object_model, "Slic3r::Model::Object") ); %}; void delete_object_from_list() %code%{ Slic3r::GUI::delete_object_from_list(); %}; From 3e0ff5e9efe545493e20837269ba96b8834a244c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 14 Jun 2018 21:48:06 +0200 Subject: [PATCH 044/185] Added LambdaObjectDialog --- xs/CMakeLists.txt | 2 + xs/src/slic3r/GUI/GUI.cpp | 7 ++ xs/src/slic3r/GUI/GUI.hpp | 11 ++ xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 26 ++-- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 2 +- xs/src/slic3r/GUI/LambdaObjectDialog.cpp | 151 +++++++++++++++++++++++ xs/src/slic3r/GUI/LambdaObjectDialog.hpp | 34 +++++ 7 files changed, 220 insertions(+), 13 deletions(-) create mode 100644 xs/src/slic3r/GUI/LambdaObjectDialog.cpp create mode 100644 xs/src/slic3r/GUI/LambdaObjectDialog.hpp diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 8555fb0155..46072b091c 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -194,6 +194,8 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/GUI.hpp ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.cpp ${LIBDIR}/slic3r/GUI/GUI_ObjectParts.hpp + ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.cpp + ${LIBDIR}/slic3r/GUI/LambdaObjectDialog.hpp ${LIBDIR}/slic3r/GUI/Tab.cpp ${LIBDIR}/slic3r/GUI/Tab.hpp ${LIBDIR}/slic3r/GUI/TabIface.cpp diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 46e1a96d6f..6b4195e940 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -60,6 +60,7 @@ #include "../Utils/PresetUpdater.hpp" #include "../Config/Snapshot.hpp" #include "Model.hpp" +#include "LambdaObjectDialog.hpp" namespace Slic3r { namespace GUI { @@ -1025,6 +1026,12 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) on_btn_load(win, true); }); + btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) + { + auto dlg = new LambdaObjectDialog(win); + dlg->ShowModal(); + }); + btn_delete->Bind(wxEVT_BUTTON, [](wxEvent&) { auto item = m_objects_ctrl->GetSelection(); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 5c5a1a6a90..12eef0b627 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -89,6 +89,17 @@ inline t_file_wild_card& get_file_wild_card() { return FILE_WILDCARDS; } +struct OBJECT_PARAMETERS +{ + std::string type = "box"; + double dim[3];// = { 1.0, 1.0, 1.0 }; + int cyl_r = 1; + int cyl_h = 1; + double sph_rho = 1.0; + double slab_h = 1.0; + double slab_z = 0.0; +}; + void disable_screensaver(); void enable_screensaver(); bool debugged(); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 57d46c977d..2f88b58f79 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -55,8 +55,6 @@ void load_part(wxWindow* parent, ModelObject* model_object, wxArrayString& part_ } } } - - parts_changed(); } void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/) @@ -77,21 +75,25 @@ void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/) ModelObjectPtrs& objects = get_objects(); load_part(parent, objects[obj_idx], part_names, is_modifier); - auto event = get_event_object_settings_changed(); - if (event > 0) { - wxCommandEvent e(event); - auto event_str = wxString::Format("%d %d %d", obj_idx, - is_parts_changed() ? 1 : 0, - is_part_settings_changed() ? 1 : 0); - e.SetString(event_str); - get_main_frame()->ProcessWindowEvent(e); - } + parts_changed(obj_idx); for (int i = 0; i < part_names.size(); ++i) objects_ctrl->Select(objects_model->AddChild(item, part_names.Item(i))); } -void parts_changed(){ ; } +void parts_changed(int obj_idx) +{ + auto event = get_event_object_settings_changed(); + if (event <= 0) + return; + + wxCommandEvent e(event); + auto event_str = wxString::Format("%d %d %d", obj_idx, + is_parts_changed() ? 1 : 0, + is_part_settings_changed() ? 1 : 0); + e.SetString(event_str); + get_main_frame()->ProcessWindowEvent(e); +} } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 9ee982b96b..cf07bb2427 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -11,7 +11,7 @@ bool is_part_settings_changed(); void load_part( wxWindow* parent, ModelObject* model_object, wxArrayString& part_names, bool is_modifier); void on_btn_load(wxWindow* parent, bool is_modifier = false); -void parts_changed(); +void parts_changed(int obj_idx); } //namespace GUI } //namespace Slic3r #endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.cpp b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp new file mode 100644 index 0000000000..5657c7a71e --- /dev/null +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp @@ -0,0 +1,151 @@ +#include "LambdaObjectDialog.hpp" + +#include +#include +#include "OptionsGroup.hpp" + +namespace Slic3r +{ +namespace GUI +{ +static wxString dots("…", wxConvUTF8); + +LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) +{ + Create(parent, wxID_ANY, _(L("Lambda Object")), + wxDefaultPosition, wxSize(500, 500), + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + + // instead of double dim[3] = { 1.0, 1.0, 1.0 }; + object_parameters.dim[0] = 1.0; + object_parameters.dim[1] = 1.0; + object_parameters.dim[2] = 1.0; + + sizer = new wxBoxSizer(wxVERTICAL); + + // modificator options + m_modificator_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(300, -1), wxCHB_TOP); + sizer->Add(m_modificator_options_book, 1, wxEXPAND| wxALL, 10); + + auto optgroup = init_modificator_options_page(_(L("Box"))); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + int opt_id = opt_key == "L" ? 0 : + opt_key == "W" ? 1 : + opt_key == "L" ? 2 : -1; + if (opt_id < 0) return; + object_parameters.dim[opt_id] = boost::any_cast(value); + }; + + ConfigOptionDef def; + def.type = coFloat; + def.default_value = new ConfigOptionFloat{ 1.0 }; + def.label = L("L"); + Option option(def, "l"); + optgroup->append_single_option_line(option); + + def.label = L("W"); + option = Option(def, "w"); + optgroup->append_single_option_line(option); + + def.label = L("H"); + option = Option(def, "h"); + optgroup->append_single_option_line(option); + + optgroup = init_modificator_options_page(_(L("Cylinder"))); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + int val = boost::any_cast(value); + if (opt_key == "cyl_r") + object_parameters.cyl_r = val; + else if (opt_key == "cyl_h") + object_parameters.cyl_h = val; + else return; + }; + + def.type = coInt; + def.default_value = new ConfigOptionInt{ 1 }; + def.label = L("Radius"); + option = Option(def, "cyl_r"); + optgroup->append_single_option_line(option); + + def.label = L("Height"); + option = Option(def, "cyl_h"); + optgroup->append_single_option_line(option); + + optgroup = init_modificator_options_page(_(L("Sphere"))); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + if (opt_key == "sph_rho") + object_parameters.sph_rho = boost::any_cast(value); + else return; + }; + + def.type = coFloat; + def.default_value = new ConfigOptionFloat{ 1.0 }; + def.label = L("Rho"); + option = Option(def, "sph_rho"); + optgroup->append_single_option_line(option); + + optgroup = init_modificator_options_page(_(L("Slab"))); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ + double val = boost::any_cast(value); + if (opt_key == "slab_z") + object_parameters.slab_z = val; + else if (opt_key == "slab_h") + object_parameters.slab_h = val; + else return; + }; + + def.label = L("H"); + option = Option(def, "slab_h"); + optgroup->append_single_option_line(option); + + def.label = L("Initial Z"); + option = Option(def, "slab_z"); + optgroup->append_single_option_line(option); + + + auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + + wxButton* btn_OK = static_cast(FindWindowById(wxID_OK, this)); + btn_OK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + // validate user input + if (!CanClose())return; + EndModal(wxID_OK); + Destroy(); + }); + + wxButton* btn_CANCEL = static_cast(FindWindowById(wxID_CANCEL, this)); + btn_CANCEL->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + // validate user input + if (!CanClose())return; + EndModal(wxID_CANCEL); + Destroy(); + }); + + sizer->Add(button_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + + SetSizer(sizer); + sizer->Fit(this); + sizer->SetSizeHints(this); +} + +// Called from the constructor. +// Create a panel for a rectangular / circular / custom bed shape. +ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(wxString title){ + + auto panel = new wxPanel(m_modificator_options_book); + + ConfigOptionsGroupShp optgroup; + optgroup = std::make_shared(panel, _(L("Add")) + " " +title + " " +dots); + optgroup->label_width = 100; + + m_optgroups.push_back(optgroup); + + panel->SetSizerAndFit(optgroup->sizer); + m_modificator_options_book->AddPage(panel, title); + + return optgroup; +} + + +} //namespace GUI +} //namespace Slic3r diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.hpp b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp new file mode 100644 index 0000000000..712722b2d6 --- /dev/null +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp @@ -0,0 +1,34 @@ +#ifndef slic3r_LambdaObjectDialog_hpp_ +#define slic3r_LambdaObjectDialog_hpp_ + +#include "GUI.hpp" + +#include +#include +#include + +namespace Slic3r +{ +namespace GUI +{ +using ConfigOptionsGroupShp = std::shared_ptr; +class LambdaObjectDialog : public wxDialog +{ + wxChoicebook* m_modificator_options_book; + std::vector m_optgroups; +public: + LambdaObjectDialog(wxWindow* parent); + ~LambdaObjectDialog(){} + + bool CanClose() { return true; } // ??? + + ConfigOptionsGroupShp init_modificator_options_page(wxString title); + + // Note whether the window was already closed, so a pending update is not executed. + bool m_already_closed = false; + OBJECT_PARAMETERS object_parameters; + wxBoxSizer* sizer = nullptr; +}; +} //namespace GUI +} //namespace Slic3r +#endif //slic3r_LambdaObjectDialog_hpp_ From a0090fccb5c8ba0484df721a5b5376a2dd92cf8b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 14 Jun 2018 23:31:15 +0200 Subject: [PATCH 045/185] Button "Load Lambda" (in the Object Settings) works now --- xs/src/slic3r/GUI/GUI.cpp | 3 +- xs/src/slic3r/GUI/GUI.hpp | 21 +++++--- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 61 ++++++++++++++++++++++-- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 9 +++- xs/src/slic3r/GUI/LambdaObjectDialog.cpp | 29 ++++++++++- xs/src/slic3r/GUI/LambdaObjectDialog.hpp | 1 + 6 files changed, 107 insertions(+), 17 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 6b4195e940..f738ca693f 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1028,8 +1028,7 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { - auto dlg = new LambdaObjectDialog(win); - dlg->ShowModal(); + on_btn_load(win, true, true); }); btn_delete->Bind(wxEVT_BUTTON, [](wxEvent&) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 12eef0b627..fdad1e2914 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -68,6 +68,13 @@ enum ogGroup{ ogPartSettings }; +enum LambdaTypeIDs{ + LambdaTypeBox, + LambdaTypeCylinder, + LambdaTypeSphere, + LambdaTypeSlab +}; + class Tab; class ConfigOptionsGroup; typedef std::vector ModelObjectPtrs; @@ -91,13 +98,13 @@ inline t_file_wild_card& get_file_wild_card() { struct OBJECT_PARAMETERS { - std::string type = "box"; - double dim[3];// = { 1.0, 1.0, 1.0 }; - int cyl_r = 1; - int cyl_h = 1; - double sph_rho = 1.0; - double slab_h = 1.0; - double slab_z = 0.0; + LambdaTypeIDs type = LambdaTypeBox; + double dim[3];// = { 1.0, 1.0, 1.0 }; + int cyl_r = 1; + int cyl_h = 1; + double sph_rho = 1.0; + double slab_h = 1.0; + double slab_z = 0.0; }; void disable_screensaver(); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 2f88b58f79..5aa0d8b157 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -6,6 +6,7 @@ #include #include #include +#include "LambdaObjectDialog.hpp" namespace Slic3r { @@ -17,7 +18,8 @@ bool m_part_settings_changed = false; bool is_parts_changed(){return m_parts_changed;} bool is_part_settings_changed(){ return m_part_settings_changed; } -void load_part(wxWindow* parent, ModelObject* model_object, wxArrayString& part_names, bool is_modifier) +void load_part( wxWindow* parent, ModelObject* model_object, + wxArrayString& part_names, const bool is_modifier) { wxArrayString input_files; open_model(parent, input_files); @@ -47,7 +49,6 @@ void load_part(wxWindow* parent, ModelObject* model_object, wxArrayString& part_ new_volume->mesh.translate( model_object->origin_translation.x, model_object->origin_translation.y, model_object->origin_translation.y ); - // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); @@ -57,7 +58,56 @@ void load_part(wxWindow* parent, ModelObject* model_object, wxArrayString& part_ } } -void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/) +void load_lambda( wxWindow* parent, ModelObject* model_object, + wxArrayString& part_names, const bool is_modifier) +{ + auto dlg = new LambdaObjectDialog(parent); + if (dlg->ShowModal() == wxID_CANCEL) { + return; + } + + std::string name = "lambda-"; + TriangleMesh mesh; + + auto params = dlg->ObjectParameters(); + switch (params.type) + { + case LambdaTypeBox:{ + mesh = make_cube(params.dim[0], params.dim[1], params.dim[2]); + name += "Box"; + break;} + case LambdaTypeCylinder:{ + mesh = make_cylinder(params.cyl_r, params.cyl_h); + name += "Cylinder"; + break;} + case LambdaTypeSphere:{ + mesh = make_sphere(params.sph_rho); + name += "Sphere"; + break;} + case LambdaTypeSlab:{ + const auto& size = model_object->bounding_box().size(); + mesh = make_cube(size.x*1.5, size.y*1.5, params.slab_h); + // box sets the base coordinate at 0, 0, move to center of plate and move it up to initial_z + mesh.translate(-size.x*1.5 / 2.0, -size.y*1.5 / 2.0, params.slab_z); + name += "Slab"; + break; } + default: + break; + } + mesh.repair(); + + auto new_volume = model_object->add_volume(mesh); + new_volume->modifier = is_modifier; + new_volume->name = name; + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + + part_names.Add(name); + + m_parts_changed = true; +} + +void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/* = false*/) { auto objects_ctrl = get_objects_ctrl(); auto item = objects_ctrl->GetSelection(); @@ -73,7 +123,10 @@ void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/) if (obj_idx < 0) return; wxArrayString part_names; ModelObjectPtrs& objects = get_objects(); - load_part(parent, objects[obj_idx], part_names, is_modifier); + if (is_lambda) + load_lambda(parent, objects[obj_idx], part_names, is_modifier); + else + load_part(parent, objects[obj_idx], part_names, is_modifier); parts_changed(obj_idx); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index cf07bb2427..40bcc012e8 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -9,8 +9,13 @@ bool is_parts_changed(); bool is_part_settings_changed(); void load_part( wxWindow* parent, ModelObject* model_object, - wxArrayString& part_names, bool is_modifier); -void on_btn_load(wxWindow* parent, bool is_modifier = false); + wxArrayString& part_names, const bool is_modifier); + +void load_lambda(wxWindow* parent, ModelObject* model_object, + wxArrayString& part_names, const bool is_modifier); + +void on_btn_load(wxWindow* parent, bool is_modifier = false, bool is_lambda = false); + void parts_changed(int obj_idx); } //namespace GUI } //namespace Slic3r diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.cpp b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp index 5657c7a71e..b5479fb129 100644 --- a/xs/src/slic3r/GUI/LambdaObjectDialog.cpp +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp @@ -13,7 +13,7 @@ static wxString dots("…", wxConvUTF8); LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) { Create(parent, wxID_ANY, _(L("Lambda Object")), - wxDefaultPosition, wxSize(500, 500), + wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); // instead of double dim[3] = { 1.0, 1.0, 1.0 }; @@ -24,7 +24,8 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) sizer = new wxBoxSizer(wxVERTICAL); // modificator options - m_modificator_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(300, -1), wxCHB_TOP); + m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition, + wxDefaultSize, wxCHB_TOP); sizer->Add(m_modificator_options_book, 1, wxEXPAND| wxALL, 10); auto optgroup = init_modificator_options_page(_(L("Box"))); @@ -37,6 +38,7 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) }; ConfigOptionDef def; + def.width = 70; def.type = coFloat; def.default_value = new ConfigOptionFloat{ 1.0 }; def.label = L("L"); @@ -102,6 +104,29 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) option = Option(def, "slab_z"); optgroup->append_single_option_line(option); + Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e) + { + auto page_idx = m_modificator_options_book->GetSelection(); + if (page_idx < 0) return; + switch (page_idx) + { + case 0: + object_parameters.type = LambdaTypeBox; + break; + case 1: + object_parameters.type = LambdaTypeCylinder; + break; + case 2: + object_parameters.type = LambdaTypeSphere; + break; + case 3: + object_parameters.type = LambdaTypeSlab; + break; + default: + break; + } + })); + auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL); diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.hpp b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp index 712722b2d6..a70c124490 100644 --- a/xs/src/slic3r/GUI/LambdaObjectDialog.hpp +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp @@ -21,6 +21,7 @@ public: ~LambdaObjectDialog(){} bool CanClose() { return true; } // ??? + OBJECT_PARAMETERS& ObjectParameters(){ return object_parameters; } ConfigOptionsGroupShp init_modificator_options_page(wxString title); From 942a3340aae6f71943691beb0ea042d1c2f4d1f0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 14 Jun 2018 23:56:44 +0200 Subject: [PATCH 046/185] Added a check for the correctness of the entered characters in numerical fields. --- xs/src/slic3r/GUI/Field.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index acab4f4b6d..a500946620 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -105,7 +105,11 @@ namespace Slic3r { namespace GUI { break; } double val; - str.ToCDouble(&val); + if(!str.ToCDouble(&val)) + { + show_error(m_parent, _(L("Input value contains incorrect symbol(s).\nUse, please, only digits"))); + set_value(double_to_string(val), true); + } if (m_opt.min > val || val > m_opt.max) { show_error(m_parent, _(L("Input value is out of range"))); From a91cb5b267df4f4209c78a3ce8bed977e0472f7d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 15 Jun 2018 22:42:51 +0200 Subject: [PATCH 047/185] Upgraded object_control to use icons near the name Renamed some classes Deleted unused classes --- xs/src/slic3r/GUI/Field.cpp | 4 +- xs/src/slic3r/GUI/GUI.cpp | 31 +-- xs/src/slic3r/GUI/GUI.hpp | 4 +- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 7 +- xs/src/slic3r/GUI/wxExtensions.cpp | 357 +++----------------------- xs/src/slic3r/GUI/wxExtensions.hpp | 252 +++--------------- 6 files changed, 99 insertions(+), 556 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index a500946620..dcb251be6f 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -677,11 +677,11 @@ void SliderCtrl::BUILD() auto temp = new wxBoxSizer(wxHORIZONTAL); - auto default = static_cast(m_opt.default_value)->value; + auto def_val = static_cast(m_opt.default_value)->value; auto min = m_opt.min == INT_MIN ? 0 : m_opt.min; auto max = m_opt.max == INT_MAX ? 100 : m_opt.max; - m_slider = new wxSlider(m_parent, wxID_ANY, default * m_scale, + m_slider = new wxSlider(m_parent, wxID_ANY, def_val * m_scale, min * m_scale, max * m_scale, wxDefaultPosition, size); wxSize field_size(40, -1); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index f738ca693f..3e76c99f7f 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -144,7 +144,7 @@ wxSizer *m_sizer_object_buttons = nullptr; wxSizer *m_sizer_part_buttons = nullptr; wxSizer *m_sizer_object_movers = nullptr; wxDataViewCtrl *m_objects_ctrl = nullptr; -MyObjectTreeModel *m_objects_model = nullptr; +PrusaObjectDataViewModel *m_objects_model = nullptr; wxCollapsiblePane *m_collpane_settings = nullptr; int m_event_object_selection_changed = 0; int m_event_object_settings_changed = 0; @@ -815,7 +815,7 @@ unsigned get_colour_approx_luma(const wxColour &colour) wxDataViewCtrl* get_objects_ctrl() { return m_objects_ctrl; } -MyObjectTreeModel* get_objects_model() { +PrusaObjectDataViewModel* get_objects_model() { return m_objects_model; } @@ -925,36 +925,28 @@ wxBoxSizer* content_objects_list(wxWindow *win) { m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_objects_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects + auto objects_sz = new wxBoxSizer(wxVERTICAL); objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT, 20); - m_objects_model = new MyObjectTreeModel; + m_objects_model = new PrusaObjectDataViewModel; m_objects_ctrl->AssociateModel(m_objects_model); #if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE m_objects_ctrl->EnableDragSource(wxDF_UNICODETEXT); m_objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); #endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - // column 0 of the view control: - - wxDataViewTextRenderer *tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); - wxDataViewColumn *column00 = new wxDataViewColumn("Name", tr, 0, 110, wxALIGN_LEFT, - wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - m_objects_ctrl->AppendColumn(column00); + // column 0(Icon+Text) of the view control: + m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 150, + wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); // column 1 of the view control: - - tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); - wxDataViewColumn *column01 = new wxDataViewColumn("Copy", tr, 1, 75, wxALIGN_CENTER_HORIZONTAL, - wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - m_objects_ctrl->AppendColumn(column01); + m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 65, + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); // column 2 of the view control: - - tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); - wxDataViewColumn *column02 = new wxDataViewColumn("Scale", tr, 2, 80, wxALIGN_CENTER_HORIZONTAL, - wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - m_objects_ctrl->AppendColumn(column02); + m_objects_ctrl->AppendTextColumn(_(L("Scale")), 2, wxDATAVIEW_CELL_INERT, 70, + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { @@ -988,6 +980,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) auto show_obj_sizer = m_objects_model->GetParent(item) == wxDataViewItem(0); m_sizer_object_buttons->Show(show_obj_sizer); m_sizer_part_buttons->Show(!show_obj_sizer); + m_sizer_object_movers->Show(!show_obj_sizer); m_collpane_settings->SetLabelText((show_obj_sizer ? _(L("Object Settings")) : _(L("Part Settings"))) + ":"); m_collpane_settings->Show(true); }); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index fdad1e2914..ef34d2bf85 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -26,7 +26,7 @@ class wxFileDialog; class wxStaticBitmap; class wxFont; class wxDataViewCtrl; -class MyObjectTreeModel; +class PrusaObjectDataViewModel; namespace Slic3r { @@ -149,7 +149,7 @@ const wxFont& bold_font(); void open_model(wxWindow *parent, wxArrayString& input_files); wxDataViewCtrl* get_objects_ctrl (); -MyObjectTreeModel* get_objects_model(); +PrusaObjectDataViewModel* get_objects_model(); ModelObjectPtrs& get_objects(); const int& get_event_object_settings_changed(); wxFrame* get_main_frame(); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 5aa0d8b157..8783471a5f 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -2,11 +2,12 @@ #include "GUI_ObjectParts.hpp" #include "Model.hpp" #include "wxExtensions.hpp" +#include "LambdaObjectDialog.hpp" +#include "../../libslic3r/Utils.hpp" #include #include #include -#include "LambdaObjectDialog.hpp" namespace Slic3r { @@ -130,8 +131,10 @@ void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/ parts_changed(obj_idx); + const std::string icon_name = is_modifier ? "plugin.png" : "package.png"; + auto icon = wxIcon(Slic3r::GUI::from_u8(Slic3r::var(icon_name)), wxBITMAP_TYPE_PNG); for (int i = 0; i < part_names.size(); ++i) - objects_ctrl->Select(objects_model->AddChild(item, part_names.Item(i))); + objects_ctrl->Select(objects_model->AddChild(item, part_names.Item(i), icon)); } void parts_changed(int obj_idx) diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index b0c60d4c15..36801634ec 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -341,12 +341,12 @@ void PrusaCollapsiblePaneMSW::Collapse(bool collapse) // ***************************************************************************** // ---------------------------------------------------------------------------- -// MyObjectTreeModel +// PrusaObjectDataViewModel // ---------------------------------------------------------------------------- -wxDataViewItem MyObjectTreeModel::Add(wxString &name) +wxDataViewItem PrusaObjectDataViewModel::Add(wxString &name) { - auto root = new MyObjectTreeModelNode(name); + auto root = new PrusaObjectDataViewModelNode(name); m_objects.push_back(root); // notify control wxDataViewItem child((void*)root); @@ -355,9 +355,9 @@ wxDataViewItem MyObjectTreeModel::Add(wxString &name) return child; } -wxDataViewItem MyObjectTreeModel::Add(wxString &name, int instances_count, int scale) +wxDataViewItem PrusaObjectDataViewModel::Add(wxString &name, int instances_count, int scale) { - auto root = new MyObjectTreeModelNode(name, instances_count, scale); + auto root = new PrusaObjectDataViewModelNode(name, instances_count, scale); m_objects.push_back(root); // notify control wxDataViewItem child((void*)root); @@ -366,21 +366,24 @@ wxDataViewItem MyObjectTreeModel::Add(wxString &name, int instances_count, int s return child; } -wxDataViewItem MyObjectTreeModel::AddChild(const wxDataViewItem &parent_item, wxString &name) +wxDataViewItem PrusaObjectDataViewModel::AddChild( const wxDataViewItem &parent_item, + const wxString &name, + const wxIcon& icon) { - MyObjectTreeModelNode *root = (MyObjectTreeModelNode*)parent_item.GetID(); + PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (!root) return wxDataViewItem(0); if (root->GetChildren().Count() == 0) { - auto node = new MyObjectTreeModelNode(root, root->m_name); + auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh); root->Append(node); // notify control wxDataViewItem child((void*)node); ItemAdded(parent_item, child); } - auto node = new MyObjectTreeModelNode(root, name); + auto node = new PrusaObjectDataViewModelNode(root, name, icon); root->Append(node); // notify control wxDataViewItem child((void*)node); @@ -388,10 +391,10 @@ wxDataViewItem MyObjectTreeModel::AddChild(const wxDataViewItem &parent_item, wx return child; } -wxDataViewItem MyObjectTreeModel::Delete(const wxDataViewItem &item) +wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) { auto ret_item = wxDataViewItem(0); - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); if (!node) // happens if item.IsOk()==false return ret_item; @@ -434,7 +437,7 @@ wxDataViewItem MyObjectTreeModel::Delete(const wxDataViewItem &item) return ret_item; } -void MyObjectTreeModel::DeleteAll() +void PrusaObjectDataViewModel::DeleteAll() { while (!m_objects.empty()) { @@ -444,7 +447,7 @@ void MyObjectTreeModel::DeleteAll() } } -wxDataViewItem MyObjectTreeModel::GetItemById(int obj_idx) +wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx) { if (obj_idx >= m_objects.size()) { @@ -455,11 +458,11 @@ wxDataViewItem MyObjectTreeModel::GetItemById(int obj_idx) } -int MyObjectTreeModel::GetIdByItem(wxDataViewItem& item) +int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item) { wxASSERT(item.IsOk()); - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); auto it = find(m_objects.begin(), m_objects.end(), node); if (it == m_objects.end()) return -1; @@ -467,43 +470,44 @@ int MyObjectTreeModel::GetIdByItem(wxDataViewItem& item) return it - m_objects.begin(); } -wxString MyObjectTreeModel::GetName(const wxDataViewItem &item) const +wxString PrusaObjectDataViewModel::GetName(const wxDataViewItem &item) const { - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); if (!node) // happens if item.IsOk()==false return wxEmptyString; return node->m_name; } -wxString MyObjectTreeModel::GetCopy(const wxDataViewItem &item) const +wxString PrusaObjectDataViewModel::GetCopy(const wxDataViewItem &item) const { - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); if (!node) // happens if item.IsOk()==false return wxEmptyString; return node->m_copy; } -wxString MyObjectTreeModel::GetScale(const wxDataViewItem &item) const +wxString PrusaObjectDataViewModel::GetScale(const wxDataViewItem &item) const { - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); if (!node) // happens if item.IsOk()==false return wxEmptyString; return node->m_scale; } -void MyObjectTreeModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const +void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const { wxASSERT(item.IsOk()); - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); switch (col) { - case 0: - variant = node->m_name; - break; + case 0:{ + const wxDataViewIconText data(node->m_name, node->m_icon); + variant << data; + break;} case 1: variant = node->m_copy; break; @@ -515,15 +519,15 @@ void MyObjectTreeModel::GetValue(wxVariant &variant, const wxDataViewItem &item, } } -bool MyObjectTreeModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned int col) +bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const wxDataViewItem &item, unsigned int col) { wxASSERT(item.IsOk()); - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); return node->SetValue(variant, col); } -bool MyObjectTreeModel::SetValue(const wxVariant &variant, const int item_idx, unsigned int col) +bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const int item_idx, unsigned int col) { if (item_idx < 0 || item_idx >= m_objects.size()) return false; @@ -536,13 +540,13 @@ bool MyObjectTreeModel::SetValue(const wxVariant &variant, const int item_idx, u // // } -wxDataViewItem MyObjectTreeModel::GetParent(const wxDataViewItem &item) const +wxDataViewItem PrusaObjectDataViewModel::GetParent(const wxDataViewItem &item) const { // the invisible root node has no parent if (!item.IsOk()) return wxDataViewItem(0); - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); // objects nodes has no parent too if (find(m_objects.begin(), m_objects.end(),node) != m_objects.end()) @@ -551,19 +555,19 @@ wxDataViewItem MyObjectTreeModel::GetParent(const wxDataViewItem &item) const return wxDataViewItem((void*)node->GetParent()); } -bool MyObjectTreeModel::IsContainer(const wxDataViewItem &item) const +bool PrusaObjectDataViewModel::IsContainer(const wxDataViewItem &item) const { // the invisible root node can have children if (!item.IsOk()) return true; - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)item.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); return node->IsContainer(); } -unsigned int MyObjectTreeModel::GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const +unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, wxDataViewItemArray &array) const { - MyObjectTreeModelNode *node = (MyObjectTreeModelNode*)parent.GetID(); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID(); if (!node) { for (auto object : m_objects) @@ -579,288 +583,7 @@ unsigned int MyObjectTreeModel::GetChildren(const wxDataViewItem &parent, wxData unsigned int count = node->GetChildren().GetCount(); for (unsigned int pos = 0; pos < count; pos++) { - MyObjectTreeModelNode *child = node->GetChildren().Item(pos); - array.Add(wxDataViewItem((void*)child)); - } - - return count; -} - -// ***************************************************************************** -// ---------------------------------------------------------------------------- -// MyMusicTreeModel -// ---------------------------------------------------------------------------- - -MyMusicTreeModel::MyMusicTreeModel() -{ - m_root = new MyMusicTreeModelNode(NULL, "");// , "My Music"); - - // setup pop music - m_pop = new MyMusicTreeModelNode(m_root, "Pop music"); - m_pop->Append( - new MyMusicTreeModelNode(m_pop, "You are not alone", "Michael Jackson", 1995)); - m_pop->Append( - new MyMusicTreeModelNode(m_pop, "Take a bow", "Madonna", 1994)); - m_root->Append(m_pop); - - // setup classical music - m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); - m_ninth = new MyMusicTreeModelNode(m_classical, "Ninth symphony", - "Ludwig van Beethoven", 1824); - m_classical->Append(m_ninth); - m_classical->Append(new MyMusicTreeModelNode(m_classical, "German Requiem", - "Johannes Brahms", 1868)); - m_root->Append(m_classical); - - m_classicalMusicIsKnownToControl = false; -} - -wxString MyMusicTreeModel::GetTitle(const wxDataViewItem &item) const -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return wxEmptyString; - - return node->m_title; -} - -wxString MyMusicTreeModel::GetArtist(const wxDataViewItem &item) const -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return wxEmptyString; - - return node->m_artist; -} - -int MyMusicTreeModel::GetYear(const wxDataViewItem &item) const -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return 2000; - - return node->m_year; -} - -void MyMusicTreeModel::AddToClassical(const wxString &title, const wxString &artist, - unsigned int year) -{ - if (!m_classical) - { - wxASSERT(m_root); - - // it was removed: restore it - m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); - m_root->Append(m_classical); - - // notify control - wxDataViewItem child((void*)m_classical); - wxDataViewItem parent((void*)m_root); - ItemAdded(parent, child); - } - - // add to the classical music node a new node: - MyMusicTreeModelNode *child_node = - new MyMusicTreeModelNode(m_classical, title, artist, year); - m_classical->Append(child_node); - - // FIXME: what's m_classicalMusicIsKnownToControl for? - if (m_classicalMusicIsKnownToControl) - { - // notify control - wxDataViewItem child((void*)child_node); - wxDataViewItem parent((void*)m_classical); - ItemAdded(parent, child); - } -} - -void MyMusicTreeModel::Delete(const wxDataViewItem &item) -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return; - - wxDataViewItem parent(node->GetParent()); - if (!parent.IsOk()) - { - wxASSERT(node == m_root); - - // don't make the control completely empty: - //wxLogError("Cannot remove the root item!"); - return; - } - - // is the node one of those we keep stored in special pointers? - if (node == m_pop) - m_pop = NULL; - else if (node == m_classical) - m_classical = NULL; - else if (node == m_ninth) - m_ninth = NULL; - - // first remove the node from the parent's array of children; - // NOTE: MyMusicTreeModelNodePtrArray is only an array of _pointers_ - // thus removing the node from it doesn't result in freeing it - node->GetParent()->GetChildren().Remove(node); - - // free the node - delete node; - - // notify control - ItemDeleted(parent, item); -} - -int MyMusicTreeModel::Compare(const wxDataViewItem &item1, const wxDataViewItem &item2, - unsigned int column, bool ascending) const -{ - wxASSERT(item1.IsOk() && item2.IsOk()); - // should never happen - - if (IsContainer(item1) && IsContainer(item2)) - { - wxVariant value1, value2; - GetValue(value1, item1, 0); - GetValue(value2, item2, 0); - - wxString str1 = value1.GetString(); - wxString str2 = value2.GetString(); - int res = str1.Cmp(str2); - if (res) return res; - - // items must be different - wxUIntPtr litem1 = (wxUIntPtr)item1.GetID(); - wxUIntPtr litem2 = (wxUIntPtr)item2.GetID(); - - return litem1 - litem2; - } - - return wxDataViewModel::Compare(item1, item2, column, ascending); -} - -void MyMusicTreeModel::GetValue(wxVariant &variant, - const wxDataViewItem &item, unsigned int col) const -{ - wxASSERT(item.IsOk()); - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - switch (col) - { - case 0: - variant = node->m_title; - break; - case 1: - variant = node->m_artist; - break; - case 2: - variant = (long)node->m_year; - break; - case 3: - variant = node->m_quality; - break; - case 4: - variant = 80L; // all music is very 80% popular - break; - case 5: - if (GetYear(item) < 1900) - variant = "old"; - else - variant = "new"; - break; - - default: - ;// wxLogError("MyMusicTreeModel::GetValue: wrong column %d", col); - } -} - -bool MyMusicTreeModel::SetValue(const wxVariant &variant, - const wxDataViewItem &item, unsigned int col) -{ - wxASSERT(item.IsOk()); - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - switch (col) - { - case 0: - node->m_title = variant.GetString(); - return true; - case 1: - node->m_artist = variant.GetString(); - return true; - case 2: - node->m_year = variant.GetLong(); - return true; - case 3: - node->m_quality = variant.GetString(); - return true; - - default:; -// wxLogError("MyMusicTreeModel::SetValue: wrong column"); - } - return false; -} - -bool MyMusicTreeModel::IsEnabled(const wxDataViewItem &item, - unsigned int col) const -{ - wxASSERT(item.IsOk()); - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - - // disable Beethoven's ratings, his pieces can only be good - return !(col == 3 && node->m_artist.EndsWith("Beethoven")); -} - -wxDataViewItem MyMusicTreeModel::GetParent(const wxDataViewItem &item) const -{ - // the invisible root node has no parent - if (!item.IsOk()) - return wxDataViewItem(0); - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - - // "MyMusic" also has no parent - if (node == m_root) - return wxDataViewItem(0); - - return wxDataViewItem((void*)node->GetParent()); -} - -bool MyMusicTreeModel::IsContainer(const wxDataViewItem &item) const -{ - // the invisble root node can have children - // (in our model always "MyMusic") - if (!item.IsOk()) - return true; - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - return node->IsContainer(); -} - -unsigned int MyMusicTreeModel::GetChildren(const wxDataViewItem &parent, - wxDataViewItemArray &array) const -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)parent.GetID(); - if (!node) - { - array.Add(wxDataViewItem((void*)m_root)); - return 1; - } - - if (node == m_classical) - { - MyMusicTreeModel *model = (MyMusicTreeModel*)(const MyMusicTreeModel*) this; - model->m_classicalMusicIsKnownToControl = true; - } - - if (node->GetChildCount() == 0) - { - return 0; - } - - unsigned int count = node->GetChildren().GetCount(); - for (unsigned int pos = 0; pos < count; pos++) - { - MyMusicTreeModelNode *child = node->GetChildren().Item(pos); + PrusaObjectDataViewModelNode *child = node->GetChildren().Item(pos); array.Add(wxDataViewItem((void*)child)); } diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index d3330a8275..dc7ecb4fd6 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -145,25 +145,25 @@ public: // ***************************************************************************** // ---------------------------------------------------------------------------- -// MyObjectTreeModelNode: a node inside MyObjectTreeModel +// PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel // ---------------------------------------------------------------------------- -class MyObjectTreeModelNode; -WX_DEFINE_ARRAY_PTR(MyObjectTreeModelNode*, MyObjectTreeModelNodePtrArray); +class PrusaObjectDataViewModelNode; +WX_DEFINE_ARRAY_PTR(PrusaObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); -class MyObjectTreeModelNode +class PrusaObjectDataViewModelNode { - MyObjectTreeModelNode* m_parent; + PrusaObjectDataViewModelNode* m_parent; MyObjectTreeModelNodePtrArray m_children; public: - MyObjectTreeModelNode(const wxString &name, int instances_count=1, int scale=100) { + PrusaObjectDataViewModelNode(const wxString &name, int instances_count=1, int scale=100) { m_parent = NULL; m_name = name; m_copy = wxString::Format("%d", instances_count); m_scale = wxString::Format("%d%%", scale); } - MyObjectTreeModelNode( MyObjectTreeModelNode* parent, + PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent, const wxString& sub_obj) { m_parent = parent; m_name = sub_obj; @@ -171,18 +171,25 @@ public: m_scale = wxEmptyString; } - ~MyObjectTreeModelNode() + PrusaObjectDataViewModelNode(PrusaObjectDataViewModelNode* parent, + const wxString& sub_obj, const wxIcon& icon): + PrusaObjectDataViewModelNode(parent, sub_obj){ + m_icon = icon; + } + + ~PrusaObjectDataViewModelNode() { // free all our children nodes size_t count = m_children.GetCount(); for (size_t i = 0; i < count; i++) { - MyObjectTreeModelNode *child = m_children[i]; + PrusaObjectDataViewModelNode *child = m_children[i]; delete child; } } wxString m_name; + wxIcon m_icon; wxString m_copy; wxString m_scale; bool m_container = false; @@ -192,7 +199,7 @@ public: return m_container; } - MyObjectTreeModelNode* GetParent() + PrusaObjectDataViewModelNode* GetParent() { return m_parent; } @@ -200,15 +207,15 @@ public: { return m_children; } - MyObjectTreeModelNode* GetNthChild(unsigned int n) + PrusaObjectDataViewModelNode* GetNthChild(unsigned int n) { return m_children.Item(n); } - void Insert(MyObjectTreeModelNode* child, unsigned int n) + void Insert(PrusaObjectDataViewModelNode* child, unsigned int n) { m_children.Insert(child, n); } - void Append(MyObjectTreeModelNode* child) + void Append(PrusaObjectDataViewModelNode* child) { if (!m_container) m_container = true; @@ -237,33 +244,39 @@ public: { switch (col) { - case 0: - m_name = variant.GetString(); - return true; + case 0:{ + wxDataViewIconText data; + data << variant; + m_icon = data.GetIcon(); + m_name = data.GetText(); + return true;} case 1: m_copy = variant.GetString(); return true; case 2: m_scale = variant.GetString(); return true; - default: printf("MyObjectTreeModel::SetValue: wrong column"); } return false; } + void SetIcon(const wxIcon &icon) + { + m_icon = icon; + } }; // ---------------------------------------------------------------------------- -// MyObjectTreeModel +// PrusaObjectDataViewModel // ---------------------------------------------------------------------------- -class MyObjectTreeModel :public wxDataViewModel +class PrusaObjectDataViewModel :public wxDataViewModel { - std::vector m_objects; + std::vector m_objects; public: - MyObjectTreeModel(){} - ~MyObjectTreeModel() + PrusaObjectDataViewModel(){} + ~PrusaObjectDataViewModel() { for (auto object : m_objects) delete object; @@ -271,7 +284,9 @@ public: wxDataViewItem Add(wxString &name); wxDataViewItem Add(wxString &name, int instances_count, int scale); - wxDataViewItem AddChild(const wxDataViewItem &parent_item, wxString &name); + wxDataViewItem AddChild(const wxDataViewItem &parent_item, + const wxString &name, + const wxIcon& icon); wxDataViewItem Delete(const wxDataViewItem &item); void DeleteAll(); wxDataViewItem GetItemById(int obj_idx); @@ -310,197 +325,6 @@ public: - -// ***************************************************************************** -// ---------------------------------------------------------------------------- -// MyMusicTreeModelNode: a node inside MyMusicTreeModel -// ---------------------------------------------------------------------------- - -class MyMusicTreeModelNode; -WX_DEFINE_ARRAY_PTR(MyMusicTreeModelNode*, MyMusicTreeModelNodePtrArray); - -class MyMusicTreeModelNode -{ -public: - MyMusicTreeModelNode(MyMusicTreeModelNode* parent, - const wxString &title, const wxString &artist, - unsigned int year) - { - m_parent = parent; - - m_title = title; - m_artist = artist; - m_year = year; - m_quality = "good"; - - m_container = false; - } - - MyMusicTreeModelNode(MyMusicTreeModelNode* parent, - const wxString &branch) - { - m_parent = parent; - - m_title = branch; - m_year = -1; - - m_container = true; - } - - ~MyMusicTreeModelNode() - { - // free all our children nodes - size_t count = m_children.GetCount(); - for (size_t i = 0; i < count; i++) - { - MyMusicTreeModelNode *child = m_children[i]; - delete child; - } - } - - bool IsContainer() const - { - return m_container; - } - - MyMusicTreeModelNode* GetParent() - { - return m_parent; - } - MyMusicTreeModelNodePtrArray& GetChildren() - { - return m_children; - } - MyMusicTreeModelNode* GetNthChild(unsigned int n) - { - return m_children.Item(n); - } - void Insert(MyMusicTreeModelNode* child, unsigned int n) - { - m_children.Insert(child, n); - } - void Append(MyMusicTreeModelNode* child) - { - m_children.Add(child); - } - unsigned int GetChildCount() const - { - return m_children.GetCount(); - } - -public: // public to avoid getters/setters - wxString m_title; - wxString m_artist; - int m_year; - wxString m_quality; - - // TODO/FIXME: - // the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded) - // needs to know in advance if a node is or _will be_ a container. - // Thus implementing: - // bool IsContainer() const - // { return m_children.GetCount()>0; } - // doesn't work with wxGTK when MyMusicTreeModel::AddToClassical is called - // AND the classical node was removed (a new node temporary without children - // would be added to the control) - bool m_container; - -private: - MyMusicTreeModelNode *m_parent; - MyMusicTreeModelNodePtrArray m_children; -}; - - -// ---------------------------------------------------------------------------- -// MyMusicTreeModel -// ---------------------------------------------------------------------------- - -/* -Implement this data model -Title Artist Year Judgement --------------------------------------------------------------------------- -1: My Music: -2: Pop music -3: You are not alone Michael Jackson 1995 good -4: Take a bow Madonna 1994 good -5: Classical music -6: Ninth Symphony Ludwig v. Beethoven 1824 good -7: German Requiem Johannes Brahms 1868 good -*/ - -class MyMusicTreeModel : public wxDataViewModel -{ -public: - MyMusicTreeModel(); - ~MyMusicTreeModel() - { - if (m_root) - delete m_root; - - } - - // helper method for wxLog - - wxString GetTitle(const wxDataViewItem &item) const; - wxString GetArtist(const wxDataViewItem &item) const; - int GetYear(const wxDataViewItem &item) const; - - // helper methods to change the model - - void AddToClassical(const wxString &title, const wxString &artist, - unsigned int year); - void Delete(const wxDataViewItem &item); - - wxDataViewItem GetNinthItem() const - { - return wxDataViewItem(m_ninth); - } - - // override sorting to always sort branches ascendingly - - int Compare(const wxDataViewItem &item1, const wxDataViewItem &item2, - unsigned int column, bool ascending) const override/*wxOVERRIDE*/; - - // implementation of base class virtuals to define model - - virtual unsigned int GetColumnCount() const override/*wxOVERRIDE*/ - { - return 6; - } - - virtual wxString GetColumnType(unsigned int col) const override/*wxOVERRIDE*/ - { - if (col == 2) - return wxT("long"); - - return wxT("string"); - } - - virtual void GetValue(wxVariant &variant, - const wxDataViewItem &item, unsigned int col) const override/*wxOVERRIDE*/; - virtual bool SetValue(const wxVariant &variant, - const wxDataViewItem &item, unsigned int col) override/*wxOVERRIDE*/; - - virtual bool IsEnabled(const wxDataViewItem &item, - unsigned int col) const override/*wxOVERRIDE*/; - - virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override/*wxOVERRIDE*/; - virtual bool IsContainer(const wxDataViewItem &item) const override/*wxOVERRIDE*/; - virtual unsigned int GetChildren(const wxDataViewItem &parent, - wxDataViewItemArray &array) const override/*wxOVERRIDE*/; - -private: - MyMusicTreeModelNode* m_root; - - // pointers to some "special" nodes of the tree: - MyMusicTreeModelNode* m_pop; - MyMusicTreeModelNode* m_classical; - MyMusicTreeModelNode* m_ninth; - - // ?? - bool m_classicalMusicIsKnownToControl; -}; - // ---------------------------------------------------------------------------- // MyCustomRenderer // ---------------------------------------------------------------------------- From 54975a4e364705f058564b318d7bb2be1303d71e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Sat, 16 Jun 2018 01:21:25 +0200 Subject: [PATCH 048/185] Some changes in GUI-files All functions of object settings are moved to GUI_ObjectParts --- xs/src/slic3r/GUI/GUI.cpp | 401 ++------------------------ xs/src/slic3r/GUI/GUI.hpp | 66 +---- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 387 ++++++++++++++++++++++++- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 61 +++- 4 files changed, 465 insertions(+), 450 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 3e76c99f7f..ec8eec0918 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -140,18 +140,6 @@ wxButton *g_btn_send_gcode = nullptr; wxStaticBitmap *g_manifold_warning_icon = nullptr; bool g_show_print_info = false; bool g_show_manifold_warning_icon = false; -wxSizer *m_sizer_object_buttons = nullptr; -wxSizer *m_sizer_part_buttons = nullptr; -wxSizer *m_sizer_object_movers = nullptr; -wxDataViewCtrl *m_objects_ctrl = nullptr; -PrusaObjectDataViewModel *m_objects_model = nullptr; -wxCollapsiblePane *m_collpane_settings = nullptr; -int m_event_object_selection_changed = 0; -int m_event_object_settings_changed = 0; -bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() - // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler - // calls this method again and again and again -ModelObjectPtrs m_objects; wxFont g_small_font; wxFont g_bold_font; @@ -812,25 +800,19 @@ unsigned get_colour_approx_luma(const wxColour &colour) b * b * .068 )); } -wxDataViewCtrl* get_objects_ctrl() { - return m_objects_ctrl; -} -PrusaObjectDataViewModel* get_objects_model() { - return m_objects_model; -} -ModelObjectPtrs& get_objects() { - return m_objects; -} - -const int& get_event_object_settings_changed() { - return m_event_object_settings_changed; +wxWindow* get_right_panel(){ + return g_right_panel; } wxFrame* get_main_frame() { return g_wxMainFrame; } +const int& label_width(){ + return m_label_width; +} + void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value) { if (comboCtrl == nullptr) @@ -899,216 +881,18 @@ wxString from_u8(const std::string &str) return wxString::FromUTF8(str.c_str()); } -// add Collapsible Pane to sizer -wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) +void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, + int event_object_selection_changed, + int event_object_settings_changed) { -#ifdef __WXMSW__ - auto *collpane = new PrusaCollapsiblePaneMSW(parent, wxID_ANY, name); -#else - auto *collpane = new PrusaCollapsiblePane/*wxCollapsiblePane*/(parent, wxID_ANY, name); -#endif // __WXMSW__ - // add the pane with a zero proportion value to the sizer which contains it - sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0); + set_event_object_selection_changed(event_object_selection_changed); + set_event_object_settings_changed(event_object_settings_changed); + wxWindowUpdateLocker noUpdates(parent); - wxWindow *win = collpane->GetPane(); - - wxSizer *sizer = content_function(win); - - wxSizer *sizer_pane = new wxBoxSizer(wxVERTICAL); - sizer_pane->Add(sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); - win->SetSizer(sizer_pane); -// sizer_pane->SetSizeHints(win); - return collpane; + add_collapsible_panes(parent, sizer); } -wxBoxSizer* content_objects_list(wxWindow *win) -{ - m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); - m_objects_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects - - auto objects_sz = new wxBoxSizer(wxVERTICAL); - objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT, 20); - - m_objects_model = new PrusaObjectDataViewModel; - m_objects_ctrl->AssociateModel(m_objects_model); -#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - m_objects_ctrl->EnableDragSource(wxDF_UNICODETEXT); - m_objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); -#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - - // column 0(Icon+Text) of the view control: - m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 150, - wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - - // column 1 of the view control: - m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 65, - wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - - // column 2 of the view control: - m_objects_ctrl->AppendTextColumn(_(L("Scale")), 2, wxDATAVIEW_CELL_INERT, 70, - wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - - m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) - { - if (g_prevent_list_events) return; - - wxWindowUpdateLocker noUpdates(g_right_panel); - auto item = m_objects_ctrl->GetSelection(); - int obj_idx = -1; - if (!item) - unselect_objects(); - else - { - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) - obj_idx = m_objects_model->GetIdByItem(item); - else { - auto parent = m_objects_model->GetParent(item); - obj_idx = m_objects_model->GetIdByItem(parent); // TODO Temporary decision for sub-objects selection - } - } - - if (m_event_object_selection_changed > 0) { - wxCommandEvent event(m_event_object_selection_changed); - event.SetInt(obj_idx); - g_wxMainFrame->ProcessWindowEvent(event); - } - - if (obj_idx < 0) return; - -// m_objects_ctrl->SetSize(m_objects_ctrl->GetBestSize()); // TODO override GetBestSize(), than use it - - auto show_obj_sizer = m_objects_model->GetParent(item) == wxDataViewItem(0); - m_sizer_object_buttons->Show(show_obj_sizer); - m_sizer_part_buttons->Show(!show_obj_sizer); - m_sizer_object_movers->Show(!show_obj_sizer); - m_collpane_settings->SetLabelText((show_obj_sizer ? _(L("Object Settings")) : _(L("Part Settings"))) + ":"); - m_collpane_settings->Show(true); - }); - - m_objects_ctrl->Bind(wxEVT_KEY_DOWN, [](wxKeyEvent& event) - { - if (event.GetKeyCode() == WXK_TAB) - m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); - else - event.Skip(); - }); - - return objects_sz; -} - -wxBoxSizer* content_edit_object_buttons(wxWindow* win) -{ - auto sizer = new wxBoxSizer(wxVERTICAL); - - auto btn_load_part = new wxButton(win, wxID_ANY, /*Load */"part"+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_load_modifier = new wxButton(win, wxID_ANY, /*Load */"modifier" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, /*Load */"generic" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_delete = new wxButton(win, wxID_ANY, "Delete"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_split = new wxButton(win, wxID_ANY, "Split"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); - auto btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); - - //*** button's functions - btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) - { - on_btn_load(win); - }); - - btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) - { - on_btn_load(win, true); - }); - - btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) - { - on_btn_load(win, true, true); - }); - - btn_delete->Bind(wxEVT_BUTTON, [](wxEvent&) - { - auto item = m_objects_ctrl->GetSelection(); - if (!item) return; - m_objects_ctrl->Select(m_objects_model->Delete(item)); - }); - //*** - - btn_move_up->SetMinSize(wxSize(20, -1)); - btn_move_down->SetMinSize(wxSize(20, -1)); - btn_load_part->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); - btn_load_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); - btn_load_lambda_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); - btn_delete->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG)); - btn_split->SetBitmap(wxBitmap(from_u8(Slic3r::var("shape_ungroup.png")), wxBITMAP_TYPE_PNG)); - btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG)); - btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG)); - - m_sizer_object_buttons = new wxGridSizer(1, 3, 0, 0); - m_sizer_object_buttons->Add(btn_load_part, 0, wxEXPAND); - m_sizer_object_buttons->Add(btn_load_modifier, 0, wxEXPAND); - m_sizer_object_buttons->Add(btn_load_lambda_modifier, 0, wxEXPAND); - m_sizer_object_buttons->Show(false); - - m_sizer_part_buttons = new wxGridSizer(1, 3, 0, 0); - m_sizer_part_buttons->Add(btn_delete, 0, wxEXPAND); - m_sizer_part_buttons->Add(btn_split, 0, wxEXPAND); - { - auto up_down_sizer = new wxGridSizer(1, 2, 0, 0); - up_down_sizer->Add(btn_move_up, 1, wxEXPAND); - up_down_sizer->Add(btn_move_down, 1, wxEXPAND); - m_sizer_part_buttons->Add(up_down_sizer, 0, wxEXPAND); - } - m_sizer_part_buttons->Show(false); - - btn_load_part->SetFont(Slic3r::GUI::small_font()); - btn_load_modifier->SetFont(Slic3r::GUI::small_font()); - btn_load_lambda_modifier->SetFont(Slic3r::GUI::small_font()); - btn_delete->SetFont(Slic3r::GUI::small_font()); - btn_split->SetFont(Slic3r::GUI::small_font()); - btn_move_up->SetFont(Slic3r::GUI::small_font()); - btn_move_down->SetFont(Slic3r::GUI::small_font()); - - sizer->Add(m_sizer_object_buttons, 0, wxEXPAND|wxLEFT, 20); - sizer->Add(m_sizer_part_buttons, 0, wxEXPAND|wxLEFT, 20); - return sizer; -} - -wxSizer* object_movers(wxWindow *win) -{ - DynamicPrintConfig* config = &g_PresetBundle->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume - std::shared_ptr optgroup = std::make_shared(win, "Move", config); - optgroup->label_width = 20; - - ConfigOptionDef def; - def.label = L("X"); - def.type = coInt; - def.gui_type = "slider"; - def.default_value = new ConfigOptionInt(0); -// def.min = -(model_object->bounding_box->size->x) * 4; -// def.max = model_object->bounding_box->size->x * 4; - - Option option = Option(def, "x"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); - - def.label = L("Y"); -// def.min = -(model_object->bounding_box->size->y) * 4; -// def.max = model_object->bounding_box->size->y * 4; - option = Option(def, "y"); - optgroup->append_single_option_line(option); - - def.label = L("Z"); -// def.min = -(model_object->bounding_box->size->z) * 4; -// def.max = model_object->bounding_box->size->z * 4; - option = Option(def, "z"); - optgroup->append_single_option_line(option); - - m_optgroups.push_back(optgroup); // ogObjectMovers - m_sizer_object_movers = optgroup->sizer; - m_sizer_object_movers->Show(false); - return optgroup->sizer; -} - -Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value=0) +Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value = 0) { Line line = { _(option_name), "" }; ConfigOptionDef def; @@ -1117,7 +901,7 @@ Line add_og_to_object_settings(const std::string& option_name, const std::string def.type = coInt; def.default_value = new ConfigOptionInt(def_value); def.sidetext = sidetext; - def.width = 70; + def.width = 70; const std::string lower_name = boost::algorithm::to_lower_copy(option_name); @@ -1149,152 +933,6 @@ Line add_og_to_object_settings(const std::string& option_name, const std::string return line; } -wxBoxSizer* content_settings(wxWindow *win) -{ - DynamicPrintConfig* config = &g_PresetBundle->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume - std::shared_ptr optgroup = std::make_shared(win, "Extruders", config); - optgroup->label_width = m_label_width; - - Option option = optgroup->get_option("extruder"); - option.opt.default_value = new ConfigOptionInt(1); - optgroup->append_single_option_line(option); - - m_optgroups.push_back(optgroup); // ogObjectSettings - - auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(content_edit_object_buttons(win), 0, wxEXPAND, 0); // *** Edit Object Buttons*** - - sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); - - auto add_btn = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); - sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT, 20); - - sizer->Add(object_movers(win), 0, wxEXPAND | wxLEFT, 20); - - return sizer; -} - -void add_object_to_list(const std::string &name, ModelObject* model_object) -{ - wxString item = name; - int scale = model_object->instances[0]->scaling_factor * 100; - m_objects_ctrl->Select(m_objects_model->Add(item, model_object->instances.size(), scale)); - m_objects.push_back(model_object); -} - -void delete_object_from_list() -{ - auto item = m_objects_ctrl->GetSelection(); - if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) - return; -// m_objects_ctrl->Select(m_objects_model->Delete(item)); - m_objects_model->Delete(item); - - if (m_objects_model->IsEmpty()) - m_collpane_settings->Show(false); -} - -void delete_all_objects_from_list() -{ - m_objects_model->DeleteAll(); - m_collpane_settings->Show(false); -} - -void set_object_count(int idx, int count) -{ - m_objects_model->SetValue(wxString::Format("%d", count), idx, 1); - m_objects_ctrl->Refresh(); -} - -void set_object_scale(int idx, int scale) -{ - m_objects_model->SetValue(wxString::Format("%d%%", scale), idx, 2); - m_objects_ctrl->Refresh(); -} - -void unselect_objects() -{ - m_objects_ctrl->UnselectAll(); - if (m_sizer_object_buttons->IsShown(1)) - m_sizer_object_buttons->Show(false); - if (m_sizer_part_buttons->IsShown(1)) - m_sizer_part_buttons->Show(false); - if (m_sizer_object_movers->IsShown(1)) - m_sizer_object_movers->Show(false); - if (m_collpane_settings->IsShown()) - m_collpane_settings->Show(false); -} - -void select_current_object(int idx) -{ - g_prevent_list_events = true; - m_objects_ctrl->UnselectAll(); - if (idx < 0) { - g_prevent_list_events = false; - return; - } - m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); - g_prevent_list_events = false; - - if (get_view_mode() == ConfigMenuModeExpert){ - if (!m_sizer_object_buttons->IsShown(1)) - m_sizer_object_buttons->Show(true); - if (!m_sizer_object_movers->IsShown(1)) - m_sizer_object_movers->Show(true); - if (!m_collpane_settings->IsShown()) - m_collpane_settings->Show(true); - } -} - -void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, - int event_object_selection_changed, - int event_object_settings_changed) -{ - m_event_object_selection_changed = event_object_selection_changed; - m_event_object_settings_changed = event_object_settings_changed; - wxWindowUpdateLocker noUpdates(parent); - - // *** Objects List *** - auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); - collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){ -// wxWindowUpdateLocker noUpdates(g_right_panel); - if (collpane->IsCollapsed()) { - m_sizer_object_buttons->Show(false); - m_sizer_part_buttons->Show(false); - m_sizer_object_movers->Show(false); - m_collpane_settings->Show(false); - } -// else -// m_objects_ctrl->UnselectAll(); - -// e.Skip(); -// g_right_panel->Layout(); - })); - - // *** Object/Part Settings *** - m_collpane_settings = add_collapsible_pane(parent, sizer, "Object Settings", content_settings); - - // More experiments with UI -// auto listctrl = new wxDataViewListCtrl(main_page, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); -// listctrl->AppendToggleColumn("Toggle"); -// listctrl->AppendTextColumn("Text"); -// wxVector data; -// data.push_back(wxVariant(true)); -// data.push_back(wxVariant("row 1")); -// listctrl->AppendItem(data); -// data.clear(); -// data.push_back(wxVariant(false)); -// data.push_back(wxVariant("row 3")); -// listctrl->AppendItem(data); -// data.clear(); -// data.push_back(wxVariant(false)); -// data.push_back(wxVariant("row 2")); -// listctrl->AppendItem(data); -// main_sizer->Add(listctrl, 0, wxEXPAND | wxALL, 1); -} - void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) { DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; @@ -1507,17 +1145,24 @@ void update_mode() // TODO There is a not the best place of it! // *** Update showing of the collpane_settings - m_collpane_settings->Show(mode == ConfigMenuModeExpert && !m_objects_model->IsEmpty()); + show_collpane_settings(mode == ConfigMenuModeExpert); // ************************* g_right_panel->GetParent()->Layout(); g_right_panel->Layout(); } +bool is_expert_mode(){ + return get_view_mode() == ConfigMenuModeExpert; +} + ConfigOptionsGroup* get_optgroup(size_t i) { return m_optgroups[i].get(); } +std::vector >& get_optgroups() { + return m_optgroups; +} wxButton* get_wiping_dialog_button() { diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index ef34d2bf85..8e1be3216a 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -4,6 +4,7 @@ #include #include #include "Config.hpp" +#include "GUI_ObjectParts.hpp" #include #include @@ -11,7 +12,6 @@ class wxApp; class wxWindow; class wxFrame; -class wxWindow; class wxMenuBar; class wxNotebook; class wxComboCtrl; @@ -25,8 +25,6 @@ class wxButton; class wxFileDialog; class wxStaticBitmap; class wxFont; -class wxDataViewCtrl; -class PrusaObjectDataViewModel; namespace Slic3r { @@ -36,7 +34,6 @@ class AppConfig; class PresetUpdater; class DynamicPrintConfig; class TabIface; -class ModelObject; #define _(s) Slic3r::translate((s)) inline wxString translate(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)); } @@ -60,24 +57,8 @@ inline wxString translate(const std::wstring &s) { return wxGetTranslation(s.c_s namespace GUI { -enum ogGroup{ - ogFrequentlyChangingParameters, - ogFrequentlyObjectSettings, - ogObjectSettings, - ogObjectMovers, - ogPartSettings -}; - -enum LambdaTypeIDs{ - LambdaTypeBox, - LambdaTypeCylinder, - LambdaTypeSphere, - LambdaTypeSlab -}; - class Tab; class ConfigOptionsGroup; -typedef std::vector ModelObjectPtrs; // Map from an file_type name to full file wildcard name. typedef std::map t_file_wild_card; inline t_file_wild_card& get_file_wild_card() { @@ -96,17 +77,6 @@ inline t_file_wild_card& get_file_wild_card() { return FILE_WILDCARDS; } -struct OBJECT_PARAMETERS -{ - LambdaTypeIDs type = LambdaTypeBox; - double dim[3];// = { 1.0, 1.0, 1.0 }; - int cyl_r = 1; - int cyl_h = 1; - double sph_rho = 1.0; - double slab_h = 1.0; - double slab_z = 0.0; -}; - void disable_screensaver(); void enable_screensaver(); bool debugged(); @@ -132,9 +102,10 @@ void set_objects_from_perl( wxWindow* parent, void set_show_print_info(bool show); void set_show_manifold_warning_icon(bool show); -AppConfig* get_app_config(); -wxApp* get_app(); -PresetBundle* get_preset_bundle(); +AppConfig* get_app_config(); +wxApp* get_app(); +PresetBundle* get_preset_bundle(); +wxFrame* get_main_frame(); const wxColour& get_label_clr_modified(); const wxColour& get_label_clr_sys(); @@ -148,11 +119,8 @@ const wxFont& bold_font(); void open_model(wxWindow *parent, wxArrayString& input_files); -wxDataViewCtrl* get_objects_ctrl (); -PrusaObjectDataViewModel* get_objects_model(); -ModelObjectPtrs& get_objects(); -const int& get_event_object_settings_changed(); -wxFrame* get_main_frame(); +wxWindow* get_right_panel(); +const int& label_width(); extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); @@ -217,30 +185,16 @@ wxString L_str(const std::string &str); // Return wxString from std::string in UTF8 wxString from_u8(const std::string &str); -// Add object to the list -//void add_object(const std::string &name); -void add_object_to_list(const std::string &name, ModelObject* model_object); -// Delete object from the list -void delete_object_from_list(); -// Delete all objects from the list -void delete_all_objects_from_list(); -// Set count of object on c++ side -void set_object_count(int idx, int count); -// Set object scale on c++ side -void set_object_scale(int idx, int scale); -// Unselect all objects in the list on c++ side -void unselect_objects(); -// Select current object in the list on c++ side -void select_current_object(int idx); - void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed, int event_object_settings_changed); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); // Update view mode according to selected menu void update_mode(); +bool is_expert_mode(); -ConfigOptionsGroup* get_optgroup(size_t i); +ConfigOptionsGroup* get_optgroup(size_t i); +std::vector >& get_optgroups(); wxButton* get_wiping_dialog_button(); void add_export_option(wxFileDialog* dlg, const std::string& format); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 8783471a5f..f1c5df1e76 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -1,4 +1,6 @@ #include "GUI.hpp" +#include "OptionsGroup.hpp" +#include "PresetBundle.hpp" #include "GUI_ObjectParts.hpp" #include "Model.hpp" #include "wxExtensions.hpp" @@ -8,17 +10,381 @@ #include #include #include +#include namespace Slic3r { namespace GUI { +wxSizer *m_sizer_object_buttons = nullptr; +wxSizer *m_sizer_part_buttons = nullptr; +wxSizer *m_sizer_object_movers = nullptr; +wxDataViewCtrl *m_objects_ctrl = nullptr; +PrusaObjectDataViewModel *m_objects_model = nullptr; +wxCollapsiblePane *m_collpane_settings = nullptr; + +bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() + // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler + // calls this method again and again and again +ModelObjectPtrs m_objects; + +int m_event_object_selection_changed = 0; +int m_event_object_settings_changed = 0; + bool m_parts_changed = false; bool m_part_settings_changed = false; +void set_event_object_selection_changed(const int& event){ + m_event_object_selection_changed = event; +} +void set_event_object_settings_changed(const int& event){ + m_event_object_settings_changed = event; +} + bool is_parts_changed(){return m_parts_changed;} bool is_part_settings_changed(){ return m_part_settings_changed; } +static wxString dots("…", wxConvUTF8); + +// ****** from GUI.cpp +wxBoxSizer* content_objects_list(wxWindow *win) +{ + m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); + m_objects_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects + + auto objects_sz = new wxBoxSizer(wxVERTICAL); + objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT, 20); + + m_objects_model = new PrusaObjectDataViewModel; + m_objects_ctrl->AssociateModel(m_objects_model); +#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + m_objects_ctrl->EnableDragSource(wxDF_UNICODETEXT); + m_objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); +#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + + // column 0(Icon+Text) of the view control: + m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 150, + wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + + // column 1 of the view control: + m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 65, + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + + // column 2 of the view control: + m_objects_ctrl->AppendTextColumn(_(L("Scale")), 2, wxDATAVIEW_CELL_INERT, 70, + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + + m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) + { + if (g_prevent_list_events) return; + + wxWindowUpdateLocker noUpdates(get_right_panel()); + auto item = m_objects_ctrl->GetSelection(); + int obj_idx = -1; + if (!item) + unselect_objects(); + else + { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + obj_idx = m_objects_model->GetIdByItem(item); + else { + auto parent = m_objects_model->GetParent(item); + obj_idx = m_objects_model->GetIdByItem(parent); // TODO Temporary decision for sub-objects selection + } + } + + if (m_event_object_selection_changed > 0) { + wxCommandEvent event(m_event_object_selection_changed); + event.SetInt(obj_idx); + get_main_frame()->ProcessWindowEvent(event); + } + + if (obj_idx < 0) return; + +// m_objects_ctrl->SetSize(m_objects_ctrl->GetBestSize()); // TODO override GetBestSize(), than use it + + auto show_obj_sizer = m_objects_model->GetParent(item) == wxDataViewItem(0); + m_sizer_object_buttons->Show(show_obj_sizer); + m_sizer_part_buttons->Show(!show_obj_sizer); + m_sizer_object_movers->Show(!show_obj_sizer); + m_collpane_settings->SetLabelText((show_obj_sizer ? _(L("Object Settings")) : _(L("Part Settings"))) + ":"); + m_collpane_settings->Show(true); + }); + + m_objects_ctrl->Bind(wxEVT_KEY_DOWN, [](wxKeyEvent& event) + { + if (event.GetKeyCode() == WXK_TAB) + m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); + else + event.Skip(); + }); + + return objects_sz; +} + +wxBoxSizer* content_edit_object_buttons(wxWindow* win) +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + + auto btn_load_part = new wxButton(win, wxID_ANY, /*Load */"part" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_load_modifier = new wxButton(win, wxID_ANY, /*Load */"modifier" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, /*Load */"generic" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_delete = new wxButton(win, wxID_ANY, "Delete"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_split = new wxButton(win, wxID_ANY, "Split"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); + auto btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); + auto btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); + + //*** button's functions + btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) + { + on_btn_load(win); + }); + + btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) + { + on_btn_load(win, true); + }); + + btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) + { + on_btn_load(win, true, true); + }); + + btn_delete->Bind(wxEVT_BUTTON, [](wxEvent&) + { + auto item = m_objects_ctrl->GetSelection(); + if (!item) return; + m_objects_ctrl->Select(m_objects_model->Delete(item)); + }); + //*** + + btn_move_up->SetMinSize(wxSize(20, -1)); + btn_move_down->SetMinSize(wxSize(20, -1)); + btn_load_part->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); + btn_load_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); + btn_load_lambda_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); + btn_delete->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG)); + btn_split->SetBitmap(wxBitmap(from_u8(Slic3r::var("shape_ungroup.png")), wxBITMAP_TYPE_PNG)); + btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG)); + btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG)); + + m_sizer_object_buttons = new wxGridSizer(1, 3, 0, 0); + m_sizer_object_buttons->Add(btn_load_part, 0, wxEXPAND); + m_sizer_object_buttons->Add(btn_load_modifier, 0, wxEXPAND); + m_sizer_object_buttons->Add(btn_load_lambda_modifier, 0, wxEXPAND); + m_sizer_object_buttons->Show(false); + + m_sizer_part_buttons = new wxGridSizer(1, 3, 0, 0); + m_sizer_part_buttons->Add(btn_delete, 0, wxEXPAND); + m_sizer_part_buttons->Add(btn_split, 0, wxEXPAND); + { + auto up_down_sizer = new wxGridSizer(1, 2, 0, 0); + up_down_sizer->Add(btn_move_up, 1, wxEXPAND); + up_down_sizer->Add(btn_move_down, 1, wxEXPAND); + m_sizer_part_buttons->Add(up_down_sizer, 0, wxEXPAND); + } + m_sizer_part_buttons->Show(false); + + btn_load_part->SetFont(Slic3r::GUI::small_font()); + btn_load_modifier->SetFont(Slic3r::GUI::small_font()); + btn_load_lambda_modifier->SetFont(Slic3r::GUI::small_font()); + btn_delete->SetFont(Slic3r::GUI::small_font()); + btn_split->SetFont(Slic3r::GUI::small_font()); + btn_move_up->SetFont(Slic3r::GUI::small_font()); + btn_move_down->SetFont(Slic3r::GUI::small_font()); + + sizer->Add(m_sizer_object_buttons, 0, wxEXPAND | wxLEFT, 20); + sizer->Add(m_sizer_part_buttons, 0, wxEXPAND | wxLEFT, 20); + return sizer; +} + +wxSizer* object_movers(wxWindow *win) +{ + DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume + std::shared_ptr optgroup = std::make_shared(win, "Move", config); + optgroup->label_width = 20; + + ConfigOptionDef def; + def.label = L("X"); + def.type = coInt; + def.gui_type = "slider"; + def.default_value = new ConfigOptionInt(0); + // def.min = -(model_object->bounding_box->size->x) * 4; + // def.max = model_object->bounding_box->size->x * 4; + + Option option = Option(def, "x"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + def.label = L("Y"); + // def.min = -(model_object->bounding_box->size->y) * 4; + // def.max = model_object->bounding_box->size->y * 4; + option = Option(def, "y"); + optgroup->append_single_option_line(option); + + def.label = L("Z"); + // def.min = -(model_object->bounding_box->size->z) * 4; + // def.max = model_object->bounding_box->size->z * 4; + option = Option(def, "z"); + optgroup->append_single_option_line(option); + + get_optgroups().push_back(optgroup); // ogObjectMovers + m_sizer_object_movers = optgroup->sizer; + m_sizer_object_movers->Show(false); + return optgroup->sizer; +} + +wxBoxSizer* content_settings(wxWindow *win) +{ + DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume + std::shared_ptr optgroup = std::make_shared(win, "Extruders", config); + optgroup->label_width = label_width(); + + Option option = optgroup->get_option("extruder"); + option.opt.default_value = new ConfigOptionInt(1); + optgroup->append_single_option_line(option); + + get_optgroups().push_back(optgroup); // ogObjectSettings + + auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(content_edit_object_buttons(win), 0, wxEXPAND, 0); // *** Edit Object Buttons*** + + sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); + + auto add_btn = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) add_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + add_btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG)); + sizer->Add(add_btn, 0, wxALIGN_LEFT | wxLEFT, 20); + + sizer->Add(object_movers(win), 0, wxEXPAND | wxLEFT, 20); + + return sizer; +} + + +// add Collapsible Pane to sizer +wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) +{ +#ifdef __WXMSW__ + auto *collpane = new PrusaCollapsiblePaneMSW(parent, wxID_ANY, name); +#else + auto *collpane = new PrusaCollapsiblePane/*wxCollapsiblePane*/(parent, wxID_ANY, name); +#endif // __WXMSW__ + // add the pane with a zero proportion value to the sizer which contains it + sizer_parent->Add(collpane, 0, wxGROW | wxALL, 0); + + wxWindow *win = collpane->GetPane(); + + wxSizer *sizer = content_function(win); + + wxSizer *sizer_pane = new wxBoxSizer(wxVERTICAL); + sizer_pane->Add(sizer, 1, wxGROW | wxEXPAND | wxBOTTOM, 2); + win->SetSizer(sizer_pane); + // sizer_pane->SetSizeHints(win); + return collpane; +} + +void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer) +{ + // *** Objects List *** + auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); + collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){ + // wxWindowUpdateLocker noUpdates(g_right_panel); + if (collpane->IsCollapsed()) { + m_sizer_object_buttons->Show(false); + m_sizer_part_buttons->Show(false); + m_sizer_object_movers->Show(false); + m_collpane_settings->Show(false); + } +// else +// m_objects_ctrl->UnselectAll(); + +// e.Skip(); +// g_right_panel->Layout(); + })); + + // *** Object/Part Settings *** + m_collpane_settings = add_collapsible_pane(parent, sizer, "Object Settings", content_settings); +} + +void show_collpane_settings(bool expert_mode) +{ + m_collpane_settings->Show(expert_mode && !m_objects_model->IsEmpty()); +} + +void add_object_to_list(const std::string &name, ModelObject* model_object) +{ + wxString item = name; + int scale = model_object->instances[0]->scaling_factor * 100; + m_objects_ctrl->Select(m_objects_model->Add(item, model_object->instances.size(), scale)); + m_objects.push_back(model_object); +} + +void delete_object_from_list() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) + return; +// m_objects_ctrl->Select(m_objects_model->Delete(item)); + m_objects_model->Delete(item); + + if (m_objects_model->IsEmpty()) + m_collpane_settings->Show(false); +} + +void delete_all_objects_from_list() +{ + m_objects_model->DeleteAll(); + m_collpane_settings->Show(false); +} + +void set_object_count(int idx, int count) +{ + m_objects_model->SetValue(wxString::Format("%d", count), idx, 1); + m_objects_ctrl->Refresh(); +} + +void set_object_scale(int idx, int scale) +{ + m_objects_model->SetValue(wxString::Format("%d%%", scale), idx, 2); + m_objects_ctrl->Refresh(); +} + +void unselect_objects() +{ + m_objects_ctrl->UnselectAll(); + if (m_sizer_object_buttons->IsShown(1)) + m_sizer_object_buttons->Show(false); + if (m_sizer_part_buttons->IsShown(1)) + m_sizer_part_buttons->Show(false); + if (m_sizer_object_movers->IsShown(1)) + m_sizer_object_movers->Show(false); + if (m_collpane_settings->IsShown()) + m_collpane_settings->Show(false); +} + +void select_current_object(int idx) +{ + g_prevent_list_events = true; + m_objects_ctrl->UnselectAll(); + if (idx < 0) { + g_prevent_list_events = false; + return; + } + m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); + g_prevent_list_events = false; + + if (is_expert_mode()){ + if (!m_sizer_object_buttons->IsShown(1)) + m_sizer_object_buttons->Show(true); + if (!m_sizer_object_movers->IsShown(1)) + m_sizer_object_movers->Show(true); + if (!m_collpane_settings->IsShown()) + m_collpane_settings->Show(true); + } +} +// ****** + void load_part( wxWindow* parent, ModelObject* model_object, wxArrayString& part_names, const bool is_modifier) { @@ -110,40 +476,35 @@ void load_lambda( wxWindow* parent, ModelObject* model_object, void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/* = false*/) { - auto objects_ctrl = get_objects_ctrl(); - auto item = objects_ctrl->GetSelection(); + auto item = m_objects_ctrl->GetSelection(); if (!item) return; int obj_idx = -1; - auto objects_model = get_objects_model(); - if (objects_model->GetParent(item) == wxDataViewItem(0)) - obj_idx = objects_model->GetIdByItem(item); + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + obj_idx = m_objects_model->GetIdByItem(item); else return; if (obj_idx < 0) return; wxArrayString part_names; - ModelObjectPtrs& objects = get_objects(); if (is_lambda) - load_lambda(parent, objects[obj_idx], part_names, is_modifier); + load_lambda(parent, m_objects[obj_idx], part_names, is_modifier); else - load_part(parent, objects[obj_idx], part_names, is_modifier); + load_part(parent, m_objects[obj_idx], part_names, is_modifier); parts_changed(obj_idx); const std::string icon_name = is_modifier ? "plugin.png" : "package.png"; auto icon = wxIcon(Slic3r::GUI::from_u8(Slic3r::var(icon_name)), wxBITMAP_TYPE_PNG); for (int i = 0; i < part_names.size(); ++i) - objects_ctrl->Select(objects_model->AddChild(item, part_names.Item(i), icon)); + m_objects_ctrl->Select(m_objects_model->AddChild(item, part_names.Item(i), icon)); } void parts_changed(int obj_idx) { - auto event = get_event_object_settings_changed(); - if (event <= 0) - return; + if (m_event_object_settings_changed <= 0) return; - wxCommandEvent e(event); + wxCommandEvent e(m_event_object_settings_changed); auto event_str = wxString::Format("%d %d %d", obj_idx, is_parts_changed() ? 1 : 0, is_part_settings_changed() ? 1 : 0); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 40bcc012e8..4cbd9ba7fe 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -1,10 +1,65 @@ #ifndef slic3r_GUI_ObjectParts_hpp_ #define slic3r_GUI_ObjectParts_hpp_ -namespace Slic3r -{ -namespace GUI +class wxWindow; +class wxSizer; +class wxBoxSizer; +class wxString; +class wxArrayString; + +namespace Slic3r { +class ModelObject; + +namespace GUI { + +enum ogGroup{ + ogFrequentlyChangingParameters, + ogFrequentlyObjectSettings, + ogObjectSettings, + ogObjectMovers, + ogPartSettings +}; + +enum LambdaTypeIDs{ + LambdaTypeBox, + LambdaTypeCylinder, + LambdaTypeSphere, + LambdaTypeSlab +}; + +struct OBJECT_PARAMETERS { + LambdaTypeIDs type = LambdaTypeBox; + double dim[3];// = { 1.0, 1.0, 1.0 }; + int cyl_r = 1; + int cyl_h = 1; + double sph_rho = 1.0; + double slab_h = 1.0; + double slab_z = 0.0; +}; + +void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer); +void show_collpane_settings(bool expert_mode); + +// Add object to the list +//void add_object(const std::string &name); +void add_object_to_list(const std::string &name, ModelObject* model_object); +// Delete object from the list +void delete_object_from_list(); +// Delete all objects from the list +void delete_all_objects_from_list(); +// Set count of object on c++ side +void set_object_count(int idx, int count); +// Set object scale on c++ side +void set_object_scale(int idx, int scale); +// Unselect all objects in the list on c++ side +void unselect_objects(); +// Select current object in the list on c++ side +void select_current_object(int idx); + +void set_event_object_selection_changed(const int& event); +void set_event_object_settings_changed (const int& event); + bool is_parts_changed(); bool is_part_settings_changed(); From fb9ba1a55c2bca2650f10bb8afd1df21853e3233 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Sat, 16 Jun 2018 03:04:59 +0200 Subject: [PATCH 049/185] Set correct man/max value for movers --- lib/Slic3r/GUI/MainFrame.pm | 5 ++-- lib/Slic3r/GUI/Plater.pm | 7 +++--- xs/src/slic3r/GUI/Field.cpp | 1 - xs/src/slic3r/GUI/Field.hpp | 3 ++- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 36 ++++++++++++++++++++------- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index fd2844783e..7f14c40685 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -179,9 +179,10 @@ sub _init_tabpanel { # The following event is emited by the C++ Tab implementation on object selection change. EVT_COMMAND($self, -1, $OBJECT_SELECTION_CHANGED_EVENT, sub { my ($self, $event) = @_; - my $obj_idx = $event->GetInt; + my $obj_idx = $event->GetId; + my $child = $event->GetInt == 1 ? 1 : undef; - $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx); + $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx, $child); $self->{plater}->item_changed_selection($obj_idx); }); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 1d2168fe89..3905228bc4 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -2081,7 +2081,7 @@ sub selection_changed { } sub select_object { - my ($self, $obj_idx) = @_; + my ($self, $obj_idx, $child) = @_; # remove current selection foreach my $o (0..$#{$self->{objects}}) { @@ -2090,8 +2090,9 @@ sub select_object { if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); - # Select current object in the list on c++ side - Slic3r::GUI::select_current_object($obj_idx); + # Select current object in the list on c++ side, if item isn't child + if (!defined $child){ + Slic3r::GUI::select_current_object($obj_idx);} } else { # Unselect all objects in the list on c++ side Slic3r::GUI::unselect_objects(); diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index dcb251be6f..1b4aef0332 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -710,7 +710,6 @@ void SliderCtrl::BUILD() } }), m_textctrl->GetId()); - // // recast as a wxWindow to fit the calling convention m_sizer = dynamic_cast(temp); } diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 40f56c0d36..04ee8c87b7 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -213,7 +213,7 @@ protected: inline bool is_bad_field(const t_field& obj) { return obj->getSizer() == nullptr && obj->getWindow() == nullptr; } /// Covenience function to determine whether this field is a valid window field. -inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr; } +inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr && obj->getSizer() == nullptr; } /// Covenience function to determine whether this field is a valid sizer field. inline bool is_sizer_field(const t_field& obj) { return !is_bad_field(obj) && obj->getSizer() != nullptr; } @@ -414,6 +414,7 @@ public: m_textctrl->SetEditable(false); } wxSizer* getSizer() override { return m_sizer; } + wxWindow* getWindow() override { return dynamic_cast(m_slider); } }; } // GUI diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index f1c5df1e76..77cacadffe 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -23,6 +23,10 @@ wxDataViewCtrl *m_objects_ctrl = nullptr; PrusaObjectDataViewModel *m_objects_model = nullptr; wxCollapsiblePane *m_collpane_settings = nullptr; +wxSlider* mover_x = nullptr; +wxSlider* mover_y = nullptr; +wxSlider* mover_z = nullptr; + bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler // calls this method again and again and again @@ -95,7 +99,8 @@ wxBoxSizer* content_objects_list(wxWindow *win) if (m_event_object_selection_changed > 0) { wxCommandEvent event(m_event_object_selection_changed); - event.SetInt(obj_idx); + event.SetInt(int(m_objects_model->GetParent(item) != wxDataViewItem(0))); + event.SetId(obj_idx); get_main_frame()->ProcessWindowEvent(event); } @@ -107,6 +112,22 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_sizer_object_buttons->Show(show_obj_sizer); m_sizer_part_buttons->Show(!show_obj_sizer); m_sizer_object_movers->Show(!show_obj_sizer); + + if (!show_obj_sizer) + { + auto bb_size = m_objects[obj_idx]->bounding_box().size(); + int scale = 10; //?? + + mover_x->SetMin(-bb_size.x * 4*scale); + mover_x->SetMax( bb_size.x * 4*scale); + + mover_y->SetMin(-bb_size.y * 4*scale); + mover_y->SetMax( bb_size.y * 4*scale); + + mover_z->SetMin(-bb_size.z * 4 * scale); + mover_z->SetMax( bb_size.z * 4 * scale); + } + m_collpane_settings->SetLabelText((show_obj_sizer ? _(L("Object Settings")) : _(L("Part Settings"))) + ":"); m_collpane_settings->Show(true); }); @@ -200,8 +221,8 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) wxSizer* object_movers(wxWindow *win) { - DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume - std::shared_ptr optgroup = std::make_shared(win, "Move", config); +// DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume + std::shared_ptr optgroup = std::make_shared(win, "Move"/*, config*/); optgroup->label_width = 20; ConfigOptionDef def; @@ -209,24 +230,21 @@ wxSizer* object_movers(wxWindow *win) def.type = coInt; def.gui_type = "slider"; def.default_value = new ConfigOptionInt(0); - // def.min = -(model_object->bounding_box->size->x) * 4; - // def.max = model_object->bounding_box->size->x * 4; Option option = Option(def, "x"); option.opt.full_width = true; optgroup->append_single_option_line(option); + mover_x = dynamic_cast(optgroup->get_field("x")->getWindow()); def.label = L("Y"); - // def.min = -(model_object->bounding_box->size->y) * 4; - // def.max = model_object->bounding_box->size->y * 4; option = Option(def, "y"); optgroup->append_single_option_line(option); + mover_y = dynamic_cast(optgroup->get_field("y")->getWindow()); def.label = L("Z"); - // def.min = -(model_object->bounding_box->size->z) * 4; - // def.max = model_object->bounding_box->size->z * 4; option = Option(def, "z"); optgroup->append_single_option_line(option); + mover_z = dynamic_cast(optgroup->get_field("z")->getWindow()); get_optgroups().push_back(optgroup); // ogObjectMovers m_sizer_object_movers = optgroup->sizer; From 99082bfe67e7d7b9059d0745250df923aa3db4e0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 18 Jun 2018 14:20:29 +0200 Subject: [PATCH 050/185] Modifiers are moving now --- xs/src/slic3r/GUI/GUI.cpp | 2 + xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 215 +++++++++++++++++++++----- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 2 + xs/src/slic3r/GUI/wxExtensions.cpp | 15 +- xs/src/slic3r/GUI/wxExtensions.hpp | 31 +++- 5 files changed, 213 insertions(+), 52 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index ec8eec0918..10dafe13e6 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -887,6 +887,8 @@ void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, { set_event_object_selection_changed(event_object_selection_changed); set_event_object_settings_changed(event_object_settings_changed); + init_mesh_icons(); + wxWindowUpdateLocker noUpdates(parent); add_collapsible_panes(parent, sizer); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 77cacadffe..3f183c8d31 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -23,9 +23,17 @@ wxDataViewCtrl *m_objects_ctrl = nullptr; PrusaObjectDataViewModel *m_objects_model = nullptr; wxCollapsiblePane *m_collpane_settings = nullptr; -wxSlider* mover_x = nullptr; -wxSlider* mover_y = nullptr; -wxSlider* mover_z = nullptr; +wxIcon m_icon_modifiermesh; +wxIcon m_icon_solidmesh; + +wxSlider* m_mover_x = nullptr; +wxSlider* m_mover_y = nullptr; +wxSlider* m_mover_z = nullptr; +wxButton* m_btn_move_up = nullptr; +wxButton* m_btn_move_down = nullptr; +Point3 m_move_options; +Point3 m_last_coords; +int m_selected_object_id = -1; bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler @@ -45,6 +53,11 @@ void set_event_object_settings_changed(const int& event){ m_event_object_settings_changed = event; } +void init_mesh_icons(){ + m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); + m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); +} + bool is_parts_changed(){return m_parts_changed;} bool is_part_settings_changed(){ return m_part_settings_changed; } @@ -96,6 +109,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) obj_idx = m_objects_model->GetIdByItem(parent); // TODO Temporary decision for sub-objects selection } } + m_selected_object_id = obj_idx; if (m_event_object_selection_changed > 0) { wxCommandEvent event(m_event_object_selection_changed); @@ -107,29 +121,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) if (obj_idx < 0) return; // m_objects_ctrl->SetSize(m_objects_ctrl->GetBestSize()); // TODO override GetBestSize(), than use it - - auto show_obj_sizer = m_objects_model->GetParent(item) == wxDataViewItem(0); - m_sizer_object_buttons->Show(show_obj_sizer); - m_sizer_part_buttons->Show(!show_obj_sizer); - m_sizer_object_movers->Show(!show_obj_sizer); - - if (!show_obj_sizer) - { - auto bb_size = m_objects[obj_idx]->bounding_box().size(); - int scale = 10; //?? - - mover_x->SetMin(-bb_size.x * 4*scale); - mover_x->SetMax( bb_size.x * 4*scale); - - mover_y->SetMin(-bb_size.y * 4*scale); - mover_y->SetMax( bb_size.y * 4*scale); - - mover_z->SetMin(-bb_size.z * 4 * scale); - mover_z->SetMax( bb_size.z * 4 * scale); - } - - m_collpane_settings->SetLabelText((show_obj_sizer ? _(L("Object Settings")) : _(L("Part Settings"))) + ":"); - m_collpane_settings->Show(true); + part_selection_changed(); }); m_objects_ctrl->Bind(wxEVT_KEY_DOWN, [](wxKeyEvent& event) @@ -152,8 +144,8 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) auto btn_load_lambda_modifier = new wxButton(win, wxID_ANY, /*Load */"generic" + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); auto btn_delete = new wxButton(win, wxID_ANY, "Delete"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); auto btn_split = new wxButton(win, wxID_ANY, "Split"/*" part"*/, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER/*wxBU_LEFT*/); - auto btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); - auto btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); + m_btn_move_up = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); + m_btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); //*** button's functions btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) @@ -176,18 +168,19 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) auto item = m_objects_ctrl->GetSelection(); if (!item) return; m_objects_ctrl->Select(m_objects_model->Delete(item)); + parts_changed(m_selected_object_id); }); //*** - btn_move_up->SetMinSize(wxSize(20, -1)); - btn_move_down->SetMinSize(wxSize(20, -1)); + m_btn_move_up->SetMinSize(wxSize(20, -1)); + m_btn_move_down->SetMinSize(wxSize(20, -1)); btn_load_part->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); btn_load_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); btn_load_lambda_modifier->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_add.png")), wxBITMAP_TYPE_PNG)); btn_delete->SetBitmap(wxBitmap(from_u8(Slic3r::var("brick_delete.png")), wxBITMAP_TYPE_PNG)); btn_split->SetBitmap(wxBitmap(from_u8(Slic3r::var("shape_ungroup.png")), wxBITMAP_TYPE_PNG)); - btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG)); - btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG)); + m_btn_move_up->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_up.png")), wxBITMAP_TYPE_PNG)); + m_btn_move_down->SetBitmap(wxBitmap(from_u8(Slic3r::var("bullet_arrow_down.png")), wxBITMAP_TYPE_PNG)); m_sizer_object_buttons = new wxGridSizer(1, 3, 0, 0); m_sizer_object_buttons->Add(btn_load_part, 0, wxEXPAND); @@ -200,8 +193,8 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) m_sizer_part_buttons->Add(btn_split, 0, wxEXPAND); { auto up_down_sizer = new wxGridSizer(1, 2, 0, 0); - up_down_sizer->Add(btn_move_up, 1, wxEXPAND); - up_down_sizer->Add(btn_move_down, 1, wxEXPAND); + up_down_sizer->Add(m_btn_move_up, 1, wxEXPAND); + up_down_sizer->Add(m_btn_move_down, 1, wxEXPAND); m_sizer_part_buttons->Add(up_down_sizer, 0, wxEXPAND); } m_sizer_part_buttons->Show(false); @@ -211,19 +204,58 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) btn_load_lambda_modifier->SetFont(Slic3r::GUI::small_font()); btn_delete->SetFont(Slic3r::GUI::small_font()); btn_split->SetFont(Slic3r::GUI::small_font()); - btn_move_up->SetFont(Slic3r::GUI::small_font()); - btn_move_down->SetFont(Slic3r::GUI::small_font()); + m_btn_move_up->SetFont(Slic3r::GUI::small_font()); + m_btn_move_down->SetFont(Slic3r::GUI::small_font()); sizer->Add(m_sizer_object_buttons, 0, wxEXPAND | wxLEFT, 20); sizer->Add(m_sizer_part_buttons, 0, wxEXPAND | wxLEFT, 20); return sizer; } +void update_after_moving() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item || m_selected_object_id<0) + return; + + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) + return; + + Point3 m = m_move_options; + Point3 l = m_last_coords; + + auto d = Pointf3(m.x - l.x, m.y - l.y, m.z - l.z); + auto volume = m_objects[m_selected_object_id]->volumes[volume_id]; + volume->mesh.translate(d.x,d.y,d.z); + m_last_coords = m; + + m_parts_changed = true; + parts_changed(m_selected_object_id); +} + wxSizer* object_movers(wxWindow *win) { // DynamicPrintConfig* config = &get_preset_bundle()->/*full_config();//*/printers.get_edited_preset().config; // TODO get config from Model_volume std::shared_ptr optgroup = std::make_shared(win, "Move"/*, config*/); optgroup->label_width = 20; + optgroup->m_on_change = [](t_config_option_key opt_key, boost::any value){ + int val = boost::any_cast(value); + bool update = false; + if (opt_key == "x" && m_move_options.x != val){ + update = true; + m_move_options.x = val; + } + else if (opt_key == "y" && m_move_options.y != val){ + update = true; + m_move_options.y = val; + } + else if (opt_key == "z" && m_move_options.z != val){ + update = true; + m_move_options.z = val; + } + if (update) update_after_moving(); + }; ConfigOptionDef def; def.label = L("X"); @@ -234,21 +266,26 @@ wxSizer* object_movers(wxWindow *win) Option option = Option(def, "x"); option.opt.full_width = true; optgroup->append_single_option_line(option); - mover_x = dynamic_cast(optgroup->get_field("x")->getWindow()); + m_mover_x = dynamic_cast(optgroup->get_field("x")->getWindow()); def.label = L("Y"); option = Option(def, "y"); optgroup->append_single_option_line(option); - mover_y = dynamic_cast(optgroup->get_field("y")->getWindow()); + m_mover_y = dynamic_cast(optgroup->get_field("y")->getWindow()); def.label = L("Z"); option = Option(def, "z"); optgroup->append_single_option_line(option); - mover_z = dynamic_cast(optgroup->get_field("z")->getWindow()); + m_mover_z = dynamic_cast(optgroup->get_field("z")->getWindow()); get_optgroups().push_back(optgroup); // ogObjectMovers + m_sizer_object_movers = optgroup->sizer; m_sizer_object_movers->Show(false); + + m_move_options = Point3(0, 0, 0); + m_last_coords = Point3(0, 0, 0); + return optgroup->sizer; } @@ -512,10 +549,9 @@ void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/ parts_changed(obj_idx); - const std::string icon_name = is_modifier ? "plugin.png" : "package.png"; - auto icon = wxIcon(Slic3r::GUI::from_u8(Slic3r::var(icon_name)), wxBITMAP_TYPE_PNG); for (int i = 0; i < part_names.size(); ++i) - m_objects_ctrl->Select(m_objects_model->AddChild(item, part_names.Item(i), icon)); + m_objects_ctrl->Select( m_objects_model->AddChild(item, part_names.Item(i), + is_modifier ? m_icon_modifiermesh : m_icon_solidmesh)); } void parts_changed(int obj_idx) @@ -530,5 +566,100 @@ void parts_changed(int obj_idx) get_main_frame()->ProcessWindowEvent(e); } +void part_selection_changed() +{ + m_move_options = Point3(0, 0, 0); + m_last_coords = Point3(0, 0, 0); + // reset move sliders + std::vector opt_keys = {"x", "y", "z"}; + auto og = get_optgroup(ogObjectMovers); + for (auto opt_key: opt_keys) + og->set_value(opt_key, int(0)); + + auto item = m_objects_ctrl->GetSelection(); + if (!item || m_selected_object_id < 0){ + m_sizer_object_buttons->Show(false); + m_sizer_part_buttons->Show(false); + m_sizer_object_movers->Show(false); + m_collpane_settings->Show(false); + return; + } + + m_collpane_settings->Show(true); + + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0){ + m_sizer_object_buttons->Show(true); + m_sizer_part_buttons->Show(false); + m_sizer_object_movers->Show(false); + m_collpane_settings->SetLabelText(_(L("Object Settings")) + ":"); + +// elsif($itemData->{type} eq 'object') { +// # select nothing in 3D preview +// +// # attach object config to settings panel +// $self->{optgroup_movers}->disable; +// $self->{staticbox}->SetLabel('Object Settings'); +// @opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new); +// $config = $self->{model_object}->config; +// } + + return; + } + + m_collpane_settings->SetLabelText(_(L("Part Settings")) + ":"); + + m_sizer_object_buttons->Show(false); + m_sizer_part_buttons->Show(true); + m_sizer_object_movers->Show(true); + + auto bb_size = m_objects[m_selected_object_id]->bounding_box().size(); + int scale = 10; //?? + + m_mover_x->SetMin(-bb_size.x * 4 * scale); + m_mover_x->SetMax(bb_size.x * 4 * scale); + + m_mover_y->SetMin(-bb_size.y * 4 * scale); + m_mover_y->SetMax(bb_size.y * 4 * scale); + + m_mover_z->SetMin(-bb_size.z * 4 * scale); + m_mover_z->SetMax(bb_size.z * 4 * scale); + + + +// my ($config, @opt_keys); + m_btn_move_up->Enable(volume_id > 0); + m_btn_move_down->Enable(volume_id + 1 < m_objects[m_selected_object_id]->volumes.size()); + + // attach volume config to settings panel + auto volume = m_objects[m_selected_object_id]->volumes[volume_id]; + + if (volume->modifier) + og->enable(); + else + og->disable(); + +// auto config = volume->config; + + // get default values +// @opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys}; +// } +/* + # get default values + my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys); + + # append default extruder + push @opt_keys, 'extruder'; + $default_config->set('extruder', 0); + $config->set_ifndef('extruder', 0); + $self->{settings_panel}->set_default_config($default_config); + $self->{settings_panel}->set_config($config); + $self->{settings_panel}->set_opt_keys(\@opt_keys); + $self->{settings_panel}->set_fixed_options([qw(extruder)]); + $self->{settings_panel}->enable; + } + */ +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 4cbd9ba7fe..9f1afc8613 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -57,6 +57,7 @@ void unselect_objects(); // Select current object in the list on c++ side void select_current_object(int idx); +void init_mesh_icons(); void set_event_object_selection_changed(const int& event); void set_event_object_settings_changed (const int& event); @@ -72,6 +73,7 @@ void load_lambda(wxWindow* parent, ModelObject* model_object, void on_btn_load(wxWindow* parent, bool is_modifier = false, bool is_lambda = false); void parts_changed(int obj_idx); +void part_selection_changed(); } //namespace GUI } //namespace Slic3r #endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 36801634ec..f8d8cfc20f 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -376,14 +376,15 @@ wxDataViewItem PrusaObjectDataViewModel::AddChild( const wxDataViewItem &parent_ if (root->GetChildren().Count() == 0) { auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); - auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh); + auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, 0); root->Append(node); // notify control wxDataViewItem child((void*)node); ItemAdded(parent_item, child); } - auto node = new PrusaObjectDataViewModelNode(root, name, icon); + auto volume_id = root->GetChildCount(); + auto node = new PrusaObjectDataViewModelNode(root, name, icon, volume_id); root->Append(node); // notify control wxDataViewItem child((void*)node); @@ -470,6 +471,16 @@ int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item) return it - m_objects.begin(); } +int PrusaObjectDataViewModel::GetVolumeIdByItem(wxDataViewItem& item) +{ + wxASSERT(item.IsOk()); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return -1; + return node->GetVolumeId(); +} + wxString PrusaObjectDataViewModel::GetName(const wxDataViewItem &item) const { PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index dc7ecb4fd6..b8df8c98fd 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -153,7 +153,7 @@ WX_DEFINE_ARRAY_PTR(PrusaObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray class PrusaObjectDataViewModelNode { - PrusaObjectDataViewModelNode* m_parent; + PrusaObjectDataViewModelNode* m_parent; MyObjectTreeModelNodePtrArray m_children; public: PrusaObjectDataViewModelNode(const wxString &name, int instances_count=1, int scale=100) { @@ -161,20 +161,21 @@ public: m_name = name; m_copy = wxString::Format("%d", instances_count); m_scale = wxString::Format("%d%%", scale); + m_type = "object"; + m_volume_id = -1; } PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent, - const wxString& sub_obj) { + const wxString& sub_obj, + const wxIcon& icon, + int volume_id=-1) { m_parent = parent; m_name = sub_obj; m_copy = wxEmptyString; m_scale = wxEmptyString; - } - - PrusaObjectDataViewModelNode(PrusaObjectDataViewModelNode* parent, - const wxString& sub_obj, const wxIcon& icon): - PrusaObjectDataViewModelNode(parent, sub_obj){ - m_icon = icon; + m_icon = icon; + m_type = "volume"; + m_volume_id = volume_id; } ~PrusaObjectDataViewModelNode() @@ -192,6 +193,8 @@ public: wxIcon m_icon; wxString m_copy; wxString m_scale; + std::string m_type; + int m_volume_id; bool m_container = false; bool IsContainer() const @@ -265,6 +268,17 @@ public: { m_icon = icon; } + + void SetType(const std::string& type){ + m_type = type; + } + const std::string& GetType(){ + return m_type; + } + + const int GetVolumeId(){ + return m_volume_id; + } }; // ---------------------------------------------------------------------------- @@ -291,6 +305,7 @@ public: void DeleteAll(); wxDataViewItem GetItemById(int obj_idx); int GetIdByItem(wxDataViewItem& item); + int GetVolumeIdByItem(wxDataViewItem& item); bool IsEmpty() { return m_objects.empty(); } // helper method for wxLog From 12232f1a6db753ccc1171380ed2fbbde7ea18cb5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 18 Jun 2018 15:26:09 +0200 Subject: [PATCH 051/185] "Delete part" button works correctly Prepared functions for "Split", "MoveUp" & "MoveDown" buttons (update_model function is missing) --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 96 +++++++++++++++++++++++---- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 4 ++ xs/src/slic3r/GUI/wxExtensions.cpp | 10 +++ xs/src/slic3r/GUI/wxExtensions.hpp | 5 +- 4 files changed, 101 insertions(+), 14 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 3f183c8d31..deb303c1a6 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -148,28 +148,22 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) m_btn_move_down = new wxButton(win, wxID_ANY, "", wxDefaultPosition, wxDefaultSize/*wxSize(30, -1)*/, wxBU_LEFT); //*** button's functions - btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) - { + btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) { on_btn_load(win); }); - btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) - { + btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { on_btn_load(win, true); }); - btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) - { + btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { on_btn_load(win, true, true); }); - btn_delete->Bind(wxEVT_BUTTON, [](wxEvent&) - { - auto item = m_objects_ctrl->GetSelection(); - if (!item) return; - m_objects_ctrl->Select(m_objects_model->Delete(item)); - parts_changed(m_selected_object_id); - }); + btn_delete ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_del(); }); + btn_split ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_split(); }); + m_btn_move_up ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_up(); }); + m_btn_move_down ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_down(); }); //*** m_btn_move_up->SetMinSize(wxSize(20, -1)); @@ -554,6 +548,82 @@ void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/ is_modifier ? m_icon_modifiermesh : m_icon_solidmesh)); } +void on_btn_del() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item) return; + + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) + return; + auto volume = m_objects[m_selected_object_id]->volumes[volume_id]; + + // if user is deleting the last solid part, throw error + int solid_cnt = 0; + for (auto vol : m_objects[m_selected_object_id]->volumes) + if (!vol->modifier) + ++solid_cnt; + if (!volume->modifier && solid_cnt == 1) { + Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from this object."))); + return; + } + + m_objects_ctrl->Select(m_objects_model->Delete(item)); + m_objects[m_selected_object_id]->delete_volume(volume_id); + m_parts_changed = true; + + parts_changed(m_selected_object_id); +} + +void on_btn_split() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item) + return; + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) + return; + + auto volume = m_objects[m_selected_object_id]->volumes[volume_id]; + DynamicPrintConfig& config = get_preset_bundle()->prints.get_edited_preset().config; + auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size(); + if (volume->split(nozzle_dmrs_cnt) > 1) { + // TODO update model + m_parts_changed = true; + parts_changed(m_selected_object_id); + } +} + +void on_btn_move_up(){ + auto item = m_objects_ctrl->GetSelection(); + if (!item) + return; + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) + return; + auto& volumes = m_objects[m_selected_object_id]->volumes; + if (0 < volume_id && volume_id < volumes.size()) { + std::swap(volumes[volume_id - 1], volumes[volume_id]); + m_parts_changed = true; + // TODO update model ($volume_id - 1); + } +} + +void on_btn_move_down(){ + auto item = m_objects_ctrl->GetSelection(); + if (!item) + return; + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) + return; + auto& volumes = m_objects[m_selected_object_id]->volumes; + if (0 <= volume_id && volume_id+1 < volumes.size()) { + std::swap(volumes[volume_id + 1], volumes[volume_id - 1]); + m_parts_changed = true; + // TODO update model ($volume_id - 1); + } +} + void parts_changed(int obj_idx) { if (m_event_object_settings_changed <= 0) return; diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 9f1afc8613..77552c4955 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -71,6 +71,10 @@ void load_lambda(wxWindow* parent, ModelObject* model_object, wxArrayString& part_names, const bool is_modifier); void on_btn_load(wxWindow* parent, bool is_modifier = false, bool is_lambda = false); +void on_btn_del(); +void on_btn_split(); +void on_btn_move_up(); +void on_btn_move_down(); void parts_changed(int obj_idx); void part_selection_changed(); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index f8d8cfc20f..63c20e086e 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -407,11 +407,21 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) // thus removing the node from it doesn't result in freeing it if (node_parent){ auto id = node_parent->GetChildren().Index(node); + auto v_id = node->GetVolumeId(); node_parent->GetChildren().Remove(node); if (id > 0){ if(id == node_parent->GetChildCount()) id--; ret_item = wxDataViewItem(node_parent->GetChildren().Item(id)); } + + //update volume_id value for remaining child-nodes + auto children = node_parent->GetChildren(); + for (size_t i = 0; i < node_parent->GetChildCount(); i++) + { + auto volume_id = children[i]->GetVolumeId(); + if (volume_id > v_id) + children[i]->SetVolumeId(volume_id-1); + } } else { diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index b8df8c98fd..c28be59b3b 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -276,7 +276,10 @@ public: return m_type; } - const int GetVolumeId(){ + void SetVolumeId(const int& volume_id){ + m_volume_id = volume_id; + } + const int& GetVolumeId(){ return m_volume_id; } }; From a772a19915667bde484ed66a75ba8ccc68a6c611 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 19 Jun 2018 12:24:16 +0200 Subject: [PATCH 052/185] "MoveUp" & "MoveDown" work correctly --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 15 +++++---- xs/src/slic3r/GUI/wxExtensions.cpp | 48 +++++++++++++++++++++++++++ xs/src/slic3r/GUI/wxExtensions.hpp | 30 +++++++++++++++++ 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index deb303c1a6..c4dc631a2f 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -81,15 +81,15 @@ wxBoxSizer* content_objects_list(wxWindow *win) // column 0(Icon+Text) of the view control: m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 150, - wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + wxALIGN_LEFT, /*wxDATAVIEW_COL_SORTABLE | */wxDATAVIEW_COL_RESIZABLE); // column 1 of the view control: m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 65, - wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); // column 2 of the view control: m_objects_ctrl->AppendTextColumn(_(L("Scale")), 2, wxDATAVIEW_CELL_INERT, 70, - wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { @@ -106,7 +106,8 @@ wxBoxSizer* content_objects_list(wxWindow *win) obj_idx = m_objects_model->GetIdByItem(item); else { auto parent = m_objects_model->GetParent(item); - obj_idx = m_objects_model->GetIdByItem(parent); // TODO Temporary decision for sub-objects selection + // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene + obj_idx = m_objects_model->GetIdByItem(parent); } } m_selected_object_id = obj_idx; @@ -605,7 +606,7 @@ void on_btn_move_up(){ if (0 < volume_id && volume_id < volumes.size()) { std::swap(volumes[volume_id - 1], volumes[volume_id]); m_parts_changed = true; - // TODO update model ($volume_id - 1); + m_objects_ctrl->Select(m_objects_model->MoveChildUp(item)); } } @@ -618,9 +619,9 @@ void on_btn_move_down(){ return; auto& volumes = m_objects[m_selected_object_id]->volumes; if (0 <= volume_id && volume_id+1 < volumes.size()) { - std::swap(volumes[volume_id + 1], volumes[volume_id - 1]); + std::swap(volumes[volume_id + 1], volumes[volume_id]); m_parts_changed = true; - // TODO update model ($volume_id - 1); + m_objects_ctrl->Select(m_objects_model->MoveChildDown(item)); } } diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 63c20e086e..7b35ce9a05 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -556,6 +556,54 @@ bool PrusaObjectDataViewModel::SetValue(const wxVariant &variant, const int item return m_objects[item_idx]->SetValue(variant, col); } +wxDataViewItem PrusaObjectDataViewModel::MoveChildUp(const wxDataViewItem &item) +{ + auto ret_item = wxDataViewItem(0); + wxASSERT(item.IsOk()); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return ret_item; + + auto node_parent = node->GetParent(); + if (!node_parent) // If isn't part, but object + return ret_item; + + auto volume_id = node->GetVolumeId(); + if (0 < volume_id && volume_id < node_parent->GetChildCount()){ + node_parent->SwapChildrens(volume_id - 1, volume_id); + ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id - 1)); + ItemChanged(item); + ItemChanged(ret_item); + } + else + ret_item = wxDataViewItem(node_parent->GetNthChild(0)); + return ret_item; +} + +wxDataViewItem PrusaObjectDataViewModel::MoveChildDown(const wxDataViewItem &item) +{ + auto ret_item = wxDataViewItem(0); + wxASSERT(item.IsOk()); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return ret_item; + + auto node_parent = node->GetParent(); + if (!node_parent) // If isn't part, but object + return ret_item; + + auto volume_id = node->GetVolumeId(); + if (0 <= volume_id && volume_id+1 < node_parent->GetChildCount()){ + node_parent->SwapChildrens(volume_id + 1, volume_id); + ret_item = wxDataViewItem(node_parent->GetNthChild(volume_id + 1)); + ItemChanged(item); + ItemChanged(ret_item); + } + else + ret_item = wxDataViewItem(node_parent->GetNthChild(node_parent->GetChildCount()-1)); + return ret_item; +} + // bool MyObjectTreeModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const // { // diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index c28be59b3b..54631e14a5 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -282,6 +282,33 @@ public: const int& GetVolumeId(){ return m_volume_id; } + + // use this function only for childrens + void AssignAllVal(PrusaObjectDataViewModelNode& from_node) + { + // ! Don't overwrite other values because of equality of this values for all children -- + m_name = from_node.m_name; + m_icon = from_node.m_icon; + m_volume_id = from_node.m_volume_id; + } + + bool SwapChildrens(int frst_id, int scnd_id) { + if (GetChildCount() < 2 || + frst_id < 0 || frst_id >= GetChildCount() || + scnd_id < 0 || scnd_id >= GetChildCount()) + return false; + + PrusaObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); + PrusaObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); + + new_scnd.m_volume_id = m_children.Item(scnd_id)->m_volume_id; + new_frst.m_volume_id = m_children.Item(frst_id)->m_volume_id; + + m_children.Item(frst_id)->AssignAllVal(new_frst); + m_children.Item(scnd_id)->AssignAllVal(new_scnd); + return true; + } + }; // ---------------------------------------------------------------------------- @@ -328,6 +355,9 @@ public: const wxDataViewItem &item, unsigned int col) override; bool SetValue(const wxVariant &variant, const int item_idx, unsigned int col); + wxDataViewItem MoveChildUp(const wxDataViewItem &item); + wxDataViewItem MoveChildDown(const wxDataViewItem &item); + // virtual bool IsEnabled(const wxDataViewItem &item, // unsigned int col) const override; From 8a3cf3f71d2b2ef68887ff312f5b41a0d22aa0f1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 19 Jun 2018 16:24:49 +0200 Subject: [PATCH 053/185] Correct selection update on MSW --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 113 ++++++++++++++------------ xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 2 + 2 files changed, 61 insertions(+), 54 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index c4dc631a2f..fe8efaeddb 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -93,36 +93,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { - if (g_prevent_list_events) return; - - wxWindowUpdateLocker noUpdates(get_right_panel()); - auto item = m_objects_ctrl->GetSelection(); - int obj_idx = -1; - if (!item) - unselect_objects(); - else - { - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) - obj_idx = m_objects_model->GetIdByItem(item); - else { - auto parent = m_objects_model->GetParent(item); - // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene - obj_idx = m_objects_model->GetIdByItem(parent); - } - } - m_selected_object_id = obj_idx; - - if (m_event_object_selection_changed > 0) { - wxCommandEvent event(m_event_object_selection_changed); - event.SetInt(int(m_objects_model->GetParent(item) != wxDataViewItem(0))); - event.SetId(obj_idx); - get_main_frame()->ProcessWindowEvent(event); - } - - if (obj_idx < 0) return; - -// m_objects_ctrl->SetSize(m_objects_ctrl->GetBestSize()); // TODO override GetBestSize(), than use it - part_selection_changed(); + object_ctrl_selection_changed(); }); m_objects_ctrl->Bind(wxEVT_KEY_DOWN, [](wxKeyEvent& event) @@ -344,13 +315,9 @@ void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer) m_sizer_object_buttons->Show(false); m_sizer_part_buttons->Show(false); m_sizer_object_movers->Show(false); - m_collpane_settings->Show(false); + if (!m_objects_ctrl->HasSelection()) + m_collpane_settings->Show(false); } -// else -// m_objects_ctrl->UnselectAll(); - -// e.Skip(); -// g_right_panel->Layout(); })); // *** Object/Part Settings *** @@ -367,6 +334,10 @@ void add_object_to_list(const std::string &name, ModelObject* model_object) wxString item = name; int scale = model_object->instances[0]->scaling_factor * 100; m_objects_ctrl->Select(m_objects_model->Add(item, model_object->instances.size(), scale)); +// part_selection_changed(); +#ifdef __WXMSW__ + object_ctrl_selection_changed(); +#endif //__WXMSW__ m_objects.push_back(model_object); } @@ -403,14 +374,7 @@ void set_object_scale(int idx, int scale) void unselect_objects() { m_objects_ctrl->UnselectAll(); - if (m_sizer_object_buttons->IsShown(1)) - m_sizer_object_buttons->Show(false); - if (m_sizer_part_buttons->IsShown(1)) - m_sizer_part_buttons->Show(false); - if (m_sizer_object_movers->IsShown(1)) - m_sizer_object_movers->Show(false); - if (m_collpane_settings->IsShown()) - m_collpane_settings->Show(false); + part_selection_changed(); } void select_current_object(int idx) @@ -422,17 +386,26 @@ void select_current_object(int idx) return; } m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); + part_selection_changed(); g_prevent_list_events = false; +} - if (is_expert_mode()){ - if (!m_sizer_object_buttons->IsShown(1)) - m_sizer_object_buttons->Show(true); - if (!m_sizer_object_movers->IsShown(1)) - m_sizer_object_movers->Show(true); - if (!m_collpane_settings->IsShown()) - m_collpane_settings->Show(true); +void object_ctrl_selection_changed() +{ + if (g_prevent_list_events) return; + + part_selection_changed(); + + if (m_selected_object_id < 0) return; + + if (m_event_object_selection_changed > 0) { + wxCommandEvent event(m_event_object_selection_changed); + event.SetInt(int(m_objects_model->GetParent(/*item*/ m_objects_ctrl->GetSelection()) != wxDataViewItem(0))); + event.SetId(m_selected_object_id); + get_main_frame()->ProcessWindowEvent(event); } } + // ****** void load_part( wxWindow* parent, ModelObject* model_object, @@ -547,6 +520,10 @@ void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/ for (int i = 0; i < part_names.size(); ++i) m_objects_ctrl->Select( m_objects_model->AddChild(item, part_names.Item(i), is_modifier ? m_icon_modifiermesh : m_icon_solidmesh)); +// part_selection_changed(); +#ifdef __WXMSW__ + object_ctrl_selection_changed(); +#endif //__WXMSW__ } void on_btn_del() @@ -569,11 +546,16 @@ void on_btn_del() return; } - m_objects_ctrl->Select(m_objects_model->Delete(item)); m_objects[m_selected_object_id]->delete_volume(volume_id); m_parts_changed = true; parts_changed(m_selected_object_id); + + m_objects_ctrl->Select(m_objects_model->Delete(item)); + part_selection_changed(); +// #ifdef __WXMSW__ +// object_ctrl_selection_changed(); +// #endif //__WXMSW__ } void on_btn_split() @@ -607,6 +589,10 @@ void on_btn_move_up(){ std::swap(volumes[volume_id - 1], volumes[volume_id]); m_parts_changed = true; m_objects_ctrl->Select(m_objects_model->MoveChildUp(item)); + part_selection_changed(); +// #ifdef __WXMSW__ +// object_ctrl_selection_changed(); +// #endif //__WXMSW__ } } @@ -622,6 +608,10 @@ void on_btn_move_down(){ std::swap(volumes[volume_id + 1], volumes[volume_id]); m_parts_changed = true; m_objects_ctrl->Select(m_objects_model->MoveChildDown(item)); + part_selection_changed(); +// #ifdef __WXMSW__ +// object_ctrl_selection_changed(); +// #endif //__WXMSW__ } } @@ -639,6 +629,22 @@ void parts_changed(int obj_idx) void part_selection_changed() { + auto item = m_objects_ctrl->GetSelection(); + int obj_idx = -1; + if (item) + { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + obj_idx = m_objects_model->GetIdByItem(item); + else { + auto parent = m_objects_model->GetParent(item); + // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene + obj_idx = m_objects_model->GetIdByItem(parent); + } + } + m_selected_object_id = obj_idx; + + wxWindowUpdateLocker noUpdates(get_right_panel()); + m_move_options = Point3(0, 0, 0); m_last_coords = Point3(0, 0, 0); // reset move sliders @@ -647,8 +653,7 @@ void part_selection_changed() for (auto opt_key: opt_keys) og->set_value(opt_key, int(0)); - auto item = m_objects_ctrl->GetSelection(); - if (!item || m_selected_object_id < 0){ + if (/*!item || */m_selected_object_id < 0){ m_sizer_object_buttons->Show(false); m_sizer_part_buttons->Show(false); m_sizer_object_movers->Show(false); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 77552c4955..bece5f0f89 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -57,6 +57,8 @@ void unselect_objects(); // Select current object in the list on c++ side void select_current_object(int idx); +void object_ctrl_selection_changed(); + void init_mesh_icons(); void set_event_object_selection_changed(const int& event); void set_event_object_settings_changed (const int& event); From c1724f45c933c252ff410e4dbd0fe5a0aacb6caa Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 26 Jun 2018 13:34:25 +0200 Subject: [PATCH 054/185] Fixed post-merge bugs --- lib/Slic3r/GUI/Plater.pm | 23 ++++++++--------------- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 2 +- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index f3ef531ada..2b6fa4b3fb 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1927,25 +1927,16 @@ sub on_config_change { $self->schedule_background_process; } -sub list_item_deselected { - my ($self, $event) = @_; - return if $PreventListEvents; - $self->{_lecursor} = Wx::BusyCursor->new(); - if ($self->{list}->GetFirstSelected == -1) { - $self->select_object(undef); - $self->{canvas}->Refresh; - Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}) if $self->{canvas3D}; - Slic3r::GUI::_3DScene::render($self->{canvas3D}) if $self->{canvas3D}; - } - undef $self->{_lecursor}; -} sub item_changed_selection{ my ($self, $obj_idx) = @_; $self->{canvas}->Refresh; if ($self->{canvas3D}) { - my $selections = $self->collect_selections; - Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections); + Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}); + if ($obj_idx >= 0){ + my $selections = $self->collect_selections; + Slic3r::GUI::_3DScene::update_volumes_selection($self->{canvas3D}, \@$selections); + } Slic3r::GUI::_3DScene::render($self->{canvas3D}); } } @@ -2077,7 +2068,9 @@ sub changed_object_settings { $self->{print}->reload_object($obj_idx); $self->schedule_background_process; $self->{canvas}->reload_scene if $self->{canvas}; - $self->{canvas3D}->reload_scene if $self->{canvas3D}; + my $selections = $self->collect_selections; + Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); + Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 0); } else { $self->resume_background_process; } diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index fe8efaeddb..331fde749e 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -396,7 +396,7 @@ void object_ctrl_selection_changed() part_selection_changed(); - if (m_selected_object_id < 0) return; +// if (m_selected_object_id < 0) return; if (m_event_object_selection_changed > 0) { wxCommandEvent event(m_event_object_selection_changed); From f8ab8b43de1eccc6faa4bf73480083ea6cd8a5ea Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 29 Jun 2018 13:07:58 +0200 Subject: [PATCH 055/185] Experiment with Toolpits of selected preset on OSX --- xs/src/slic3r/GUI/Preset.cpp | 11 ++++++++--- xs/src/slic3r/GUI/PresetBundle.cpp | 7 +++++-- xs/src/slic3r/GUI/wxExtensions.cpp | 4 ++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 68982185b4..536c370021 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -601,6 +601,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) // Otherwise fill in the list from scratch. ui->Freeze(); ui->Clear(); + size_t selected_preset_item = 0; const Preset &selected_preset = this->get_selected_preset(); // Show wide icons if the currently selected preset is not compatible with the current printer, @@ -641,7 +642,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); if (i == m_idx_selected) - ui->SetSelection(ui->GetCount() - 1); + selected_preset_item = ui->GetCount() - 1; } else { @@ -658,10 +659,13 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected) - ui->SetSelection(ui->GetCount() - 1); + selected_preset_item = ui->GetCount() - 1; } } - ui->Thaw(); + + ui->SetSelection(selected_preset_item); + ui->SetToolTip(ui->GetString(selected_preset_item)); + ui->Thaw(); } size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible) @@ -719,6 +723,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati } } ui->SetSelection(selected_preset_item); + ui->SetToolTip(ui->GetString(selected_preset_item)); ui->Thaw(); return selected_preset_item; } diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index d36ef7b6fe..5914637bb0 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -1108,6 +1108,7 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma // Fill in the list from scratch. ui->Freeze(); ui->Clear(); + size_t selected_preset_item = 0; const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]); // Show wide icons if the currently selected preset is not compatible with the current printer, // and draw a red flag in front of the selected preset. @@ -1159,7 +1160,7 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); if (selected) - ui->SetSelection(ui->GetCount() - 1); + selected_preset_item = ui->GetCount() - 1; } else { @@ -1178,9 +1179,11 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected_str) - ui->SetSelection(ui->GetCount() - 1); + selected_preset_item = ui->GetCount() - 1; } } + ui->SetSelection(selected_preset_item); + ui->SetToolTip(ui->GetString(selected_preset_item)); ui->Thaw(); } diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 7b35ce9a05..d0e394996c 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -194,6 +194,9 @@ void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& e // ---------------------------------------------------------------------------- void PrusaCollapsiblePane::OnStateChange(const wxSize& sz) { +#ifndef __WXOSX__ + wxCollapsiblePane::OnStateChange(sz); +#else SetSize(sz); if (this->HasFlag(wxCP_NO_TLW_RESIZE)) @@ -219,6 +222,7 @@ void PrusaCollapsiblePane::OnStateChange(const wxSize& sz) // top->SetClientSize(newBestSize); top->GetParent()->Layout(); top->Refresh(); +#endif //__WXOSX__ } // ---------------------------------------------------------------------------- From 678498bed637765b405cda974a75fb22201e9320 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 29 Jun 2018 14:00:22 +0200 Subject: [PATCH 056/185] Typo fixed --- xs/src/slic3r/GUI/wxExtensions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index d0e394996c..725edbd87b 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -194,7 +194,7 @@ void wxDataViewTreeCtrlComboPopup::OnDataViewTreeCtrlSelection(wxCommandEvent& e // ---------------------------------------------------------------------------- void PrusaCollapsiblePane::OnStateChange(const wxSize& sz) { -#ifndef __WXOSX__ +#ifdef __WXOSX__ wxCollapsiblePane::OnStateChange(sz); #else SetSize(sz); From 54298c8e61c18bec738b61e2311f029be17470a8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 4 Jul 2018 08:54:30 +0200 Subject: [PATCH 057/185] First right-panel reorganization: * Replaced Object list from CollapsiblePane * Sub-object are adding by context menu from list * (sub)objects can be deleted by only one "Delete" button * Added extruder selection to list --- lib/Slic3r/GUI/MainFrame.pm | 9 ++ lib/Slic3r/GUI/Plater.pm | 11 ++- xs/src/slic3r/GUI/GUI.cpp | 14 ++- xs/src/slic3r/GUI/GUI.hpp | 4 +- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 127 ++++++++++++++++++++++++-- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 10 +- xs/src/slic3r/GUI/Tab.cpp | 1 + xs/src/slic3r/GUI/wxExtensions.cpp | 18 ++++ xs/src/slic3r/GUI/wxExtensions.hpp | 20 +++- xs/xsp/GUI.xsp | 9 +- 10 files changed, 202 insertions(+), 21 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 553e287bfa..1465c12159 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -29,6 +29,8 @@ our $PRESETS_CHANGED_EVENT = Wx::NewEventType; our $OBJECT_SELECTION_CHANGED_EVENT = Wx::NewEventType; # 4) To inform about a change of object settings our $OBJECT_SETTINGS_CHANGED_EVENT = Wx::NewEventType; +# 5) To inform about a remove of object +our $OBJECT_REMOVE_EVENT = Wx::NewEventType; sub new { my ($class, %params) = @_; @@ -122,6 +124,7 @@ sub _init_tabpanel { $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel, event_object_selection_changed => $OBJECT_SELECTION_CHANGED_EVENT, event_object_settings_changed => $OBJECT_SETTINGS_CHANGED_EVENT, + event_remove_object => $OBJECT_REMOVE_EVENT, ), L("Plater")); if (!$self->{no_controller}) { $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), L("Controller")); @@ -197,6 +200,12 @@ sub _init_tabpanel { $self->{plater}->changed_object_settings($obj_idx, $parts_changed, $part_settings_changed); }); + + # The following event is emited by the C++ Tab implementation on object settings change. + EVT_COMMAND($self, -1, $OBJECT_REMOVE_EVENT, sub { + my ($self, $event) = @_; + $self->{plater}->remove(); + }); Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 2b6fa4b3fb..2da2737802 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -57,6 +57,7 @@ sub new { # store input params $self->{event_object_selection_changed} = $params{event_object_selection_changed}; $self->{event_object_settings_changed} = $params{event_object_settings_changed}; + $self->{event_remove_object} = $params{event_remove_object}; # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; @@ -151,6 +152,7 @@ sub new { Slic3r::GUI::_3DScene::register_on_increase_objects_callback($self->{canvas3D}, sub { $self->increase() }); Slic3r::GUI::_3DScene::register_on_decrease_objects_callback($self->{canvas3D}, sub { $self->decrease() }); Slic3r::GUI::_3DScene::register_on_remove_object_callback($self->{canvas3D}, sub { $self->remove() }); +# Slic3r::GUI::_3DScene::register_on_remove_object_callback($self->{canvas3D}, sub { Slic3r::GUI::remove_obj() }); Slic3r::GUI::_3DScene::register_on_instance_moved_callback($self->{canvas3D}, $on_instances_moved); Slic3r::GUI::_3DScene::register_on_enable_action_buttons_callback($self->{canvas3D}, $enable_action_buttons); Slic3r::GUI::_3DScene::register_on_gizmo_scale_uniformly_callback($self->{canvas3D}, $on_gizmo_scale_uniformly); @@ -328,7 +330,8 @@ sub new { if ($self->{htoolbar}) { EVT_TOOL($self, TB_ADD, sub { $self->add; }); - EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove +# EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove + EVT_TOOL($self, TB_REMOVE, sub { Slic3r::GUI::remove_obj() }); # explicitly pass no argument to remove EVT_TOOL($self, TB_RESET, sub { $self->reset; }); EVT_TOOL($self, TB_ARRANGE, sub { $self->arrange; }); EVT_TOOL($self, TB_MORE, sub { $self->increase; }); @@ -346,7 +349,8 @@ sub new { }); } else { EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; }); - EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove +# EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove + EVT_BUTTON($self, $self->{btn_remove}, sub { Slic3r::GUI::remove_obj() }); # explicitly pass no argument to remove EVT_BUTTON($self, $self->{btn_reset}, sub { $self->reset; }); EVT_BUTTON($self, $self->{btn_arrange}, sub { $self->arrange; }); EVT_BUTTON($self, $self->{btn_increase}, sub { $self->increase; }); @@ -446,7 +450,8 @@ sub new { my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); Slic3r::GUI::add_expert_mode_part( $self->{right_panel}, $expert_mode_part_sizer, $self->{event_object_selection_changed}, - $self->{event_object_settings_changed}); + $self->{event_object_settings_changed}, + $self->{event_remove_object}); # if ($expert_mode_part_sizer->IsShown(2)==1) # { # $expert_mode_part_sizer->Layout; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 487ede6559..6997592c43 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -811,6 +811,10 @@ wxFrame* get_main_frame() { return g_wxMainFrame; } +wxNotebook * get_tab_panel() { + return g_wxTabPanel; +} + const int& label_width(){ return m_label_width; } @@ -885,15 +889,19 @@ wxString from_u8(const std::string &str) void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed, - int event_object_settings_changed) + int event_object_settings_changed, + int event_remove_object) { set_event_object_selection_changed(event_object_selection_changed); set_event_object_settings_changed(event_object_settings_changed); + set_event_remove_object(event_remove_object); init_mesh_icons(); wxWindowUpdateLocker noUpdates(parent); - add_collapsible_panes(parent, sizer); + add_objects_list(parent, sizer); + +// add_collapsible_panes(parent, sizer); } Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value = 0) @@ -1149,7 +1157,7 @@ void update_mode() // TODO There is a not the best place of it! // *** Update showing of the collpane_settings - show_collpane_settings(mode == ConfigMenuModeExpert); +// show_collpane_settings(mode == ConfigMenuModeExpert); // ************************* g_right_panel->GetParent()->Layout(); g_right_panel->Layout(); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 8e1be3216a..0a8d9072b4 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -106,6 +106,7 @@ AppConfig* get_app_config(); wxApp* get_app(); PresetBundle* get_preset_bundle(); wxFrame* get_main_frame(); +wxNotebook * get_tab_panel(); const wxColour& get_label_clr_modified(); const wxColour& get_label_clr_sys(); @@ -187,7 +188,8 @@ wxString from_u8(const std::string &str); void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, int event_object_selection_changed, - int event_object_settings_changed); + int event_object_settings_changed, + int event_remove_object); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); // Update view mode according to selected menu void update_mode(); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 331fde749e..69738e7f83 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -42,6 +42,7 @@ ModelObjectPtrs m_objects; int m_event_object_selection_changed = 0; int m_event_object_settings_changed = 0; +int m_event_remove_object = 0; bool m_parts_changed = false; bool m_part_settings_changed = false; @@ -52,6 +53,9 @@ void set_event_object_selection_changed(const int& event){ void set_event_object_settings_changed(const int& event){ m_event_object_settings_changed = event; } +void set_event_remove_object(const int& event){ + m_event_remove_object = event; +} void init_mesh_icons(){ m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); @@ -80,15 +84,30 @@ wxBoxSizer* content_objects_list(wxWindow *win) #endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE // column 0(Icon+Text) of the view control: - m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 150, + m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 120, wxALIGN_LEFT, /*wxDATAVIEW_COL_SORTABLE | */wxDATAVIEW_COL_RESIZABLE); // column 1 of the view control: - m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 65, + m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 45, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); // column 2 of the view control: - m_objects_ctrl->AppendTextColumn(_(L("Scale")), 2, wxDATAVIEW_CELL_INERT, 70, + m_objects_ctrl->AppendTextColumn(_(L("Scale")), 2, wxDATAVIEW_CELL_INERT, 55, + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + + // column 2 of the view control: + wxArrayString choices; + choices.Add("1"); + choices.Add("2"); + choices.Add("3"); + choices.Add("4"); + wxDataViewChoiceRenderer *c = + new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL); + wxDataViewColumn *column3 = + new wxDataViewColumn(_(L("Extruder")), c, 3, 60, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + m_objects_ctrl->AppendColumn(column3); + + m_objects_ctrl->AppendBitmapColumn("", 4, wxDATAVIEW_CELL_INERT, 25, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) @@ -96,6 +115,13 @@ wxBoxSizer* content_objects_list(wxWindow *win) object_ctrl_selection_changed(); }); + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxEvent& event) + { + event.Skip(); + object_ctrl_context_menu(); + + }); + m_objects_ctrl->Bind(wxEVT_KEY_DOWN, [](wxKeyEvent& event) { if (event.GetKeyCode() == WXK_TAB) @@ -305,6 +331,11 @@ wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_pare return collpane; } +void add_objects_list(wxWindow* parent, wxBoxSizer* sizer) +{ + sizer->Add(content_objects_list(parent), 1, wxEXPAND | wxALL, 0); +} + void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer) { // *** Objects List *** @@ -349,14 +380,14 @@ void delete_object_from_list() // m_objects_ctrl->Select(m_objects_model->Delete(item)); m_objects_model->Delete(item); - if (m_objects_model->IsEmpty()) - m_collpane_settings->Show(false); +// if (m_objects_model->IsEmpty()) +// m_collpane_settings->Show(false); } void delete_all_objects_from_list() { m_objects_model->DeleteAll(); - m_collpane_settings->Show(false); +// m_collpane_settings->Show(false); } void set_object_count(int idx, int count) @@ -375,6 +406,8 @@ void unselect_objects() { m_objects_ctrl->UnselectAll(); part_selection_changed(); + + get_optgroup(ogFrequentlyObjectSettings)->disable(); } void select_current_object(int idx) @@ -388,6 +421,25 @@ void select_current_object(int idx) m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); part_selection_changed(); g_prevent_list_events = false; + + get_optgroup(ogFrequentlyObjectSettings)->enable(); +} + +void remove() +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item) + return; + + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + if (m_event_remove_object > 0) { + wxCommandEvent event(m_event_remove_object); + get_main_frame()->ProcessWindowEvent(event); + } +// delete_object_from_list(); + } + else + on_btn_del(); } void object_ctrl_selection_changed() @@ -406,6 +458,59 @@ void object_ctrl_selection_changed() } } +wxMenu *CreateAddPartPopupMenu(){ + wxMenu *menu = new wxMenu; + wxWindowID config_id_base = wxWindow::NewControlId(3); + + menu->Append(config_id_base, _(L("Add part"))); + menu->AppendSeparator(); + menu->Append(config_id_base + 1, _(L("Add modifier"))); + menu->AppendSeparator(); + menu->AppendCheckItem(config_id_base + 2, _(L("Add generic"))); + + wxWindow* win = get_tab_panel()->GetPage(0); + + menu->Bind(wxEVT_MENU, [config_id_base, win](wxEvent &event){ + switch (event.GetId() - config_id_base) { + case 0: + on_btn_load(win); + break; + case 1: + on_btn_load(win, true); + break; + case 2: + on_btn_load(win, true, true); + break; + default: + break; + } + }); + return menu; +} + +void object_ctrl_context_menu() +{ +// auto cur_column = m_objects_ctrl->GetCurrentColumn(); +// auto action_column = m_objects_ctrl->GetColumn(4); +// if (cur_column == action_column) + { + auto item = m_objects_ctrl->GetSelection(); + if (item) + { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + auto menu = CreateAddPartPopupMenu(); + get_tab_panel()->GetPage(0)->PopupMenu(menu); +// wxMessageBox(m_objects_model->GetName(item)); + } + // else { + // auto parent = m_objects_model->GetParent(item); + // // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene + // obj_idx = m_objects_model->GetIdByItem(parent); + // } + } + } +} + // ****** void load_part( wxWindow* parent, ModelObject* model_object, @@ -643,7 +748,7 @@ void part_selection_changed() } m_selected_object_id = obj_idx; - wxWindowUpdateLocker noUpdates(get_right_panel()); +/* wxWindowUpdateLocker noUpdates(get_right_panel()); m_move_options = Point3(0, 0, 0); m_last_coords = Point3(0, 0, 0); @@ -653,7 +758,8 @@ void part_selection_changed() for (auto opt_key: opt_keys) og->set_value(opt_key, int(0)); - if (/*!item || */m_selected_object_id < 0){ +// if (!item || m_selected_object_id < 0){ + if (m_selected_object_id < 0){ m_sizer_object_buttons->Show(false); m_sizer_part_buttons->Show(false); m_sizer_object_movers->Show(false); @@ -737,5 +843,10 @@ void part_selection_changed() */ } +void set_extruder_column_hidden(bool hide) +{ + m_objects_ctrl->GetColumn(3)->SetHidden(hide); +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index bece5f0f89..a0015c1467 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -39,6 +39,7 @@ struct OBJECT_PARAMETERS }; void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer); +void add_objects_list(wxWindow* parent, wxBoxSizer* sizer); void show_collpane_settings(bool expert_mode); // Add object to the list @@ -56,12 +57,16 @@ void set_object_scale(int idx, int scale); void unselect_objects(); // Select current object in the list on c++ side void select_current_object(int idx); +// Remove objects/sub-object from the list +void remove(); void object_ctrl_selection_changed(); +void object_ctrl_context_menu(); void init_mesh_icons(); void set_event_object_selection_changed(const int& event); -void set_event_object_settings_changed (const int& event); +void set_event_object_settings_changed(const int& event); +void set_event_remove_object(const int& event); bool is_parts_changed(); bool is_part_settings_changed(); @@ -80,6 +85,9 @@ void on_btn_move_down(); void parts_changed(int obj_idx); void part_selection_changed(); + +// show/hide "Extruder" column for Objects List +void set_extruder_column_hidden(bool hide); } //namespace GUI } //namespace Slic3r #endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 712d1db03f..89628d72bb 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -1910,6 +1910,7 @@ void Tab::load_current_preset() const Preset* parent_preset = m_presets->get_selected_preset_parent(); static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + set_extruder_column_hidden(static_cast(this)->m_sys_extruders_count <= 1); } m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; init_options_list(); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 725edbd87b..23a87af9e9 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -343,6 +343,18 @@ void PrusaCollapsiblePaneMSW::Collapse(bool collapse) } #endif //__WXMSW__ +// ***************************************************************************** +// ---------------------------------------------------------------------------- +// PrusaObjectDataViewModelNode +// ---------------------------------------------------------------------------- + +void PrusaObjectDataViewModelNode::set_object_action_icon() { + m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG); +} +void PrusaObjectDataViewModelNode::set_part_action_icon() { + m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); +} + // ***************************************************************************** // ---------------------------------------------------------------------------- // PrusaObjectDataViewModel @@ -539,6 +551,12 @@ void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem case 2: variant = node->m_scale; break; + case 3: + variant = node->m_extruder; + break; + case 4: + variant << node->m_action_icon; + break; default: ; } diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 54631e14a5..3d0ca85640 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -163,19 +163,21 @@ public: m_scale = wxString::Format("%d%%", scale); m_type = "object"; m_volume_id = -1; + set_object_action_icon(); } PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent, - const wxString& sub_obj, + const wxString& sub_obj_name, const wxIcon& icon, int volume_id=-1) { m_parent = parent; - m_name = sub_obj; + m_name = sub_obj_name; m_copy = wxEmptyString; m_scale = wxEmptyString; m_icon = icon; m_type = "volume"; - m_volume_id = volume_id; + m_volume_id = volume_id; + set_part_action_icon(); } ~PrusaObjectDataViewModelNode() @@ -196,6 +198,8 @@ public: std::string m_type; int m_volume_id; bool m_container = false; + wxString m_extruder = "default"; + wxBitmap m_action_icon; bool IsContainer() const { @@ -259,6 +263,12 @@ public: case 2: m_scale = variant.GetString(); return true; + case 3: + m_extruder = variant.GetString(); + return true; + case 4: + m_action_icon << variant; + return true; default: printf("MyObjectTreeModel::SetValue: wrong column"); } @@ -290,6 +300,7 @@ public: m_name = from_node.m_name; m_icon = from_node.m_icon; m_volume_id = from_node.m_volume_id; + m_extruder = from_node.m_extruder; } bool SwapChildrens(int frst_id, int scnd_id) { @@ -309,6 +320,9 @@ public: return true; } + // Set action icons for node + void set_object_action_icon(); + void set_part_action_icon(); }; // ---------------------------------------------------------------------------- diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index eef7b0fd94..470bbcdd28 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -89,11 +89,13 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz void add_expert_mode_part( SV *ui_parent, SV *ui_sizer, int event_object_selection_changed, - int event_object_settings_changed) + int event_object_settings_changed, + int event_remove_object) %code%{ Slic3r::GUI::add_expert_mode_part( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), event_object_selection_changed, - event_object_settings_changed); %}; + event_object_settings_changed, + event_remove_object); %}; void set_objects_from_perl( SV *ui_parent, SV *frequently_changed_parameters_sizer, @@ -149,6 +151,9 @@ void unselect_objects() void select_current_object(int idx) %code%{ Slic3r::GUI::select_current_object(idx); %}; +void remove_obj() + %code%{ Slic3r::GUI::remove(); %}; + std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; From 38768a7bdade481265c00d6e4e247a92b3ce6de6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 4 Jul 2018 12:38:34 +0200 Subject: [PATCH 058/185] Replaced Object list before Object(Part) Settings --- lib/Slic3r/GUI/Plater.pm | 4 ++-- xs/src/slic3r/GUI/GUI.cpp | 31 ++++++++++++++++++--------- xs/src/slic3r/GUI/GUI.hpp | 3 ++- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 4 +++- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 2da2737802..661a81a648 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -519,7 +519,7 @@ sub new { my $box = Wx::StaticBox->new($self->{right_panel}, -1, L("Sliced Info")); $box->SetFont($Slic3r::GUI::small_bold_font); $print_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); - $print_info_sizer->SetMinSize([300,-1]); + $print_info_sizer->SetMinSize([316,-1]); my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5); $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); $grid_sizer->AddGrowableCol(1, 1); @@ -555,7 +555,7 @@ sub new { ### Sizer for info boxes my $info_sizer = Wx::BoxSizer->new(wxVERTICAL); - $info_sizer->SetMinSize([310, -1]); + $info_sizer->SetMinSize([318, -1]); $info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); $info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 6997592c43..0262e20e5d 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -133,6 +133,7 @@ wxWindow *g_right_panel = nullptr; wxBoxSizer *g_frequently_changed_parameters_sizer = nullptr; wxBoxSizer *g_expert_mode_part_sizer = nullptr; wxBoxSizer *g_scrolled_window_sizer = nullptr; +wxBoxSizer *g_object_list_sizer = nullptr; wxButton *g_btn_export_gcode = nullptr; wxButton *g_btn_export_stl = nullptr; wxButton *g_btn_reslice = nullptr; @@ -244,6 +245,10 @@ void set_show_manifold_warning_icon(bool show) g_show_manifold_warning_icon = show; } +void set_objects_list_sizer(wxBoxSizer *objects_list_sizer){ + g_object_list_sizer = objects_list_sizer; +} + std::vector& get_tabs_list() { return g_tabs_list; @@ -815,7 +820,7 @@ wxNotebook * get_tab_panel() { return g_wxTabPanel; } -const int& label_width(){ +const size_t& label_width(){ return m_label_width; } @@ -899,7 +904,7 @@ void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, wxWindowUpdateLocker noUpdates(parent); - add_objects_list(parent, sizer); +// add_objects_list(parent, sizer); // add_collapsible_panes(parent, sizer); } @@ -952,6 +957,8 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl const wxArrayInt& ar = preset_sizer->GetColWidths(); m_label_width = ar.IsEmpty() ? 100 : ar.front()-4; optgroup->label_width = m_label_width; + + //Frequently changed parameters optgroup->m_on_change = [config](t_config_option_key opt_key, boost::any value){ TabPrint* tab_print = nullptr; for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { @@ -1067,17 +1074,20 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl m_optgroups.push_back(optgroup);// ogFrequentlyChangingParameters + // Object List + add_objects_list(parent, sizer); + // Frequently Object Settings optgroup = std::make_shared(parent, _(L("Object Settings")), config); optgroup->label_width = 100; optgroup->set_grid_vgap(5); - def.label = L("Name"); - def.type = coString; - def.tooltip = L("Object name"); - def.full_width = true; - def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; - optgroup->append_single_option_line(Option(def, "object_name")); +// def.label = L("Name"); +// def.type = coString; +// def.tooltip = L("Object name"); +// def.full_width = true; +// def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; +// optgroup->append_single_option_line(Option(def, "object_name")); optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL); optgroup->sidetext_width = 25; @@ -1096,7 +1106,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl def.default_value = new ConfigOptionBool{ false }; optgroup->append_single_option_line(Option(def, "place_on_bed")); - sizer->Add(optgroup->sizer, 0, wxEXPAND | wxLEFT, 20); + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20); m_optgroups.push_back(optgroup); // ogFrequentlyObjectSettings } @@ -1151,7 +1161,8 @@ void update_mode() ConfigMenuIDs mode = get_view_mode(); // show_frequently_changed_parameters(mode >= ConfigMenuModeRegular); - g_expert_mode_part_sizer->Show(mode == ConfigMenuModeExpert); +// g_expert_mode_part_sizer->Show(mode == ConfigMenuModeExpert); + g_object_list_sizer->Show(mode == ConfigMenuModeExpert); show_info_sizer(mode == ConfigMenuModeExpert); show_buttons(mode == ConfigMenuModeExpert); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 0a8d9072b4..8240fc7c1b 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -101,6 +101,7 @@ void set_objects_from_perl( wxWindow* parent, wxStaticBitmap *manifold_warning_icon); void set_show_print_info(bool show); void set_show_manifold_warning_icon(bool show); +void set_objects_list_sizer(wxBoxSizer *objects_list_sizer); AppConfig* get_app_config(); wxApp* get_app(); @@ -121,7 +122,7 @@ const wxFont& bold_font(); void open_model(wxWindow *parent, wxArrayString& input_files); wxWindow* get_right_panel(); -const int& label_width(); +const size_t& label_width(); extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 69738e7f83..682ed2f266 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -333,7 +333,9 @@ wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_pare void add_objects_list(wxWindow* parent, wxBoxSizer* sizer) { - sizer->Add(content_objects_list(parent), 1, wxEXPAND | wxALL, 0); + const auto ol_sizer = content_objects_list(parent); + sizer->Add(ol_sizer, 1, wxEXPAND | wxTOP, 20); + set_objects_list_sizer(ol_sizer); } void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer) From 60f703e7c7537bc813eae1c23f1a520bb72b6dc4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 4 Jul 2018 14:52:36 +0200 Subject: [PATCH 059/185] Added function for the sidetext changing + some code reorganization --- xs/src/slic3r/GUI/Field.cpp | 7 +- xs/src/slic3r/GUI/Field.hpp | 6 ++ xs/src/slic3r/GUI/GUI.cpp | 73 +------------------ xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 101 ++++++++++++++++++++++++-- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 1 + xs/src/slic3r/GUI/OptionsGroup.cpp | 1 + xs/src/slic3r/GUI/OptionsGroup.hpp | 8 ++ 7 files changed, 115 insertions(+), 82 deletions(-) diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index d4a351e74a..5e33ea7848 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -547,8 +547,11 @@ boost::any& Choice::get_value() // boost::any m_value; wxString ret_str = static_cast(window)->GetValue(); - if (m_opt_id == "support") - return m_value = boost::any(ret_str);//ret_str; + // options from right panel + std::vector right_panel_options{ "support", "scale_unit" }; + for (auto rp_option: right_panel_options) + if (m_opt_id == rp_option) + return m_value = boost::any(ret_str); if (m_opt.type != coEnum) /*m_value = */get_value_by_opt_type(ret_str); diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 04ee8c87b7..54dc596b55 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -188,6 +188,10 @@ public: return false; } + void set_side_text_ptr(wxStaticText* side_text) { + m_side_text = side_text; + } + protected: MyButton* m_Undo_btn = nullptr; // Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one. @@ -202,6 +206,8 @@ protected: // Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one. const wxColour* m_label_color = nullptr; + wxStaticText* m_side_text = nullptr; + // current value boost::any m_value; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 0262e20e5d..82cd21efef 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -909,47 +909,6 @@ void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, // add_collapsible_panes(parent, sizer); } -Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value = 0) -{ - Line line = { _(option_name), "" }; - ConfigOptionDef def; - - def.label = L("X"); - def.type = coInt; - def.default_value = new ConfigOptionInt(def_value); - def.sidetext = sidetext; - def.width = 70; - - const std::string lower_name = boost::algorithm::to_lower_copy(option_name); - - Option option = Option(def, lower_name + "_X"); - option.opt.full_width = true; - line.append_option(option); - - def.label = L("Y"); - option = Option(def, lower_name + "_Y"); - line.append_option(option); - - def.label = L("Z"); - option = Option(def, lower_name + "_Z"); - line.append_option(option); - - if (option_name == "Scale") - { - def.label = L("Units"); - def.type = coStrings; - def.gui_type = "select_open"; - def.enum_labels.push_back(L("%")); - def.enum_labels.push_back(L("mm")); - def.default_value = new ConfigOptionStrings{ "%" }; - def.sidetext = " "; - - option = Option(def, lower_name + "_unit"); - line.append_option(option); - } - return line; -} - void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer) { DynamicPrintConfig* config = &g_PresetBundle->prints.get_edited_preset().config; @@ -1078,37 +1037,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl add_objects_list(parent, sizer); // Frequently Object Settings - optgroup = std::make_shared(parent, _(L("Object Settings")), config); - optgroup->label_width = 100; - optgroup->set_grid_vgap(5); - -// def.label = L("Name"); -// def.type = coString; -// def.tooltip = L("Object name"); -// def.full_width = true; -// def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; -// optgroup->append_single_option_line(Option(def, "object_name")); - - optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL); - optgroup->sidetext_width = 25; - - optgroup->append_line(add_og_to_object_settings(L("Position"), L("mm"))); - optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°", 1)); - optgroup->append_line(add_og_to_object_settings(L("Scale"), "%", 2)); - - optgroup->set_flag(ogDEFAULT); - - def.label = L("Place on bed"); - def.type = coBool; - def.tooltip = L("Automatic placing of models on printing bed in Y axis"); - def.gui_type = ""; - def.sidetext = ""; - def.default_value = new ConfigOptionBool{ false }; - optgroup->append_single_option_line(Option(def, "place_on_bed")); - - sizer->Add(optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20); - - m_optgroups.push_back(optgroup); // ogFrequentlyObjectSettings + add_object_settings(parent, sizer); } void show_frequently_changed_parameters(bool show) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 682ed2f266..6909c274b1 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -65,7 +65,7 @@ void init_mesh_icons(){ bool is_parts_changed(){return m_parts_changed;} bool is_part_settings_changed(){ return m_part_settings_changed; } -static wxString dots("…", wxConvUTF8); +static wxString dots("…", wxConvUTF8); // ****** from GUI.cpp wxBoxSizer* content_objects_list(wxWindow *win) @@ -308,6 +308,98 @@ wxBoxSizer* content_settings(wxWindow *win) return sizer; } +void add_objects_list(wxWindow* parent, wxBoxSizer* sizer) +{ + const auto ol_sizer = content_objects_list(parent); + sizer->Add(ol_sizer, 1, wxEXPAND | wxTOP, 20); + set_objects_list_sizer(ol_sizer); +} + +Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value = 0) +{ + Line line = { _(option_name), "" }; + + ConfigOptionDef def; + def.type = coInt; + def.default_value = new ConfigOptionInt(def_value); + def.sidetext = sidetext; + def.width = 70; + + const std::string lower_name = boost::algorithm::to_lower_copy(option_name); + + std::vector axes{ "x", "y", "z" }; + for (auto axis : axes) { + def.label = boost::algorithm::to_upper_copy(axis); + Option option = Option(def, lower_name + "_" + axis); + option.opt.full_width = true; + line.append_option(option); + } + + if (option_name == "Scale") + { + def.label = L("Units"); + def.type = coStrings; + def.gui_type = "select_open"; + def.enum_labels.push_back(L("%")); + def.enum_labels.push_back(L("mm")); + def.default_value = new ConfigOptionStrings{ "%" }; + def.sidetext = " "; + + Option option = Option(def, lower_name + "_unit"); + line.append_option(option); + } + return line; +} + +void add_object_settings(wxWindow* parent, wxBoxSizer* sizer) +{ + auto optgroup = std::make_shared(parent, _(L("Object Settings"))); + optgroup->label_width = 100; + optgroup->set_grid_vgap(5); + + optgroup->m_on_change = [](t_config_option_key opt_key, boost::any value){ + if (opt_key == "scale_unit"){ + const wxString& selection = boost::any_cast(value); + std::vector axes{ "x", "y", "z" }; + for (auto axis : axes) { + std::string key = "scale_" + axis; + get_optgroup(ogFrequentlyObjectSettings)->set_side_text(key, selection); + } + } + }; + +// def.label = L("Name"); +// def.type = coString; +// def.tooltip = L("Object name"); +// def.full_width = true; +// def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; +// optgroup->append_single_option_line(Option(def, "object_name")); + + optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL); + optgroup->sidetext_width = 25; + + optgroup->append_line(add_og_to_object_settings(L("Position"), L("mm"))); + optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°", 1)); + optgroup->append_line(add_og_to_object_settings(L("Scale"), "%", 2)); + + optgroup->set_flag(ogDEFAULT); + + ConfigOptionDef def; + def.label = L("Place on bed"); + def.type = coBool; + def.tooltip = L("Automatic placing of models on printing bed in Y axis"); + def.gui_type = ""; + def.sidetext = ""; + def.default_value = new ConfigOptionBool{ false }; + optgroup->append_single_option_line(Option(def, "place_on_bed")); + + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20); + + optgroup->disable(); + + get_optgroups().push_back(optgroup); // ogFrequentlyObjectSettings +} + // add Collapsible Pane to sizer wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_parent, const wxString& name, std::function content_function) @@ -331,13 +423,6 @@ wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_pare return collpane; } -void add_objects_list(wxWindow* parent, wxBoxSizer* sizer) -{ - const auto ol_sizer = content_objects_list(parent); - sizer->Add(ol_sizer, 1, wxEXPAND | wxTOP, 20); - set_objects_list_sizer(ol_sizer); -} - void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer) { // *** Objects List *** diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index a0015c1467..8d54fa29bb 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -40,6 +40,7 @@ struct OBJECT_PARAMETERS void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer); void add_objects_list(wxWindow* parent, wxBoxSizer* sizer); +void add_object_settings(wxWindow* parent, wxBoxSizer* sizer); void show_collpane_settings(bool expert_mode); // Add object to the list diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 4778047be2..ca612e95cd 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -230,6 +230,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT); sidetext->SetFont(sidetext_font); sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, m_flag == ogSIDE_OPTIONS_VERTICAL ? 0 : 4); + field->set_side_text_ptr(sidetext); } // add side widget if any diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index cd604fc8ed..6697ed75a4 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -128,6 +128,14 @@ public: return out; } + bool set_side_text(const t_config_option_key& opt_key, const wxString& side_text) { + if (m_fields.find(opt_key) == m_fields.end()) return false; + auto st = m_fields.at(opt_key)->m_side_text; + if (!st) return false; + st->SetLabel(side_text); + return true; + } + inline void enable() { for (auto& field : m_fields) field.second->enable(); } inline void disable() { for (auto& field : m_fields) field.second->disable(); } void set_flag(ogDrawFlag flag) { m_flag = flag; } From 182e4232b2e170d832ae1c87350ac4c3c825b320 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 4 Jul 2018 16:32:01 +0200 Subject: [PATCH 060/185] Added error icon before object if errors auto-repaire was detected + Added size object updating (in the object setting panel) --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 36 +++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 6909c274b1..0cc5557fca 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -25,6 +25,7 @@ wxCollapsiblePane *m_collpane_settings = nullptr; wxIcon m_icon_modifiermesh; wxIcon m_icon_solidmesh; +wxIcon m_icon_manifold_warning; wxSlider* m_mover_x = nullptr; wxSlider* m_mover_y = nullptr; @@ -60,6 +61,7 @@ void set_event_remove_object(const int& event){ void init_mesh_icons(){ m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + m_icon_manifold_warning = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); } bool is_parts_changed(){return m_parts_changed;} @@ -449,9 +451,22 @@ void show_collpane_settings(bool expert_mode) void add_object_to_list(const std::string &name, ModelObject* model_object) { - wxString item = name; + wxString item_name = name; int scale = model_object->instances[0]->scaling_factor * 100; - m_objects_ctrl->Select(m_objects_model->Add(item, model_object->instances.size(), scale)); + auto item = m_objects_model->Add(item_name, model_object->instances.size(), scale); + m_objects_ctrl->Select(item); + + // Add error icon if detected auto-repaire + auto stats = model_object->volumes[0]->mesh.stl.stats; + int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + if (errors > 0) { + const wxDataViewIconText data(item_name, m_icon_manifold_warning); + wxVariant variant; + variant << data; + m_objects_model->SetValue(variant, item, 0); + } + // part_selection_changed(); #ifdef __WXMSW__ object_ctrl_selection_changed(); @@ -818,6 +833,21 @@ void parts_changed(int obj_idx) e.SetString(event_str); get_main_frame()->ProcessWindowEvent(e); } + +void update_settings_value() +{ + auto og = get_optgroup(ogFrequentlyObjectSettings); + if (m_selected_object_id < 0 || m_objects.size() <= m_selected_object_id) { + og->set_value("scale_x", 0); + og->set_value("scale_y", 0); + og->set_value("scale_z", 0); + return; + } + auto bb_size = m_objects[m_selected_object_id]->instance_bounding_box(0).size(); + og->set_value("scale_x", int(bb_size.x+0.5)); + og->set_value("scale_y", int(bb_size.y+0.5)); + og->set_value("scale_z", int(bb_size.z+0.5)); +} void part_selection_changed() { @@ -835,6 +865,8 @@ void part_selection_changed() } m_selected_object_id = obj_idx; + update_settings_value(); + /* wxWindowUpdateLocker noUpdates(get_right_panel()); m_move_options = Point3(0, 0, 0); From a6b1e8466d34336f903cdafdd642d556d3708c23 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 9 Jul 2018 16:42:31 +0200 Subject: [PATCH 061/185] Added popupmenu for add_settings --- xs/src/slic3r/GUI/GUI.hpp | 16 +++--- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 70 ++++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 8240fc7c1b..58a927cf6f 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -64,15 +64,15 @@ typedef std::map t_file_wild_card; inline t_file_wild_card& get_file_wild_card() { static t_file_wild_card FILE_WILDCARDS; if (FILE_WILDCARDS.empty()){ - FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA"; - FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL"; - FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ"; - FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML"; - FILE_WILDCARDS["3mf"] = "3MF files (*.3mf)|*.3mf;*.3MF;"; - FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA"; - FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI"; + FILE_WILDCARDS["known"] = "Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA"; + FILE_WILDCARDS["stl"] = "STL files (*.stl)|*.stl;*.STL"; + FILE_WILDCARDS["obj"] = "OBJ files (*.obj)|*.obj;*.OBJ"; + FILE_WILDCARDS["amf"] = "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML"; + FILE_WILDCARDS["3mf"] = "3MF files (*.3mf)|*.3mf;*.3MF;"; + FILE_WILDCARDS["prusa"] = "Prusa Control files (*.prusa)|*.prusa;*.PRUSA"; + FILE_WILDCARDS["ini"] = "INI files *.ini|*.ini;*.INI"; FILE_WILDCARDS["gcode"] = "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC"; - FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG"; + FILE_WILDCARDS["svg"] = "SVG files *.svg|*.svg;*.SVG"; } return FILE_WILDCARDS; } diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 0cc5557fca..759c43156f 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -48,6 +48,28 @@ int m_event_remove_object = 0; bool m_parts_changed = false; bool m_part_settings_changed = false; +// typedef std::map t_category_icon; +typedef std::map t_category_icon; +inline t_category_icon& get_category_icon() { + static t_category_icon CATEGORY_ICON; + if (CATEGORY_ICON.empty()){ + CATEGORY_ICON[L("Layers and Perimeters")] = wxBitmap(from_u8(Slic3r::var("layers.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Infill")] = wxBitmap(from_u8(Slic3r::var("infill.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Support material")] = wxBitmap(from_u8(Slic3r::var("building.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Speed")] = wxBitmap(from_u8(Slic3r::var("time.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Extruders")] = wxBitmap(from_u8(Slic3r::var("funnel.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Extrusion Width")] = wxBitmap(from_u8(Slic3r::var("funnel.png")), wxBITMAP_TYPE_PNG); +// CATEGORY_ICON[L("Skirt and brim")] = wxBitmap(from_u8(Slic3r::var("box.png")), wxBITMAP_TYPE_PNG); +// CATEGORY_ICON[L("Speed > Acceleration")] = wxBitmap(from_u8(Slic3r::var("time.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Advanced")] = wxBitmap(from_u8(Slic3r::var("wand.png")), wxBITMAP_TYPE_PNG); + } + return CATEGORY_ICON; +} + +// C++ class Slic3r::DynamicPrintConfig, initially empty. +std::shared_ptr default_config = std::make_shared(); +std::shared_ptr config = std::make_shared(); + void set_event_object_selection_changed(const int& event){ m_event_object_selection_changed = event; } @@ -560,7 +582,7 @@ void object_ctrl_selection_changed() } } -wxMenu *CreateAddPartPopupMenu(){ +wxMenu *create_add_part_popupmenu(){ wxMenu *menu = new wxMenu; wxWindowID config_id_base = wxWindow::NewControlId(3); @@ -590,6 +612,35 @@ wxMenu *CreateAddPartPopupMenu(){ return menu; } +wxMenu *create_add_settings_popupmenu() +{ + wxMenu *menu = new wxMenu; + + auto categories = get_category_icon(); + int category_cnt = categories.size(); + wxWindowID config_id_base = wxWindow::NewControlId(category_cnt); + + int inc = 0; + for (auto cat : categories) + { + auto menu_item = new wxMenuItem(menu, config_id_base + inc, _(cat.first)); + menu_item->SetBitmap(cat.second); + + auto sub_menu = new wxMenu; + sub_menu->AppendCheckItem(wxID_ANY, "Check#1"); + sub_menu->AppendCheckItem(wxID_ANY, "Check#2"); + sub_menu->AppendCheckItem(wxID_ANY, "Check#3"); + sub_menu->AppendCheckItem(wxID_ANY, "Check#4"); + + menu_item->SetSubMenu(sub_menu); + + menu->Append(menu_item); + inc++; + } + + return menu; +} + void object_ctrl_context_menu() { // auto cur_column = m_objects_ctrl->GetCurrentColumn(); @@ -600,15 +651,18 @@ void object_ctrl_context_menu() if (item) { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { - auto menu = CreateAddPartPopupMenu(); + auto menu = create_add_part_popupmenu(); + get_tab_panel()->GetPage(0)->PopupMenu(menu); + } + else { +// auto parent = m_objects_model->GetParent(item); +// // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene +// obj_idx = m_objects_model->GetIdByItem(parent); +// auto volume_id = m_objects_model->GetVolumeIdByItem(item); +// if (volume_id < 0) return; + auto menu = create_add_settings_popupmenu(); get_tab_panel()->GetPage(0)->PopupMenu(menu); -// wxMessageBox(m_objects_model->GetName(item)); } - // else { - // auto parent = m_objects_model->GetParent(item); - // // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene - // obj_idx = m_objects_model->GetIdByItem(parent); - // } } } } From f261c83653c2302cf5c626129ec7d8c67c08f8ec Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 23 Jul 2018 13:22:24 +0200 Subject: [PATCH 062/185] Placeholder icons for 3D scene toolbar --- resources/icons/add_disabled_36.png | Bin 0 -> 831 bytes resources/icons/add_hover_36.png | Bin 0 -> 956 bytes resources/icons/add_normal_36.png | Bin 0 -> 934 bytes resources/icons/add_pressed_36.png | Bin 0 -> 929 bytes resources/icons/arrow_out_disabled_36.png | Bin 0 -> 723 bytes resources/icons/arrow_out_hover_36.png | Bin 0 -> 826 bytes resources/icons/arrow_out_normal_36.png | Bin 0 -> 804 bytes resources/icons/arrow_out_pressed_36.png | Bin 0 -> 800 bytes .../arrow_rotate_anticlockwise_disabled_36.png | Bin 0 -> 691 bytes .../arrow_rotate_anticlockwise_hover_36.png | Bin 0 -> 799 bytes .../arrow_rotate_anticlockwise_normal_36.png | Bin 0 -> 780 bytes .../arrow_rotate_anticlockwise_pressed_36.png | Bin 0 -> 777 bytes .../icons/arrow_rotate_clockwise_disabled_36.png | Bin 0 -> 709 bytes .../icons/arrow_rotate_clockwise_hover_36.png | Bin 0 -> 796 bytes .../icons/arrow_rotate_clockwise_normal_36.png | Bin 0 -> 773 bytes .../icons/arrow_rotate_clockwise_pressed_36.png | Bin 0 -> 770 bytes resources/icons/brick_add_disabled_36.png | Bin 0 -> 947 bytes resources/icons/brick_add_hover_36.png | Bin 0 -> 1024 bytes resources/icons/brick_add_normal_36.png | Bin 0 -> 1004 bytes resources/icons/brick_add_pressed_36.png | Bin 0 -> 999 bytes resources/icons/brick_delete_disabled_36.png | Bin 0 -> 953 bytes resources/icons/brick_delete_hover_36.png | Bin 0 -> 1022 bytes resources/icons/brick_delete_normal_36.png | Bin 0 -> 1002 bytes resources/icons/brick_delete_pressed_36.png | Bin 0 -> 1000 bytes resources/icons/bricks_disabled_36.png | Bin 0 -> 880 bytes resources/icons/bricks_hover_36.png | Bin 0 -> 1013 bytes resources/icons/bricks_normal_36.png | Bin 0 -> 998 bytes resources/icons/bricks_pressed_36.png | Bin 0 -> 990 bytes resources/icons/cog_disabled_36.png | Bin 0 -> 942 bytes resources/icons/cog_hover_36.png | Bin 0 -> 1007 bytes resources/icons/cog_normal_36.png | Bin 0 -> 984 bytes resources/icons/cog_pressed_36.png | Bin 0 -> 981 bytes resources/icons/cross_disabled_36.png | Bin 0 -> 800 bytes resources/icons/cross_hover_36.png | Bin 0 -> 875 bytes resources/icons/cross_normal_36.png | Bin 0 -> 845 bytes resources/icons/cross_pressed_36.png | Bin 0 -> 843 bytes resources/icons/delete_disabled_36.png | Bin 0 -> 839 bytes resources/icons/delete_hover_36.png | Bin 0 -> 913 bytes resources/icons/delete_normal_36.png | Bin 0 -> 895 bytes resources/icons/delete_pressed_36.png | Bin 0 -> 893 bytes resources/icons/package_disabled_36.png | Bin 0 -> 935 bytes resources/icons/package_hover_36.png | Bin 0 -> 1038 bytes resources/icons/package_normal_36.png | Bin 0 -> 1021 bytes resources/icons/package_pressed_36.png | Bin 0 -> 1015 bytes resources/icons/shape_ungroup_disabled_36.png | Bin 0 -> 770 bytes resources/icons/shape_ungroup_hover_36.png | Bin 0 -> 873 bytes resources/icons/shape_ungroup_normal_36.png | Bin 0 -> 841 bytes resources/icons/shape_ungroup_pressed_36.png | Bin 0 -> 836 bytes .../icons/variable_layer_height_disabled_36.png | Bin 0 -> 450 bytes .../icons/variable_layer_height_hover_36.png | Bin 0 -> 473 bytes .../icons/variable_layer_height_normal_36.png | Bin 0 -> 443 bytes .../icons/variable_layer_height_pressed_36.png | Bin 0 -> 446 bytes 52 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/icons/add_disabled_36.png create mode 100644 resources/icons/add_hover_36.png create mode 100644 resources/icons/add_normal_36.png create mode 100644 resources/icons/add_pressed_36.png create mode 100644 resources/icons/arrow_out_disabled_36.png create mode 100644 resources/icons/arrow_out_hover_36.png create mode 100644 resources/icons/arrow_out_normal_36.png create mode 100644 resources/icons/arrow_out_pressed_36.png create mode 100644 resources/icons/arrow_rotate_anticlockwise_disabled_36.png create mode 100644 resources/icons/arrow_rotate_anticlockwise_hover_36.png create mode 100644 resources/icons/arrow_rotate_anticlockwise_normal_36.png create mode 100644 resources/icons/arrow_rotate_anticlockwise_pressed_36.png create mode 100644 resources/icons/arrow_rotate_clockwise_disabled_36.png create mode 100644 resources/icons/arrow_rotate_clockwise_hover_36.png create mode 100644 resources/icons/arrow_rotate_clockwise_normal_36.png create mode 100644 resources/icons/arrow_rotate_clockwise_pressed_36.png create mode 100644 resources/icons/brick_add_disabled_36.png create mode 100644 resources/icons/brick_add_hover_36.png create mode 100644 resources/icons/brick_add_normal_36.png create mode 100644 resources/icons/brick_add_pressed_36.png create mode 100644 resources/icons/brick_delete_disabled_36.png create mode 100644 resources/icons/brick_delete_hover_36.png create mode 100644 resources/icons/brick_delete_normal_36.png create mode 100644 resources/icons/brick_delete_pressed_36.png create mode 100644 resources/icons/bricks_disabled_36.png create mode 100644 resources/icons/bricks_hover_36.png create mode 100644 resources/icons/bricks_normal_36.png create mode 100644 resources/icons/bricks_pressed_36.png create mode 100644 resources/icons/cog_disabled_36.png create mode 100644 resources/icons/cog_hover_36.png create mode 100644 resources/icons/cog_normal_36.png create mode 100644 resources/icons/cog_pressed_36.png create mode 100644 resources/icons/cross_disabled_36.png create mode 100644 resources/icons/cross_hover_36.png create mode 100644 resources/icons/cross_normal_36.png create mode 100644 resources/icons/cross_pressed_36.png create mode 100644 resources/icons/delete_disabled_36.png create mode 100644 resources/icons/delete_hover_36.png create mode 100644 resources/icons/delete_normal_36.png create mode 100644 resources/icons/delete_pressed_36.png create mode 100644 resources/icons/package_disabled_36.png create mode 100644 resources/icons/package_hover_36.png create mode 100644 resources/icons/package_normal_36.png create mode 100644 resources/icons/package_pressed_36.png create mode 100644 resources/icons/shape_ungroup_disabled_36.png create mode 100644 resources/icons/shape_ungroup_hover_36.png create mode 100644 resources/icons/shape_ungroup_normal_36.png create mode 100644 resources/icons/shape_ungroup_pressed_36.png create mode 100644 resources/icons/variable_layer_height_disabled_36.png create mode 100644 resources/icons/variable_layer_height_hover_36.png create mode 100644 resources/icons/variable_layer_height_normal_36.png create mode 100644 resources/icons/variable_layer_height_pressed_36.png diff --git a/resources/icons/add_disabled_36.png b/resources/icons/add_disabled_36.png new file mode 100644 index 0000000000000000000000000000000000000000..bbeeef5a8d33f6ddcd2e57ca20bf804f3b5bfca4 GIT binary patch literal 831 zcmV-F1Hk-=P)9NH`o9G0?bK7 zK~z}7?U=nz>qr!UKbr(20}jCs4aNdFDG(h>LBU%+%2fCvzV z;g{c?8H4!UF7*;<3G}}M%KgbR9S({2$FOvvZ+^m;wwIR0&1IXgQ8pjNBV?RMGO*N=H_o^(lkX;6doTR+1=gE&d~PuHnwf^^76v|{yxoS zlO#!~)oSw;iK2*Un$+uc;yA`IjO>6UNy0D;8jS`~6j7;E==FN^dOgxKrC2PYC<^s@ zoo2I1wOVCkVA^ChBRaLpVx)S(~@i&CcPm*YZ9vJ`t002ov JPDHLkV1jNyflB}Y literal 0 HcmV?d00001 diff --git a/resources/icons/add_hover_36.png b/resources/icons/add_hover_36.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff71b73381dbe5a3beb69ab0a96af9933bc49af GIT binary patch literal 956 zcmV;t14I0YP)`R525d_M^xw3d(oQ?$VTEKb9!#cPO@G(-`;sTm zn|$A!SK#mHW9U>c!ZZRv?Gel;=RPJB3X#v}%L-yPoAG!&=)9o|(+Gk-hVJk3V={C+K-3FkK1$*hx6WlEYxjZnv#6F$854(xS~@&QE;w^>cj(bVW>(EBo0(MCZl zu$PH2w{eHS<~#$gmpJ=ym+>u&9&Qszd`EYCFN0@?k+n3-$whum{zA^AaN)^Onq5s? z-};K*$Ej>u$EfoKt4cLWRls$aXlQ|LCt5$relQUAOjr(+>QKfT+8G_3X53Jb~;Ew%kxg( zd-v5p9sLr3iQ5+mC%1UQ`7|+Q#yCTzQjx5hCS8zGYyNpc&`TNuI%=tCoFP%L5Ke4k zGuue!;!J%18reOW&tJWIYzDw2m~h)$+1U$X6B~>()MoP$2+q@5??qSh2&Ez?9VhNP zqm^__n!sthmmAA7^q%TB&d?(dbrRTHV18qk{`Mh$+`dIoD>8BWB6$hApmUJ0=qskdT-WZe2}cV&eQJGC`vv42B{sMg*ENZD|<#_g?!-``Y(hLBhs`$!pQ{u5NPfO}>2R z=AM%Sr)&vFo4L6;Jsb{`&*#euU^biadcBN~j|;~@pZfi}#cXC`Vxl5eW@l$P%;)*= z#toqs;NZy<{C+>{>+2N(a<{bbaAgGm6M(9!6@NQN45riesH;F#p#L3E?Z3UK6lEg0 zZFV!8WK@Zgrcoo*ap6oSK6?lDI!F0{Vuv9%Qj0V-dKmJ(gH^Oq&j}w03R<3z`ad}l|6=SL04DEW zC6e0V1?MZom3iX~l}bgjs*GHaP;36VL(oea0y=7`Xq+KYuna$JT?Ph5=?mPt?cdxv55`F8EUh25(qBQTJJ+w^9ZFPXB}rwoY6`;CQabH-N&7^ zdHT)`7-#62r@9F2FSE3@z(D&jzl82l)QU{rzlvT0(5W-k@J9O;?gefksI?4uhl~TV z*EtyST;}%nO;q&%C5&_fmOkS*NoHSG}9n*fLA&izIRj zl(ZsNv4OYFkMgSbHJWWL<*oKkYa_$HQHFh^)dsB!R0aC)0g0kmagZa1S^$^J#mvkM z=RBT@z(k@^TrStifz|Z%v>uPg%RUJaMG>dd$<)-8z-e561A$*iLO~so?f?J)07*qo IM6N<$f*g^yW&i*H literal 0 HcmV?d00001 diff --git a/resources/icons/add_pressed_36.png b/resources/icons/add_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..54272e42ce4482a1e85cb300cae6cdb9caa01bad GIT binary patch literal 929 zcmV;S177@zP)#)WouJXFpoXF>Mg*ENZE1kM?rm>r@5|MYurV={OG)g%I>~=>^5y$a z&f|v@7Q&;!!oq@?OeU#TtH%o9a5xAA0?f?J2!}wQT)SpE9S;0mU3IatlgV&Ut@6Ru ztAY(+e{ZjDFaZ9pF825K0B8nKRrN3#3Ww_gbw3&fz;@C$bpxmY)ByV50X0AF0*WS+ zs%)}dTBoE+=!QX)(8AeMA%dQMJT2bi1IipESSv*7Xzyk`_%<%Po0?H$x0EKjagW&M z5@Y_iIQQg$^(D(3B#7mIV6bnP@pF?%hRkZ=H~uL6PQ|8h;e}~B{Tu3r(Hg43?(ZYxZ2#D zZ4dENCPGC$Kv65qzVs3AJ@-C^YMxv%Lv&}6^UuCPS;(`JS+=fZ+og4SJNkK0Tw`C_ zM^YpT<@{kj72ig!mMP0wl2V+p{xN<{|6*Oqq*@WDy_JLR*6_=NY^#Ws*GHdP@B|$oong_0w$`iS!c*DI7#IbxE*c^l^nA_zDs#m z;>&lgADRKM2{yVtJ#6pBaogLhGt}!25sNR;(;CE7s|dQrY452ca}3?YW(b_|1i7^u zVfd+0>kK{pM3~s_GD{najP_0NbK(w~p)q^+5~dDd(qe1k^}dVTiQPa@n;8v^TLPy25ba{PEmnA>SZktDlw3GE09($yFB7 z4GovQjW^Fs@k-!TI^F;6d-d42z0cLoL~x3U;8f#*)&M#=Kz6&m?jVmCYyhGtl1iuR z1`~-y5JmB5!)kth-pppRM>-+LWya_8F*i3Sa1!Gma+O5pmX1Gq00000NkvXXu0mjf D(1^2n literal 0 HcmV?d00001 diff --git a/resources/icons/arrow_out_disabled_36.png b/resources/icons/arrow_out_disabled_36.png new file mode 100644 index 0000000000000000000000000000000000000000..0c9e5ef322ba9c47dd30b01babf5c5e19b93c795 GIT binary patch literal 723 zcmV;^0xbQBP)`dIt*xzzRORgKjJA~Q?d}>D!1L1++uPgR+}unAsQv!VbG;6L2@rWsnH)N~wV#nwpv-2m%_7 z2BvAUv9U3pBU@Nlz_KhdnGC+~qqQcJ$@J+K7Z?8|Hcgh7mjT$^+#LO1)_cLSSXx^8 zQWG_GJQ!o3&*Jv>_RGs$sZ^p+DE!lPcxa$fsnmm!QWAzC*Vos5y35N;q?9C+NzTvD zky3JUax!|LZnsOZSVT&R5CX$6Xf~Vu2~88NHPh462q7pG3gfd5&+{-%6W{k~Hk+)j zuJ-9xR#vcWo0pfDp8t)-IwJSBZ5!YB$>nk+5{ZG@TrS7!>npD7Vi?AFdMNU)TCGMH zhFn}+e5yS@J_g|K?(VO>Z0Je7zP>(m(tJK255^cM2KoSG+xEmkA_fb<^E^&ZPYJ$x zHxxy}aqv9v5#V*tX5g%nbYc`vyNTegpc$8m%^+@=5>z002ovPDHLk FV1lTCOBMhC literal 0 HcmV?d00001 diff --git a/resources/icons/arrow_out_hover_36.png b/resources/icons/arrow_out_hover_36.png new file mode 100644 index 0000000000000000000000000000000000000000..f62796c96fe010579c66d783c31e2d81b1bc5daf GIT binary patch literal 826 zcmV-A1I7G_P)?>2 zK~z}7?U-LkQ&AkpKX>kObDL?dO=s#;R%j1VTJ+j;FJV~FKlLG~&f_gO)Ee9b{pt;$^=D$sI-raM0SPC%}Bjn=W@k4Pl4svw%Ck)NNBq(&+p6h&4d-NkO!X&SY)wMj{*t*woj zNQAo2b+Lqgj*Q@TyYYIxNda=@=JKJd3jh;s?3@Nz3o0XLP&%V2qX5sP;_PG6Dr%Vm(r{~OTqwDyUvh?nVrs3%JOz246*V{ zCZ!}QqJ;G@PR+%e(5nS~)5a8@!TaQ-=P)@lfgX(_L?VEM_~UP4RmC zWt{Jk?*R_06Nkk~aC(gQ-#al9k&uv*gVtjVg@^D@`w@mfH{u&US9LL?N9Z5z$0jnk zT+)!3C9BS?A>Cx5BI`JTPyjWmvEN=E=R1&h2raq}n_N$M;b8!-?zpk?yKGJtla(sx z=_NfCg-27xm;zc-FMd3y!}oO2?@zvXX#CJXXf~8MpgRN2^DyJaC}9zzW8hhQ5A{7{ zR1cD6&7!5NiO_6_i|@`Spoa{>?XQiD8>8e{a!FIn42B2dx2 zBCx1l?4{@>f_gO+E%y>pptj&9c7~DY2!YuE!Vu_2V#DXFZsznDgW*B!B8%&#jmep^nrsgi zlaGr;*5fBzZs&hFX?jCXFyr5ut17Rbmf)drDXjkz>oDwYQnb ze1xl?t|Xy{48i>$O-veL@~rt}C}xJELy6l~F=GfpuFXNN-9dBr?c_AIFa)K!`_L4P zv1pL7sWHy&Nj%R^m7K+4+Cd-`Kru;nyNi)RZak171X6;u~@`sn}p^`8=2cT+d)y|wP2 zwjOCq7h@VI4YUkMRn?S(EHan@oK7cgZEfrkxhC-oDzX?%Q i6^Fw?LqmhYR@7e!tRt40kv&EL0000@e2R|03hfy1S;i?Yb-KDKoP}gce;pcL~FS%Fshlu~cAYP$ZFQVo(?n zL|{=}>{4`zux@D-T6&2n(A?D#TmS89uCw!A2T_n!QE*+M{opYJ?;H5>y?Nh*2b)aF z{7Xwqi}d^bL?V$51<*8&+}vDzKA*Axbi1iZrfM2qk0&9i4Cy*EkqDP-Y7`aV&&Wu^ zU;w-x4}V5R0I&cUhOscYv#2N`P#-%w08lsEPn`rx0wsasEz4Rzcslg}04e3%5R%Fa z-;&<@{w9Qw2q6&W+_Spi>iQ>Cy7d4hnfq6S0jTnxBLxe4-6fRf>_eDh{g+HiNmN7$ z8)4jQ=M01}d~V_i%b6Xd1k@mHPV?fB~$-xL2M zTy{4uo15VD7@vN3V#XsOAtgubCm9S65||DkOo3qT9C2jua%g#Uh(MtX2IzqFd_yOJPZ(M*GH%AGJ5N-W0VteTOV@8c2nf6SY zzcdh<4RPti#W?hkDY)~qo^f-OY@3G^#lk?iKX&^n7EB??vOCFgIBDp-5uc_Orl2@$ zH=3d`9M&1080Pej*z@do;VGQzHUhx_iYnReEkFvn{y?S>NC{~c8~UWqnZolptd1qU z6~&e4<2vbD1}Tb)kTM<#NxFx+=+%3O7_&6@+*;OK{r&<{NP7Bu>HhU?RSB)RscUy# zU$wXS;N4^~{wF|It2N;u^9(A0%jF_4IG8Y)_V#vMF4y7p&EM;Xlo!sR&9<0tF3pC*8Sp(Y>$GwR^W-AEDQ+ParPc34MXmC#bq8 zNU2r`w5Ap}Nt&6-T{RD&HZ7MP2xJxmADJ_ACI>bwjOEMC%}w_H{!Wr4g#v8b=IH2% zi;D{*2mQUi&MLM|v)NqBSLyY7#7V-R%S%H5d`zdbS}k5)Ue*GNqlk|%1fT?vrs>*q zXK5&HT2gO8ThJD?>JUGfJfF|`{QM+I5~P#}A#fZA&-01}Nht|}03igf>tb0JS(XvU zF<}@I1Odm#$Jn-A%#sB`K)GC|TCHLj23l*BQW%DT=Xvbx?C|vTR7?)d=W|TcWN&Ye zEX&YZGo4PEOeQF$$g&LAbumqo*Vot8gJ!cCySuwcDOoHQ3r4&l3)q|GHqP5PSWef%b&d$z?J%@x4 zL{UVVrtI(UGa8L}e0CckMk9v9Az>Jzl*02owAPHrW6sadi?3z%dYyW`o{w*DZ&6Bp8}sYc{->B<2mKVY zy`XJD|1Ze0thGay1_9uC9^G!2MZvwHWt43@|1roLR_%5>3&XJRPKae$IF7^B)s?{} Z#y{!A7}HWnNiqNc002ovPDHLkV1l&8Gj0F? literal 0 HcmV?d00001 diff --git a/resources/icons/arrow_rotate_anticlockwise_hover_36.png b/resources/icons/arrow_rotate_anticlockwise_hover_36.png new file mode 100644 index 0000000000000000000000000000000000000000..ebc68791b88397136b061fcf939cf62cfc26188e GIT binary patch literal 799 zcmV+)1K|9LP)0<1|y zK~z}7?bzK*Q*j)}@%PzwoJpE7mu^}Maw{r&l8_*K{2PKEs0@07$e@erPpDmqb)OMR z=$&9tXpeN2JB8(P2rAFK*yrx>}G5V z7^58nz%z@v(r}sa@p1C`yrU4W*GpYp9X6+JqaDMro3W#-qm+8Rw6wGoWOv%y+Q{Ye zoEthfvqQ;5f@m~KEEX#WC{RbuENDtkay#9FS>hp56=s3C9;G$uT$D+fj3AO$@?vLJ? z`x;8+ldN&CMcaz^$#*m^-paP>-7{aOT-47L?W~5wzC7)_UVT zT-w>{Y#%D8l5bPr$V_Lbt_*PZQy({muLB^KaHjX9v!D$X4SYy`z-_oO4A{0|I|=oL zv$swWs0tET668ep5$8age2q+}h4@4q(L!cALwzXB?k&5Btcb8Gypmub$l+`Iox3{r zh7U2B$};$NkZd-GD8bSS(H5cv5%pIE{nwqkqGS=L8_x3Z<2@b>-DCXg7$QP2P>WKE z(RZVC9PM)cE^7+ypeeND*Ym-v`%t3jJl5r04gIMl{0rv4gF26Q7k|)-pd#qEAk#Dp z4w*H00Q`PGt*x!p27?8`e4d!V@ApH3nW>fCjD2a2Q&Akp@%P%_M5nfIVb8Pb|N(xHt`DJ~HUWy8f zs4WX(7*0e9N-G==2cgOk>xv8M8t=3xLvKj9#1}8{S3u3F}6E* zptFjPu@BU5IKaWmqrcuxIk-R6i&zQ~K`gcUAY}=YH;t-v&S|;p)B1_JVeo)-e^E!f7}$3^=&^5VPtV*ZQvDEBE88 z@N?8Z(L!=DNo^p=(fvmVZ3(e0xD~(8&)Hii?b|wz2hT7cPciy_ zlvFB>D8bSS(H5cv5%o`j{^w3zTCkp`x@$cBG{}>&L8j-vAR_pE)hMNy{4hz^`5ycC zvW7q-4S~kx=hK}hP@?F*&|_bQ{?-y!g8A>D?u&Qx2edpW5Bej>G|iktmJBWcuh&ak zTN~AWe@-x8XJ+tvy}w^rwYIiq2Q&Akp@%P=FGf6Y%baz?{YAY$ql8_+n*WVEIgNmSE5EAsD{1dcCfq#HR z7?IBe1H<*hC?E7Yt;}UwnhD#2mUEl;d(L_w3O)qjNcMdEIkAe9z&)GAm<# z(AL(LotT&)lgZc$aJ$`9S69>2)MPAxTCZHm7P;M+K3`5&=ij0dOq0Q0GB;P#%;A<+=*~#Y^6%Uont;&bQe( zN)#o=Dz>hyV|&?7dqFdLnmdUuT!jUM%7d(MZ9{2IBAwt}Y=9TTPdT{mC?$oZ_WZJ* zL>I+H#cU`GVi-i~!)TYd@j&AvOg!;y3-AxOT+8t>aMeIMeYI zgC7P-rP7EJEUgf2AxaQYzZK}e?$o7)t7xjb$iq+f>3e&hiErbG2!68)r4*whqja3Q zW&d8*5ZFsYVDG~7vG&6#QQSCv%f1Z#swMmhX30P|&UEGvX#W|=<#Od5GH-AIc)eb} zOitzu6Ap**dcBJ;tXf)HvhjF)(M^b5E93EaXl`ycSdR4*Wd9bLG*rFa00000NkvXX Hu0mjfG{axz literal 0 HcmV?d00001 diff --git a/resources/icons/arrow_rotate_clockwise_disabled_36.png b/resources/icons/arrow_rotate_clockwise_disabled_36.png new file mode 100644 index 0000000000000000000000000000000000000000..0733c40051c015454798ba719560f6b58008ce73 GIT binary patch literal 709 zcmV;$0y_PPP);f4kYu-k?OnW}~QOK*7Qn5X9EbLNE`J)|*Gjwe>;l#M;#d5VTN4 z@N*$?H5OralKHq*7H)Mx$tFeq)l4z)os9aoJkP5-?!$Zpfc<**OI5^<`{(d4rX_D|B#{k#>!{Kn^ z-Wf62(`M=!&G;TRRIo#{@`274t2!WIm$8qp|pCAbG1(H${MG=nUP_0&RT^C~v zX_^wpF;Nt;wzh`ndHGyf6h#z^MJkmFmSv%}Mk$45Sp-2qsZ`?n`Z^yE^?E%Dg#zVr z8Dk7uYr5SoolXa(6vh~;)hdNTfyc+k@dJH)d@wgRhcN~r1h==h*tU)1INaRaAcQ~& zf$#e~JUoowLrIeG^z?+35-BCi%gd~+tYqHmbUM^(HC)%_>+5U$KpPty|LiX>FEgN# zmy9terM}fg{HP9vVThCxfUB!3?(XjJeIFqN{eGWPsgz$G3d4|vg$0ySoSmIfsZ`k9 z+yuZF!|UrS^?E%YpfC(6m&+_JE@F&fX=w?qHN)W$+qQXrekMs0*4NkbD~wV~wAN^? zv-D5Xl()Ax(lljzdpo~My|c5!>FFth!63UHr4;k?^VIA0Z$;+E9@^dA<>cf9DJA>+ z`_u8-@9phnSvEE8N2Af0HE1)S8PI=$T-TjA$cVuK2!ep~^K%9{&xS@SdS3Q1$OcyJ rcH6{pockoibzOYlr`2j%Or!n+>@geAPeKZ400000NkvXXu0mjfN{>rI literal 0 HcmV?d00001 diff --git a/resources/icons/arrow_rotate_clockwise_hover_36.png b/resources/icons/arrow_rotate_clockwise_hover_36.png new file mode 100644 index 0000000000000000000000000000000000000000..92aa3b7c86689060d977b938de03efae77dcdc28 GIT binary patch literal 796 zcmV+%1LOROP)>y6WwJoB9Lg}#EW9Xy9S8Zs8GSC#kSAgJr010lLPiCLx0=l^5psC?()COUt=p~ zBPMB#)`tdQ-^TP-_prRYOd^r66yk6=@caFkG@DusIvljLw4`Nqy1ToH zCla(ywQjD^>dFd%K!9K{m==)N<6&fU6aX85VHl)4+XfrEEE!M+lmVqW_MLn@6`vp& z8|Ozdj1-c5Z7)aisySA0+*;6viZT%CN6)rXRai-meiW$`k$8mZ)gbTYM!8USiTvz6 z)@<3!NI$vGTq+7G(X=dt6i6kp*=$r5SCNzFKYtGQ*uL@AvF@|ObaTy*8rI*3?L&TCBM3XT}+H;p7=> zK!za@LLj6-mV7B zj)vN^*575Fr+a96ei>m%?wx;P`Jo35@dN_@VwV7`PxxZQ48VY6#xHe%jAbcb*_Y4UgiH5OCA|#2A10GuC!Nnw>-O_{eq`r!D4AQ~`{FmG zkO*DUQq@Ycy9EG|7tAL3CP+zzE(E&LQOZE%g}HU; z=2#c{-%X!QK4$Ra3w+gH()l#2sa0H#D)Z`4ceD$y(}NI_&`1YfmybYmE6R}Al*ZKj zG=YXTb3omrozytJ9I9(ceZGQV zz9bU3-R`X)Ry{pEMlzW+{U=1zG+Zthot>Q)c4Pbo_3a!?w0i2%00000NkvXXu0mjf D1T0X& literal 0 HcmV?d00001 diff --git a/resources/icons/arrow_rotate_clockwise_pressed_36.png b/resources/icons/arrow_rotate_clockwise_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..043d1dfa2972a2554a52314fcf7929c73cd87a70 GIT binary patch literal 770 zcmV+d1O5DoP)0Vu_H6<@aLHp!-3!LClm@jK-cw+!2^wrc|pA$90b6+(~`OXDu4>00w~{M+lomhGNX*8NBEgbAcdsN zvYUgYM>t%5)LhV-O3|0>#a?Wq!BbC({SZlD)W` zE&}ECSS&>dDUeEHwOVPYY@npn$&;yv=H$@$?@?^_BKDN+MJS1qirM%qGfOiF@fTK8 zQG?-7y!rBa`#~R5!}wepQb|(TG|}mP+`12s)ad^ZC7oL%l}l6Y_48_OVEc0@kzMA; z!gr*Q2wl?bX`#v641mZ9=3<|zt*XQ6bP`*QZ9k~ZAKuizIdXjiq@+R@0$u4SWgv3G z+&Xk)xC8y)qEE*k(f8>&RTUbkT#A+C3T~&zygJl5)PYv+LkLND;1ZgjEJJLJEYT zP)Z^VMP@C-#QYS-FSs1MVqT@bb*zW0FGFN98H7|UEX*T>pwgvrA`qmt`Ly|WSIL3X=6?~rwa6~OED@--IA8zvf! z;`Mqre^_;Obs337V$)BESv%u)yJ>H4x3ClA4;G6U@bQ_!C;$Ke07*qoM6N<$f&w3B ANdN!< literal 0 HcmV?d00001 diff --git a/resources/icons/brick_add_disabled_36.png b/resources/icons/brick_add_disabled_36.png new file mode 100644 index 0000000000000000000000000000000000000000..5d9ddb12b0db539930132fd870b188c10545a002 GIT binary patch literal 947 zcmV;k15EshP)6>@@f=@pB}Ic$Vq}kQR^ulaAv~H8PRL-CY=7;OK|^FuSSg%HF4m?XrkC4 zhJ%8L3Q?p;4|_xt_l*4`nrzpk{hF^?Pv5M!YvqG)7DDG{dwbg{7K^l6t-b;%io)2~ z7#kZKf(!Kb@86E9D6Fike2FSYM@JZ~7JvQvCCC8PhX>Zz*U4luUjk$_n^Yek0T2P& z?e>?~PKQDKZbkhA^aJ##A^(wG4TD;(My*x@AQTD_3WZQrwf{h-X>xme%i7x7hv&Py zyJ(t*EX$t{WEckZdY$LzX8;BV2f4YqLDMt1u7K|D?pR%2 zb=Rd*iHnO1Ow$A)pU;y>Bp4YParf-)?J+$)-TR`hR;x&oL^hknwry+5T`LDuVaTCJArGRJY;cRvNFlR!}vVzC&_W)sJ8sMqTl zhJn}XMV4g*LBO&sY}@`EAj`5`>07N9mStg@CW0Vf+cu_YGB7YerBdPk{vN|Hcz=H< z9*?^&>?x3{s+^ynqiGsGpU+L$`Fh*7DVNKbrpfa1GU0HTbUKZ$>)5v4d)tqWjv|U8 zr>Cb}U0ng-^ZDFbv2B}NF2~f=6oMcSkH-O+o13FtF83Zt6h%Uz5Spf87zW43$K>;Q zyk0MXKmfnr&&$gTk|eRTv;;sZl_DCAcBN3)4M&z`VzC&(V310sLN=RaVPSzrqd}w5 zpj0ZkJ#J@b2U(W8vi*M^KU7s^Vq${H$w_j#91jl<%+Ah|PNxw?k+ZWiBuV1(@{&X% z(Vy`HAP@);2m}C_pP%RC7eB*NzAroeZMe*pbi VcGTxd?^^%>002ovPDHLkV1i{Ow+#RQ literal 0 HcmV?d00001 diff --git a/resources/icons/brick_add_hover_36.png b/resources/icons/brick_add_hover_36.png new file mode 100644 index 0000000000000000000000000000000000000000..961f1c39578694597abf19fc90c0505f828f3a04 GIT binary patch literal 1024 zcmV+b1poVqP)U zK~z}7?U>C=Tvr%}pL6Hl`MB-qI3H@_hzqAWZA@*k5-GHhmTU^mMwBcq*)_yXAkCkU zx)NKu=%%g}DJ8T>HzgoRrPHD_j-w+33Oa-1jPyq5i~H4ub->U~l2HTow>yV(IM4a< zp7*@Gf`4U?VRMcW(+B|h80Oo7Z`j`6rdF$+DTvGEA{-85bJrHr2w{(5PrNgM+vQ?> zeEeM8nVOoSTB~t?_5P7Vg}pr{CMHNE66XSRslA;)VleM*N zl}gdu+ly`6q*5u|Za2wf@}(VnA)u|TEk;I0PK;x*SUstw(`g0=2kGkSs;`-yoh2HL zHa@6#cXyFxnbp-*EX%^OEONOV9*>83JWf0wM^#mFxg4fxVi*RAL;_is$z(E(cSzGT z`uh4Pl}aoxFXM1H*xA`(czF24y1BVIR8>XSbxNfYKA(?FCPR03cVmHs5Oj2OP%f7_ zJUm1QL2GL(8yg$-!!nr+ZEbBholf%kd_9gNNou@9wtZB;x!rCQMd9G!07X&IG>w&& zmHG#{zrRnlTCKk_+qUa_PX)-fZ7j>eRmI_OU|ClE_LgN4&pst*$NA*rA-=qIhxb2-5G(#jeE)IdfsS*_!omVe zOG^kLXliOgmXAy}O_SKxZ@d@n$COO^FWtZtIzv}KB$0pGcpyoV2nK`n^z=|Fm6)HO zC!J2?^?LF9{j{{SPZg;b0Te|c91i36`^jdrB$G)7 z1_sbHjlU1}NoJq0oL%6-jURaU>le5kE@X$&c!&NgrXrCD(P)(Q^>wn@EFZn~8NbJ$ z;6&z8;yau|W?|_u*RR|>oALv|=kww7`2gsrpSeXRKmGEEn*K8`#l!V0pEG>z_SpeF zui9LNNjffHJ{OpsY?e?c1bZBHt?V)EucHqs u7K>-z2~iYeg@tW)k)B-u0000C=Tvr&zf9KA<^KskJaX!?<5f@H%N=$9B5-GHhmTU^mMwD(#b}hOIr2Yxk zl_+%4P4|kF5?Z925|E_QY0(+Si6a9FdIu*n(i@#G?pGGY0Yf)Q#t*2^>fFmY+~0k8 z&U;=CTr!E1!TkKZv$wZLywR)Xz zA3P8;K=Ii#W@ctcrBW9Hbgi?KKNAT6Bml!OE<8IY4ANx_^%c++(3?trlf~6)l~Soh zu~-Bk91ar-h4A@&=MQ9>CObPjjE|3>dcUx+Kr9wRQIxX>dfAcI)>bl^4E_E6IF3Um zlfmotl1`^z+p$*y+TGn{Y;5e*b0U#w6t!$N%kc0pJv}{*Ig5*n#N+Yihx*{)09lsV z+}y;rZEV{npU>m-`A8;{B$G*0RVAO#V_6oaX_88%kY$-%F4ugAbX{j)V1RPD%=-E| zE|-h_{e4D9M^DXLT3SL?RSd(RTrLv`1jyxb^!D~P7f1*}S63I6N`>R&V}ua2x3{ys zz1{d&E|;UDqXW0wO`%X|_%KGI^b^U$N6iO1sVyrjE3B=pA%vi%r3G33 z*JR7GNbLU3`|&|6$zt%@9V}rma`Ping(uAik|c?6I80w(ALVkH<>h6v*(`p)pI|UZ zTU#5&=Laa_ZSKB10l@vwexSGgJxt3un@SC!C<@VNlwdH3rfHL)4zR%*X2QWDb082zcLkz#fZn_Y;A3!X&N8D^ErPcALB;mVd{I_LS|*{ z5w~yLJ)iaiAP@);2m}BaWRRs*H$VUSkh<{;9>vG)8(%Pb>)!bRy{yq&_g)F$s)fA* z`hNhuZbRNooiNA%kw}Eu*;%@-U%wETeN7`0iJZQ$nx39^N~O}d--IZNLMRksYHCVw a8TA5JZg^U03R89f00006;V^9}dHBGH3sa4ZQ(LTz6k3>;*%Ue(QMxU&YjIVW`X|`g z3SD&5y&|OyEmDv&5VV3V8jTqv0R_DoVOkK-dE$6I9(uaFuf>(zix*UDHNJlQ zSV#bc)6;8%0qE)Orf_-+fDB+5M!hpWJbW!st7~fjNH;C0Z-H)sZh@LDxe<$0Diw;w zB85T$fN(fWC=|ly^R*txG))c;4w#;vzVd!yVS!jIhN39V2YS_!_V#wt=`=$_LpY8@ zI-SPr^^!`ZUfZ$%1ax?K$kf!-mG;KQMm+^(G8x9l$La6yua8+=TqGWkH$JFOPEL?z zncdx8Y}>}RZF0FBKA(?dGD$L-L{(LCxg3^dVVWk}+uO*pOg5WsyhFOKGcqzlsZ?TX zYYUgl#qsemlarHI#w{%^p{go|VNfcS2m}ITvsnfP1{w<_grK*#mvXtx#l;0e2s%4E z+27xAnkJGY z;W!S4VPIJnhG8Itz_x8n)5PQP@I3!J&+~t9QGH3ZR^e{%1aBDKjX!0os^x1YdmiCGWl$W25*3$+KsT2fF;XBoYZWH#ZSN(AL(5 zEdOn`Wm#+-{>nS?Q7p+~wC4eqFqpXeKHK@V#sf)`L^vE~aBz@Psl@W~GMP*Uzu!+V z7^I`4gTl)b6!9hxhi3rz>f`Si=zJT~GMbmm6h$E#jS>t7(KL-zD#h5?7`m?W=lK~a zZIvx8!R&+Ynfv8)ye<#2OKH4A{}fZPSd4f)&feZ0nx^r=Tc7Y-auqi+Pq)9tEo2g# z&$xf*VQb0{fIuKXAP@jxlu?$}-Te6TQ)t2GF=$Qf1_hFH0CBzD^j5#L_82W?M&(#x`|HTuS@8APrHV0qKF`Zprfz| z6Srv_=GEM!Ig_*bjvDAYi&w3K{wFhQSc~;rf1YQahXvnE!u!kq{=VI4G|+W@ssNHC zvAn#@&d!eD0R8*>w=GK&8yg#6;>y|C8Je#1&#zyC2+;iVhwbfcipAoW0BOS^%~lHl z7rF70+30I%0erBWf0NB~eSmysmN`N0N*LHvIIryTnf(8I$6 zo12@?`0??Po0}U9!vNs!?v7L{#p2?kGw0~&h?SL<@dtIY*~IO3Q!baWEDOuBXf~V3 zvP_{+pin3v%QDSo6U(wNO_O4=h}-R^Uaybep?<&5>gp=P;gCY1fZOdxRaLUt?1y>B z$H&OB%xE-1(=_Jh=4dn;#A31W0ttdZBoaZ>G+tj{5d?wx`FU!!nzLEGUMCa^A&Mf; z&(BWgxm>RCJ7n9o)4zzK$Y3yVo~2UBDUjW67hTt#EVFIfSvwJ+_YEXTA{vb{91gK< zn{Ky@rfJO1&LWB;f*@d;CYEJQ2FNr`$9i4YF-;T0Fc1U*%d#*GgPEBb%H=YpQi*oE zjiM;5t*s#l!uWw?S?22MibNuTq9|yZ#@pMQb9&3Ns8*|VIvujvEDH+@RI62Txf~vk zXZ&fuw6uiF<>K=4lH1!`02D=WV#Ts7^7%ZOOa@g|IXOAu>FJ3`B!a4{<9EpAauEmw z@Or(pS}o4c&xyri#N%;fS!OsK((m^P27|1xuRH4w4i0#Ed6`@-6Gf3|G>YHvM^#nI zNaXz0Xm&?)X_3(PVq|<3KnauQn{;y;*Niv!IX<>hW zet{hEa^_vin$3rL-Vt02} b;5+I+zw3FB^5@cY00000NkvXXu0mjfv_Q#& literal 0 HcmV?d00001 diff --git a/resources/icons/brick_delete_hover_36.png b/resources/icons/brick_delete_hover_36.png new file mode 100644 index 0000000000000000000000000000000000000000..eb1687858bc32a34fd3a78ee94035fd704400406 GIT binary patch literal 1022 zcmVC^V^tW3pL2UJZSP3i=?5@F8Jy4p8A)bANX%pwEL^!1Y19=9SCb427o$s; z%8HfP!PAUADv9vwSW+}~$nVuDmEbtynMIy!i|v;=?xV45bEtrG@?=S){XS3u7y z^;v>zwHl>TiDIz`KsX#G6bj+>dM_TxvMe??H%TNCr@qh3%n*yk&@}D*f&S@;-|r`z z&C=J`hm?|RHjBsOA)QX2*|Glww6(Rx=;-LF@zT;#<562*UuS4&i0ZB*|nFUDwIy^VqhHWm%+BDO6P@m&-NZp>ny* zz`y|2YL%6h6=|@X)cfug3iuP48!2? z@DL#cZEbCAY-}_Z%jI&kx3|;M(!$=}UgI1oiqd?Cq&%+QJRT34rctR>&@_#5xy1SB^pK-2-5HG)udF@W~fplGG zadDA&JdV%j!!Qi$^?GCXj^m&#KgPH79ldWozbcLyDph4u|pk{S*oX z=I7^$$K&+&_M+=LhGFpAuLV3Ex8c=y{$Aky8&E2qUoO)$jc7DVFc@Tacb9ZJ&EVi5 z<#L%nUhrcqKjzvO52)9E!2l}1yO7r-=bv14T_+Zc5e|o0TU*1nZEoFvgY@@b(-rih zx-=^L7Mpu6^x?$Clpg?rK!89Xa6GtsmwIb}%#&#-N@TDDeR!1CSKhujpnv`N-AmAR zFLCDhs}lAK=>G|H)`Gm5I$=-&B9RD_laqAbym=`wJG;9?A`#f}C#56)YZ!Nt)0U88;420vPO#lD@07*qoM6N<$f~+9iivR!s literal 0 HcmV?d00001 diff --git a/resources/icons/brick_delete_normal_36.png b/resources/icons/brick_delete_normal_36.png new file mode 100644 index 0000000000000000000000000000000000000000..b648e0ede08ca9a9d94dc6d8ac28c0cb32334f87 GIT binary patch literal 1002 zcmVI`V^tK#Klk-L+P;zYO@F}*WpF|ZL`h~rNX%pwEL^!1Y1Fl=Nnqh(Wa$#S zBJdCB$^?QYnn{S7iHpWah8Som4UifzKrOUrEq$fFUoIxppevd3BZjlO_vW75&;91y zd(ORZ%}ks&W@l%e-Q8UpjmD({xZQ4|(J15Nr z@bawK5c^Lo+z%Yy}kDXHn>AD5=4bTnH%bNT$&h>hoa=A>Y zR01Fr3K0wj@p`?N4`iAq+uPeD5{Wb8b8~aVVlfm&xp<&|D&qJ1$>nnN_xIyC4!K+o zkHlt3wz;{<@bEA_Jw44i^Yilz3=FhBsEfrSvMjT{ zzK(6%*tSifP{8Z;l1`^dr_-paN}*7|vMfx~B$LS?%QE?VzV!-KDisC?2dUL+tgf!& za=F;s+hcTe^vt}4g#}bq#V`zNwHlhHk)5tUsZ^p~uhZV%jx5UvA+Rh9%d##8$TUqPNy2d) z48y>(EDXay2!U^;<0ko}Wtyf5rBtoGOe!rh$vB=`$BJp^fzP>(G zRYlize*dkAr}I9%_2J(Oe0&GWrHjjDilPvWMhOH0?CY>T@feUpc zaXIA&fTn3^n)age-~o+xjrC`rLdn4ibfS)o(f;NKmk0E(hu^~lk%x(M``?tXH$eYS zpz{{wt+Z1H86X@EGd(>`*WJ5U0<*WjPdFSt`(ZUTHRY7cFv#TOq~JQ* YU+CU&`9iR8mjD0&07*qoM6N<$f}|(sjsO4v literal 0 HcmV?d00001 diff --git a/resources/icons/brick_delete_pressed_36.png b/resources/icons/brick_delete_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..e6fd3ab74e04e8c8fdce82cf8a817ce3ff6558ec GIT binary patch literal 1000 zcmV>P)I`V^tK#Klk-L+P;zYO@BZMWpF|ZjFQZPkeJCVSh#X2(x_`!ldy3yvUG{F zU18dS31{{0d-vY+x!;`o z&bb$^nTd;s+1XiVe}A7^t=3imx7$rL8f9u~O4NZqd-BBbxZU(bB3GhH?&yf~T8&R0 zKNb=|;pF7XU;ug|5eg?K0LTD_Vbnhk-@A7uP|shy06@BKL45;s19St_YLlBWNwr#~ zSS(T~6aWZ?LIi_Byk2knflSk6cXyXWBGK@EZf=fPEQX>etq1z2B7VQ0OeVw7&=8K} zkjZ55cs!)j=}Rm2pMduE_L!WUY-q2ntkhG`_VzYoV`KF9_t*Q(&(AYFJly=C&gb*U zvdre@Cbn&3+cvpe4zJfsDwQIYN};MMxm*s*vM^1P_4RdRStgs!HeaDqsl>?02$f2O zwY4=|E*A#}2TV*%H1u0oSU^=(48x#Osi0{Z*=&}9fq~`%2_fk1?M2sh&d$ydLeSOK z#m>%7-B~u9rMtVEj*bqFkB{qdBuP^96>=O00FTFmq9~NhWfVoBR4TEtvB5<-b9#Ep z`T2Q$mpP78AKMZj$8oT28?V<(Utb@(u4CIag+hU9wMu7aC$cOfgut>aEX!&QkZGDo zl7!0pmSr~?a5CYq_F-;S<+l~0;TdbeI!p!}KeLIMF>phHHZ!{lBRaKUkmx;&Y z_5+8pTPxeQ|7aySh`ZcfL383vfYraB~BoPXQ@caGb^LZ8*7m3H? z3=R&Wsw%p!^T+RbJdwNb&c}Z{`m_g%h1TUVMNx=GqXYs04i67Wr_+p%j#4U>`13VC z`s!0|ee;M~^#~m(zjWc;4!1tJs;WvX79$i2v9+~@Wm(*L_&({MzNatXMRqBaPfT`? zU8v)U_LLt0nx>&?+N;L>`_wu$HlKYC1qUY(K^>o@^X-q?2lTHa@`D6j4-%IicT>Xt zKY=b=X12mE7$ksjILyJ};g!KGEiDlahZ`@fW@cubVzJn86Vfhag25ou)6;_MXnz56 Wt!sT0vpPrl~+7>Le!4_k?=t58zZloYC-1r^*5^nGK5uCgA+qe|Bf>?@B zq!pYcj3*QR`1UO$0e4$2ha27W(?Kc4&dv@1&(F^|j)PJPAp}X1%m=95ZX=~6 zj$?!n2qDPja@e+w<2by$y!@fcm#DV0h*Jw1^m2?qxUAM211f;f&*N+G4hvMe?>HZV;S-}iZV zc)<64;y5Ok%TXv4sMTs1hC#R6{dk9xB*F7Mlu{%~LLA2=NkSNg93LOk>2!E|d!tsX z&8p3HUBWO#*Y%GF+TPx#)oLMxz_x7+!$3-jrfC?4LA6>X2m%I!0YMNDMG=`yW`3Qz zP$^n*D*~KDJ4-9F&d4~b)Cb*!}$YERV)@c zIXS^~UGDGi5kkzI+TGoyUav1^+W%9wZDZT^^2N9WS_1tm(0gXhyfkHy0G4HOeSJ;1 z;N8%y$(kmXW&OIay12MV2ZO=FHzB&Nv%bF0`T2Q;=!aypSsceXP)H~gqNk?^%{y95BZMA9PoyUZheC{xk2j<` zQ&Uq^9fxzb&ebZknaeRTF~Rckazj9gt}d=EEC3(@JkO)i*)vEUupEL8LH|3D`CmWO z%17PH;}>>F%v?a1wreXO+QRzj(^xGJVYIa&4D(<>YHpTCvUkx=l|ToCj`oA^Hoqh~v7UeNV18Nb#&zuG7Gi@VU|SS_zC$sa0ox)rFpM2-Cc5x7eqp;l74ie0 zd@)ajRpE*7G#n$H9O7hZl=)YNSv5R1zr09wH(eEj%XF0N^2u4eYX zhlG&$zK82Ogsc#;REn9WHVH4C;)V4qbYHoQ3N(L2BFyzY$FE(3oH*8jf3M79#^cO> zgJdAsDE^7k3Z;A;vzf2H&teMk_ap{E9al$xa)AEi09Ch&X_%bPtZ_GA zLL&&2qUr@GrLimntpiHMDqf{h|6LZo-xqH@-N7eUZ%_zY7)d9HMQm)#q~f>~w|8l? zO1$>W(fWh-l8m?V_DjRu%4~4q+A6NqiXSK}x5$~1Bb-Y2AI!G@XVSeLq;_*0>laq8OlMTWAzP?U89)}#Y(@u|}KNvXA*4Ea6zl2zpMSFWY j@2P?Md%1h}3ZKvfmCq00000NkvXXu0mjf2)fPN literal 0 HcmV?d00001 diff --git a/resources/icons/bricks_normal_36.png b/resources/icons/bricks_normal_36.png new file mode 100644 index 0000000000000000000000000000000000000000..c94708d13344efcd30f8426533cfa91277aa9b4d GIT binary patch literal 998 zcmV3$gbxF)?AX*(|Q>9w;Cb3enNg!QkMacnJDnbku}HA%=#A4kat&J25@Ca5@6T-3&2BfE_cr1G#<5mbvKE{8+-@Q#?VGiw;sS}ct@7D%2vwRIVTEO+H2umQOL^U=c z(!K0_H%}?E1j67KcaTaVg#|*^uFF=+KTzk?ak}*swp0jfcXKgFB4_%rJ5u<$98yWd z-d1CR+JUOZ<%y1SY!-9)MpKnG`KgZqfe8Z88e`0EW&m#|Pf1tV@N9e?>_5m77SV8w zbh3}LsdLP{*3XjVvGMs;DvNiqBXwA|gXg*ww>P*KZREozFL8CnFnKex|1~6p#P>a_ zzDr1jh^10YKC?l1{yZji#k>*dU;7W@Y#<}w}^_ZuVw!CK)D zw9#no<7$t<1k8&BXWxFGsl{K(=3LTU38DZ$uCbh1r{a28PI&(_^lInpd^-0D<#36< zR3Fh$6x(u;QVOE=A&|C(lr?Dk)<@g8^wgUyuP!mOcnuK^ zFeX4C@pV8|2Smb($6M?9;@d2?5Dz;s2x>Swx|2P0Cwr(=E7+FJNM?omxgrKZpfwdQ zKx>0i7RCe=3l+R_x%RUx{8wGP^=u0t-Mm9SXkZ|nAQo|Olug;KQrOy|NfmkHxs$aA z?L{))$h)uf^L=KGE4P-Ysz&@kqpAfi4xHe8y8B?J{XZk!*+ROr<#=N}1|5U`6{M6p zbjY5;28hSwjE#-ao=hGJ=C{>V;_>+3AFM`3M$G2s=7GP2D5Yp_Zf1CRSa1~MPglKT U-QrSKtpET307*qoM6N<$g3kBQr~m)} literal 0 HcmV?d00001 diff --git a/resources/icons/bricks_pressed_36.png b/resources/icons/bricks_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..51c7c201c104f4e04f349533a48b451bc512633d GIT binary patch literal 990 zcmV<410np0P)HFe0|Q5bnwy^oKpwZEJ^`J8PC$Oojp-P$(5BR=4@4 z@CEPHYRKMRD&KyM@D-sLfzcY2!Wgrg8NlBuQc-pCzD=O?!GkPe5e>&k zcMWkqb%EJ8hFP|J@?YMdwsaRe(u8HZ_?}03JI}pnD<407g&V7e$+^tI*N_mBAn>UN z9w8@0ER|yNxjf;;i@dUSi_^DeP};B&i7?&!5`QEH893WPaKFMr#^>@ugQOO0mi|T= zg;D{Y^3g`KD3Y9i_d}+Z{v=!QNcSd*0{ptkN+w6m^Re9U!Ds08o;Ue?;WMh?3PY(O zqM<0ZMT%SgEt)`Pf1rZ3`(I(Dt2=C%E#=+pMfDGrM#X5!D!@ z5l8~1sVhw+?C@k~Ghh9X#TH_}6SZ#O=;-U}r?0D@TD^vC*^FmaSud0^2((hve2r2D z$FVR*Q!dr;tJTKOvhcrk@y_!dd@^^3qHbX%og@}6oTQ8hxJZLYv z;;p>@+Au$7Ho5-YGIghwKr5VjiOVCWxR~xcoN52xNcVJ*?&&z$7#}f^<2XkS*)vFh zcs$NuYimb_nVFd(9*;l#U^PBIZnn0z9(oBmtjx5xw=*_2COD4q4&~|L-lkToeF=~=9Mb7_ z0Z;*a-~V#&yfUcY&D1|YKR|yQ+TR`^guwTGq?D+tieVU^uaJ~-^mv{}Hk&1z&GPv8 zh?J5r4BuT!wD-UWArL~K>pDURJkLW)iKb}?A!xN)xUNe)9w(Q}jZYydC8lXIGc&{F z z-}mu6k7l#U#l;14b93VZ(lm`|G>YrGc%H{_IHce2(`vQQG>t-`KqL|&2m&0(p`2tgQz z0GOspBoZ0D6NVu|h(E5r<2X2u^YNeT`T3cfn;YWsI1394xUNeklcCXQpePDbO78CN zux%UP_fb`q%jI}@ct8k&ZQF!li064Yj>F>O zA}cE^o8?M!9XG#Y&u%!kZFrBdn5?cryH{Q>&_0qMH_pH1airw8^h3^>u0S-oo;NM3x QZ~y=R07*qoM6N<$g4bZTmjD0& literal 0 HcmV?d00001 diff --git a/resources/icons/cog_hover_36.png b/resources/icons/cog_hover_36.png new file mode 100644 index 0000000000000000000000000000000000000000..0626cd123e8beb1ede75e2526aa256e6e3d60892 GIT binary patch literal 1007 zcmV|qBwyF1sLF7?w_;8tZui(j>r`Ll9Qb3__$OimK@TGx zAOR@PFn|2?JEc+y(=^`{MAI~4u^57%g>--O%KqDVMaQ@{B05X7OS@b&33^G4h-htkMzBkJE9^g0*olXbWb@6yS z_M{k!6`e zq0l`;j^j`+mjN(MlV~)GEXyxdHl0pmnkJ@cQY;oZJv}9zPInJTQ4};y!!QgC!=Tx0 zQm@y!ySqbGRT7B=ilSgy7Ohr`OeVwh^mKQC+U+*BZDZRu!C(+cl6ZQ0BA?Hr>pHS5 zb9HrvuIosWL?jX+pU+b&mGF2xWV6}VeM6RIkxV8Dhr`tCbtFlmQmHUAGsDQp2+?R1 zUDv5pD#)@-tyW`UVS%-^HS+oV>pyDCvM@~(+qQ8W2O$Lg{rxD)zmi?o#kOr+*L|r^ z&1REUtM&Rbaa$Lp@1Yw2qCz>z9yMW(&==NWtpR+BRZW9 ze!u_qfplF**LAAZDy>!v+qMxxFg7;E{rx?Tf=7Z=># z-XerxWn~4|bvZmdL{(Mh=jREB!wd}#(Qdc9XDAwtQYw|$+uP&p?2OgbRa8|)(==37 zMF_$A`Z^~kCv0zT=OjRwtT6T>hF1OjYsZc?w; zi9{lA(|&|Pp_l#C)D*|Z$5@ud($W%=B%x{A|AKjyekhenkxHfBTiAD?ccA|SQdPC* zAkPd6KsX#`cXyZ3j~{yi^YHa6;cyrp`8Kr@8;H-T->B7UZ$1f8Rh8l4VSf4RSEK_Z d=|>ys{S7#>mEgkwvmpQg002ovPDHLkV1k?e$jATy literal 0 HcmV?d00001 diff --git a/resources/icons/cog_normal_36.png b/resources/icons/cog_normal_36.png new file mode 100644 index 0000000000000000000000000000000000000000..f9bff51d997342db4f221ba8f64c9f248d1c3492 GIT binary patch literal 984 zcmV;}11J26P) zK~z}7?U+ALB3l^7e>2Q52(m%Vz`CM{k`auBAx0ac#1CNQhp^FduZ_Le%0J)1#!p~j zV`btthQbgfisA;cP=Enum_KJLy}8XT?uuIYCMS8TlV6_my&8&394d;NSq$viR%!cS!-L{rbi3?k@R!{#}5E zzkcQ7#}5Ex0Nb|T-8;_=@<%iEC(tL*pO*5c7r3rVx7)?@JbXSMfk5Ex6%s%kwu~_V#LayslsZ;>4 zEQ?qyhAhi3DVxb;uq+G9vM7~GoSmJK$z*y5q$moSreT^UrfJe@wP-XN+~41$sw&B3 z5=Bw4ZJTzxO*WflYHF%CK%Gtp$8m5Rhj2KIBuP9yJy9qWFbo4(mbt#Z#xM*dNg^7J zQYaKCm&^EkK61I->w3txZBnTex~|h`G>{~TYPHJr^fbf6!^C1S48x#Wts=`Z^?IH8 z`FU1XS1A+(9fegdIFbrz7 z8trx)$8iusFg7;E!@~ov>yk>Pa9tP6vS_th%+AiTu&~g(9IDl7R4NrBkqC;S;P?9p z27@FL3C73A357yvnnoZHAel^(&*!2!LhP%IXsTrRV}zt8#kIV&qGsH%#lX{f4-5Q4R}HBL`Y+1}p9 z^E{T8mU{b_WwTi(CMF05gQ%*C>$)_XO2Q52x3qJXao@@BNz)qj5bDzAHd2FVPpH&#@>CMdEddtPhde~ zvCqWHSQw&2QG5##3NSD;%%8i}WAnP*4XDX_lAGM*CO5zQ?#VeP7d}`>KR4gLeG}Dc z70a^T6hPB75{U%+`}1DU zya1p8*tXq2%;s|M0`r$)L z0I)2Jcs!0Q%l)31%jK{v3(K;oR4SaGpOgECJ@P*RDT;!oX_%&oX_|C89a^mxkB^V2 zs!BSYMo|=O+os#?lF#Q^TwELukYN}&j)UVkgu`JZN#gbOl~SpMuItFM%TE*w{Q79A!n?ts3lgVU=Mx(S^EhI^zUazyXw8ZrEH1T*GUDv7C>&UW9 zv)N>AZH=9s9ZIFr;FH?6Z7j>eaU5LNMF_#@=qQTvw`|Yza2yBE^ZNCv)9KLdb_f4t z&1RFEn;XL6Fw4u!7>2>j%napn8A*~5LU4C?M<$b@*Xtq6GN-4f^m;vl!QkM5bX`Z+ zbsCKZ-EJ4haS%c4n#p7|R)hfrw$6Q=ou)V#Fs;X$3hN`LvA=usBrC2O- zczB5Cd2DWO4!4)(^Lggy=LrUbsH%$Vy0qJEI-L%tX%Y&B*xTEq)oKxo#olK8h(sd& z{ldZmXJ=>Fw#~-I29hM9Y1+Sn8R$He&1T7Fv!5gE{}f17)prN^$shwnqfwrJ{CGE* zVzEdx8vXUd>gec5G@H#|enQ^L%;e-G2L}fdA2I#{(yxVak=Guq00000NkvXXu0mjf D<8Rb8 literal 0 HcmV?d00001 diff --git a/resources/icons/cross_disabled_36.png b/resources/icons/cross_disabled_36.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a138a7c1c699921cb44aa533109a350b3c8df1 GIT binary patch literal 800 zcmV+*1K<3KP){&029@$Y2LnKz7yBoPa3EN!(3h_$Vtkjlo&!gjBJf_Gd04zcndkobav zg)SU?0Jx0?l}d&C`}(C_y@y>|wN%vWpb31|ZPvYiY0M@kHXxx2d~ zO;eVZmT(;B7tGbw6+#Fc$H6cRvf1otc4&Z+Qeqeex3{;nTCI1O%gakF%VK(Znji?M z*XyKdIvS9ak~of0N+G30O3BU54N(-mVQkyRwryP3<@NQI#l=N5O&iT4({-Jdl@%^7 zF6j6B*tU&QitFoZ;yA{#tT#*$1Z-?+ z7$L-XPMNOjtgWpPh9OERT-T-1Xpkfc&1Q3G%pc`YyWK|9G?Y>(rAU&5BuUV9ohXXN z8;oHXa(;e}VHhMy!pzJJ`FtLw6prI?a&khm*&Gij3`5S&&M*uE+qUsMkH^PH9v&We zd3hn5%`!DL#qsg+P{Ek_D5hGiVwxt7y8fe^ACXMc?3bg*47q62!s%9Z*SxKKDAnnOeTY-X>>ZB(SeG^BFoFm1VQi)^A>7C zu(Pv+>$)xp6*>UrMSCm~Ww3WWmu e`}-PSvHk&DJ2KfS&4H%?0000Oc7>GIlGWYK!=Bd*!-q5X zoO?J5e}^1J(#B9`Pyn<#=Ial>!f_nR<#J0QrfHH+r;(hM$_$DkN0FJs87$LeY;3G8 z*_oP}VzXT4{G;==4CQ>EOeVv^!a`d>g98KHo1X_j0EA&kyQ6Lp?6By7I-ng3QW~t> zhCq6;h*>E7=ed!~)BE5d*7N0Ue_Lw=GyKc1NGW;p@kf;7@qb`)?7umOVj8R`6I5ar zVSj&fGE~Q4nqXNR{P`}%`ubMP>@C!uZj3~N__Blb+wV}VHU|{NVpM|w8AT|u7+4ku z?)-$2%hxb7HxYIZn!St7l@&^>t85(m5QJ#XFO#auqmfS-o}NVn0b*A#G6>jz>qlf5 zA#5AnwriMd7H8rzsA_XBwbV2gPn{$z6o~va)b1|K!4y_Hjc)h6$&52`8FalhRVFo! zCudF*%K}0HDiMSsL=i+0LMUjy-`d7#_&$4o zm`06tK@>tN1*t)ZBFx@i2F52)9k(?g-S^r1{WMyv3*ELUc^>NzACS9$pFf{3W9^P( z#^dyV_BraxN^?QF@3Zf_I;NYF>r%>Qaj#wF#pHF0p2x=1CAt!Q#1aV-7cU{U*{!z) zQj0}owF0tA$$i1=wJh!znc4-`b=JQ9h7Hf-)gMn0LZAqNc*_&>&Y;bqA)FJ(+4OU~ zUd!Trd2MSo83y^;8@zH|imprU+^0lGk2Lo!D<3?7H#$NvH1zMKZ5X_~J%{#qaod=N zKmfs^L;q8TMnjm+#Ml9KKrI954UP)$GwKEnAel@uIXTImRH`i)*Yijwli*X^TFFu5 z)!{4T^ZAxmkVnwq{_$0 z$Vg~e7R6$*t^i%vNhXu@^z=wSK<84a(9m^ydwW+UE5pOXEEbEL=0KV_9dhJvUVyzW*1JnSmS&&p^=T-$$^Lg}K?zih&CQIwH=NKQS zSNw0K8qBswPYA=1w|jO|j7ERLWY~1;E|RV>ACFOr82Ih&waHKggRX;Nu;t-nwE6kv zn6cZ)EzM}L7}049&6b6-%!>p2*)}?c@I1u2RzlBX)9pKiet0>(gbSqVV?U5xi^`{1SZl08>*?Dp8nC zV-23I3iD5MNd25cNI)q7--jT8AV3HS#dYgD7&X_W>-s2iqzM8a5(!9jLJ*+0w$gES z0NJwZ15#a=u4|(xktS5rq~JKrKYvDMVuG(9r!m$?(W6n?Pn|}dnW-&EbzL@Itzeod z*fxc98vFb?J`G(W?>H>HpQ0(&MkE#^)_)SQ!fCxCketsGmP-(pDcGO*HkZb}kXkBW zRb}qVWfmNVFYn$Xgg_Dk@rNVkpFxY=-B|nfvgl^`HkZb^cz$^|X&TwF8+@^C^0rOp z_%VVV+iQE46}N20IdlN8yZhIzt!aF|cNgX5s}*Ca0s(kixBjIKwW=_U8)F010M!ko zR(VzUpHVR=0P%R7p`jr*BoeEFu^oqaJih$FYH)Bc%x1H7e+f}lmA1Av`uqDO)?$1I XUMwJzgOAyB00000NkvXXu0mjfSUi1L literal 0 HcmV?d00001 diff --git a/resources/icons/cross_pressed_36.png b/resources/icons/cross_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..9b80bc5998d1e57b76a681b60db80fe55c3e0b34 GIT binary patch literal 843 zcmV-R1GM~!P)Hq-gP=7p02}LO~F< zT3k@@py;(%J&GROid|=>NhhhnM$^npG9GG+6_?hUv_jr#;KAd=Z+^?;!5UlA{6}A3 zUuant#bU9l0A1HfCX;k@bVv)Jp5ETj&~;i8iB*ZpWGcmMvB=32CnN!oaottJ0JJ0$ zWLy^j1;F?Hh0XSD+g1hj?D=y5#9B-08mI=Uf&P1t^j(juI7rRs(Q~<9?rWJWjW1te zjEyh(Ol2jQ_Qy{N!;qmpyD3JaKVUL!x_u8x*O-pSC`AnX=H|*oXdZ*EgJH1c(G#@k z>BX3VJID?7Xt5a4aSP+!dnlJH0}3J$%AQ9U1W1tx7zUdkJVeW6=PCsR@YgO<;o^? zs3^QWd=NjEBXFmX>+8^4S}~GIRI_0rGFI1FP}S;GS*R!s9X*1V&4OQo(NP!~fl`S= zDvi~BZdsUr(udTKIfMk10`PqZ0tf=1n7;8 zB+hjqTXuCos_U}hdOu2}4%IX%I1baVUXpq7g3n{)80(|x(J0NQ&md1sR2HPVE*r1S zW9li`HidK=`+N_RmoJfb9A<_`sEaibiN%O@o`A^%rLiyc z&Lyy_GIjL|GmgWj55ou{kc2?|X2h%*G~3pOwQn!8ZiX*YX`GAa7gv*}ksY|nC)*}( z+hmR(BiOOMva_tXWi!s919)w1KQC=fi4 literal 0 HcmV?d00001 diff --git a/resources/icons/delete_disabled_36.png b/resources/icons/delete_disabled_36.png new file mode 100644 index 0000000000000000000000000000000000000000..665a56aadf80d993ca5347559f4f827c54b1a48c GIT binary patch literal 839 zcmV-N1GxN&P)O{>R1$pKe1306pUg7iBxVtYX~NKqdthQ;Dxtmd<5rQ;v1>=)hI@YQ4myW zX*40Wr7i7TOgzctn3J4Iiq3>}wX^f@op1kZC2MEF7mKm?#nI7`-)^_*_xnQy5Jiz} zHp~A0{@4$oKfizXqoT;p&d#T}a(a4-)$jAiw{K$tKv&b)-QDHp=H^p?EYqZ`>i~oR z9LM?e+IeRPeYK(<0gZtEcR<3wJ!qOHs;ctz^n_`e0EEL~(&;qmbedQ!Has9r)2P*I zEG#Urv9ZC##00ME^7{J9#l;18cX!CLOeT{FK4qGwadmY?u~?*BF5@^3tyYV6yN%;G zY;SLqOeRqjg@=cSU>!0|lX|^Qp->=~%TcXX>G%71o`)a^SeAvZ>lBMcOw;82{G3E0 zK|CH0#-UE9Lo60!X=#aCt%l<`*tU&r+Zcv{VHnu9&Gq#)Yinz~y}faNe;=Gfolb{B zp}^zgBfVaaR;%@)E}2a7{QOKlpQq7iu)4Y$43K46L?RJXRmF8(_V)HZ)Kw}K48tIw z&;Rxh;3w+wD@RR6c%XD8$^{9IC1! ziehjMWilD+^*S>%Gnl4HG#dR$<9QyQ=P^G&&+Y9k%gf8b0j1Mv;_*0_mzR`EB?f~5 zq0rw~_`Z+lc>t8lWzNpd7#|-epU(#e6pO`JSy`di>!BzLrBaD(HcKQDK@bFTxg6{3 z>nMuC%gYO!o108cO$FOJGMNm%@1rORCnqOJl7u8l0O-0-wOU0G1h%%ee(VR{%N7?G zNhA_9n@yU{CXGe|*L9hkoJ5voBuQd=dV09sJ|2%F%QCVok3MK4pb^l24=5ZCe>%uJ zg8-09r8qu5#vbx)=shB$NGg^3=fLXl@X*(Fedv>ra5&8D>?{Wd2V;E2_zOH&FBCV* R?}z{Z002ovPDHLkV1lykwo)9m)kP6n+&Ffoo3iXimeoH& zvtm$JZdm)ea5~zdSjVV1Se;5#XgZLVQdB}pbCV{yxo<8I7rrf=xk+{Mte?f_{LYzk z&OCzO${ok0i6y2H0P+szjf1bVwzh_07!8G}s!DfvH!d%^Vj3abaokigML<=Vn3!lv zI#W|q)C_}jKc3r8XfvNDl}eGxWSRo%ipTl-@?`)d0Mj&ScI_A>?y+itTA=?ONdDI= z(RGw-*O0C*LQ%)T!40aIy-D2tNr<*L4y3H)@PD2rx+g(+bQlqhK&^)P%R09&US#Xr zuW+9kM%w?7_gki{K(+DFAxiJsCRV|r(Pi5(T;WgZS19tw+TtQ zj+#!>Idq6ne-itX52#dY2-`&UD~P)#+?!e2o*1P5afw?O-Y2lT3#q-+n}p(=%wtjuF_*Q(u_(E+IL)MELOmoW(0twl=Xc z%K%jV(C3f0Vda+TIxxi5@8$?RJ>m_>DHoALA;@OIFo;dR^Ot=ar^irACV_*4Sl_3; zJJfF7!qIh{QW3vh`^R3(a=-;gSl(@!9S%~xah+f|g1uT`jv*m@ zzHJBs+jhZ`i433M#_V}2s!H(DCp`p3+YwJ4=jT#^{Dm2U$4{ap`ymuT$YBEgj}RFd z?pqk;D$2lPsD}(KBW9VKvdwDm!2 zfm)#d9!OD?rbBiNGJvLOOioVnKqAo;%&qlxG);p%+fyrd9QU=JS1A+<4WEQ4ib5V+$pj70zrJ)D|L7|`$gOa*ROw%;9N!-+H*YN_w%GA^pRm0%? zxpR^Nu(i2KDwQIW$utDi5s&lTl`8yA~AHKzXc@X)) zLGQOrtmFvH&Y~SXLiFSia^)V<)vu8j=1}kJB+k4>ysZ`M!h6_D8E+GkbsaUGrhVWr zq246+=O0t9RFP~G)i03l7jbW9X?eb%+Gj;>UHpi^-VWr}c5f0=vRNL91_?fy#QySA zY@>o>lo4ee=UyILzl&2XW2R@&jvptmwMlJZ-n)d9>=NPU`fwJnQr_Oe$}9s={zIKV z-h!1|rsL26*M6KM@Y0YsAg5G7356h=1;Zfr?)!gPw{dm^Q8WoW(~tF2+PkK9YZi{K z;}i?{?dm_)T9yMYIFjYvmf7JTl^eeh3`ek6^K6_Q`I}|R_#fX#A(ul6MZ8PM>gpyx zKaZQ(k6kbjkBUE|T^HN+A)eUJ>d%)696IXZIn>=lX-_-(FD@enhj9!U$>;kWf`o0m z;3z}}PjO@R0%cVt`1A`Nf}*WRFP`9bG0*138GjlJ@Y1^p8opW zWlC2s(w+{?g?W}1zQJCegPMuc5+V4?X|%oplvupJx4o}T!*^_$z_H=x1FZ>ag8q9T zAw&QD002ovPDHLkV1n#rqCNls literal 0 HcmV?d00001 diff --git a/resources/icons/delete_pressed_36.png b/resources/icons/delete_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..99c04f5cf5a8cc74357a8a80583a13ef82fc0bc4 GIT binary patch literal 893 zcmV-@1A_dCP)3pAvnKsgW+5-wGdxNzl?E96f| zxkf$p(sNtr0Uy3+^Wa zh&y+AKV|ZK4mFiR+rN*ek0b759Q>$R6C(#ZcMqSHOpPuqAAt^gc=)+!|ncGy>*Rbx)0Z{o{ zoj=xsm762JyPvziWl#?edINIGMWkR5vRN<;qF;UUuXU@ZMvzM;>IZ#Tzo)%h)Nb9v z(RG|s5x-q~Wvyj7;DRGq-c6Yu3Q&D;pFk*#y-;BF)X0AfSH%C$Hj23%LNM%ILRMFT z!qgOQ#}4eGf&8}oPqgb|yFTRYJ6QPR2I}tp9_~YlZpvHQDg1B)d0+^~5D|R7zaR+M zwhN9#c;Gk>QkSVH3W4_zcnFHLB7A(5l~RHHwMha;hmnDX(oA=IAt2oH|%aPkrh z>+^(9oJMNf>g`S~c6H-B{u#?wnVE0D#3)yhd-tI1-AAQd=GO~fuuxwma`Ftp?!JvV z%cQOzge`H*=_zKXZ(`47pl;%{gb5rOM(gcIivGXftJgm5eQg@PLqn*ChME_&CTR13 zWLa)Fsl1R@qf}htc^)&Q`;0D6fGz!ICA6%@k=;8*N@;mQ@@V`aV*Y!(1DgJ z#nZ;NvCTXCjx6_b;lklo>%awpl|kT_wO6t?3%*!L@5$NOndo#ngkkup0A1HvSy|!u z_*j~P{`~nLesSQfYLN&JRUP1kEz$|gkeax+r=;pOw&YB zl+OVPA@F@4&-1Wto1L8L z*+f-U!Z76i{vO}=sn_dlY-|t&0qu61(P;EB6&enQ_`c8T>ME9H;dvhIcAGek34(xp zJ`cd#+ZziD3pATe`u#qgPKVdmSJv0pXIr81c+A1U0j6ov>-888hlF8>=XpFmJ+Za5 zMYUSRwrz5`9Fxfe+qT)>-o|y^kLe*H1iG$cnkIw60Mj&CT3SMqB%&xH%QB)UB8nnh z*X8;78OL#uBx&{_Ap}{L0bm#gnx^5pF23(0%Q8U_kjv%h^?FPu6I4}2Q54cNojx~n z(BE@emMN7=c%DZb$GEObk|ZdKLN1ph3`4RkBaY*b0i|g=&9f|ZrI=7XE+@4@bEyPP+)g= zciKOnw%h0Pc^ZudrBaEzyE}vs)BCxehb+s&vaIhT>^JBe^q(MARp$r^V0e-5lpPfta^-~aSUh^nfT%VkbZP9(l!`~`k?qR-dm;Ozha002ov JPDHLkV1jJLxKaQB literal 0 HcmV?d00001 diff --git a/resources/icons/package_hover_36.png b/resources/icons/package_hover_36.png new file mode 100644 index 0000000000000000000000000000000000000000..c853281fed3dca81d6c361530884aa4f9a0d4535 GIT binary patch literal 1038 zcmV+p1o8WcP)^QFN*lrxhO**GaOVSo7QzazSqACkE6;d}yk=THMY!HP# z8;FF6k}Oz;EfV7fi6FHgL<_iqs%ofO7e$$z#2KBJ=Qm##ZCJ9Y9e0u6U+~en_nv!n zBpyceBWPfxF(d$W8#DUk2(z=Z*tWf+5M9^F=ko~O5z-ivB>EA9-GhX6oq>UYhE!*G zco@sJxjcFKL4}sfWd;WaxpnJSLqKiWEPoUV0LTEY>(c0K8)P1{?1FYdj~exnJFK&a zvi<|Yv+z@Wptm=uLcW2rK8~{R8R@nzTGH*v)1ydB-+{ZagB21!^40`O2O#yICq;Wxe3*2=3T=K3-cPhFr=y31nmYplg>S-3c6XwtdywC%yIhC z2wEfxTT_UwB2wipsay~77MS?{%i4p4P!LFj!aA{*4yxrznvw|;$p{flu%^Kefi(x! z8O(*>Fs6Pby#FFXXth5wAp`_~6hez1LDOScO9cXB2~7dXybrR#S@@C7$~s;o0U;gX zdI%BJ0pte{M@k=r4QiC|-cxv?2w`i5U}2mMcM~t%jIYNCOb0wc;CKi>sC^$2zW?8| zCvYJ!LD;Cx?ZmQ2F*R`XFrMw(&Y6*~pp(N(6)jfenELeigzwJORS? z2}}piuyG9wEq)wXfbgt3bn3_f{`q-`>e@8uIZ&FwcR;v6fN%qR%fT}plu!rpu2%{C z2*tuRSnAUXA?bOVvQ>m9C{5mGWo`m(-#MC3zl|J8 z)%P!xv^}Vu7m?G?P@4Ldncw?4^y2GOmu8v&`z9pvM4$T*C2??P#}7H0Cv^NRwqb#LCLbj&DLVO(T^`@&26;kj9Xt$8DweFR7T12zVTs%m4rY07*qo IM6N<$f>DU$Gynhq literal 0 HcmV?d00001 diff --git a/resources/icons/package_normal_36.png b/resources/icons/package_normal_36.png new file mode 100644 index 0000000000000000000000000000000000000000..9cc9e43082486ad963c0aa564264fb8807f9cea0 GIT binary patch literal 1021 zcmVV zONfMsk`An_NIVxLg4BW#E#L;Ks0Fnyit@4(=jnX;{Jk#&N+lMS+Hr=Q>5f00d+xbM z2X>z<~4!^x?>e2_+@@@^~YJGkKTe=f@<{Hw{H{fpUV1|T`yfukZ{egY$PtksE969hYN(BPuwMKK(l0^kc_ca1IFMu&Q8%s=i6&`3_b%b&F%S;k+5^gee9D0nFOWU-EXn;x zSS;RVvvLc$;}UZ6bp1i<+84;iZ8Ar%5N>-CbA6f8gP-sn19z*0-r7NcMzDI9P-_?6 zXD(4K-Dk1*CD!6iv?t!GFG$+_o#Pj-;chNtlx`3>Rdln0SuIhWo#*V8akNMjwx$tV zMWpI|Qn_B@Z7})m=k*5(p&*b5g>_LI?-|DTEe3hNj1`mI?&M5}E>%`4D7*Q~sXK>N;K|0U;gXdI%9T0OSXV zf8RR@8`LP_{b%q(5yI9AL3x4=cM~t%im%5AOb0wc;CKi>sDB?4zW?ai6SxqVAZ*mu zPGZ^Pm>M{G7|-_bEeGGUa7~-QbsGfoY-CLXB?7{Nz=psAzXstPo&aI{1g3*$*tmv; z7C(tBKzLRII(6g_fB!g2ZEXhh94IZ|J0M&jK)33pJ!na)lFG}p-MZB8D?41IJIgft$6{6i2>kpD- z6)AllDc;NK!UUDi-XnMFWpbzck@P$**&4zVlVrXbcVmHP=c!7cS2Ff1L00000NkvXXu0mjfB;VM1 literal 0 HcmV?d00001 diff --git a/resources/icons/package_pressed_36.png b/resources/icons/package_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..ed49516f243c5d7456c25fdd1697783c77c0f4f6 GIT binary patch literal 1015 zcmV#HD0TS^vgiqMT9xKT7Kv1ack z7DORkxOFS|+z7_dD$-O*qg}L0JC%^vOfq>+?%cW0b6iLR-OpqK=35;&n;+jf-*-L^ z?66RtEJjC1<=osHj^k`Az%UF7g#trEL&_HD5!2rx^N zB=aX&D&Atf_z7fRh0IGRYPfkI#V!)sxJfjYqT}5AL{djUy7+64DF68d`%b(}{=f^Q z_8wxfc$dw}9n{XtsHxM92Zh$YMm6t}JA9Q$`WdYCWlE2JC2&o=S_z}Q6M>Fcy-&Ek zhu$-nsg@qHSo{WiaRU9=JBb1Dp=JL)!BK@UL8e`#-KKh ztQAqJ56N`(lSsqV4_`GNB&CK#AsjJ?r+cVZ9?+Ibl1xR3Vu3vaK^W|LsLx`Rf5V)< zO=SNSq|_UKWKv2HfD%Se96>kY*h`ZLa|vAo#d-{?#4Z2CW_2AunuM@{^n9cgO#lS~ z`Cq+*bRZNXviA&rI7-A`L6pbX@HX)y?F2>~VY%Q-gzF;%(fB?jgJ7%d5grH&q(i8^ zn|S^xmJY5F!FPND+a<7UJj+3NUXwt+gR1MGMM2si91u1Hb%=D~OOQ^0uv~o8!82|2 z#7R^M(zlz?siOz@=aA`(g=BLp2U7-E!d#KS&bKf#W-$SVP z3TpOwO4Hvl`}><5dgU$ZOLHvzbq|sSVlRG#mOQw<z5ka!6xDW)FU5hKhjSELm_fEx){sTb}T)1+r zh>G|NT!>w?`6x-0l=zX++{~&$$C&|Pl9r(dF1eQzxDW5W=iI}A4zpBivb(z**6VeA z-%k`kmSsjpMp#-}N<~25E0r)K%c!c_7FEvA&w25EzOAgJdI4<9qFgR>dwbg!pqJ-o zY{vnh2jKPfwe5GO#n97fm%0no1^VxRdcX4X_4PHr?~}=77#|=1fhdV0e1JUq|CbzNN7#V`z}rlzngD;b5d*(|50rzna-Utb>pLI}bz z1YlrbfTN=$1_uX|0U8?{!}B~8MIj7BgbRIAna6>8Nnhlhtql7ylt$g&(MB!nQB%dxYw^X23F641%X3ASyM%jGDQO6a;y zKA-;p(i(Gfb2J)_-=>Fuc4Uo4gPEBbOw+`5-DvY52nd4U!&(Tz+uK{bc}P(d4h{~; z=kpYc#Xmv>K@fdojk4L<*-z2-9_rT-JJ5UE-rmM>9QynFDVNKHVHicA5Q6>veX7+e z$H&K1DwX&jSKGE(TwKI7O-$27*L4iTK+`ldP2=wFj*X2CYPDK23Ka?kwzjqy9v)_5 zV&aqk^74}P^>s!^N0R|sUS9sIe{F58TcCA;x znfIA_`5PQd51^@|j0Fq;&k4-KZ>Gs+vs5aTnu2(}ULuhQnwT~gFbq9_j$e<{?DaA@ zI9L~VrlzJSS1R1!xbHZ$yR$<)9%pTBtu8?AZEgJV{5b$k0M%-hdh3Lti8H1KPy^_* z1M>WzhgVluIXXJRvMjo~x<09|tgMjBG; z>dxXHo0}sP3e^Uvv$K<} zBb(L>O(@MrLuAWhXG*T&ko%oKwHlFe9@f8*q7v2B4=JR>R#>OaQ{v`3b z#AfCLo0$?Df4}Eds>JsC@66B7lT0RS<4`ypCKiif+cw{Keww81CtnkdM(OD2s14A> z#KeErXJ=;{2ebxI1L*7mnWkBHkP`+EKrk3&a&nTd!r{8WGi zI2>kmb+sy>)|M7tzj*^dJwT~cs(S4l80t@2QP)5<(0>PV{L975%ggNT?O~cG9UUFV z=9iY1NT<`dTrRr1yX{Y?P$`fkwBF3uo$4fL5S1XbtY>%goNs;`8|`18Q$?XJgNz>1;hpb7a#Ru65oc^Fwj9 zd9yMgDBoBqc=3Fiov$ez4KA*Cg)kbcsx#PYbzroBldNflI29Ua!Go7 zd)fYyV*T?LD~TOe;#=gt|KQo$IbyLG!^6WwqfvVk5*nliqtV0k^faeVoA_J~oQ(!j zX_VARr7_R@85|rW5D3@@T3lQtm&;*#TDaLW#9Cqp|M}A>rIAu073ICublsU`Bc12Y zje>n`pGu_&27_di8)SCGKB9~f0)$Wqp%GFerKVKU_P&SQZa0xg1dqqVHD8>P)JUNa zQX`~7NL4-;`VePpm-Y4a9jzx(hRFRy4r^%tSO{r<+aHX9fdO*49Igv~Zry*3lI2IG zltwDW(}~-BeEW)5PcAV(KX3oXHIvCOIyy==`GM%?Jnxggd7sSl_R}xk#q(^gzhrK1 zj#w;KnS}g)KcP?v%d)uJe!P=5pI;#u4AR!tRvFO5#Kd3cXJ%$U3<_V#wgS3(TKpsA^ep`oEVPU83jHmYt0 T=|%Z=00000NkvXXu0mjfw9tNe literal 0 HcmV?d00001 diff --git a/resources/icons/shape_ungroup_pressed_36.png b/resources/icons/shape_ungroup_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..c89c88b558ac353fcde4dfac775efc0dfa8db66b GIT binary patch literal 836 zcmV-K1H1f*P)hNmDR*d?(h z=pYu0=}aa=xm>O(z~yog4u^?GqfN)4rxO#p#pNOx2-GE&yk$`;mwEE|agzg}u(MM) z3_vgtps=$8Kr=w4QaLVO?dYfr>h;aeweKPFiE9S^!4?T&*y8C zkYN}sEG*FK@zZwBfdaGwr9o@(T)M*S>@0r2zc!$*t}eC?Oxn*oP?{5))^MZeA-V60 z>zB4`1A^*}m4eqx)9iiC(A?tY=A9_cmh;t`Q~*p)PLfO}2?m3Vj*i;bWlB~PDOhRx z`}^7bl41RGfw!qW-X;rJ-@fze-31bf1S2CO#N%;$6A~Jv24}06>FH_CoHg*fn{l-| zkxHYaMk)gcmv$-8|GbVkI==lD5a57Ar;lV)ZDrMjIC^u z`?pH=wS6X&Args@Pj8Xi6NiW@MhFl>A%sRqjg*>7Mcexx@_0PNVllj4FE{*2DpDhb zLP(8}3L#bXTp}y#Mr*jbxGS^)=?^ z=13$GwMi%x3K5M)F-?;PU4M4c_R=*XkqDigowWf?O-=o^J~K1Z_(5xcY7cTcoppyC z85{sUpO0)VS2xVc$_hT8@8p8j`1rWq-Q7L05>l&V+S}V19v*JuG>+ddG-|4BoARUp O0000W?&6k5$rCs^#?@UXNt4aZG}$6J>gej^14tetqg&#f*gH)p zB@Jmqq3S{J;NTpDAAjx?4&ZD^?IH+*Yf%)as%lUGAq3Ou6t-_S?0Ht*Fz0(>dylrX$5+DKkcOdz< zFJxJUrfE>ubvGGfpp=3!)=f$&D5Y>+w_lL1>wFbT)3mGds7PC?U3;F_FUT+qz6vEt z@}VMf(5%0cJ@= zK~z}7?bxwu!cZ87;rB>Fx=X-XFQ9bo?KlW-LLF`J0wm28(&8pMxOxL#fJ+4jq4WZ* zi*@hG5l;sj39W4r8WU)K_{b0f4(4Z$eDdq*yF+`|KbF2oc@L%1ZAYX`nb;a` P00000NkvXXu0mjf6P3wT literal 0 HcmV?d00001 diff --git a/resources/icons/variable_layer_height_normal_36.png b/resources/icons/variable_layer_height_normal_36.png new file mode 100644 index 0000000000000000000000000000000000000000..1c5565ed1e6110d3a7b594e424a2509129498d96 GIT binary patch literal 443 zcmV;s0Yv_ZP)+MYa`RvD5WThf-T5(UA`J}9Or5~)0Ab&7UcUrUkzST2`v97h6=>k~MO6K^X&+&lmP002ovPDHLkV1mFjwQT?Z literal 0 HcmV?d00001 diff --git a/resources/icons/variable_layer_height_pressed_36.png b/resources/icons/variable_layer_height_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..6bc8018f945e3026346e0439aad8b43a97ef012d GIT binary patch literal 446 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB*pj^6T^Rm@;DWu&Cj&(|3p^r= z85p>QL70(Y)*K0-AbW|YuPgf_b^#tMQ4{`%00ss|Z%-G;kch)?Z*0tJc93EFa9&{J zZW)Oa%qH)r=*~_!7E;qta8^XuWBa$IS4HL7^o>&aU5W?t)Es`4}z)=dLvMXf^?&7g@jl*BW*y zg@m?l(wWVnIIG~?Z0+YVr=JGtW!T8Az8WCWc0Vf9ylC#*GHr2_nLbytGPXwDy*oX+ z^Kpb;hSl6vS1;7q{ogpTEZd1g#ZyUKgrzZx_vgO<+177UBGtrQG(%S_UN1c;v3sZf z^ey`z{r(%Lm$CWgi~W1qGB3<`T$WkJw^O2u!GYu8?T Date: Mon, 23 Jul 2018 13:49:48 +0200 Subject: [PATCH 063/185] 1st installment of 3D scene toolbar --- lib/Slic3r/GUI/Plater.pm | 25 ++ xs/CMakeLists.txt | 6 +- xs/src/slic3r/GUI/3DScene.cpp | 14 + xs/src/slic3r/GUI/3DScene.hpp | 7 + xs/src/slic3r/GUI/GLCanvas3D.cpp | 176 +++++++++++ xs/src/slic3r/GUI/GLCanvas3D.hpp | 22 +- xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 18 ++ xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 7 + xs/src/slic3r/GUI/GLToolbar.cpp | 388 ++++++++++++++++++++++++ xs/src/slic3r/GUI/GLToolbar.hpp | 132 ++++++++ xs/xsp/GUI_3DScene.xsp | 15 + 11 files changed, 807 insertions(+), 3 deletions(-) create mode 100644 xs/src/slic3r/GUI/GLToolbar.cpp create mode 100644 xs/src/slic3r/GUI/GLToolbar.hpp diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4a56ce632b..9fdc320f38 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -178,6 +178,9 @@ sub new { Slic3r::GUI::_3DScene::register_on_gizmo_rotate_callback($self->{canvas3D}, $on_gizmo_rotate); Slic3r::GUI::_3DScene::register_on_update_geometry_info_callback($self->{canvas3D}, $on_update_geometry_info); Slic3r::GUI::_3DScene::enable_gizmos($self->{canvas3D}, 1); +#====================================================================================================================================================== + Slic3r::GUI::_3DScene::enable_toolbar($self->{canvas3D}, 1); +#====================================================================================================================================================== Slic3r::GUI::_3DScene::enable_shader($self->{canvas3D}, 1); Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($self->{canvas3D}, 1); @@ -627,6 +630,9 @@ sub on_layer_editing_toggled { $self->{"btn_layer_editing"}->Disable; $self->{"btn_layer_editing"}->SetValue(0); } +#=================================================================================================================================================== + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0); +#=================================================================================================================================================== } $self->{canvas3D}->Refresh; $self->{canvas3D}->Update; @@ -1907,6 +1913,9 @@ sub on_config_change { $self->{"btn_layer_editing"}->Disable; $self->{"btn_layer_editing"}->SetValue(0); } +#=================================================================================================================================================== + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0); +#=================================================================================================================================================== Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, 0); $self->{canvas3D}->Refresh; $self->{canvas3D}->Update; @@ -1917,6 +1926,9 @@ sub on_config_change { } else { $self->{"btn_layer_editing"}->Enable; } +#=================================================================================================================================================== + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 1); +#=================================================================================================================================================== } } elsif ($opt_key eq 'extruder_colour') { $update_scheduled = 1; @@ -2098,6 +2110,13 @@ sub object_list_changed { $self->{"btn_layer_editing"}->Disable if (! $variable_layer_height_allowed); } +#=================================================================================================================================================== + for my $toolbar_item (qw(deleteall arrange layersediting)) { + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_objects); + } + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0) if (! $variable_layer_height_allowed); +#=================================================================================================================================================== + my $export_in_progress = $self->{export_gcode_output_file} || $self->{send_gcode_file}; my $model_fits = $self->{canvas3D} ? Slic3r::GUI::_3DScene::check_volumes_outside_state($self->{canvas3D}, $self->{config}) : 1; my $method = ($have_objects && ! $export_in_progress && $model_fits) ? 'Enable' : 'Disable'; @@ -2123,6 +2142,12 @@ sub selection_changed { for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); } +#=================================================================================================================================================== + for my $toolbar_item (qw(delete more fewer ccw45 cw45 scale split cut settings)) { + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_sel); + } +#=================================================================================================================================================== + if ($self->{object_info_size}) { # have we already loaded the info pane? if ($have_sel) { my $model_object = $self->{model}->objects->[$obj_idx]; diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 976943d64d..972a1b094e 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -195,9 +195,11 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.hpp ${LIBDIR}/slic3r/GUI/GLCanvas3DManager.cpp ${LIBDIR}/slic3r/GUI/GLGizmo.hpp - ${LIBDIR}/slic3r/GUI/GLGizmo.cpp + ${LIBDIR}/slic3r/GUI/GLGizmo.cpp ${LIBDIR}/slic3r/GUI/GLTexture.hpp - ${LIBDIR}/slic3r/GUI/GLTexture.cpp + ${LIBDIR}/slic3r/GUI/GLTexture.cpp + ${LIBDIR}/slic3r/GUI/GLToolbar.hpp + ${LIBDIR}/slic3r/GUI/GLToolbar.cpp ${LIBDIR}/slic3r/GUI/Preferences.cpp ${LIBDIR}/slic3r/GUI/Preferences.hpp ${LIBDIR}/slic3r/GUI/Preset.cpp diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 2ffd788eb2..29ea5fb9ca 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -1776,6 +1776,13 @@ void _3DScene::enable_gizmos(wxGLCanvas* canvas, bool enable) s_canvas_mgr.enable_gizmos(canvas, enable); } +//################################################################################################################################### +void _3DScene::enable_toolbar(wxGLCanvas* canvas, bool enable) +{ + s_canvas_mgr.enable_toolbar(canvas, enable); +} +//################################################################################################################################### + void _3DScene::enable_shader(wxGLCanvas* canvas, bool enable) { s_canvas_mgr.enable_shader(canvas, enable); @@ -1791,6 +1798,13 @@ void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow) s_canvas_mgr.allow_multisample(canvas, allow); } +//################################################################################################################################### +void _3DScene::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable) +{ + s_canvas_mgr.enable_toolbar_item(canvas, name, enable); +} +//################################################################################################################################### + void _3DScene::zoom_to_bed(wxGLCanvas* canvas) { s_canvas_mgr.zoom_to_bed(canvas); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index a2ca1de7ca..b7ef9a0c88 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -495,10 +495,17 @@ public: static void enable_picking(wxGLCanvas* canvas, bool enable); static void enable_moving(wxGLCanvas* canvas, bool enable); static void enable_gizmos(wxGLCanvas* canvas, bool enable); +//################################################################################################################################### + static void enable_toolbar(wxGLCanvas* canvas, bool enable); +//################################################################################################################################### static void enable_shader(wxGLCanvas* canvas, bool enable); static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); static void allow_multisample(wxGLCanvas* canvas, bool allow); +//################################################################################################################################### + static void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable); +//################################################################################################################################### + static void zoom_to_bed(wxGLCanvas* canvas); static void zoom_to_volumes(wxGLCanvas* canvas); static void select_view(wxGLCanvas* canvas, const std::string& direction); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 064a9adce9..eee601520c 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1803,6 +1803,11 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) if (m_gizmos.is_enabled() && !m_gizmos.init()) return false; +//################################################################################################################################### + if (!_init_toolbar()) + return false; +//################################################################################################################################### + m_initialized = true; return true; @@ -2053,6 +2058,13 @@ void GLCanvas3D::enable_gizmos(bool enable) m_gizmos.set_enabled(enable); } +//################################################################################################################################### +void GLCanvas3D::enable_toolbar(bool enable) +{ + m_toolbar.set_enabled(enable); +} +//################################################################################################################################### + void GLCanvas3D::enable_shader(bool enable) { m_shader_enabled = enable; @@ -2068,6 +2080,16 @@ void GLCanvas3D::allow_multisample(bool allow) m_multisample_allowed = allow; } +//################################################################################################################################### +void GLCanvas3D::enable_toolbar_item(const std::string& name, bool enable) +{ + if (enable) + m_toolbar.enable_item(name); + else + m_toolbar.disable_item(name); +} +//################################################################################################################################### + void GLCanvas3D::zoom_to_bed() { _zoom_to_bounding_box(m_bed.get_bounding_box()); @@ -2197,6 +2219,9 @@ void GLCanvas3D::render() _render_warning_texture(); _render_legend_texture(); _render_gizmo(); +//################################################################################################################################### + _render_toolbar(); +//################################################################################################################################### _render_layer_editing_overlay(); m_canvas->SwapBuffers(); @@ -3422,6 +3447,146 @@ void GLCanvas3D::_force_zoom_to_bed() m_force_zoom_to_bed_enabled = false; } +//################################################################################################################################### +bool GLCanvas3D::_init_toolbar() +{ + if (!m_toolbar.is_enabled()) + return true; + + GLToolbar::ItemCreationData item; + + item.name = "add"; + item.tooltip = GUI::L_str("Add..."); + item.textures[GLToolbarItem::Normal] = "brick_add_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "brick_add_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "brick_add_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "brick_add_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "delete"; + item.tooltip = GUI::L_str("Delete"); + item.textures[GLToolbarItem::Normal] = "brick_delete_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "brick_delete_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "brick_delete_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "brick_delete_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "deleteall"; + item.tooltip = GUI::L_str("Delete all"); + item.textures[GLToolbarItem::Normal] = "cross_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "cross_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "cross_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "cross_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "arrange"; + item.tooltip = GUI::L_str("Arrange"); + item.textures[GLToolbarItem::Normal] = "bricks_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "bricks_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "bricks_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "bricks_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + if (!m_toolbar.add_separator()) + return false; + + item.name = "more"; + item.tooltip = GUI::L_str("Add instance"); + item.textures[GLToolbarItem::Normal] = "add_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "add_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "add_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "add_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "fewer"; + item.tooltip = GUI::L_str("Remove instance"); + item.textures[GLToolbarItem::Normal] = "delete_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "delete_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "delete_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "delete_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + if (!m_toolbar.add_separator()) + return false; + + item.name = "ccw45"; + item.tooltip = GUI::L_str("Rotate CCW 45°"); + item.textures[GLToolbarItem::Normal] = "arrow_rotate_anticlockwise_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "arrow_rotate_anticlockwise_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "arrow_rotate_anticlockwise_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "arrow_rotate_anticlockwise_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "cw45"; + item.tooltip = GUI::L_str("Rotate CW 45°"); + item.textures[GLToolbarItem::Normal] = "arrow_rotate_clockwise_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "arrow_rotate_clockwise_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "arrow_rotate_clockwise_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "arrow_rotate_clockwise_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "scale"; + item.tooltip = GUI::L_str("Scale..."); + item.textures[GLToolbarItem::Normal] = "arrow_out_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "arrow_out_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "arrow_out_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "arrow_out_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "split"; + item.tooltip = GUI::L_str("Split"); + item.textures[GLToolbarItem::Normal] = "shape_ungroup_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "shape_ungroup_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "shape_ungroup_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "shape_ungroup_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "cut"; + item.tooltip = GUI::L_str("Cut..."); + item.textures[GLToolbarItem::Normal] = "package_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "package_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "package_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "package_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + if (!m_toolbar.add_separator()) + return false; + + item.name = "settings"; + item.tooltip = GUI::L_str("Settings..."); + item.textures[GLToolbarItem::Normal] = "cog_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "cog_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "cog_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "cog_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "layersediting"; + item.tooltip = GUI::L_str("Layers editing"); + item.textures[GLToolbarItem::Normal] = "variable_layer_height_normal_36.png"; + item.textures[GLToolbarItem::Hover] = "variable_layer_height_hover_36.png"; + item.textures[GLToolbarItem::Pressed] = "variable_layer_height_pressed_36.png"; + item.textures[GLToolbarItem::Disabled] = "variable_layer_height_disabled_36.png"; + if (!m_toolbar.add_item(item)) + return false; + + enable_toolbar_item("add", true); + + return true; +} +//################################################################################################################################### + void GLCanvas3D::_resize(unsigned int w, unsigned int h) { if ((m_canvas == nullptr) && (m_context == nullptr)) @@ -3717,6 +3882,10 @@ void GLCanvas3D::_picking_pass() const m_gizmos.update_hover_state(*this, pos); else m_gizmos.reset_all_states(); + +//################################################################################################################################### + m_toolbar.update_hover_state(*this, pos); +//################################################################################################################################### } } @@ -3963,6 +4132,13 @@ void GLCanvas3D::_render_gizmo() const m_gizmos.render(*this, _selected_volumes_bounding_box()); } +//################################################################################################################################### +void GLCanvas3D::_render_toolbar() const +{ + m_toolbar.render(*this, m_mouse.position); +} +//################################################################################################################################### + float GLCanvas3D::_get_layers_editing_cursor_z_relative() const { return m_layers_editing.get_cursor_z_relative(*this); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index d18ca0cabe..67f6ef32fa 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -2,7 +2,10 @@ #define slic3r_GLCanvas3D_hpp_ #include "../../slic3r/GUI/3DScene.hpp" -#include "../../slic3r/GUI/GLTexture.hpp" +//################################################################################################################################### +#include "../../slic3r/GUI/GLToolbar.hpp" +//#include "../../slic3r/GUI/GLTexture.hpp" +//################################################################################################################################### class wxTimer; class wxSizeEvent; @@ -432,6 +435,9 @@ private: Shader m_shader; Mouse m_mouse; mutable Gizmos m_gizmos; +//################################################################################################################################### + mutable GLToolbar m_toolbar; +//################################################################################################################################### mutable GLVolumeCollection m_volumes; DynamicPrintConfig* m_config; @@ -537,10 +543,17 @@ public: void enable_picking(bool enable); void enable_moving(bool enable); void enable_gizmos(bool enable); +//################################################################################################################################### + void enable_toolbar(bool enable); +//################################################################################################################################### void enable_shader(bool enable); void enable_force_zoom_to_bed(bool enable); void allow_multisample(bool allow); +//################################################################################################################################### + void enable_toolbar_item(const std::string& name, bool enable); +//################################################################################################################################### + void zoom_to_bed(); void zoom_to_volumes(); void select_view(const std::string& direction); @@ -610,6 +623,10 @@ private: bool _is_shown_on_screen() const; void _force_zoom_to_bed(); +//################################################################################################################################### + bool _init_toolbar(); +//################################################################################################################################### + void _resize(unsigned int w, unsigned int h); BoundingBoxf3 _max_bounding_box() const; @@ -635,6 +652,9 @@ private: void _render_layer_editing_overlay() const; void _render_volumes(bool fake_colors) const; void _render_gizmo() const; +//################################################################################################################################### + void _render_toolbar() const; +//################################################################################################################################### float _get_layers_editing_cursor_z_relative() const; void _perform_layer_editing_action(wxMouseEvent* evt = nullptr); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 23a8f4c15c..ef1d7fd192 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -404,6 +404,15 @@ void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable) it->second->enable_gizmos(enable); } +//################################################################################################################################### +void GLCanvas3DManager::enable_toolbar(wxGLCanvas* canvas, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_toolbar(enable); +} +//################################################################################################################################### + void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable) { CanvasesMap::iterator it = _get_canvas(canvas); @@ -425,6 +434,15 @@ void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow) it->second->allow_multisample(allow); } +//################################################################################################################################### +void GLCanvas3DManager::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->enable_toolbar_item(name, enable); +} +//################################################################################################################################### + void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas) { CanvasesMap::iterator it = _get_canvas(canvas); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index 98205982fb..724c1e4c69 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -110,10 +110,17 @@ public: void enable_picking(wxGLCanvas* canvas, bool enable); void enable_moving(wxGLCanvas* canvas, bool enable); void enable_gizmos(wxGLCanvas* canvas, bool enable); +//################################################################################################################################### + void enable_toolbar(wxGLCanvas* canvas, bool enable); +//################################################################################################################################### void enable_shader(wxGLCanvas* canvas, bool enable); void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); void allow_multisample(wxGLCanvas* canvas, bool allow); +//################################################################################################################################### + void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable); +//################################################################################################################################### + void zoom_to_bed(wxGLCanvas* canvas); void zoom_to_volumes(wxGLCanvas* canvas); void select_view(wxGLCanvas* canvas, const std::string& direction); diff --git a/xs/src/slic3r/GUI/GLToolbar.cpp b/xs/src/slic3r/GUI/GLToolbar.cpp new file mode 100644 index 0000000000..70f7ef79c2 --- /dev/null +++ b/xs/src/slic3r/GUI/GLToolbar.cpp @@ -0,0 +1,388 @@ +#include "GLToolbar.hpp" + +#include "../../libslic3r/Utils.hpp" +#include "../../slic3r/GUI/GLCanvas3D.hpp" + +#include + +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +const unsigned char GLToolbarItem::TooltipTexture::Border_Color[3] = { 0, 0, 0 }; +const int GLToolbarItem::TooltipTexture::Border_Offset = 5; + +bool GLToolbarItem::TooltipTexture::generate(const std::string& text) +{ + reset(); + + if (text.empty()) + return false; + + wxMemoryDC memDC; + // select default font + memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); + + // calculates texture size + wxCoord w, h; + memDC.GetTextExtent(text, &w, &h); + m_width = (int)w + 2 * Border_Offset; + m_height = (int)h + 2 * Border_Offset; + + // generates bitmap + wxBitmap bitmap(m_width, m_height); + + memDC.SelectObject(bitmap); + memDC.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); + memDC.Clear(); + + // draw message + memDC.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOTEXT)); + memDC.DrawText(text, (wxCoord)Border_Offset, (wxCoord)Border_Offset); + + wxPen pen(wxSystemSettings::GetColour(wxSYS_COLOUR_ACTIVEBORDER)); + memDC.SetPen(pen); + wxCoord ww = (wxCoord)m_width - 1; + wxCoord hh = (wxCoord)m_height - 1; + memDC.DrawLine(0, 0, ww, 0); + memDC.DrawLine(ww, 0, ww, hh); + memDC.DrawLine(ww, hh, 0, hh); + memDC.DrawLine(0, hh, 0, 0); + + memDC.SelectObject(wxNullBitmap); + + // Convert the bitmap into a linear data ready to be loaded into the GPU. + wxImage image = bitmap.ConvertToImage(); + + // prepare buffer + std::vector data(4 * m_width * m_height, 0); + for (int h = 0; h < m_height; ++h) + { + int hh = h * m_width; + unsigned char* px_ptr = data.data() + 4 * hh; + for (int w = 0; w < m_width; ++w) + { + *px_ptr++ = image.GetRed(w, h); + *px_ptr++ = image.GetGreen(w, h); + *px_ptr++ = image.GetBlue(w, h); + *px_ptr++ = 255; + } + } + + // sends buffer to gpu + ::glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + ::glGenTextures(1, &m_id); + ::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id); + ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + ::glBindTexture(GL_TEXTURE_2D, 0); + + return true; +} + +GLToolbarItem::GLToolbarItem(EType type, const std::string& name, const std::string& tooltip) + : m_type(type) + , m_state(Disabled) + , m_name(name) + , m_tooltip(tooltip) + , m_tooltip_shown(false) +{ +} + +bool GLToolbarItem::load_textures(const std::string* filenames) +{ + if (filenames == nullptr) + return false; + + std::string path = resources_dir() + "/icons/"; + + for (unsigned int i = (unsigned int)Normal; i < (unsigned int)Num_States; ++i) + { + std::string filename = path + filenames[i]; + if (!m_icon_textures[i].load_from_file(filename, false)) + return false; + } + + if ((m_type == Action) && !m_tooltip.empty()) + { + if (!m_tooltip_texture.generate(m_tooltip)) + return false; + } + + return true; +} + +GLToolbarItem::EState GLToolbarItem::get_state() const +{ + return m_state; +} + +void GLToolbarItem::set_state(GLToolbarItem::EState state) +{ + m_state = state; +} + +const std::string& GLToolbarItem::get_name() const +{ + return m_name; +} + +void GLToolbarItem::show_tooltip() +{ + m_tooltip_shown = true; +} + +void GLToolbarItem::hide_tooltip() +{ + m_tooltip_shown = false; +} + +bool GLToolbarItem::is_tooltip_shown() const +{ + return m_tooltip_shown && (m_tooltip_texture.get_id() > 0); +} + +unsigned int GLToolbarItem::get_icon_texture_id() const +{ + return m_icon_textures[m_state].get_id(); +} + +int GLToolbarItem::get_icon_textures_size() const +{ + return m_icon_textures[Normal].get_width(); +} + +unsigned int GLToolbarItem::get_tooltip_texture_id() const +{ + return m_tooltip_texture.get_id(); +} + +int GLToolbarItem::get_tooltip_texture_width() const +{ + return m_tooltip_texture.get_width(); +} + +int GLToolbarItem::get_tooltip_texture_height() const +{ + return m_tooltip_texture.get_height(); +} + +bool GLToolbarItem::is_separator() const +{ + return m_type == Separator; +} + +GLToolbar::GLToolbar() + : m_enabled(false) + , m_textures_scale(1.0f) + , m_offset_y(5.0f) + , m_gap_x(2.0f) + , m_separator_x(5.0f) +{ +} + +bool GLToolbar::is_enabled() const +{ + return m_enabled; +} + +void GLToolbar::set_enabled(bool enable) +{ + m_enabled = true; +} + +void GLToolbar::set_textures_scale(float scale) +{ + m_textures_scale = scale; +} + +void GLToolbar::set_offset_y(float offset) +{ + m_offset_y = offset; +} + +void GLToolbar::set_gap_x(float gap) +{ + m_gap_x = gap; +} + +void GLToolbar::set_separator_x(float separator) +{ + m_separator_x = separator; +} + +bool GLToolbar::add_item(const GLToolbar::ItemCreationData& data) +{ + GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data.name, data.tooltip); + if ((item == nullptr) || !item->load_textures(data.textures)) + return false; + + m_items.push_back(item); + + if (data.name == "add") + item->show_tooltip(); + + return true; +} + +bool GLToolbar::add_separator() +{ + GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Separator, "", ""); + if (item == nullptr) + return false; + + m_items.push_back(item); + return true; +} + +void GLToolbar::enable_item(const std::string& name) +{ + for (GLToolbarItem* item : m_items) + { + if (item->get_name() == name) + { + item->set_state(GLToolbarItem::Normal); + return; + } + } +} + +void GLToolbar::disable_item(const std::string& name) +{ + for (GLToolbarItem* item : m_items) + { + if (item->get_name() == name) + { + item->set_state(GLToolbarItem::Disabled); + return; + } + } +} + +void GLToolbar::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos) +{ + if (!m_enabled) + return; + + float cnv_w = (float)canvas.get_canvas_size().get_width(); + float width = _get_total_width(); + float left = 0.5f * (cnv_w - width); + float top = m_offset_y; + + for (GLToolbarItem* item : m_items) + { + if (item->is_separator()) + left += (m_separator_x + m_gap_x); + else + { + float tex_size = (float)item->get_icon_textures_size() * m_textures_scale; + float right = left + tex_size; + float bottom = top + tex_size; + + GLToolbarItem::EState state = item->get_state(); + bool inside = (left <= mouse_pos.x) && (mouse_pos.x <= right) && (top <= mouse_pos.y) && (mouse_pos.y <= bottom); + + switch (state) + { + case GLToolbarItem::Normal: + { + if (inside) + item->set_state(GLToolbarItem::Hover); + + break; + } + case GLToolbarItem::Hover: + case GLToolbarItem::Pressed: + { + if (!inside) + item->set_state(GLToolbarItem::Normal); + + break; + } + default: + case GLToolbarItem::Disabled: + { + break; + } + } + left += (tex_size + m_gap_x); + } + } +} + +void GLToolbar::render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const +{ + if (m_items.empty()) + return; + + ::glDisable(GL_DEPTH_TEST); + + ::glPushMatrix(); + ::glLoadIdentity(); + + float cnv_w = (float)canvas.get_canvas_size().get_width(); + float cnv_h = (float)canvas.get_canvas_size().get_height(); + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + float width = _get_total_width(); + float top_x = -0.5f * width * inv_zoom; + float top_y = (0.5f * cnv_h - m_offset_y * m_textures_scale) * inv_zoom; + float scaled_gap_x = m_gap_x * inv_zoom; + float scaled_separator_x = m_separator_x * inv_zoom; + + // renders icons + for (const GLToolbarItem* item : m_items) + { + if (item->is_separator()) + top_x += (scaled_separator_x + scaled_gap_x); + else + { + float tex_size = (float)item->get_icon_textures_size() * m_textures_scale * inv_zoom; + GLTexture::render_texture(item->get_icon_texture_id(), top_x, top_x + tex_size, top_y - tex_size, top_y); + top_x += (tex_size + scaled_gap_x); + } + } + + // renders tooltip + for (const GLToolbarItem* item : m_items) + { + if (!item->is_separator() && item->is_tooltip_shown()) + { + float l = (-0.5f * cnv_w + (float)mouse_pos.x) * inv_zoom; + float r = l + (float)item->get_tooltip_texture_width() * inv_zoom; + float t = (0.5f * cnv_h - (float)mouse_pos.y) * inv_zoom; + float b = t - (float)item->get_tooltip_texture_height() * inv_zoom; + GLTexture::render_texture(item->get_tooltip_texture_id(), l, r, b, t); + break; + } + } + + ::glPopMatrix(); +} + +float GLToolbar::_get_total_width() const +{ + float width = 0.0f; + + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) + { + if (m_items[i]->is_separator()) + width += m_separator_x; + else + width += m_items[i]->get_icon_textures_size(); + + if (i < (unsigned int)m_items.size() - 1) + width += m_gap_x; + } + + return width; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLToolbar.hpp b/xs/src/slic3r/GUI/GLToolbar.hpp new file mode 100644 index 0000000000..09429677c8 --- /dev/null +++ b/xs/src/slic3r/GUI/GLToolbar.hpp @@ -0,0 +1,132 @@ +#ifndef slic3r_GLToolbar_hpp_ +#define slic3r_GLToolbar_hpp_ + +#include "../../slic3r/GUI/GLTexture.hpp" + +#include +#include + +namespace Slic3r { + +class Pointf; + +namespace GUI { + +class GLCanvas3D; + +class GLToolbarItem +{ +public: + enum EType : unsigned char + { + Action, + Separator, + Num_Types + }; + + enum EState : unsigned char + { + Normal, + Hover, + Pressed, + Disabled, + Num_States + }; + + class TooltipTexture : public GLTexture + { + static const unsigned char Border_Color[3]; + static const int Border_Offset; + + public: + bool generate(const std::string& text); + }; + +private: + // icon textures are assumed to be square and all with the same size in pixels, no internal check is done + GLTexture m_icon_textures[Num_States]; + TooltipTexture m_tooltip_texture; + + EType m_type; + EState m_state; + + std::string m_name; + std::string m_tooltip; + + bool m_tooltip_shown; + +public: + GLToolbarItem(EType type, const std::string& name, const std::string& tooltip); + + bool load_textures(const std::string* filenames); + + EState get_state() const; + void set_state(EState state); + + const std::string& get_name() const; + + void show_tooltip(); + void hide_tooltip(); + + bool is_tooltip_shown() const; + + unsigned int get_icon_texture_id() const; + int get_icon_textures_size() const; + + unsigned int get_tooltip_texture_id() const; + int get_tooltip_texture_width() const; + int get_tooltip_texture_height() const; + + bool is_separator() const; +}; + +class GLToolbar +{ +public: + struct ItemCreationData + { + std::string name; + std::string tooltip; + std::string textures[GLToolbarItem::Num_States]; + }; + +private: + typedef std::vector ItemsList; + + bool m_enabled; + ItemsList m_items; + + float m_textures_scale; + float m_offset_y; + float m_gap_x; + float m_separator_x; + +public: + GLToolbar(); + + bool is_enabled() const; + void set_enabled(bool enable); + + void set_textures_scale(float scale); + void set_offset_y(float offset); + void set_gap_x(float gap); + void set_separator_x(float separator); + + bool add_item(const ItemCreationData& data); + bool add_separator(); + + void enable_item(const std::string& name); + void disable_item(const std::string& name); + + void update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos); + + void render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const; + +private: + float _get_total_width() const; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLToolbar_hpp_ diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 38c85c3283..0caaac1d63 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -423,6 +423,13 @@ enable_shader(canvas, enable) CODE: _3DScene::enable_shader((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); +void +enable_toolbar(canvas, enable) + SV *canvas; + bool enable; + CODE: + _3DScene::enable_toolbar((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), enable); + void enable_force_zoom_to_bed(canvas, enable) SV *canvas; @@ -437,6 +444,14 @@ allow_multisample(canvas, allow) CODE: _3DScene::allow_multisample((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), allow); +void +enable_toolbar_item(canvas, item, enable) + SV *canvas; + std::string item; + bool enable; + CODE: + _3DScene::enable_toolbar_item((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), item, enable); + void zoom_to_bed(canvas) SV *canvas; From 36f8050d7b1f7368bd7de431b25cf693bad53126 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 23 Jul 2018 17:35:50 +0200 Subject: [PATCH 064/185] Added popup menu with multiple choice of settings --- xs/src/slic3r/GUI/GUI.cpp | 2 +- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 142 ++++++++++++++++++++------ xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 11 +- 3 files changed, 122 insertions(+), 33 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 82cd21efef..83c428ea18 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -902,7 +902,7 @@ void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, set_event_remove_object(event_remove_object); init_mesh_icons(); - wxWindowUpdateLocker noUpdates(parent); +// wxWindowUpdateLocker noUpdates(parent); // add_objects_list(parent, sizer); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 759c43156f..26e4fbbf71 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -26,6 +26,7 @@ wxCollapsiblePane *m_collpane_settings = nullptr; wxIcon m_icon_modifiermesh; wxIcon m_icon_solidmesh; wxIcon m_icon_manifold_warning; +wxBitmap m_bmp_cog; wxSlider* m_mover_x = nullptr; wxSlider* m_mover_y = nullptr; @@ -66,6 +67,49 @@ inline t_category_icon& get_category_icon() { return CATEGORY_ICON; } +void get_part_options(std::vector& part_options) +{ + PrintRegionConfig config; + part_options = config.keys(); +} + +void get_object_options(std::vector& object_options) +{ + PrintRegionConfig reg_config; + object_options = reg_config.keys(); + PrintObjectConfig obj_config; + std::vector obj_options = obj_config.keys(); + object_options.insert(object_options.end(), obj_options.begin(), obj_options.end()); +} + +// category -> vector ( option ; label ) +typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; +void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) +{ + PrintRegionConfig reg_config; + auto options = reg_config.keys(); + if (!is_part) { + PrintObjectConfig obj_config; + std::vector obj_options = obj_config.keys(); + options.insert(options.end(), obj_options.begin(), obj_options.end()); + } + + DynamicPrintConfig config; + for (auto& option : options) + { + auto const opt = config.def()->get(option); + auto category = opt->category; + if (category.empty()) continue; + + std::pair option_label(option, opt->label); + std::vector< std::pair > new_category; + auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); + cat_opt_label.push_back(option_label); + if (cat_opt_label.size() == 1) + settings_menu[category] = cat_opt_label; + } +} + // C++ class Slic3r::DynamicPrintConfig, initially empty. std::shared_ptr default_config = std::make_shared(); std::shared_ptr config = std::make_shared(); @@ -83,7 +127,12 @@ void set_event_remove_object(const int& event){ void init_mesh_icons(){ m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + + // init icon for manifold warning m_icon_manifold_warning = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); + + // init bitmap for "Add Settings" context menu + m_bmp_cog = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); } bool is_parts_changed(){return m_parts_changed;} @@ -146,11 +195,17 @@ wxBoxSizer* content_objects_list(wxWindow *win) }); - m_objects_ctrl->Bind(wxEVT_KEY_DOWN, [](wxKeyEvent& event) + m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { if (event.GetKeyCode() == WXK_TAB) m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); - else + else if (event.GetKeyCode() == WXK_DELETE +#ifdef __WXOSX__ + || event.GetKeyCode() == WXK_BACK +#endif //__WXOSX__ + ) + remove(); + else event.Skip(); }); @@ -422,6 +477,8 @@ void add_object_settings(wxWindow* parent, wxBoxSizer* sizer) optgroup->disable(); get_optgroups().push_back(optgroup); // ogFrequentlyObjectSettings + +// add_current_settings(); } @@ -582,19 +639,39 @@ void object_ctrl_selection_changed() } } -wxMenu *create_add_part_popupmenu(){ +void get_settings_choice(wxMenu *menu, int id, bool is_part) +{ + auto category_name = menu->GetLabel(id); + + wxArrayString names; + + settings_menu_hierarchy settings_menu; + get_options_menu(settings_menu, is_part); + for (auto cat : settings_menu) + { + if (_(cat.first) == category_name) { + for (auto& pair : cat.second) + names.Add(_(pair.second)); + break; + } + } + + wxArrayInt selections; + auto index = wxGetMultipleChoices(selections, _(L("Select showing settings")), category_name, names); +} + +wxMenu *create_add_part_popupmenu() +{ wxMenu *menu = new wxMenu; - wxWindowID config_id_base = wxWindow::NewControlId(3); + wxWindowID config_id_base = wxWindow::NewControlId(4); menu->Append(config_id_base, _(L("Add part"))); - menu->AppendSeparator(); menu->Append(config_id_base + 1, _(L("Add modifier"))); - menu->AppendSeparator(); - menu->AppendCheckItem(config_id_base + 2, _(L("Add generic"))); + menu->Append(config_id_base + 2, _(L("Add generic"))); wxWindow* win = get_tab_panel()->GetPage(0); - menu->Bind(wxEVT_MENU, [config_id_base, win](wxEvent &event){ + menu->Bind(wxEVT_MENU, [config_id_base, win, menu](wxEvent &event){ switch (event.GetId() - config_id_base) { case 0: on_btn_load(win); @@ -605,39 +682,46 @@ wxMenu *create_add_part_popupmenu(){ case 2: on_btn_load(win, true, true); break; - default: - break; + default:{ + get_settings_choice(menu, event.GetId(), false); + break;} } }); + + menu->AppendSeparator(); + // Append settings popupmenu + auto menu_item = new wxMenuItem(menu, config_id_base + 3, _(L("Add settings"))); + menu_item->SetBitmap(m_bmp_cog); + + auto sub_menu = create_add_settings_popupmenu(false); + + menu_item->SetSubMenu(sub_menu); + menu->Append(menu_item); + return menu; } -wxMenu *create_add_settings_popupmenu() +wxMenu *create_add_settings_popupmenu(bool is_part) { wxMenu *menu = new wxMenu; - auto categories = get_category_icon(); - int category_cnt = categories.size(); - wxWindowID config_id_base = wxWindow::NewControlId(category_cnt); + auto categories = get_category_icon(); - int inc = 0; - for (auto cat : categories) + settings_menu_hierarchy settings_menu; + get_options_menu(settings_menu, is_part); + + for (auto cat : settings_menu) { - auto menu_item = new wxMenuItem(menu, config_id_base + inc, _(cat.first)); - menu_item->SetBitmap(cat.second); - - auto sub_menu = new wxMenu; - sub_menu->AppendCheckItem(wxID_ANY, "Check#1"); - sub_menu->AppendCheckItem(wxID_ANY, "Check#2"); - sub_menu->AppendCheckItem(wxID_ANY, "Check#3"); - sub_menu->AppendCheckItem(wxID_ANY, "Check#4"); - - menu_item->SetSubMenu(sub_menu); - + auto menu_item = new wxMenuItem(menu, wxID_ANY/*config_id_base + inc*/, _(cat.first)); + menu_item->SetBitmap(categories.find(cat.first) == categories.end() ? + wxNullBitmap : categories.at(cat.first)); menu->Append(menu_item); - inc++; } + menu->Bind(wxEVT_MENU, [menu](wxEvent &event) { + get_settings_choice(menu, event.GetId(), true); + }); + return menu; } @@ -660,7 +744,7 @@ void object_ctrl_context_menu() // obj_idx = m_objects_model->GetIdByItem(parent); // auto volume_id = m_objects_model->GetVolumeIdByItem(item); // if (volume_id < 0) return; - auto menu = create_add_settings_popupmenu(); + auto menu = create_add_settings_popupmenu(true); get_tab_panel()->GetPage(0)->PopupMenu(menu); } } diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 8d54fa29bb..532614cab2 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -6,6 +6,7 @@ class wxSizer; class wxBoxSizer; class wxString; class wxArrayString; +class wxMenu; namespace Slic3r { class ModelObject; @@ -15,9 +16,10 @@ namespace GUI { enum ogGroup{ ogFrequentlyChangingParameters, ogFrequentlyObjectSettings, - ogObjectSettings, - ogObjectMovers, - ogPartSettings + ogCurrentSettings +// ogObjectSettings, +// ogObjectMovers, +// ogPartSettings }; enum LambdaTypeIDs{ @@ -43,6 +45,9 @@ void add_objects_list(wxWindow* parent, wxBoxSizer* sizer); void add_object_settings(wxWindow* parent, wxBoxSizer* sizer); void show_collpane_settings(bool expert_mode); +wxMenu *create_add_settings_popupmenu(bool is_part); +wxMenu *create_add_part_popupmenu(); + // Add object to the list //void add_object(const std::string &name); void add_object_to_list(const std::string &name, ModelObject* model_object); From 97a25cf6080a18c6a25eb846411e1adb75568d7c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 24 Jul 2018 12:15:36 +0200 Subject: [PATCH 065/185] Work with model objects like reference (from/to perl side) --- lib/Slic3r/GUI/Plater.pm | 1 + xs/src/slic3r/GUI/GUI.cpp | 2 ++ xs/src/slic3r/GUI/GUI.hpp | 1 + xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 30 +++++++++++++++------------ xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 2 ++ xs/xsp/GUI.xsp | 2 ++ 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 661a81a648..ac54e67d8e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -449,6 +449,7 @@ sub new { my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); Slic3r::GUI::add_expert_mode_part( $self->{right_panel}, $expert_mode_part_sizer, + $self->{model}, $self->{event_object_selection_changed}, $self->{event_object_settings_changed}, $self->{event_remove_object}); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 83c428ea18..72efd7f292 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -893,6 +893,7 @@ wxString from_u8(const std::string &str) } void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, + Model &model, int event_object_selection_changed, int event_object_settings_changed, int event_remove_object) @@ -900,6 +901,7 @@ void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, set_event_object_selection_changed(event_object_selection_changed); set_event_object_settings_changed(event_object_settings_changed); set_event_remove_object(event_remove_object); + set_objects_from_model(model); init_mesh_icons(); // wxWindowUpdateLocker noUpdates(parent); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 58a927cf6f..8199a1349b 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -188,6 +188,7 @@ wxString L_str(const std::string &str); wxString from_u8(const std::string &str); void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, + Model &model, int event_object_selection_changed, int event_object_settings_changed, int event_remove_object); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 26e4fbbf71..4587de6ada 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -40,7 +40,7 @@ int m_selected_object_id = -1; bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler // calls this method again and again and again -ModelObjectPtrs m_objects; +ModelObjectPtrs* m_objects; int m_event_object_selection_changed = 0; int m_event_object_settings_changed = 0; @@ -124,6 +124,10 @@ void set_event_remove_object(const int& event){ m_event_remove_object = event; } +void set_objects_from_model(Model &model) { + m_objects = &(model.objects); +} + void init_mesh_icons(){ m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); @@ -297,7 +301,7 @@ void update_after_moving() Point3 l = m_last_coords; auto d = Pointf3(m.x - l.x, m.y - l.y, m.z - l.z); - auto volume = m_objects[m_selected_object_id]->volumes[volume_id]; + auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; volume->mesh.translate(d.x,d.y,d.z); m_last_coords = m; @@ -546,11 +550,11 @@ void add_object_to_list(const std::string &name, ModelObject* model_object) m_objects_model->SetValue(variant, item, 0); } + ModelObjectPtrs* objects = m_objects; // part_selection_changed(); #ifdef __WXMSW__ object_ctrl_selection_changed(); #endif //__WXMSW__ - m_objects.push_back(model_object); } void delete_object_from_list() @@ -856,9 +860,9 @@ void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/ if (obj_idx < 0) return; wxArrayString part_names; if (is_lambda) - load_lambda(parent, m_objects[obj_idx], part_names, is_modifier); + load_lambda(parent, (*m_objects)[obj_idx], part_names, is_modifier); else - load_part(parent, m_objects[obj_idx], part_names, is_modifier); + load_part(parent, (*m_objects)[obj_idx], part_names, is_modifier); parts_changed(obj_idx); @@ -879,11 +883,11 @@ void on_btn_del() auto volume_id = m_objects_model->GetVolumeIdByItem(item); if (volume_id < 0) return; - auto volume = m_objects[m_selected_object_id]->volumes[volume_id]; + auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; // if user is deleting the last solid part, throw error int solid_cnt = 0; - for (auto vol : m_objects[m_selected_object_id]->volumes) + for (auto vol : (*m_objects)[m_selected_object_id]->volumes) if (!vol->modifier) ++solid_cnt; if (!volume->modifier && solid_cnt == 1) { @@ -891,7 +895,7 @@ void on_btn_del() return; } - m_objects[m_selected_object_id]->delete_volume(volume_id); + (*m_objects)[m_selected_object_id]->delete_volume(volume_id); m_parts_changed = true; parts_changed(m_selected_object_id); @@ -912,7 +916,7 @@ void on_btn_split() if (volume_id < 0) return; - auto volume = m_objects[m_selected_object_id]->volumes[volume_id]; + auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; DynamicPrintConfig& config = get_preset_bundle()->prints.get_edited_preset().config; auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size(); if (volume->split(nozzle_dmrs_cnt) > 1) { @@ -929,7 +933,7 @@ void on_btn_move_up(){ auto volume_id = m_objects_model->GetVolumeIdByItem(item); if (volume_id < 0) return; - auto& volumes = m_objects[m_selected_object_id]->volumes; + auto& volumes = (*m_objects)[m_selected_object_id]->volumes; if (0 < volume_id && volume_id < volumes.size()) { std::swap(volumes[volume_id - 1], volumes[volume_id]); m_parts_changed = true; @@ -948,7 +952,7 @@ void on_btn_move_down(){ auto volume_id = m_objects_model->GetVolumeIdByItem(item); if (volume_id < 0) return; - auto& volumes = m_objects[m_selected_object_id]->volumes; + auto& volumes = (*m_objects)[m_selected_object_id]->volumes; if (0 <= volume_id && volume_id+1 < volumes.size()) { std::swap(volumes[volume_id + 1], volumes[volume_id]); m_parts_changed = true; @@ -975,13 +979,13 @@ void parts_changed(int obj_idx) void update_settings_value() { auto og = get_optgroup(ogFrequentlyObjectSettings); - if (m_selected_object_id < 0 || m_objects.size() <= m_selected_object_id) { + if (m_selected_object_id < 0 || m_objects->size() <= m_selected_object_id) { og->set_value("scale_x", 0); og->set_value("scale_y", 0); og->set_value("scale_z", 0); return; } - auto bb_size = m_objects[m_selected_object_id]->instance_bounding_box(0).size(); + auto bb_size = (*m_objects)[m_selected_object_id]->instance_bounding_box(0).size(); og->set_value("scale_x", int(bb_size.x+0.5)); og->set_value("scale_y", int(bb_size.y+0.5)); og->set_value("scale_z", int(bb_size.z+0.5)); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 532614cab2..4f1c15e3ac 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -10,6 +10,7 @@ class wxMenu; namespace Slic3r { class ModelObject; +class Model; namespace GUI { @@ -73,6 +74,7 @@ void init_mesh_icons(); void set_event_object_selection_changed(const int& event); void set_event_object_settings_changed(const int& event); void set_event_remove_object(const int& event); +void set_objects_from_model(Model &model); bool is_parts_changed(); bool is_part_settings_changed(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 470bbcdd28..98d3169bd7 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -88,11 +88,13 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz (wxFlexGridSizer*)wxPli_sv_2_object(aTHX_ ui_p_sizer, "Wx::FlexGridSizer")); %}; void add_expert_mode_part( SV *ui_parent, SV *ui_sizer, + Model *model, int event_object_selection_changed, int event_object_settings_changed, int event_remove_object) %code%{ Slic3r::GUI::add_expert_mode_part( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), + *model, event_object_selection_changed, event_object_settings_changed, event_remove_object); %}; From 60224415deddd2169562b7270046383425535a66 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 24 Jul 2018 18:39:40 +0200 Subject: [PATCH 066/185] Prototype for adding object/part settings to panel --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 95 +++++++++++++++++++-------- xs/src/slic3r/GUI/OptionsGroup.cpp | 12 ++++ xs/src/slic3r/GUI/OptionsGroup.hpp | 21 ++++-- 3 files changed, 96 insertions(+), 32 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 4587de6ada..a96f8e6204 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -40,7 +40,10 @@ int m_selected_object_id = -1; bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler // calls this method again and again and again -ModelObjectPtrs* m_objects; +ModelObjectPtrs* m_objects; +std::shared_ptr m_config; +std::shared_ptr m_default_config; +wxBoxSizer* m_option_sizer = nullptr; int m_event_object_selection_changed = 0; int m_event_object_settings_changed = 0; @@ -67,24 +70,7 @@ inline t_category_icon& get_category_icon() { return CATEGORY_ICON; } -void get_part_options(std::vector& part_options) -{ - PrintRegionConfig config; - part_options = config.keys(); -} - -void get_object_options(std::vector& object_options) -{ - PrintRegionConfig reg_config; - object_options = reg_config.keys(); - PrintObjectConfig obj_config; - std::vector obj_options = obj_config.keys(); - object_options.insert(object_options.end(), obj_options.begin(), obj_options.end()); -} - -// category -> vector ( option ; label ) -typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; -void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) +std::vector get_options(const bool is_part) { PrintRegionConfig reg_config; auto options = reg_config.keys(); @@ -93,6 +79,14 @@ void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) std::vector obj_options = obj_config.keys(); options.insert(options.end(), obj_options.begin(), obj_options.end()); } + return options; +} + +// category -> vector ( option ; label ) +typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; +void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) +{ + auto options = get_options(is_part); DynamicPrintConfig config; for (auto& option : options) @@ -110,10 +104,6 @@ void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) } } -// C++ class Slic3r::DynamicPrintConfig, initially empty. -std::shared_ptr default_config = std::make_shared(); -std::shared_ptr config = std::make_shared(); - void set_event_object_selection_changed(const int& event){ m_event_object_selection_changed = event; } @@ -476,6 +466,9 @@ void add_object_settings(wxWindow* parent, wxBoxSizer* sizer) def.default_value = new ConfigOptionBool{ false }; optgroup->append_single_option_line(Option(def, "place_on_bed")); + m_option_sizer = new wxBoxSizer(wxVERTICAL); + optgroup->sizer->Add(m_option_sizer, 1, wxEXPAND | wxLEFT, 5); + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20); optgroup->disable(); @@ -651,17 +644,55 @@ void get_settings_choice(wxMenu *menu, int id, bool is_part) settings_menu_hierarchy settings_menu; get_options_menu(settings_menu, is_part); - for (auto cat : settings_menu) + std::vector< std::pair > *settings_list = nullptr; + for (auto& cat : settings_menu) { - if (_(cat.first) == category_name) { + if (_(cat.first) == category_name) { for (auto& pair : cat.second) names.Add(_(pair.second)); + settings_list = &cat.second; break; } } + if (!settings_list) + return; wxArrayInt selections; auto index = wxGetMultipleChoices(selections, _(L("Select showing settings")), category_name, names); + + auto szr = m_option_sizer; + szr->Clear(true); + auto parent = get_optgroup(ogFrequentlyObjectSettings)->parent(); + auto config = m_config; + auto extra_column = [parent](const Line& line) + { + auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line + + auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("erase.png")), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); +// EVT_BUTTON($self, $btn, sub{ +// $self->{config}->erase($opt_key); +// $self->{on_change}->() if $self->{on_change}; +// wxTheApp->CallAfter(sub{ $self->update_optgroup }); +// }); + return btn; + }; + + auto optgroup = std::make_shared(parent, category_name, m_default_config.get(), false, ogDEFAULT, extra_column); + + optgroup->label_width = 180; + + for (auto sel : selections) + { + Option option = optgroup->get_option((*settings_list)[sel].first); + option.opt.width = 70; + optgroup->append_single_option_line(option); +// auto str = new wxStaticText(parent, wxID_ANY, "la-la"); +// m_option_sizer->Add(str, /*0*/1, wxEXPAND | wxALL, 10); + } + m_option_sizer->Add(optgroup->sizer, /*0*/1, wxEXPAND | wxALL, 0); + get_right_panel()->Refresh(); + get_right_panel()->GetParent()->Layout();// Refresh(); } wxMenu *create_add_part_popupmenu() @@ -997,13 +1028,25 @@ void part_selection_changed() int obj_idx = -1; if (item) { - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + auto og = get_optgroup(ogFrequentlyObjectSettings); + bool is_part = false; + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { obj_idx = m_objects_model->GetIdByItem(item); + og->set_name(" "+ _(L("Object Settings")) + " "); + m_config = std::make_shared((*m_objects)[obj_idx]->config); + } else { auto parent = m_objects_model->GetParent(item); // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene obj_idx = m_objects_model->GetIdByItem(parent); + og->set_name(" "+ _(L("Part Settings")) + " "); + is_part = true; + auto volume_id = m_objects_model->GetVolumeIdByItem(item); + m_config = std::make_shared((*m_objects)[obj_idx]->volumes[volume_id]->config); } + + auto config = m_config; + m_default_config = std::make_shared(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part))); } m_selected_object_id = obj_idx; diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index ca612e95cd..c289f25dba 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -153,6 +153,18 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* m_panel->Layout(); #endif /* __WXGTK__ */ + // if we have an extra column, build it + if (extra_column) { + if (extra_column) { + grid_sizer->Add(extra_column(line), 0, wxALIGN_CENTER_VERTICAL, 0); + } + else { + // if the callback provides no sizer for the extra cell, put a spacer + grid_sizer->AddSpacer(1); + } + } + + // Build a label if we have it wxStaticText* label=nullptr; if (label_width != 0) { diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 6697ed75a4..949f8d8b33 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -33,7 +33,6 @@ enum ogDrawFlag{ /// Widget type describes a function object that returns a wxWindow (our widget) and accepts a wxWidget (parent window). using widget_t = std::function;//!std::function; -using column_t = std::function; //auto default_label_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour //auto modified_label_clr = *new wxColour(254, 189, 101); @@ -76,10 +75,13 @@ private: std::vector m_extra_widgets;//! {std::vector()}; }; +using column_t = std::function;//std::function; + using t_optionfield_map = std::map; using t_opt_map = std::map< std::string, std::pair >; class OptionsGroup { + wxStaticBox* stb; public: const bool staticbox {true}; const wxString title {wxString("")}; @@ -136,14 +138,20 @@ public: return true; } + void set_name(const wxString& new_name) { + stb->SetLabel(new_name); + } + inline void enable() { for (auto& field : m_fields) field.second->enable(); } inline void disable() { for (auto& field : m_fields) field.second->disable(); } void set_flag(ogDrawFlag flag) { m_flag = flag; } void set_grid_vgap(int gap) { m_grid_sizer->SetVGap(gap); } - OptionsGroup(wxWindow* _parent, const wxString& title, bool is_tab_opt=false, ogDrawFlag flag = ogDEFAULT) : - m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), staticbox(title!=""), m_flag(flag) { - auto stb = new wxStaticBox(_parent, wxID_ANY, title); + OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, + ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) : + m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), + staticbox(title!=""), m_flag(flag), extra_column(extra_clmn){ + stb = new wxStaticBox(_parent, wxID_ANY, title); stb->SetFont(bold_font()); sizer = (staticbox ? new wxStaticBoxSizer(stb/*new wxStaticBox(_parent, wxID_ANY, title)*/, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; @@ -199,8 +207,9 @@ protected: class ConfigOptionsGroup: public OptionsGroup { public: - ConfigOptionsGroup(wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, bool is_tab_opt = false, ogDrawFlag flag = ogDEFAULT) : - OptionsGroup(parent, title, is_tab_opt, flag), m_config(_config) {} + ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, + bool is_tab_opt = false, ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) : + OptionsGroup(parent, title, is_tab_opt, flag, extra_clmn), m_config(_config) {} /// reference to libslic3r config, non-owning pointer (?). DynamicPrintConfig* m_config {nullptr}; From 93a05c49461d2c40efb2ee77fed645c559565879 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 25 Jul 2018 10:01:17 +0200 Subject: [PATCH 067/185] Use wxWidgets tooltip for 3D scene toolbar --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 14 +++- xs/src/slic3r/GUI/GLCanvas3D.hpp | 4 + xs/src/slic3r/GUI/GLToolbar.cpp | 140 ++++--------------------------- xs/src/slic3r/GUI/GLToolbar.hpp | 23 +---- 4 files changed, 32 insertions(+), 149 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index b71f1ae91c..07020bdb70 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -3438,6 +3438,14 @@ void GLCanvas3D::reset_legend_texture() m_legend_texture.reset(); } +//################################################################################################################################### +void GLCanvas3D::set_tooltip(const std::string& tooltip) +{ + if (m_canvas != nullptr) + m_canvas->SetToolTip(tooltip); +} +//################################################################################################################################### + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; @@ -3518,7 +3526,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "ccw45"; - item.tooltip = GUI::L_str("Rotate CCW 45°"); + item.tooltip = GUI::L_str("Rotate CCW 45 degrees"); item.textures[GLToolbarItem::Normal] = "arrow_rotate_anticlockwise_normal_36.png"; item.textures[GLToolbarItem::Hover] = "arrow_rotate_anticlockwise_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "arrow_rotate_anticlockwise_pressed_36.png"; @@ -3527,7 +3535,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "cw45"; - item.tooltip = GUI::L_str("Rotate CW 45°"); + item.tooltip = GUI::L_str("Rotate CW 45 degrees"); item.textures[GLToolbarItem::Normal] = "arrow_rotate_clockwise_normal_36.png"; item.textures[GLToolbarItem::Hover] = "arrow_rotate_clockwise_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "arrow_rotate_clockwise_pressed_36.png"; @@ -3886,7 +3894,7 @@ void GLCanvas3D::_picking_pass() const m_gizmos.reset_all_states(); //################################################################################################################################### - m_toolbar.update_hover_state(*this, pos); + m_toolbar.update_hover_state(const_cast(*this), pos); //################################################################################################################################### } } diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index 9bca50dad0..24b848d6c4 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -619,6 +619,10 @@ public: void reset_legend_texture(); +//################################################################################################################################### + void set_tooltip(const std::string& tooltip); +//################################################################################################################################### + private: bool _is_shown_on_screen() const; void _force_zoom_to_bed(); diff --git a/xs/src/slic3r/GUI/GLToolbar.cpp b/xs/src/slic3r/GUI/GLToolbar.cpp index 70f7ef79c2..fe861a45de 100644 --- a/xs/src/slic3r/GUI/GLToolbar.cpp +++ b/xs/src/slic3r/GUI/GLToolbar.cpp @@ -12,85 +12,11 @@ namespace Slic3r { namespace GUI { -const unsigned char GLToolbarItem::TooltipTexture::Border_Color[3] = { 0, 0, 0 }; -const int GLToolbarItem::TooltipTexture::Border_Offset = 5; - -bool GLToolbarItem::TooltipTexture::generate(const std::string& text) -{ - reset(); - - if (text.empty()) - return false; - - wxMemoryDC memDC; - // select default font - memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); - - // calculates texture size - wxCoord w, h; - memDC.GetTextExtent(text, &w, &h); - m_width = (int)w + 2 * Border_Offset; - m_height = (int)h + 2 * Border_Offset; - - // generates bitmap - wxBitmap bitmap(m_width, m_height); - - memDC.SelectObject(bitmap); - memDC.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); - memDC.Clear(); - - // draw message - memDC.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOTEXT)); - memDC.DrawText(text, (wxCoord)Border_Offset, (wxCoord)Border_Offset); - - wxPen pen(wxSystemSettings::GetColour(wxSYS_COLOUR_ACTIVEBORDER)); - memDC.SetPen(pen); - wxCoord ww = (wxCoord)m_width - 1; - wxCoord hh = (wxCoord)m_height - 1; - memDC.DrawLine(0, 0, ww, 0); - memDC.DrawLine(ww, 0, ww, hh); - memDC.DrawLine(ww, hh, 0, hh); - memDC.DrawLine(0, hh, 0, 0); - - memDC.SelectObject(wxNullBitmap); - - // Convert the bitmap into a linear data ready to be loaded into the GPU. - wxImage image = bitmap.ConvertToImage(); - - // prepare buffer - std::vector data(4 * m_width * m_height, 0); - for (int h = 0; h < m_height; ++h) - { - int hh = h * m_width; - unsigned char* px_ptr = data.data() + 4 * hh; - for (int w = 0; w < m_width; ++w) - { - *px_ptr++ = image.GetRed(w, h); - *px_ptr++ = image.GetGreen(w, h); - *px_ptr++ = image.GetBlue(w, h); - *px_ptr++ = 255; - } - } - - // sends buffer to gpu - ::glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - ::glGenTextures(1, &m_id); - ::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id); - ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); - ::glBindTexture(GL_TEXTURE_2D, 0); - - return true; -} - GLToolbarItem::GLToolbarItem(EType type, const std::string& name, const std::string& tooltip) : m_type(type) , m_state(Disabled) , m_name(name) , m_tooltip(tooltip) - , m_tooltip_shown(false) { } @@ -108,12 +34,6 @@ bool GLToolbarItem::load_textures(const std::string* filenames) return false; } - if ((m_type == Action) && !m_tooltip.empty()) - { - if (!m_tooltip_texture.generate(m_tooltip)) - return false; - } - return true; } @@ -132,19 +52,9 @@ const std::string& GLToolbarItem::get_name() const return m_name; } -void GLToolbarItem::show_tooltip() +const std::string& GLToolbarItem::get_tooltip() const { - m_tooltip_shown = true; -} - -void GLToolbarItem::hide_tooltip() -{ - m_tooltip_shown = false; -} - -bool GLToolbarItem::is_tooltip_shown() const -{ - return m_tooltip_shown && (m_tooltip_texture.get_id() > 0); + return m_tooltip; } unsigned int GLToolbarItem::get_icon_texture_id() const @@ -157,21 +67,6 @@ int GLToolbarItem::get_icon_textures_size() const return m_icon_textures[Normal].get_width(); } -unsigned int GLToolbarItem::get_tooltip_texture_id() const -{ - return m_tooltip_texture.get_id(); -} - -int GLToolbarItem::get_tooltip_texture_width() const -{ - return m_tooltip_texture.get_width(); -} - -int GLToolbarItem::get_tooltip_texture_height() const -{ - return m_tooltip_texture.get_height(); -} - bool GLToolbarItem::is_separator() const { return m_type == Separator; @@ -224,9 +119,6 @@ bool GLToolbar::add_item(const GLToolbar::ItemCreationData& data) m_items.push_back(item); - if (data.name == "add") - item->show_tooltip(); - return true; } @@ -264,7 +156,7 @@ void GLToolbar::disable_item(const std::string& name) } } -void GLToolbar::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos) +void GLToolbar::update_hover_state(GLCanvas3D& canvas, const Pointf& mouse_pos) { if (!m_enabled) return; @@ -274,6 +166,8 @@ void GLToolbar::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse float left = 0.5f * (cnv_w - width); float top = m_offset_y; + std::string tooltip = ""; + for (GLToolbarItem* item : m_items) { if (item->is_separator()) @@ -297,6 +191,14 @@ void GLToolbar::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse break; } case GLToolbarItem::Hover: + { + if (inside) + tooltip = item->get_tooltip(); + else + item->set_state(GLToolbarItem::Normal); + + break; + } case GLToolbarItem::Pressed: { if (!inside) @@ -313,6 +215,8 @@ void GLToolbar::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse left += (tex_size + m_gap_x); } } + + canvas.set_tooltip(tooltip); } void GLToolbar::render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const @@ -349,20 +253,6 @@ void GLToolbar::render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const } } - // renders tooltip - for (const GLToolbarItem* item : m_items) - { - if (!item->is_separator() && item->is_tooltip_shown()) - { - float l = (-0.5f * cnv_w + (float)mouse_pos.x) * inv_zoom; - float r = l + (float)item->get_tooltip_texture_width() * inv_zoom; - float t = (0.5f * cnv_h - (float)mouse_pos.y) * inv_zoom; - float b = t - (float)item->get_tooltip_texture_height() * inv_zoom; - GLTexture::render_texture(item->get_tooltip_texture_id(), l, r, b, t); - break; - } - } - ::glPopMatrix(); } diff --git a/xs/src/slic3r/GUI/GLToolbar.hpp b/xs/src/slic3r/GUI/GLToolbar.hpp index 09429677c8..5a1af5b37f 100644 --- a/xs/src/slic3r/GUI/GLToolbar.hpp +++ b/xs/src/slic3r/GUI/GLToolbar.hpp @@ -33,19 +33,9 @@ public: Num_States }; - class TooltipTexture : public GLTexture - { - static const unsigned char Border_Color[3]; - static const int Border_Offset; - - public: - bool generate(const std::string& text); - }; - private: // icon textures are assumed to be square and all with the same size in pixels, no internal check is done GLTexture m_icon_textures[Num_States]; - TooltipTexture m_tooltip_texture; EType m_type; EState m_state; @@ -53,8 +43,6 @@ private: std::string m_name; std::string m_tooltip; - bool m_tooltip_shown; - public: GLToolbarItem(EType type, const std::string& name, const std::string& tooltip); @@ -65,18 +53,11 @@ public: const std::string& get_name() const; - void show_tooltip(); - void hide_tooltip(); - - bool is_tooltip_shown() const; + const std::string& get_tooltip() const; unsigned int get_icon_texture_id() const; int get_icon_textures_size() const; - unsigned int get_tooltip_texture_id() const; - int get_tooltip_texture_width() const; - int get_tooltip_texture_height() const; - bool is_separator() const; }; @@ -118,7 +99,7 @@ public: void enable_item(const std::string& name); void disable_item(const std::string& name); - void update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos); + void update_hover_state(GLCanvas3D& canvas, const Pointf& mouse_pos); void render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const; From ac9ba61f06a75ef504612dcadc50429c302c0091 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 25 Jul 2018 16:13:20 +0200 Subject: [PATCH 068/185] Correct updating for settings list of the object/part --- resources/icons/add_object.png | Bin 0 -> 591 bytes resources/icons/lambda_.png | Bin 0 -> 422 bytes resources/icons/object.png | Bin 0 -> 618 bytes xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 174 +++++++++++++++++++------- xs/src/slic3r/GUI/wxExtensions.cpp | 4 +- 5 files changed, 132 insertions(+), 46 deletions(-) create mode 100644 resources/icons/add_object.png create mode 100644 resources/icons/lambda_.png create mode 100644 resources/icons/object.png diff --git a/resources/icons/add_object.png b/resources/icons/add_object.png new file mode 100644 index 0000000000000000000000000000000000000000..40059f5ac0de3b5c5db4ab492b4de5952fe6f25d GIT binary patch literal 591 zcmV-V0Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&0pv+UK~y+TRg+Jv zaZwb;_qpT_82B?J1BT2bLng{V$`_!ND4Cd;%D`NF4&T6gm4pnGWMV=LUdg~r`Lo{h zJg<%WtJPlnS!+GEbM{g9`(19g8$)wA{FQh-E=nnx&*#*1eYsppAP``VS@k)%*=!^n z4s$RTi%GRwW!`KypB*BL#e$lK83se45DPHaZnrFg)oLZD(@AEt8S8etovYjJ7Kg(j zDxFSKNF)-n+wGoX|Bw86I@B7ChLlPr_IJBoX|-C&6@@~f@N9h#U$56+CSW`svtL7l zPNzc=F&GS3csw2qfQOaKsAMw9Je$qpzOctI-Xn%#s9y#U zU;;zJJx=Nr25};|idCxm+$4iA27wAjIj|rivR4Ol{@9%G}=3k_d2K_Gs+%j!~9N?mOyPx#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&0Xs=VK~y+Tjgm2n zgFqOCCn9E}5j!tnX<@0wf?!CO%1S#g5EX?~!3%f?58x4UfDqDpgf!m4MfaJ1bVHD^ z4;-HVyl?zx95#GZ-}l^2S^y~I`#xiAydx3I1?6lw}FC0BpBg?w;zp4rAN4x~{{Fz;3tup0wZZ(U0eOj^o(2UDq`Nv@6s( zKyH>{7|1;yk1WgLIHuz5cC#!ChJk+T;c~gCstQw86&NZCf^fZFVMahv6spcjl4P^_ zilPVxYomoc&uMA|<|BJm(=?`Ou2w78b?^5(Dzy8t`$d3sKA*`T%>f+12Ov#TjFhGy z^4AhA@TM6+ngS!BY1(skK?G2m1LMP(BuO~9SBavCI^8HrLmc?0(919kzv>=a#6l&K Q^Z)<=07*qoM6N<$g5_(f{r~^~ literal 0 HcmV?d00001 diff --git a/resources/icons/object.png b/resources/icons/object.png new file mode 100644 index 0000000000000000000000000000000000000000..ff4974a532b083cd7e8f741b1a2c81d2d1184045 GIT binary patch literal 618 zcmV-w0+s!VP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&0sl!vK~y+TjZ?qN zfl(Ab@9ReOv6_rTgp_1pmrdb*_c^@fGkkqd zz1{2F=Q;PBdmhT;@emROD2hU=s`4J#@Aq`S-^uIsz9U4>=aW}xz~=;?qUCZ)y+CKFw+SIXz}R4f)L7!1CdL{iI{#A>w?jYdOwJRae8yG6ZT7xVcXM~uVaAX=@K z@caFI-fT9-X0zclrCP0u#bQCZT#hoC4EcP%cm7wol0!P3?j0dI9*;B_47kETAVBJN zyCt*P%%sm&0EoYVw)A>ESu7S>uh*pFmQ*T5NEDC9xdK@tzXg)w!>!$Jmv4+lqlAhR ziNx!Ia5yYxvl-f9j7B3-E|-PV=@gMjM8sk-;cz%asZ;;VkXx=~!dvL&o23i%aYPZ{TI-Mw+ z&62b;)oL}`?RI1|8p&WV@E$Rofpb2esnhAOBNh2b; m_config; +std::shared_ptr m_config; std::shared_ptr m_default_config; wxBoxSizer* m_option_sizer = nullptr; +// option groups for settings +std::vector > m_og_settings; + int m_event_object_selection_changed = 0; int m_event_object_settings_changed = 0; int m_event_remove_object = 0; @@ -119,8 +122,8 @@ void set_objects_from_model(Model &model) { } void init_mesh_icons(){ - m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); - m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("lambda_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); + m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); // init icon for manifold warning m_icon_manifold_warning = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); @@ -636,63 +639,136 @@ void object_ctrl_selection_changed() } } +//update_optgroup +void update_settings_list() +{ + auto parent = get_optgroup(ogFrequentlyObjectSettings)->parent(); +// There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/Slic3r/issues/898 and https://github.com/prusa3d/Slic3r/issues/952. +// The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, +// we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. +#ifdef __linux__ + std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); +#else + wxWindowUpdateLocker noUpdates(parent); +#endif + + m_option_sizer->Clear(true); + + if (m_config) + { + auto extra_column = [parent](const Line& line) + { + auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line + + auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("erase.png")), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + btn->Bind(wxEVT_BUTTON, [opt_key](wxEvent &event){ + (*m_config)->erase(opt_key); + wxTheApp->CallAfter([]() { update_settings_list(); }); + }); + return btn; + }; + + std::map> cat_options; + auto opt_keys = (*m_config)->keys(); + if (opt_keys.size() == 1 && opt_keys[0] == "extruder") + return; + + for (auto& opt_key : opt_keys) { + auto category = (*m_config)->def()->get(opt_key)->category; + if (category.empty()) continue; + + std::vector< std::string > new_category; + + auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category); + cat_opt.push_back(opt_key); + if (cat_opt.size() == 1) + cat_options[category] = cat_opt; + } + + + m_og_settings.resize(0); + for (auto& cat : cat_options) { + if (cat.second.size() == 1 && cat.second[0] == "extruder") + continue; + + auto optgroup = std::make_shared(parent, cat.first, *m_config, false, ogDEFAULT, extra_column); + optgroup->label_width = 100; + optgroup->sidetext_width = 70; + + for (auto& opt : cat.second) + { + if (opt == "extruder") + continue; + Option option = optgroup->get_option(opt); + option.opt.width = 70; + optgroup->append_single_option_line(option); + } + optgroup->reload_config(); + m_option_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); + m_og_settings.push_back(optgroup); + } + } + +#ifdef __linux__ + no_updates.reset(nullptr); +#endif + + get_right_panel()->Refresh(); + get_right_panel()->GetParent()->Layout();; +} + void get_settings_choice(wxMenu *menu, int id, bool is_part) { auto category_name = menu->GetLabel(id); wxArrayString names; + wxArrayInt selections; settings_menu_hierarchy settings_menu; get_options_menu(settings_menu, is_part); std::vector< std::pair > *settings_list = nullptr; + + auto opt_keys = (*m_config)->keys(); + for (auto& cat : settings_menu) { if (_(cat.first) == category_name) { - for (auto& pair : cat.second) + int sel = 0; + for (auto& pair : cat.second) { names.Add(_(pair.second)); + if (find(opt_keys.begin(), opt_keys.end(), pair.first) != opt_keys.end()) + selections.Add(sel); + sel++; + } settings_list = &cat.second; break; } - } + } + if (!settings_list) return; - wxArrayInt selections; - auto index = wxGetMultipleChoices(selections, _(L("Select showing settings")), category_name, names); - - auto szr = m_option_sizer; - szr->Clear(true); - auto parent = get_optgroup(ogFrequentlyObjectSettings)->parent(); - auto config = m_config; - auto extra_column = [parent](const Line& line) - { - auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line - - auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("erase.png")), wxBITMAP_TYPE_PNG), - wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); -// EVT_BUTTON($self, $btn, sub{ -// $self->{config}->erase($opt_key); -// $self->{on_change}->() if $self->{on_change}; -// wxTheApp->CallAfter(sub{ $self->update_optgroup }); -// }); - return btn; - }; - - auto optgroup = std::make_shared(parent, category_name, m_default_config.get(), false, ogDEFAULT, extra_column); - - optgroup->label_width = 180; + if (wxGetMultipleChoices(selections, _(L("Select showing settings")), category_name, names) ==0 ) + return; + std::vector selected_options; for (auto sel : selections) + selected_options.push_back((*settings_list)[sel].first); + + for (auto& setting:(*settings_list) ) { - Option option = optgroup->get_option((*settings_list)[sel].first); - option.opt.width = 70; - optgroup->append_single_option_line(option); -// auto str = new wxStaticText(parent, wxID_ANY, "la-la"); -// m_option_sizer->Add(str, /*0*/1, wxEXPAND | wxALL, 10); + auto& opt_key = setting.first; + if (find(opt_keys.begin(), opt_keys.end(), opt_key) != opt_keys.end() && + find(selected_options.begin(), selected_options.end(), opt_key) == selected_options.end()) + (*m_config)->erase(opt_key); + + if(find(opt_keys.begin(), opt_keys.end(), opt_key) == opt_keys.end() && + find(selected_options.begin(), selected_options.end(), opt_key) != selected_options.end()) + (*m_config)->set_key_value(opt_key, m_default_config.get()->option(opt_key)->clone()); } - m_option_sizer->Add(optgroup->sizer, /*0*/1, wxEXPAND | wxALL, 0); - get_right_panel()->Refresh(); - get_right_panel()->GetParent()->Layout();// Refresh(); + + update_settings_list(); } wxMenu *create_add_part_popupmenu() @@ -700,9 +776,14 @@ wxMenu *create_add_part_popupmenu() wxMenu *menu = new wxMenu; wxWindowID config_id_base = wxWindow::NewControlId(4); - menu->Append(config_id_base, _(L("Add part"))); - menu->Append(config_id_base + 1, _(L("Add modifier"))); - menu->Append(config_id_base + 2, _(L("Add generic"))); + std::vector menu_items = { L("Add part"), L("Add modifier"), L("Add generic") }; + int i = 0; + for (auto& item : menu_items) { + auto menu_item = new wxMenuItem(menu, config_id_base + i, _(item)); + menu_item->SetBitmap(i == 0 ? m_icon_solidmesh : m_icon_modifiermesh); + menu->Append(menu_item); + i++; + } wxWindow* win = get_tab_panel()->GetPage(0); @@ -1032,22 +1113,27 @@ void part_selection_changed() bool is_part = false; if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { obj_idx = m_objects_model->GetIdByItem(item); - og->set_name(" "+ _(L("Object Settings")) + " "); - m_config = std::make_shared((*m_objects)[obj_idx]->config); + og->set_name(" " + _(L("Object Settings")) + " "); + m_config = std::make_shared(&(*m_objects)[obj_idx]->config); } else { auto parent = m_objects_model->GetParent(item); // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene obj_idx = m_objects_model->GetIdByItem(parent); - og->set_name(" "+ _(L("Part Settings")) + " "); + og->set_name(" " + _(L("Part Settings")) + " "); is_part = true; auto volume_id = m_objects_model->GetVolumeIdByItem(item); - m_config = std::make_shared((*m_objects)[obj_idx]->volumes[volume_id]->config); + m_config = std::make_shared(&(*m_objects)[obj_idx]->volumes[volume_id]->config); } auto config = m_config; m_default_config = std::make_shared(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part))); } + else + m_config = nullptr; + + update_settings_list(); + m_selected_object_id = obj_idx; update_settings_value(); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 23a87af9e9..422c999d19 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -349,7 +349,7 @@ void PrusaCollapsiblePaneMSW::Collapse(bool collapse) // ---------------------------------------------------------------------------- void PrusaObjectDataViewModelNode::set_object_action_icon() { - m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add.png")), wxBITMAP_TYPE_PNG); + m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add_object.png")), wxBITMAP_TYPE_PNG); } void PrusaObjectDataViewModelNode::set_part_action_icon() { m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); @@ -391,7 +391,7 @@ wxDataViewItem PrusaObjectDataViewModel::AddChild( const wxDataViewItem &parent_ if (root->GetChildren().Count() == 0) { - auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, 0); root->Append(node); // notify control From ef0d667d6c9d87b086702f35e8d399129e95e193 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 25 Jul 2018 17:48:15 +0200 Subject: [PATCH 069/185] Extruder updating --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 45bbd92670..252dcec268 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -165,8 +165,9 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->AppendTextColumn(_(L("Scale")), 2, wxDATAVIEW_CELL_INERT, 55, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); - // column 2 of the view control: + // column 3 of the view control: wxArrayString choices; + choices.Add("default"); choices.Add("1"); choices.Add("2"); choices.Add("3"); @@ -177,6 +178,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) new wxDataViewColumn(_(L("Extruder")), c, 3, 60, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); m_objects_ctrl->AppendColumn(column3); + // column 4 of the view control: m_objects_ctrl->AppendBitmapColumn("", 4, wxDATAVIEW_CELL_INERT, 25, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); @@ -206,6 +208,23 @@ wxBoxSizer* content_objects_list(wxWindow *win) event.Skip(); }); + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) + { + if (event.GetColumn() == 3) + { + if (!*m_config) + return; + wxVariant variant; + m_objects_model->GetValue(variant, event.GetItem(), 3); + auto str = variant.GetString(); + int extruder = str.size() > 1 ? 0 : atoi(str.c_str()); + +// if ((*m_config)->has("extruder")) + auto config = m_config; + (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); + } + }); + return objects_sz; } From da18e25dfb917a2e165973160b3340a46888a4a8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 26 Jul 2018 10:59:03 +0200 Subject: [PATCH 070/185] Added callback for 3dScene updating after extruder changing --- lib/Slic3r/GUI/MainFrame.pm | 13 +++++++++-- lib/Slic3r/GUI/Plater.pm | 4 +++- resources/icons/erase.png | Bin 0 -> 488 bytes resources/icons/exclamation_mark_.png | Bin 0 -> 327 bytes xs/src/slic3r/GUI/GUI.cpp | 4 +++- xs/src/slic3r/GUI/GUI.hpp | 3 ++- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 31 ++++++++++++++------------ xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 1 + xs/xsp/GUI.xsp | 6 +++-- 9 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 resources/icons/erase.png create mode 100644 resources/icons/exclamation_mark_.png diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 1465c12159..6cf80f24af 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -31,6 +31,8 @@ our $OBJECT_SELECTION_CHANGED_EVENT = Wx::NewEventType; our $OBJECT_SETTINGS_CHANGED_EVENT = Wx::NewEventType; # 5) To inform about a remove of object our $OBJECT_REMOVE_EVENT = Wx::NewEventType; +# 6) To inform about a update of the scene +our $UPDATE_SCENE_EVENT = Wx::NewEventType; sub new { my ($class, %params) = @_; @@ -125,6 +127,7 @@ sub _init_tabpanel { event_object_selection_changed => $OBJECT_SELECTION_CHANGED_EVENT, event_object_settings_changed => $OBJECT_SETTINGS_CHANGED_EVENT, event_remove_object => $OBJECT_REMOVE_EVENT, + event_update_scene => $UPDATE_SCENE_EVENT, ), L("Plater")); if (!$self->{no_controller}) { $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), L("Controller")); @@ -191,7 +194,7 @@ sub _init_tabpanel { $self->{plater}->item_changed_selection($obj_idx); }); - # The following event is emited by the C++ Tab implementation on object settings change. + # The following event is emited by the C++ GUI implementation on object settings change. EVT_COMMAND($self, -1, $OBJECT_SETTINGS_CHANGED_EVENT, sub { my ($self, $event) = @_; @@ -201,11 +204,17 @@ sub _init_tabpanel { $self->{plater}->changed_object_settings($obj_idx, $parts_changed, $part_settings_changed); }); - # The following event is emited by the C++ Tab implementation on object settings change. + # The following event is emited by the C++ GUI implementation on object remove. EVT_COMMAND($self, -1, $OBJECT_REMOVE_EVENT, sub { my ($self, $event) = @_; $self->{plater}->remove(); }); + + # The following event is emited by the C++ GUI implementation on extruder change for object. + EVT_COMMAND($self, -1, $UPDATE_SCENE_EVENT, sub { + my ($self, $event) = @_; + $self->{plater}->update(); + }); Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ac54e67d8e..ac4b9f78f4 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -58,6 +58,7 @@ sub new { $self->{event_object_selection_changed} = $params{event_object_selection_changed}; $self->{event_object_settings_changed} = $params{event_object_settings_changed}; $self->{event_remove_object} = $params{event_remove_object}; + $self->{event_update_scene} = $params{event_update_scene}; # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; @@ -452,7 +453,8 @@ sub new { $self->{model}, $self->{event_object_selection_changed}, $self->{event_object_settings_changed}, - $self->{event_remove_object}); + $self->{event_remove_object}, + $self->{event_update_scene}); # if ($expert_mode_part_sizer->IsShown(2)==1) # { # $expert_mode_part_sizer->Layout; diff --git a/resources/icons/erase.png b/resources/icons/erase.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4cfd755c17deb467a6b7724e3c99f2cdd36912 GIT binary patch literal 488 zcmVP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&0ewkCK~y+T)ssC- zLSYog&-IdXsNfYUj`4; z6zA{jH7rUcSe+aT-+?t?jQt*;s#TmOlQ_#{P|0R-Z=F3i8u%EG<8Cssc3d10d6H=8 zstSur1q@9CITQj@*Fio!griPJ+ySb{=W{!E1CTN4P?BZQ*Q8K@4@Ci_z2s;F+z1-n z6o5>3Y`%%e8?#ysw)E&Am4dg;2Bzh*b5WAGP7hoGL)nFWARPwLDBk7s!habK@zU?d zWw$H*J!$2|3Bb%-Exd|E@H&wYz9DABWv_>QSr)#%XXAba8vS4pFT!D5QC5bt*%|Na zb-XPW#oCJ#KyA12ED*p!E+>5Zu{X$hEQYU|Ce}7ifSs!1DM_*=%W8ZLz!mQ3@r|X8 e&AQL70(Y)*K0-;2lpF#}Etur4tTv9Wvl)`~5}E=9h?Fv-$dY zdpKWt3aT?pS}i}fY;&eZ&NaT4T7igvX7_K+%2SDK{LC7>IIAP2wLN#|vIEKfrzbp& zl2>KW`IB6{P=RSxY?u<0WL`Fx&BFe+1$OF_6doi#|Iqzl^#LFL1HYH4xS0j5=h(U4 zE+KxB^becOvnMDreiFV>Hm5=Qxoho*sE>aoGrV*klo$MKlsNOI#ZSQdLEV%@mwhgu zG&DXW$mh4ccKh1#$|dNnX2*u3T9R&)T2u=nPF#FjHc!@V(@Kv;Qb`q0^>@AFR*T;1 U>+j7O3iKy~r>mdKI;Vst05X$(rvLx| literal 0 HcmV?d00001 diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 72efd7f292..cef9d95ed9 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -896,11 +896,13 @@ void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, Model &model, int event_object_selection_changed, int event_object_settings_changed, - int event_remove_object) + int event_remove_object, + int event_update_scene) { set_event_object_selection_changed(event_object_selection_changed); set_event_object_settings_changed(event_object_settings_changed); set_event_remove_object(event_remove_object); + set_event_update_scene(event_update_scene); set_objects_from_model(model); init_mesh_icons(); diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 8199a1349b..7b9d3c3981 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -191,7 +191,8 @@ void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, Model &model, int event_object_selection_changed, int event_object_settings_changed, - int event_remove_object); + int event_remove_object, + int event_update_scene); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); // Update view mode according to selected menu void update_mode(); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 252dcec268..705d72643a 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -51,6 +51,7 @@ std::vector > m_og_settings; int m_event_object_selection_changed = 0; int m_event_object_settings_changed = 0; int m_event_remove_object = 0; +int m_event_update_scene = 0; bool m_parts_changed = false; bool m_part_settings_changed = false; @@ -116,6 +117,9 @@ void set_event_object_settings_changed(const int& event){ void set_event_remove_object(const int& event){ m_event_remove_object = event; } +void set_event_update_scene(const int& event){ + m_event_update_scene = event; +} void set_objects_from_model(Model &model) { m_objects = &(model.objects); @@ -126,7 +130,7 @@ void init_mesh_icons(){ m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); // init icon for manifold warning - m_icon_manifold_warning = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); + m_icon_manifold_warning = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); // init bitmap for "Add Settings" context menu m_bmp_cog = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); @@ -208,20 +212,19 @@ wxBoxSizer* content_objects_list(wxWindow *win) event.Skip(); }); - m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) + m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { - if (event.GetColumn() == 3) - { - if (!*m_config) - return; - wxVariant variant; - m_objects_model->GetValue(variant, event.GetItem(), 3); - auto str = variant.GetString(); - int extruder = str.size() > 1 ? 0 : atoi(str.c_str()); + if (!*m_config) + return; + auto config = m_config; -// if ((*m_config)->has("extruder")) - auto config = m_config; - (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); + wxString str = event.GetString(); + int extruder = str.size() > 1 ? 0 : atoi(str.c_str()); + (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); + + if (m_event_update_scene > 0) { + wxCommandEvent e(m_event_update_scene); + get_main_frame()->ProcessWindowEvent(e); } }); @@ -666,7 +669,7 @@ void update_settings_list() // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. #ifdef __linux__ - std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); + std::unique_ptr no_updates(new wxWindowUpdateLocker(parent)); #else wxWindowUpdateLocker noUpdates(parent); #endif diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 4f1c15e3ac..aaeff7a431 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -74,6 +74,7 @@ void init_mesh_icons(); void set_event_object_selection_changed(const int& event); void set_event_object_settings_changed(const int& event); void set_event_remove_object(const int& event); +void set_event_update_scene(const int& event); void set_objects_from_model(Model &model); bool is_parts_changed(); diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 98d3169bd7..8ad8c8184a 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -91,13 +91,15 @@ void add_expert_mode_part( SV *ui_parent, SV *ui_sizer, Model *model, int event_object_selection_changed, int event_object_settings_changed, - int event_remove_object) + int event_remove_object, + int event_update_scene) %code%{ Slic3r::GUI::add_expert_mode_part( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), *model, event_object_selection_changed, event_object_settings_changed, - event_remove_object); %}; + event_remove_object, + event_update_scene); %}; void set_objects_from_perl( SV *ui_parent, SV *frequently_changed_parameters_sizer, From ca1a11742b6304c5c8f83eb0570305958148833a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 26 Jul 2018 12:10:45 +0200 Subject: [PATCH 071/185] After merging bug fixes --- lib/Slic3r/GUI/Plater.pm | 82 ++++++++++-------------------- xs/src/slic3r/GUI/GLCanvas3D.cpp | 8 ++- xs/src/slic3r/GUI/GUI.cpp | 3 -- xs/src/slic3r/GUI/OptionsGroup.cpp | 2 +- xs/src/slic3r/GUI/OptionsGroup.hpp | 4 +- 5 files changed, 36 insertions(+), 63 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 3053f4326a..221b1cc2e2 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -135,7 +135,9 @@ sub new { } $_->set_scaling_factor($scale) for @{ $model_object->instances }; - $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); + # Set object scale on c++ side + Slic3r::GUI::set_object_scale($obj_idx, $model_object->instances->[0]->scaling_factor * 100); + $object->transform_thumbnail($self->{model}, $obj_idx); #update print and start background processing @@ -539,35 +541,9 @@ sub new { } } - my $print_info_sizer; - { - my $box = Wx::StaticBox->new($self->{right_panel}, -1, L("Sliced Info")); - $box->SetFont($Slic3r::GUI::small_bold_font); - $print_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); - $print_info_sizer->SetMinSize([316,-1]); - my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5); - $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); - $grid_sizer->AddGrowableCol(1, 1); - $grid_sizer->AddGrowableCol(3, 1); - $print_info_sizer->Add($grid_sizer, 0, wxEXPAND); - my @info = ( - fil_m => L("Used Filament (m)"), - fil_mm3 => L("Used Filament (mm³)"), - fil_g => L("Used Filament (g)"), - cost => L("Cost"), - time => L("Estimated printing time"), - ); - while (my $field = shift @info) { - my $label = shift @info; - my $text = Wx::StaticText->new($self->{right_panel}, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); - $text->SetFont($Slic3r::GUI::small_font); - $grid_sizer->Add($text, 0); - - $self->{"print_info_$field"} = Wx::StaticText->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - $self->{"print_info_$field"}->SetFont($Slic3r::GUI::small_font); - $grid_sizer->Add($self->{"print_info_$field"}, 0); - } - } + my $print_info_sizer = $self->{print_info_sizer} = Wx::StaticBoxSizer->new( + Wx::StaticBox->new($self->{right_panel}, -1, L("Sliced Info")), wxVERTICAL); + $print_info_sizer->SetMinSize([300,-1]); my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $self->{buttons_sizer} = $buttons_sizer; @@ -579,7 +555,7 @@ sub new { #$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0); ### Sizer for info boxes - my $info_sizer = Wx::BoxSizer->new(wxVERTICAL); + my $info_sizer = $self->{info_sizer} = Wx::BoxSizer->new(wxVERTICAL); $info_sizer->SetMinSize([318, -1]); $info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); $info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); @@ -593,18 +569,6 @@ sub new { $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, 10); $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20); - # Callback for showing / hiding the print info box. - $self->{"print_info_box_show"} = sub { - if ($info_sizer->IsShown(1) != $_[0]) { - Slic3r::GUI::set_show_print_info($_[0]); - return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); - $info_sizer->Show(1, $_[0]); - $self->Layout; - $self->{right_panel}->Refresh; - } - }; - # Show the box initially, let it be shown after the slicing is finished. - #$self->{"print_info_box_show"}->(0); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); @@ -963,9 +927,6 @@ sub remove { $self->select_object(undef); $self->update; $self->schedule_background_process; - - # Hide the slicing results if the current slicing status is no more valid. - $self->{"print_info_box_show"}->(0); } sub reset { @@ -986,9 +947,6 @@ sub reset { $self->select_object(undef); $self->update; - - # Hide the slicing results if the current slicing status is no more valid. - $self->{"print_info_box_show"}->(0); } sub increase { @@ -1664,9 +1622,15 @@ sub on_export_completed { # Fill in the "Sliced info" box with the result of the G-code generator. sub print_info_box_show { my ($self, $show) = @_; - my $scrolled_window_panel = $self->{scrolled_window_panel}; - my $scrolled_window_sizer = $self->{scrolled_window_sizer}; - return if $scrolled_window_sizer->IsShown(2) == $show; +# my $scrolled_window_panel = $self->{scrolled_window_panel}; +# my $scrolled_window_sizer = $self->{scrolled_window_sizer}; +# return if $scrolled_window_sizer->IsShown(2) == $show; + my $panel = $self->{right_panel}; + my $sizer = $self->{info_sizer}; + return if $sizer->IsShown(1) == $show; + + Slic3r::GUI::set_show_print_info($show); + return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); if ($show) { my $print_info_sizer = $self->{print_info_sizer}; @@ -1693,17 +1657,23 @@ sub print_info_box_show { while ( my $label = shift @info) { my $value = shift @info; next if $value eq "N/A"; - my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); +# my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + my $text = Wx::StaticText->new($panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($text, 0); - my $field = Wx::StaticText->new($scrolled_window_panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); +# my $field = Wx::StaticText->new($scrolled_window_panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + my $field = Wx::StaticText->new($panel, -1, $value, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $field->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($field, 0); } } - $scrolled_window_sizer->Show(2, $show); - $scrolled_window_panel->Layout; +# $scrolled_window_sizer->Show(2, $show); +# $scrolled_window_panel->Layout; + $sizer->Show(1, $show); + + $self->Layout; + $panel->Refresh; } sub do_print { diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 722f1c1124..578a17b7af 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -3024,7 +3024,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_2D_as_invalid(); #endif - } + } + else if (evt.Leaving()) + { + // to remove hover when mouse goes out of this canvas + m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); + render(); + } else if (evt.LeftDClick() && (m_hover_volume_id != -1)) m_on_double_click_callback.call(); else if (evt.LeftDown() || evt.RightDown()) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 8e0c2c0a67..088cbf80bd 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -148,9 +148,6 @@ wxStaticBitmap *g_manifold_warning_icon = nullptr; bool g_show_print_info = false; bool g_show_manifold_warning_icon = false; -wxFont g_small_font; -wxFont g_bold_font; - static void init_label_colours() { auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index c43e2c7ced..1c61de4572 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -96,7 +96,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field) { - if (!m_is_tab_opt) { + if (!m_show_modified_btns) { field->m_Undo_btn->Hide(); field->m_Undo_to_sys_btn->Hide(); return; diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 20a5326265..e5600f9180 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -153,8 +153,8 @@ public: OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) : - m_parent(_parent), title(title), m_is_tab_opt(is_tab_opt), - staticbox(title!=""), m_flag(flag), extra_column(extra_clmn){ + m_parent(_parent), title(title), m_show_modified_btns(is_tab_opt), + staticbox(title!=""), m_flag(flag), extra_column(extra_clmn){ stb = new wxStaticBox(_parent, wxID_ANY, title); stb->SetFont(bold_font()); sizer = (staticbox ? new wxStaticBoxSizer(stb/*new wxStaticBox(_parent, wxID_ANY, title)*/, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); From 36e8544df245dcd2bd7e31483086b3b35f5d6cc9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 26 Jul 2018 16:51:18 +0200 Subject: [PATCH 072/185] Added DATAVIEW_ITEM_VALUE_CHANGED event for Linux and OSX --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 705d72643a..580533d035 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -214,6 +214,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { + wxMessageBox("Ku-ku"); if (!*m_config) return; auto config = m_config; @@ -228,6 +229,29 @@ wxBoxSizer* content_objects_list(wxWindow *win) } }); +#ifndef __WXMSW__ + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) + { + wxMessageBox("DATAVIEW_ITEM_VALUE_CHANGED"); + if (!*m_config) + return; + if (event.GetColumn() == 3) + { + wxVariant variant; + m_objects_model->GetValue(variant, event.GetItem(), 3); + auto str = variant.GetString(); + int extruder = str.size() > 1 ? 0 : atoi(str.c_str()); + + (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); + + if (m_event_update_scene > 0) { + wxCommandEvent e(m_event_update_scene); + get_main_frame()->ProcessWindowEvent(e); + } + } + }); +#endif //__WXMSW__ + return objects_sz; } From c19fe985e8457e0d87b0008d4b47f6e03774cb0b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 26 Jul 2018 17:22:10 +0200 Subject: [PATCH 073/185] Del information msg --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 580533d035..ca0651bce5 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -232,7 +232,6 @@ wxBoxSizer* content_objects_list(wxWindow *win) #ifndef __WXMSW__ m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { - wxMessageBox("DATAVIEW_ITEM_VALUE_CHANGED"); if (!*m_config) return; if (event.GetColumn() == 3) From f7c0303acf92f8662d093c71fbb0cfcff98366d8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 27 Jul 2018 10:42:54 +0200 Subject: [PATCH 074/185] Fixed wrong drawing of the object additional settings --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 15 ++++++++++----- xs/src/slic3r/GUI/OptionsGroup.cpp | 2 +- xs/src/slic3r/GUI/OptionsGroup.hpp | 9 +++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index ca0651bce5..03ba9198e9 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -687,11 +687,16 @@ void object_ctrl_selection_changed() //update_optgroup void update_settings_list() { - auto parent = get_optgroup(ogFrequentlyObjectSettings)->parent(); +#ifdef __WXGTK__ + auto parent = get_optgroup(ogFrequentlyObjectSettings)->get_parent(); +#else + auto parent = get_optgroup(ogFrequentlyObjectSettings)->parent(); +#endif /* __WXGTK__ */ + // There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/Slic3r/issues/898 and https://github.com/prusa3d/Slic3r/issues/952. // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. -#ifdef __linux__ +#ifdef __linux__ std::unique_ptr no_updates(new wxWindowUpdateLocker(parent)); #else wxWindowUpdateLocker noUpdates(parent); @@ -701,7 +706,7 @@ void update_settings_list() if (m_config) { - auto extra_column = [parent](const Line& line) + auto extra_column = [](wxWindow* parent, const Line& line) { auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line @@ -759,8 +764,8 @@ void update_settings_list() no_updates.reset(nullptr); #endif - get_right_panel()->Refresh(); - get_right_panel()->GetParent()->Layout();; + get_right_panel()->Layout(); + get_right_panel()->GetParent()->Layout(); } void get_settings_choice(wxMenu *menu, int id, bool is_part) diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 1c61de4572..1ec799b2e7 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -158,7 +158,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // if we have an extra column, build it if (extra_column) { if (extra_column) { - grid_sizer->Add(extra_column(line), 0, wxALIGN_CENTER_VERTICAL, 0); + grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL, 0); } else { // if the callback provides no sizer for the extra cell, put a spacer diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index e5600f9180..4f263f5e3c 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -75,7 +75,7 @@ private: std::vector m_extra_widgets;//! {std::vector()}; }; -using column_t = std::function;//std::function; +using column_t = std::function;//std::function; using t_optionfield_map = std::map; using t_opt_map = std::map< std::string, std::pair >; @@ -107,6 +107,11 @@ public: return m_parent; #endif /* __WXGTK__ */ } +#ifdef __WXGTK__ + wxWindow* get_parent() const { + return m_parent; + } +#endif /* __WXGTK__ */ void append_line(const Line& line, wxStaticText** colored_Label = nullptr); Line create_single_option_line(const Option& option) const; @@ -157,7 +162,7 @@ public: staticbox(title!=""), m_flag(flag), extra_column(extra_clmn){ stb = new wxStaticBox(_parent, wxID_ANY, title); stb->SetFont(bold_font()); - sizer = (staticbox ? new wxStaticBoxSizer(stb/*new wxStaticBox(_parent, wxID_ANY, title)*/, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); + sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; if (label_width != 0) num_columns++; if (extra_column != nullptr) num_columns++; From 1148c8c018846c6864cd3163c695ea6fdb4428b2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 27 Jul 2018 10:52:16 +0200 Subject: [PATCH 075/185] Experiment on OSX to understand events order --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 03ba9198e9..9b967ffe4e 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -188,6 +188,9 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { +#ifdef __WXOSX__ + wxMessageBox("DATAVIEW_SELECTION_CHANGED"); +#endif //__WXOSX__ object_ctrl_selection_changed(); }); @@ -212,12 +215,11 @@ wxBoxSizer* content_objects_list(wxWindow *win) event.Skip(); }); +#ifdef __WXMSW__ m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { - wxMessageBox("Ku-ku"); - if (!*m_config) + if (!*m_config) return; - auto config = m_config; wxString str = event.GetString(); int extruder = str.size() > 1 ? 0 : atoi(str.c_str()); @@ -228,10 +230,12 @@ wxBoxSizer* content_objects_list(wxWindow *win) get_main_frame()->ProcessWindowEvent(e); } }); - -#ifndef __WXMSW__ +#else m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { +#ifdef __WXOSX__ + wxMessageBox("DATAVIEW_ITEM_VALUE_CHANGED"); +#endif //__WXOSX__ if (!*m_config) return; if (event.GetColumn() == 3) From a3cdc7e408d7fb1b05cf19deff17265969ef729a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 27 Jul 2018 11:36:31 +0200 Subject: [PATCH 076/185] Placeholder icons for hover-pressed toolbar items state --- resources/icons/add_hover_pressed_36.png | Bin 0 -> 953 bytes resources/icons/arrow_out_hover_pressed_36.png | Bin 0 -> 824 bytes ...row_rotate_anticlockwise_hover_pressed_36.png | Bin 0 -> 795 bytes .../arrow_rotate_clockwise_hover_pressed_36.png | Bin 0 -> 791 bytes resources/icons/brick_add_hover_pressed_36.png | Bin 0 -> 1019 bytes .../icons/brick_delete_hover_pressed_36.png | Bin 0 -> 1018 bytes resources/icons/bricks_hover_pressed_36.png | Bin 0 -> 1006 bytes resources/icons/cog_hover_pressed_36.png | Bin 0 -> 1005 bytes resources/icons/cross_hover_pressed_36.png | Bin 0 -> 875 bytes resources/icons/delete_hover_pressed_36.png | Bin 0 -> 911 bytes resources/icons/package_hover_pressed_36.png | Bin 0 -> 1034 bytes .../icons/shape_ungroup_hover_pressed_36.png | Bin 0 -> 866 bytes .../variable_layer_height_hover_pressed_36.png | Bin 0 -> 471 bytes 13 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/icons/add_hover_pressed_36.png create mode 100644 resources/icons/arrow_out_hover_pressed_36.png create mode 100644 resources/icons/arrow_rotate_anticlockwise_hover_pressed_36.png create mode 100644 resources/icons/arrow_rotate_clockwise_hover_pressed_36.png create mode 100644 resources/icons/brick_add_hover_pressed_36.png create mode 100644 resources/icons/brick_delete_hover_pressed_36.png create mode 100644 resources/icons/bricks_hover_pressed_36.png create mode 100644 resources/icons/cog_hover_pressed_36.png create mode 100644 resources/icons/cross_hover_pressed_36.png create mode 100644 resources/icons/delete_hover_pressed_36.png create mode 100644 resources/icons/package_hover_pressed_36.png create mode 100644 resources/icons/shape_ungroup_hover_pressed_36.png create mode 100644 resources/icons/variable_layer_height_hover_pressed_36.png diff --git a/resources/icons/add_hover_pressed_36.png b/resources/icons/add_hover_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..3b6e6b2a3d13817913e31e03c3edc3f7831bffc9 GIT binary patch literal 953 zcmV;q14jIbP)iMX%iC@_iti1Xml&6p(ruP0!^_k4bXox?UZ)f84oK>xR{vDP!jvD@5`58 zelPjn`<}o*VO+qVjv>q<05tEzd~)VvVzC&dQt41ZY&IJ{pAUmK3}F^QFfL$R8MuPo zX2aXnRTn#3$s~KF5|{tDj0IqKXQys30N$=Hc6WBDl}yvLTGAf~)CKBpCKmi?8YMdjR+ z({y?}xW4fff2NnsHKbM)ma}&mltz%m0Kvo!)Pjm;RIuA^6tp}MWrM+<5tO3L&GoMl z9bQg2JmwgR71waIIXTtt=Z9pFqPB;s6`48nF0ViSCfQPkbS_C~YmT#zy+A?8u$Wvh zuVkC~ReC%6S;;N4EAJw!GTB0=mXAj^(MkmhN{W~qVXS|QpA$csS2DSr!)|RQszfO3 zCEgi%??C?3=`R78xqY5^c7rE8Pm@xE<{7G1D-<*Zr7WX0Y5#iGsOkt9Xw`~&hOB~} zcqWR|<|JE8GxPmx6t-nPfAwn341h(j(CzABb3205+Gd`iUZPOuJOiI&iZKe6*&Sw-=l|cJb42VPI6qygNeZ@!#ZL{TK3NYo7`7z`qc;{C3_p*AKQ bk(J)xmZnh-dV-V!00000NkvXXu0mjfKkBiH literal 0 HcmV?d00001 diff --git a/resources/icons/arrow_out_hover_pressed_36.png b/resources/icons/arrow_out_hover_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..8c1241147729595b424aae3dbbe1d02912fc5971 GIT binary patch literal 824 zcmV-81IPS{P)w#0 zK~z}7?U+qS6k#03e>3hf?MG>Dv+jm@s;uk~rA61yUBa-Svh)yCNCoxy{IQX~yh37{x)53+exGxIbJzt5MGbovd0 zu|$HSpN^se3=IzE3t7gaN4cAD{pmYrU0J zEnbhXMCP|lN=Z^AiJNh}nxCh!$5Z-7tq}sfHz;wG@MCNkGnqt)Yyb(#`|s}5YT5=w4xq#}j?Esuwb1w6z zY(h@eMV)v5jGmgZo%v$S1I?(1{SRmh-kWy)?&kpK-<%;f9?Krk)t-x!Fi|T)T*PSW zxu4!cUAKssVTxQuw7hL1HXh^1^TS!_Axm)i%Xy+!gc7HZ0>wseyeIwmDmE-3D0X=$ zc6(@gbv8RqEi6Gr@oF?hV<2uYFg(EC<>~j?j`H1j)J25CArw`z)V~ZVWafb^A&?Rr zHYdgpgMH-(aogQfdTT4{Fro(YwL%IM6(MCd5|VWEzvH9PNx~ebrSrnH-o|H#kV4Yg z{gIBZAO0$#e{SmIt4{y5w`J3nd@=qfKz6%5=O7adDuAx*g!=k&2Gic&j;`ye!PUgT z>e7mfgu~%KCLyzxjK|~QSl4ls0E#mCuRKRadcOeCK`dfmt-FE%0000-OnJnpjL1LDMBBrAHnuX(041M zh`tpRl*SeH!L_#5+NgD@wU9(;Y?`EV?j0X$3qFZUCk1ofh6BTI&OMwv9D^TWH)4~= zFxoKyiY75<8cs7fI7lv+a}?tBda0?Y!KTSJ+A$2f5xZ?k8zo*ZW+0H4>U0d`Hp0cu8-z zi|+JOzK$nRq9`*eSU$UfRh4U;1&!%ZZlpT!6c-Vx3Ny>I0;M&nY>HQjZl3i&X8Xe3 zlogjdb7a?39h8=oQd=3uFx-d|lvcQ0E+W+tX3Z|)Lf=_udgx`Qiz1Jk`DF_bt4&x#9P+RSAY}>47L?W~5wzA* zxvQ}bTDP2Xb`F(M$;j9U>G3qxl_s}7c5bDyDuP1eRaRp5c&D{HNo4dWY8T|SQ5g};K zLn+0;+W{^dxa|C0wjs2c4WZ3b&wI}9M2X_!!OPC|(62_qpJ1j9bn(!Y!VlVi2J(13 zd526ITmXK*pU*=>dBa4bQT%@Y#NujlVbxl9l4LUZ(l#kPcoG-md zZss0qj_g&Whiq3i{=9NDEdwD1Qb}w!8x@5WWaYScHu>0^J2dup1iK@HeYyJ)N}{A< zCN#tJ;xt06!Ac5BFrA8b-`?&#=yP-kZ?=w9l4v4EV5$$d;U!<{^nD5tORf@4#whmq zcr)9-^LHqmSmM|GPo$6tLsFYxPmQM*0Fe~T2ES5TREEptBDfOVdC+BF^Oo`L;hUQv zB^8Dc7|K8?6Ok0w)}dQNtr+W@J|BBR@0XVp73f5hQI;dixLx_y)uFb*R`fhCLP(nX zub{h&sH>?*nG(BlFg`m$oxi~vP}_$V3iEUh7W)xOQRX{y}QhCm2`kOE;Ul#)nO5nqinJ~xT^2d>s#v#wI#Io`qb*GC=BWE1PKhI<)FVjY2UVOob2UCq*x3*YWDYURHdnt4;qU>?Y-dgrl*zTXO z-D^t^z0|WJr7SH{kg_0J!4{3iXe6MZlQkxxvzj;Ntp{tsrH5`wbSZvsGxN+mpZU)3 zndi$-@VA`1I9#H{6aqj#gZXCUYxei|snu#13gYp2h(@D0+;_wjLO6GE9>4h*ug61I zXXmB3lYRD#YOThD%?C&T`J?D75ME#Q)hEcy59~`_CsKuovfK!KFC0zks z0bKz#TXG{FQmIrZ6bj_?c>u!UFriQgpU-#kK&EN3ySvNeY)6>($Vlfm&X+F@4 zjNI`ai&--Qmt0&tITnn`n^p7avTTSw(B;yD?S@KaeOsZ9LG~ zzhz-zfz{Pjgb=i}v>?l;W?PoU%HFTM9UsP$EQY&oVF`n=>+i9iTWUOzBuRwBVfy>~ zDV0jh&CQWcr}6v!1cO1^+S_A*6Lh(@CXgF!S+ zBbiJxGBSd$>->3KB&jX3rY-RB*7rR6C^V^tW3pL2UJZSP3i=?9ch1}C)88A)bANX%pwEL^!1Y1Gv$TutJ}#pu$d z&Q|{bT$w=7L^BCdnYbV`5{ZFATY=Po0ZO4oYw0cZejFD_4Z6a#FebdabI&>VJm+^l z-uJwMzf9goa)~A;5di8b%qJrsv%S4dqtUof5VzY+EEYrZjuev!A@3s}-F}3}?WQLh zy%co{dwU!=8a&KCL;)xr99$XEM9ol9{I2tW4gzbtzC!pFIOOQRsQHE1)Z& zE1-6p+KQjl>vhWIGNn=pfN(fWC=|l$^=suQNJ2N`HTUv(5DM zG|6PL^_#j_ETXC^*=!caac~@mLZN`y>m{8|lTN46b)7i`0*?3A3nz_{DN~mgn0RN%xkw> z52Wil3kwS*5(#`hABJJjXf&F$cN_<0=`p^YrwqRJ0Qb$8sHeXsJO3?RzAA99wa5PS zZkeB-XK85(Ap}034^=&xn{C_F=RYGc_8Ol2cZg5lMF05_kw`E&IEb$67>2=bzZUUCZ^EnZ{yoqKJy0&S&zEVMMl2R17!0zzyGtgM zVPs^4N~OXdFZeN*9&_!>2Q=z?7(n%R7xH?f{mxa_b>i_j;c%F>wKZ(p=El7@$bA0| zeL*j(OQU*VvAOR;A4^?K_yG_I1PBBICxttAXmke1KAD7)LC^oK+OYfA_v0Gs6s^(197s7)u*IhJc~exG=ggMq_NWZrs?Ux-llYt*%|w zZuJjf>B@yPu~;BcQ}C-as4YRtiAxI=RC3c9>q56fs9YiG}5wLOB-(Sb0`!vRv$Q#_VkM>{nF9S}M(0KSh? z$k8`FgRpJxCcCj-eED!n7Q1-^yS0tj@EF(@rQh#R%4Wc}hz*WnM_Y;hIFDc4Zj3{I z;8Q3SC{}m)uJ9G_)N087ek$L8i|`c3o_-#H;;kE$eqKO%Ra%6k;`@yOnq9eu6D{F* zF2WE9DY4o+5b0rde_5oGSq7o;%ezQRB834$Hg3vRtH07>w{WWaB&M_whNw3;lE}~q zW^W3=P(WG|QC|W<2aN-AwZjv=<7|}+_*&sglj79pfItTUD23L#?hN4V7O5zgyl3L8 z;NXD_VGs?+NcW6zIyKJhYojb19{I1YQd_!(8EL^VZ9K=Jyp!inw4G0%JkQlt&E#C> z;Co02iSK*3zC*|g5lf|*d?rtL@eD7mUFZ1q8C0Ozh(ws~e}O*|!wjA5!oOExA>(oG zpn+r{*ev~x(h8+~9OV({fJKqy^xGdYwe%<1fO;$2FYL16thY#MNSNmS) z%Y`qfhAWJuMu>)@n1+p%f>=02`qXh!6^GSanPSCd*YPlgK$-?pHel?#A5C!nsW(|! zTV{6Y8X_8?b$~$Ps{mI8M8X!2cenD*PgzVM_9rn28WtBS{#sjW8qCbh4Doopo|StCtB(dRv9-1JuS>{bIn&wM$@|M6ASMvv cK|AUF14!m2Q52x3qJXap4{BN!Kk8r>KregIc~fVXth?Q1vg+qZjZKY%MY zegYS4T=bc^GA6Y|iK6%l5f)$uW|%*>i&ts7Ya5W4)RWxgCO5hH<#%t+IXM!4hIofy zh#?&!0VpppKY#NRl}ZK6vR)NL(=-x^1cGmcbciI0cZd(m9|&j~(^FGJvGaSiifLKA z|MfjGK>g`yXfOcNQ&ZHRp6ET9ZQH%WOg1|ds9!#P0{CL+Yl=6ZH=sA5L5I?>i(J>G z+wJ0c9zLIsKp^n?hJ+BkWtt|5M1o{8$>ZZALI^z1qucGijv*lguIu7B4t~EM$8oSM z3n2u4zaQ6iDVNKbrpegY7|CREaE62sghC$+glQe z1o?a(S(Yi4N`o`xx-Qjf6#&b!h{xl|vfQhQ*=!ceval?Ra=Fau=_%QNSR?-vkfJDP znuckbn5Kzg7&Mzr?(gqWRh3jKg`z0fwoSX;CYQ^xw6ruBpiZZQ<2X2uLpU5pk|ds= zpD7lL=(>(9%UoYyqw6}7BoT|nC>D!UDiwS_ANhQ~zkkTKZPMv9(P)%rvxy{0)M_=B zmzSBIo+ciTqw6}gS`As2X*3$FudlPayGyZH?Ej~>Z5zw7a2yBMbrC`^Iy#D?{3YA- zJRHZt^SoYuG7N)uyWRgMYcv{MU0o3lhgn%!q0{LwGc!Y}R6>#@gb>`^+>lPE>2|xw zvdr=EG2L#LU@+K!AYIqdb)9;>PP^U4aU6sY%+Aj8@bG}^x}?)-T-U|2EDXb7b#;}E zjg7(Op?bYewOS<-iJ&M7e!rh!Fv#589P{(@ghC-SO(PHpkV>Vvyu9T6{Cu!^C>Dz` zGBU!&#RYeFcL*Wa-rmOZJdTczP*s(+wKbyAC=(MCbUK~E8H&f_R4Nrde*DPU*%>=K zJE*FPrfI0EiV%Xmy*&zr0tW{Nc%H}B*4AKaSuU4jVPSz_Fo>$ExUNg9)xt0gOw%M3 z3bDVxPqW!17K^=3`w@vmdi%x2MNUpmux*>o%}pdpLesQ=1=H7lD3i&M$zLiS4A&P24;^X=>i2v10BRX}pP~(FgW90~ zJxKZ69oKM>UMyl33jern34Zqwh?wu2e`^*5xHJpJ?t<#_xJOpblGrcg|S zrDTFi%p&aVZA^yh7)%o^ivz#h#aLR}h?$r~?drrxB#6&BSdX7Twb~d^6pK*}0%R1S z#A0As?Em==MlN5&jNd}oU1;_$R_Es_WiqUM@-Ya}m@Sj4%A*s<862BH1OZ}KH!=v= zH+dTwMhM$Rx9u7xo5dM92ddiGLoGFpna@8XEEI_RMbyp?%)WlCbQ<06TF;C#at?I8 zIaMY#ji;wh66Es`R^a(_n4N`6g;FMqbAEVBm>s!?^p^#M0#qUhLx>`XB7{)T)|#%S zfehbg&&@H^SO-KQ^!G!5A4CymcQ>iw5md)*4oLTX_S_gli*=yeHYLwv>EQ!%_wV!S z#T?e|IA%Of?>FC~&d)a%r29U5f2d(81s1Mcrs#RBJe#E>(L*eeAaV9< z#3pa+{{&KtMP#)CvP#Ka;Pqk__q);B0oHXEuYb>q=dt|fGlUQ*LLlDp#B3Y1Ixv89 z>?o^#j@OG>yh|51R+C|npZJkw*QMyX`GMTLX6x0V+ z7Y5Ih&*wKxLYkGCo}M1g%$!9TM^V;$oA+d-_ZO(ODPHRro6i6M002ovPDHLkV1iL; Bi!J~F literal 0 HcmV?d00001 diff --git a/resources/icons/delete_hover_pressed_36.png b/resources/icons/delete_hover_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..aa4a470a95d6aa728420c70f68e4b315c1b45e3b GIT binary patch literal 911 zcmV;A191F_P)%Q+rm$P zZh;k>todA^3aZo;VW?3gP)UKLt`gHUO+y?v_1LvNo-Y<5k@%EQHE|?-w)ZTa`<|Hhj$i_Hino)0LYs#?;LoWrKKfI)7(}NRaNQl@5kdcPfQ|&cNXt_<~*9Jl1wDp zqRyR_6`H2Wxu4G=0j!luZG!(^!O zUb@8k_ut~ZFp9MAG5<@ZEELhQS@gq)h`lt1)VPPZ`VC?xk9yyr@6;DH09}OyuQ6S6%*xQ<*(45hvNlM9^K30PnXg5ANH{ir3a|(>ZSbUWt7oz zTvI{>0vjL*9LED!COUeYo7oH0Rh7__&-w@y>qb0xjFoDc(#0u4$4;PR1|b|p$Pu)` zCy0(saBJ#Q7S|VuzI+C`XSctdT1pKNIDV2lc8%E&-@~jmP==mDJ$Q(Et;TmBy~|>2 zmDs6Q2@ec!&n%NugNR*8teI)%X0G7O=b>fcc0~!jcmjQB1UdfSzE=-C?E^hJfg|H+ zN5(q~S_f$704a*nc92a52|(9%ZZ9vl4JMn-qU-vGakXh!y_I^Ca=H8mCuF;niO1u- lKKBM<5+Sx$iXCF<{RV~JCzbY0Y`Opd002ovPDHLkV1iE&p)3FZ literal 0 HcmV?d00001 diff --git a/resources/icons/package_hover_pressed_36.png b/resources/icons/package_hover_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..57b7a97b1a23f8d723942b0dcef7c2895328a5b9 GIT binary patch literal 1034 zcmV+l1oiugP)@&BFicZT8*i=Z}AVp#W0#=B^ zo()t&ASDZyVT;6bgG7j05NZla16}l?)2swzn(tCx7uG5ywH6)#4 zsf1qT-V(`>^XL@+*5< zpP}{45K7=<6emdLdRZ)7W3BKRWL|;viwGrLKaj8rgx0STjiqQk^FEQ(5s(hv>H|uD zf5pDum&hG>p48q$EEI0CQNDrFb^#@Ivi6|R>bEGyO|pkC5ovh}b8U&@gWvHT19!8C z-rPojMzC_5aC0YJr!G(_-e;lk9oE7)TF=|H1&NKnIC^dvcVh{oc#XiRpqpjPN|DOU z9H%c0p+#e`IfdLTAS(AsxA&1~fytk~sXa(a6^S4m(TTToQZ3)1DU~FdiW0>HYa0A8 zSaVRF!7TlOF?F5D{)f*#B)b6HE{F@p6%gV4!&vOnl^#!)(Pax%uUHzPvd_VcV0n~Umm<6yl@oksDixJ;{9GsHAEo^D2ehAfmG+YZ2t7|zu8Q;|#XGlIo|{D5caG-M z@1jK0^?l2PwueyHMU>2Q6sLY*=FdS6z5FKC#aZU>-hyPF*b5({CJ*lH_@Tt|gpa?2 z(t4JMli!2ypq+RFE%$6==b=!hpHQZMcZB_)f;3HQIAqJ90A#aS=H}-ch8Y_hBb&`W zYA0I@t517AVR?D^Kbw%9N+z97^WnXZ5F-fjq^|hHvj+t07*qoM6N<$ Eg4qDtUjP6A literal 0 HcmV?d00001 diff --git a/resources/icons/shape_ungroup_hover_pressed_36.png b/resources/icons/shape_ungroup_hover_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..09923e7f44f15fe580cec2ce0c131a406b92f722 GIT binary patch literal 866 zcmV-o1D*VdP)^U^oM8X}0`Eyg zK~z}7?U+wUTS*+pKhK&*`qiYQmwJE*`)dNc8zYOyD=g2r-!EjDfF=Ll0x%?;mz>lF(1A& z-X>z$7)oQh&AYQMRSS*Gnp^XI$L*GF^y7Gt)ua`(T z+!S?+woRp4<^JY<+5k$syG?@uh=jwGc6X^SS*=#9Cogw*HwEhD`Z|E4(6?}h=L+t&PW8*`K*O`4@r%TxXeB}9?bEHx!#>U1-CX?Gc%kzZ4&aeqVzHQeK+DU^*tU%s2y=7r0dF$< zM9-f_DUFm0sc^nhbL;LOZ0F0|y-{(`_PJb+csx!qyG>zV96>lRLVyqoAv8j2q}0@E z+TC-=@Aor3Jxw4G;94k6O=_f22&oZLA*6D~Lf7G3b=mOn@X7ro91wY=NPQ0-^@Wh` z+x|F4MnCMGCm-;w-K zW-I$2TiG(3fBwtcbeWxvb>`>iNu^SaQ79UXl1L=5EQ?=yzjV^hiyw){MFrt&?#IU7p#82{E)r9y%Uv? sMk&+P)x~dH4=@%mjQR`k8!73122(|L($Py68vpk*`2YX_ literal 0 HcmV?d00001 diff --git a/resources/icons/variable_layer_height_hover_pressed_36.png b/resources/icons/variable_layer_height_hover_pressed_36.png new file mode 100644 index 0000000000000000000000000000000000000000..e04634f08264493229efe06a68686ee9dd516c6d GIT binary patch literal 471 zcmV;|0Vw{7P)8>;{S0c1%; zK~z}7?U+An!ax{@pA#kBB@n?6pk4cQ90WI^jyCuK#5##BZlZ&$UqEn;qg&|*uvUof zO-{THHX*h44~3Wv_ri}GUJf3fJMQ2We3Yu9NU+2h0gw$~9&a94tyXxRcTo_-Feny_ zC~lP)BZR7=y4PK<41pGqSmC8%x1HAfm*Frx(wO2 z{i~g59LI?lsMG1B%TT>u$Md|i_a#X56<;ow@d6D71O3};APw|qpj Date: Fri, 27 Jul 2018 12:08:33 +0200 Subject: [PATCH 077/185] 3D scene toolbar actions --- lib/Slic3r/GUI/Plater.pm | 110 +++++++++++++++- xs/src/slic3r/GUI/3DScene.cpp | 72 ++++++++++ xs/src/slic3r/GUI/3DScene.hpp | 17 +++ xs/src/slic3r/GUI/GLCanvas3D.cpp | 168 +++++++++++++++++++++++- xs/src/slic3r/GUI/GLCanvas3D.hpp | 36 +++++ xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 99 ++++++++++++++ xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 17 +++ xs/src/slic3r/GUI/GLToolbar.cpp | 128 ++++++++++++++++-- xs/src/slic3r/GUI/GLToolbar.hpp | 20 ++- xs/xsp/GUI_3DScene.xsp | 102 +++++++++++++- 10 files changed, 749 insertions(+), 20 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 9e070f66b5..fbe9dc3b06 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -159,7 +159,63 @@ sub new { } } }; + +#====================================================================================================================================================== + # callbacks for toolbar + my $on_action_add = sub { + $self->add; + }; + + my $on_action_delete = sub { + $self->remove(); + }; + + my $on_action_deleteall = sub { + $self->reset; + }; + + my $on_action_arrange = sub { + $self->arrange; + }; + + my $on_action_more = sub { + $self->increase; + }; + + my $on_action_fewer = sub { + $self->decrease; + }; + + my $on_action_ccw45 = sub { + $self->rotate(45, Z, 'relative'); + }; + + my $on_action_cw45 = sub { + $self->rotate(-45, Z, 'relative'); + }; + + my $on_action_scale = sub { + $self->changescale(undef); + }; + + my $on_action_split = sub { + $self->split_object; + }; + my $on_action_cut = sub { + $self->object_cut_dialog; + }; + + my $on_action_settings = sub { + $self->object_settings_dialog; + }; + + my $on_action_layersediting = sub { + my $state = Slic3r::GUI::_3DScene::is_toolbar_item_pressed($self->{canvas3D}, "layersediting"); + $self->on_layer_editing_toggled($state); + }; +#====================================================================================================================================================== + # Initialize 3D plater if ($Slic3r::GUI::have_OpenGL) { $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config}); @@ -179,6 +235,21 @@ sub new { Slic3r::GUI::_3DScene::register_on_gizmo_scale_uniformly_callback($self->{canvas3D}, $on_gizmo_scale_uniformly); Slic3r::GUI::_3DScene::register_on_gizmo_rotate_callback($self->{canvas3D}, $on_gizmo_rotate); Slic3r::GUI::_3DScene::register_on_update_geometry_info_callback($self->{canvas3D}, $on_update_geometry_info); +#====================================================================================================================================================== + Slic3r::GUI::_3DScene::register_action_add_callback($self->{canvas3D}, $on_action_add); + Slic3r::GUI::_3DScene::register_action_delete_callback($self->{canvas3D}, $on_action_delete); + Slic3r::GUI::_3DScene::register_action_deleteall_callback($self->{canvas3D}, $on_action_deleteall); + Slic3r::GUI::_3DScene::register_action_arrange_callback($self->{canvas3D}, $on_action_arrange); + Slic3r::GUI::_3DScene::register_action_more_callback($self->{canvas3D}, $on_action_more); + Slic3r::GUI::_3DScene::register_action_fewer_callback($self->{canvas3D}, $on_action_fewer); + Slic3r::GUI::_3DScene::register_action_ccw45_callback($self->{canvas3D}, $on_action_ccw45); + Slic3r::GUI::_3DScene::register_action_cw45_callback($self->{canvas3D}, $on_action_cw45); + Slic3r::GUI::_3DScene::register_action_scale_callback($self->{canvas3D}, $on_action_scale); + Slic3r::GUI::_3DScene::register_action_split_callback($self->{canvas3D}, $on_action_split); + Slic3r::GUI::_3DScene::register_action_cut_callback($self->{canvas3D}, $on_action_cut); + Slic3r::GUI::_3DScene::register_action_settings_callback($self->{canvas3D}, $on_action_settings); + Slic3r::GUI::_3DScene::register_action_layersediting_callback($self->{canvas3D}, $on_action_layersediting); +#====================================================================================================================================================== Slic3r::GUI::_3DScene::enable_gizmos($self->{canvas3D}, 1); #====================================================================================================================================================== Slic3r::GUI::_3DScene::enable_toolbar($self->{canvas3D}, 1); @@ -2098,25 +2169,32 @@ sub object_list_changed { # Enable/disable buttons depending on whether there are any objects on the platter. my $have_objects = @{$self->{objects}} ? 1 : 0; - my $variable_layer_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D}); +#=================================================================================================================================================== +# my $variable_layer_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D}); +#=================================================================================================================================================== if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_objects) - for (TB_RESET, TB_ARRANGE, TB_LAYER_EDITING); - $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0) if (! $variable_layer_height_allowed); +#=================================================================================================================================================== + for (TB_RESET, TB_ARRANGE); +# for (TB_RESET, TB_ARRANGE, TB_LAYER_EDITING); +# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0) if (! $variable_layer_height_allowed); +#=================================================================================================================================================== } else { # On MSW my $method = $have_objects ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode layer_editing); - $self->{"btn_layer_editing"}->Disable if (! $variable_layer_height_allowed); +#=================================================================================================================================================== + for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode); +# for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode layer_editing); +# $self->{"btn_layer_editing"}->Disable if (! $variable_layer_height_allowed); +#=================================================================================================================================================== } #=================================================================================================================================================== - for my $toolbar_item (qw(deleteall arrange layersediting)) { + for my $toolbar_item (qw(deleteall arrange)) { Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_objects); } - Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0) if (! $variable_layer_height_allowed); #=================================================================================================================================================== my $export_in_progress = $self->{export_gcode_output_file} || $self->{send_gcode_file}; @@ -2132,23 +2210,41 @@ sub selection_changed { my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; +#=================================================================================================================================================== + my $layers_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D}) && $have_sel; +#=================================================================================================================================================== $self->{right_panel}->Freeze; if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_sel) for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); + +#=================================================================================================================================================== + $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, $layers_height_allowed); +#=================================================================================================================================================== + } else { # On MSW my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); + +#=================================================================================================================================================== + if ($layers_height_allowed) { + $self->{"btn_layer_editing"}->Enable; + } else { + $self->{"btn_layer_editing"}->Disable; + } +#=================================================================================================================================================== } #=================================================================================================================================================== for my $toolbar_item (qw(delete more fewer ccw45 cw45 scale split cut settings)) { Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_sel); } + + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", $layers_height_allowed); #=================================================================================================================================================== if ($self->{object_info_size}) { # have we already loaded the info pane? diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index c00a9a08e7..78a44f1f03 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -1803,6 +1803,11 @@ void _3DScene::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, { s_canvas_mgr.enable_toolbar_item(canvas, name, enable); } + +bool _3DScene::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) +{ + return s_canvas_mgr.is_toolbar_item_pressed(canvas, name); +} //################################################################################################################################### void _3DScene::zoom_to_bed(wxGLCanvas* canvas) @@ -1940,6 +1945,73 @@ void _3DScene::register_on_update_geometry_info_callback(wxGLCanvas* canvas, voi s_canvas_mgr.register_on_update_geometry_info_callback(canvas, callback); } +//################################################################################################################################### +void _3DScene::register_action_add_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_add_callback(canvas, callback); +} + +void _3DScene::register_action_delete_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_delete_callback(canvas, callback); +} + +void _3DScene::register_action_deleteall_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_deleteall_callback(canvas, callback); +} + +void _3DScene::register_action_arrange_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_arrange_callback(canvas, callback); +} + +void _3DScene::register_action_more_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_more_callback(canvas, callback); +} + +void _3DScene::register_action_fewer_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_fewer_callback(canvas, callback); +} + +void _3DScene::register_action_ccw45_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_ccw45_callback(canvas, callback); +} + +void _3DScene::register_action_cw45_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_cw45_callback(canvas, callback); +} + +void _3DScene::register_action_scale_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_scale_callback(canvas, callback); +} + +void _3DScene::register_action_split_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_split_callback(canvas, callback); +} + +void _3DScene::register_action_cut_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_cut_callback(canvas, callback); +} + +void _3DScene::register_action_settings_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_settings_callback(canvas, callback); +} + +void _3DScene::register_action_layersediting_callback(wxGLCanvas* canvas, void* callback) +{ + s_canvas_mgr.register_action_layersediting_callback(canvas, callback); +} +//################################################################################################################################### + static inline int hex_digit_to_int(const char c) { return diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 9028a17601..cb53b48ba5 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -504,6 +504,7 @@ public: //################################################################################################################################### static void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable); + static bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name); //################################################################################################################################### static void zoom_to_bed(wxGLCanvas* canvas); @@ -538,6 +539,22 @@ public: static void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback); static void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback); +//################################################################################################################################### + static void register_action_add_callback(wxGLCanvas* canvas, void* callback); + static void register_action_delete_callback(wxGLCanvas* canvas, void* callback); + static void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback); + static void register_action_arrange_callback(wxGLCanvas* canvas, void* callback); + static void register_action_more_callback(wxGLCanvas* canvas, void* callback); + static void register_action_fewer_callback(wxGLCanvas* canvas, void* callback); + static void register_action_ccw45_callback(wxGLCanvas* canvas, void* callback); + static void register_action_cw45_callback(wxGLCanvas* canvas, void* callback); + static void register_action_scale_callback(wxGLCanvas* canvas, void* callback); + static void register_action_split_callback(wxGLCanvas* canvas, void* callback); + static void register_action_cut_callback(wxGLCanvas* canvas, void* callback); + static void register_action_settings_callback(wxGLCanvas* canvas, void* callback); + static void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback); +//################################################################################################################################### + static std::vector load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs); static std::vector load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 07020bdb70..e1657fd74e 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1698,6 +1698,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_force_zoom_to_bed_enabled(false) , m_apply_zoom_to_volumes_filter(false) , m_hover_volume_id(-1) +//################################################################################################################################### + , m_toolbar_action_running(false) +//################################################################################################################################### , m_warning_texture_enabled(false) , m_legend_texture_enabled(false) , m_picking_enabled(false) @@ -2090,6 +2093,11 @@ void GLCanvas3D::enable_toolbar_item(const std::string& name, bool enable) else m_toolbar.disable_item(name); } + +bool GLCanvas3D::is_toolbar_item_pressed(const std::string& name) const +{ + return m_toolbar.is_item_pressed(name); +} //################################################################################################################################### void GLCanvas3D::zoom_to_bed() @@ -2862,6 +2870,86 @@ void GLCanvas3D::register_on_update_geometry_info_callback(void* callback) m_on_update_geometry_info_callback.register_callback(callback); } +//################################################################################################################################### +void GLCanvas3D::register_action_add_callback(void* callback) +{ + if (callback != nullptr) + m_action_add_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_delete_callback(void* callback) +{ + if (callback != nullptr) + m_action_delete_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_deleteall_callback(void* callback) +{ + if (callback != nullptr) + m_action_deleteall_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_arrange_callback(void* callback) +{ + if (callback != nullptr) + m_action_arrange_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_more_callback(void* callback) +{ + if (callback != nullptr) + m_action_more_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_fewer_callback(void* callback) +{ + if (callback != nullptr) + m_action_fewer_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_ccw45_callback(void* callback) +{ + if (callback != nullptr) + m_action_ccw45_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_cw45_callback(void* callback) +{ + if (callback != nullptr) + m_action_cw45_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_scale_callback(void* callback) +{ + if (callback != nullptr) + m_action_scale_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_split_callback(void* callback) +{ + if (callback != nullptr) + m_action_split_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_cut_callback(void* callback) +{ + if (callback != nullptr) + m_action_cut_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_settings_callback(void* callback) +{ + if (callback != nullptr) + m_action_settings_callback.register_callback(callback); +} + +void GLCanvas3D::register_action_layersediting_callback(void* callback) +{ + if (callback != nullptr) + m_action_layersediting_callback.register_callback(callback); +} +//################################################################################################################################### + void GLCanvas3D::bind_event_handlers() { if (m_canvas != nullptr) @@ -3039,6 +3127,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; m_layers_editing.last_object_id = layer_editing_object_idx; bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); +//################################################################################################################################### + int toolbar_contains_mouse = m_toolbar.contains_mouse(*this, m_mouse.position); +//################################################################################################################################### if (evt.Entering()) { @@ -3052,6 +3143,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.LeftDClick() && (m_hover_volume_id != -1)) m_on_double_click_callback.call(); +//################################################################################################################################### + else if (evt.LeftDClick() && (toolbar_contains_mouse != -1)) + { + m_toolbar_action_running = true; + m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); + } +//################################################################################################################################### else if (evt.LeftDown() || evt.RightDown()) { // If user pressed left or right button we first check whether this happened @@ -3090,6 +3188,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(); m_dirty = true; } +//################################################################################################################################### + else if (toolbar_contains_mouse != -1) + { + m_toolbar_action_running = true; + m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); + } +//################################################################################################################################### else { // Select volume in this 3D canvas. @@ -3346,7 +3451,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) { // deselect and propagate event through callback - if (m_picking_enabled) +//################################################################################################################################### + if (m_picking_enabled && !m_toolbar_action_running) +// if (m_picking_enabled) +//################################################################################################################################### { deselect_volumes(); _on_select(-1); @@ -3378,6 +3486,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_3D_as_invalid(); m_mouse.set_start_position_2D_as_invalid(); m_mouse.dragging = false; +//################################################################################################################################### + m_toolbar_action_running = false; +//################################################################################################################################### m_dirty = true; } else if (evt.Moving()) @@ -3467,36 +3578,48 @@ bool GLCanvas3D::_init_toolbar() item.name = "add"; item.tooltip = GUI::L_str("Add..."); + item.is_toggable = false; + item.action_callback = &m_action_add_callback; item.textures[GLToolbarItem::Normal] = "brick_add_normal_36.png"; item.textures[GLToolbarItem::Hover] = "brick_add_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "brick_add_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "brick_add_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "brick_add_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; item.name = "delete"; item.tooltip = GUI::L_str("Delete"); + item.is_toggable = false; + item.action_callback = &m_action_delete_callback; item.textures[GLToolbarItem::Normal] = "brick_delete_normal_36.png"; item.textures[GLToolbarItem::Hover] = "brick_delete_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "brick_delete_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "brick_delete_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "brick_delete_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; item.name = "deleteall"; item.tooltip = GUI::L_str("Delete all"); + item.is_toggable = false; + item.action_callback = &m_action_deleteall_callback; item.textures[GLToolbarItem::Normal] = "cross_normal_36.png"; item.textures[GLToolbarItem::Hover] = "cross_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "cross_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "cross_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "cross_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; item.name = "arrange"; item.tooltip = GUI::L_str("Arrange"); + item.is_toggable = false; + item.action_callback = &m_action_arrange_callback; item.textures[GLToolbarItem::Normal] = "bricks_normal_36.png"; item.textures[GLToolbarItem::Hover] = "bricks_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "bricks_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "bricks_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "bricks_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; @@ -3506,18 +3629,24 @@ bool GLCanvas3D::_init_toolbar() item.name = "more"; item.tooltip = GUI::L_str("Add instance"); + item.is_toggable = false; + item.action_callback = &m_action_more_callback; item.textures[GLToolbarItem::Normal] = "add_normal_36.png"; item.textures[GLToolbarItem::Hover] = "add_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "add_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "add_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "add_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; item.name = "fewer"; item.tooltip = GUI::L_str("Remove instance"); + item.is_toggable = false; + item.action_callback = &m_action_fewer_callback; item.textures[GLToolbarItem::Normal] = "delete_normal_36.png"; item.textures[GLToolbarItem::Hover] = "delete_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "delete_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "delete_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "delete_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; @@ -3527,45 +3656,60 @@ bool GLCanvas3D::_init_toolbar() item.name = "ccw45"; item.tooltip = GUI::L_str("Rotate CCW 45 degrees"); + item.is_toggable = false; + item.action_callback = &m_action_ccw45_callback; item.textures[GLToolbarItem::Normal] = "arrow_rotate_anticlockwise_normal_36.png"; item.textures[GLToolbarItem::Hover] = "arrow_rotate_anticlockwise_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "arrow_rotate_anticlockwise_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "arrow_rotate_anticlockwise_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "arrow_rotate_anticlockwise_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; item.name = "cw45"; item.tooltip = GUI::L_str("Rotate CW 45 degrees"); + item.is_toggable = false; + item.action_callback = &m_action_cw45_callback; item.textures[GLToolbarItem::Normal] = "arrow_rotate_clockwise_normal_36.png"; item.textures[GLToolbarItem::Hover] = "arrow_rotate_clockwise_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "arrow_rotate_clockwise_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "arrow_rotate_clockwise_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "arrow_rotate_clockwise_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; item.name = "scale"; item.tooltip = GUI::L_str("Scale..."); + item.is_toggable = false; + item.action_callback = &m_action_scale_callback; item.textures[GLToolbarItem::Normal] = "arrow_out_normal_36.png"; item.textures[GLToolbarItem::Hover] = "arrow_out_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "arrow_out_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "arrow_out_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "arrow_out_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; item.name = "split"; item.tooltip = GUI::L_str("Split"); + item.is_toggable = false; + item.action_callback = &m_action_split_callback; item.textures[GLToolbarItem::Normal] = "shape_ungroup_normal_36.png"; item.textures[GLToolbarItem::Hover] = "shape_ungroup_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "shape_ungroup_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "shape_ungroup_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "shape_ungroup_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; item.name = "cut"; item.tooltip = GUI::L_str("Cut..."); + item.is_toggable = false; + item.action_callback = &m_action_cut_callback; item.textures[GLToolbarItem::Normal] = "package_normal_36.png"; item.textures[GLToolbarItem::Hover] = "package_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "package_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "package_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "package_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; @@ -3575,18 +3719,24 @@ bool GLCanvas3D::_init_toolbar() item.name = "settings"; item.tooltip = GUI::L_str("Settings..."); + item.is_toggable = false; + item.action_callback = &m_action_settings_callback; item.textures[GLToolbarItem::Normal] = "cog_normal_36.png"; item.textures[GLToolbarItem::Hover] = "cog_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "cog_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "cog_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "cog_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; item.name = "layersediting"; item.tooltip = GUI::L_str("Layers editing"); + item.is_toggable = true; + item.action_callback = &m_action_layersediting_callback; item.textures[GLToolbarItem::Normal] = "variable_layer_height_normal_36.png"; item.textures[GLToolbarItem::Hover] = "variable_layer_height_hover_36.png"; item.textures[GLToolbarItem::Pressed] = "variable_layer_height_pressed_36.png"; + item.textures[GLToolbarItem::HoverPressed] = "variable_layer_height_hover_pressed_36.png"; item.textures[GLToolbarItem::Disabled] = "variable_layer_height_disabled_36.png"; if (!m_toolbar.add_item(item)) return false; @@ -3786,6 +3936,22 @@ void GLCanvas3D::_deregister_callbacks() m_on_gizmo_scale_uniformly_callback.deregister_callback(); m_on_gizmo_rotate_callback.deregister_callback(); m_on_update_geometry_info_callback.deregister_callback(); + +//################################################################################################################################### + m_action_add_callback.deregister_callback(); + m_action_delete_callback.deregister_callback(); + m_action_deleteall_callback.deregister_callback(); + m_action_arrange_callback.deregister_callback(); + m_action_more_callback.deregister_callback(); + m_action_fewer_callback.deregister_callback(); + m_action_ccw45_callback.deregister_callback(); + m_action_cw45_callback.deregister_callback(); + m_action_scale_callback.deregister_callback(); + m_action_split_callback.deregister_callback(); + m_action_cut_callback.deregister_callback(); + m_action_settings_callback.deregister_callback(); + m_action_layersediting_callback.deregister_callback(); +//################################################################################################################################### } void GLCanvas3D::_mark_volumes_for_layer_height() const diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index 24b848d6c4..a355389696 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -450,6 +450,9 @@ private: bool m_force_zoom_to_bed_enabled; bool m_apply_zoom_to_volumes_filter; mutable int m_hover_volume_id; +//################################################################################################################################### + bool m_toolbar_action_running; +//################################################################################################################################### bool m_warning_texture_enabled; bool m_legend_texture_enabled; bool m_picking_enabled; @@ -486,6 +489,22 @@ private: PerlCallback m_on_gizmo_rotate_callback; PerlCallback m_on_update_geometry_info_callback; +//################################################################################################################################### + PerlCallback m_action_add_callback; + PerlCallback m_action_delete_callback; + PerlCallback m_action_deleteall_callback; + PerlCallback m_action_arrange_callback; + PerlCallback m_action_more_callback; + PerlCallback m_action_fewer_callback; + PerlCallback m_action_ccw45_callback; + PerlCallback m_action_cw45_callback; + PerlCallback m_action_scale_callback; + PerlCallback m_action_split_callback; + PerlCallback m_action_cut_callback; + PerlCallback m_action_settings_callback; + PerlCallback m_action_layersediting_callback; +//################################################################################################################################### + public: GLCanvas3D(wxGLCanvas* canvas); ~GLCanvas3D(); @@ -552,6 +571,7 @@ public: //################################################################################################################################### void enable_toolbar_item(const std::string& name, bool enable); + bool is_toolbar_item_pressed(const std::string& name) const; //################################################################################################################################### void zoom_to_bed(); @@ -602,6 +622,22 @@ public: void register_on_gizmo_rotate_callback(void* callback); void register_on_update_geometry_info_callback(void* callback); +//################################################################################################################################### + void register_action_add_callback(void* callback); + void register_action_delete_callback(void* callback); + void register_action_deleteall_callback(void* callback); + void register_action_arrange_callback(void* callback); + void register_action_more_callback(void* callback); + void register_action_fewer_callback(void* callback); + void register_action_ccw45_callback(void* callback); + void register_action_cw45_callback(void* callback); + void register_action_scale_callback(void* callback); + void register_action_split_callback(void* callback); + void register_action_cut_callback(void* callback); + void register_action_settings_callback(void* callback); + void register_action_layersediting_callback(void* callback); +//################################################################################################################################### + void bind_event_handlers(); void unbind_event_handlers(); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 75cf78bdc6..f9b10e1d75 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -441,6 +441,12 @@ void GLCanvas3DManager::enable_toolbar_item(wxGLCanvas* canvas, const std::strin if (it != m_canvases.end()) it->second->enable_toolbar_item(name, enable); } + +bool GLCanvas3DManager::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->is_toolbar_item_pressed(name) : false; +} //################################################################################################################################### void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas) @@ -703,6 +709,99 @@ void GLCanvas3DManager::register_on_update_geometry_info_callback(wxGLCanvas* ca it->second->register_on_update_geometry_info_callback(callback); } +//################################################################################################################################### +void GLCanvas3DManager::register_action_add_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_add_callback(callback); +} + +void GLCanvas3DManager::register_action_delete_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_delete_callback(callback); +} + +void GLCanvas3DManager::register_action_deleteall_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_deleteall_callback(callback); +} + +void GLCanvas3DManager::register_action_arrange_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_arrange_callback(callback); +} + +void GLCanvas3DManager::register_action_more_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_more_callback(callback); +} + +void GLCanvas3DManager::register_action_fewer_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_fewer_callback(callback); +} + +void GLCanvas3DManager::register_action_ccw45_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_ccw45_callback(callback); +} + +void GLCanvas3DManager::register_action_cw45_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_cw45_callback(callback); +} + +void GLCanvas3DManager::register_action_scale_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_scale_callback(callback); +} + +void GLCanvas3DManager::register_action_split_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_split_callback(callback); +} + +void GLCanvas3DManager::register_action_cut_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_cut_callback(callback); +} + +void GLCanvas3DManager::register_action_settings_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_settings_callback(callback); +} + +void GLCanvas3DManager::register_action_layersediting_callback(wxGLCanvas* canvas, void* callback) +{ + CanvasesMap::iterator it = _get_canvas(canvas); + if (it != m_canvases.end()) + it->second->register_action_layersediting_callback(callback); +} +//################################################################################################################################### + GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) { return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index ace6515d2f..b4deb72b6f 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -119,6 +119,7 @@ public: //################################################################################################################################### void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable); + bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const; //################################################################################################################################### void zoom_to_bed(wxGLCanvas* canvas); @@ -165,6 +166,22 @@ public: void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback); void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback); +//################################################################################################################################### + void register_action_add_callback(wxGLCanvas* canvas, void* callback); + void register_action_delete_callback(wxGLCanvas* canvas, void* callback); + void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback); + void register_action_arrange_callback(wxGLCanvas* canvas, void* callback); + void register_action_more_callback(wxGLCanvas* canvas, void* callback); + void register_action_fewer_callback(wxGLCanvas* canvas, void* callback); + void register_action_ccw45_callback(wxGLCanvas* canvas, void* callback); + void register_action_cw45_callback(wxGLCanvas* canvas, void* callback); + void register_action_scale_callback(wxGLCanvas* canvas, void* callback); + void register_action_split_callback(wxGLCanvas* canvas, void* callback); + void register_action_cut_callback(wxGLCanvas* canvas, void* callback); + void register_action_settings_callback(wxGLCanvas* canvas, void* callback); + void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback); +//################################################################################################################################### + private: CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas); CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const; diff --git a/xs/src/slic3r/GUI/GLToolbar.cpp b/xs/src/slic3r/GUI/GLToolbar.cpp index fe861a45de..e6e9eeac86 100644 --- a/xs/src/slic3r/GUI/GLToolbar.cpp +++ b/xs/src/slic3r/GUI/GLToolbar.cpp @@ -1,6 +1,5 @@ #include "GLToolbar.hpp" -#include "../../libslic3r/Utils.hpp" #include "../../slic3r/GUI/GLCanvas3D.hpp" #include @@ -12,11 +11,13 @@ namespace Slic3r { namespace GUI { -GLToolbarItem::GLToolbarItem(EType type, const std::string& name, const std::string& tooltip) + GLToolbarItem::GLToolbarItem(EType type, const std::string& name, const std::string& tooltip, bool is_toggable, PerlCallback* action_callback) : m_type(type) , m_state(Disabled) , m_name(name) , m_tooltip(tooltip) + , m_is_toggable(is_toggable) + , m_action_callback(action_callback) { } @@ -29,9 +30,12 @@ bool GLToolbarItem::load_textures(const std::string* filenames) for (unsigned int i = (unsigned int)Normal; i < (unsigned int)Num_States; ++i) { - std::string filename = path + filenames[i]; - if (!m_icon_textures[i].load_from_file(filename, false)) - return false; + if (!filenames[i].empty()) + { + std::string filename = path + filenames[i]; + if (!m_icon_textures[i].load_from_file(filename, false)) + return false; + } } return true; @@ -67,6 +71,22 @@ int GLToolbarItem::get_icon_textures_size() const return m_icon_textures[Normal].get_width(); } +void GLToolbarItem::do_action() +{ + if (m_action_callback != nullptr) + m_action_callback->call(); +} + +bool GLToolbarItem::is_enabled() const +{ + return m_state != Disabled; +} + +bool GLToolbarItem::is_toggable() const +{ + return m_is_toggable; +} + bool GLToolbarItem::is_separator() const { return m_type == Separator; @@ -113,7 +133,7 @@ void GLToolbar::set_separator_x(float separator) bool GLToolbar::add_item(const GLToolbar::ItemCreationData& data) { - GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data.name, data.tooltip); + GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data.name, data.tooltip, data.is_toggable, data.action_callback); if ((item == nullptr) || !item->load_textures(data.textures)) return false; @@ -124,7 +144,7 @@ bool GLToolbar::add_item(const GLToolbar::ItemCreationData& data) bool GLToolbar::add_separator() { - GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Separator, "", ""); + GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Separator, "", "", false, nullptr); if (item == nullptr) return false; @@ -156,6 +176,17 @@ void GLToolbar::disable_item(const std::string& name) } } +bool GLToolbar::is_item_pressed(const std::string& name) const +{ + for (GLToolbarItem* item : m_items) + { + if (item->get_name() == name) + return (item->get_state() == GLToolbarItem::Pressed) || (item->get_state() == GLToolbarItem::HoverPressed); + } + + return false; +} + void GLToolbar::update_hover_state(GLCanvas3D& canvas, const Pointf& mouse_pos) { if (!m_enabled) @@ -201,8 +232,17 @@ void GLToolbar::update_hover_state(GLCanvas3D& canvas, const Pointf& mouse_pos) } case GLToolbarItem::Pressed: { - if (!inside) - item->set_state(GLToolbarItem::Normal); + if (inside) + item->set_state(GLToolbarItem::HoverPressed); + + break; + } + case GLToolbarItem::HoverPressed: + { + if (inside) + tooltip = item->get_tooltip(); + else + item->set_state(GLToolbarItem::Pressed); break; } @@ -219,9 +259,77 @@ void GLToolbar::update_hover_state(GLCanvas3D& canvas, const Pointf& mouse_pos) canvas.set_tooltip(tooltip); } +int GLToolbar::contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const +{ + if (!m_enabled) + return -1; + + float cnv_w = (float)canvas.get_canvas_size().get_width(); + float width = _get_total_width(); + float left = 0.5f * (cnv_w - width); + float top = m_offset_y; + + int id = -1; + + for (GLToolbarItem* item : m_items) + { + ++id; + + if (item->is_separator()) + left += (m_separator_x + m_gap_x); + else + { + float tex_size = (float)item->get_icon_textures_size() * m_textures_scale; + float right = left + tex_size; + float bottom = top + tex_size; + + if ((left <= mouse_pos.x) && (mouse_pos.x <= right) && (top <= mouse_pos.y) && (mouse_pos.y <= bottom)) + return id; + + left += (tex_size + m_gap_x); + } + } + + return -1; +} + +void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& canvas) +{ + if (item_id < (unsigned int)m_items.size()) + { + GLToolbarItem* item = m_items[item_id]; + if ((item != nullptr) && !item->is_separator() && item->is_enabled()) + { + if (item->is_toggable()) + { + GLToolbarItem::EState state = item->get_state(); + if (state == GLToolbarItem::Hover) + item->set_state(GLToolbarItem::HoverPressed); + else if (state == GLToolbarItem::HoverPressed) + item->set_state(GLToolbarItem::Hover); + + canvas.render(); + item->do_action(); + } + else + { + item->set_state(GLToolbarItem::HoverPressed); + canvas.render(); + item->do_action(); + if (item->get_state() != GLToolbarItem::Disabled) + { + // the item may get disabled during the action, if not, set it to normal state + item->set_state(GLToolbarItem::Hover); + canvas.render(); + } + } + } + } +} + void GLToolbar::render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const { - if (m_items.empty()) + if (!m_enabled || m_items.empty()) return; ::glDisable(GL_DEPTH_TEST); diff --git a/xs/src/slic3r/GUI/GLToolbar.hpp b/xs/src/slic3r/GUI/GLToolbar.hpp index 5a1af5b37f..e5c8f9fb85 100644 --- a/xs/src/slic3r/GUI/GLToolbar.hpp +++ b/xs/src/slic3r/GUI/GLToolbar.hpp @@ -2,6 +2,7 @@ #define slic3r_GLToolbar_hpp_ #include "../../slic3r/GUI/GLTexture.hpp" +#include "../../libslic3r/Utils.hpp" #include #include @@ -29,6 +30,7 @@ public: Normal, Hover, Pressed, + HoverPressed, Disabled, Num_States }; @@ -43,8 +45,11 @@ private: std::string m_name; std::string m_tooltip; + bool m_is_toggable; + PerlCallback* m_action_callback; + public: - GLToolbarItem(EType type, const std::string& name, const std::string& tooltip); + GLToolbarItem(EType type, const std::string& name, const std::string& tooltip, bool is_toggable, PerlCallback* action_callback); bool load_textures(const std::string* filenames); @@ -58,6 +63,10 @@ public: unsigned int get_icon_texture_id() const; int get_icon_textures_size() const; + void do_action(); + + bool is_enabled() const; + bool is_toggable() const; bool is_separator() const; }; @@ -68,6 +77,8 @@ public: { std::string name; std::string tooltip; + bool is_toggable; + PerlCallback* action_callback; std::string textures[GLToolbarItem::Num_States]; }; @@ -99,8 +110,15 @@ public: void enable_item(const std::string& name); void disable_item(const std::string& name); + bool is_item_pressed(const std::string& name) const; + void update_hover_state(GLCanvas3D& canvas, const Pointf& mouse_pos); + // returns the id of the item under the given mouse position or -1 if none + int contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const; + + void do_action(unsigned int item_id, GLCanvas3D& canvas); + void render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const; private: diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index b906a3ce93..1999b2ce3b 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -452,6 +452,15 @@ enable_toolbar_item(canvas, item, enable) CODE: _3DScene::enable_toolbar_item((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), item, enable); +bool +is_toolbar_item_pressed(canvas, item) + SV *canvas; + std::string item; + CODE: + RETVAL = _3DScene::is_toolbar_item_pressed((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), item); + OUTPUT: + RETVAL + void zoom_to_bed(canvas) SV *canvas; @@ -638,7 +647,98 @@ register_on_update_geometry_info_callback(canvas, callback) SV *callback; CODE: _3DScene::register_on_update_geometry_info_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - + +void +register_action_add_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_add_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_delete_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_delete_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_deleteall_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_deleteall_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_arrange_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_arrange_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_more_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_more_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_fewer_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_fewer_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_ccw45_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_ccw45_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_cw45_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_cw45_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_scale_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_scale_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_split_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_split_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_cut_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_cut_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_settings_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_settings_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + +void +register_action_layersediting_callback(canvas, callback) + SV *canvas; + SV *callback; + CODE: + _3DScene::register_action_layersediting_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); + void reset_legend_texture() CODE: From 575b85bfd60bad7bc9627f3504d9fb7eb4cdba35 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 27 Jul 2018 12:26:14 +0200 Subject: [PATCH 078/185] Try to fix correct extruder updating on all OS --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 55 +++++++++++++-------------- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 2 + 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 9b967ffe4e..2061a5581e 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -56,6 +56,10 @@ int m_event_update_scene = 0; bool m_parts_changed = false; bool m_part_settings_changed = false; +#ifdef __WXOSX__ + wxString g_selected_extruder = ""; +#endif //__WXOSX__ + // typedef std::map t_category_icon; typedef std::map t_category_icon; inline t_category_icon& get_category_icon() { @@ -188,10 +192,10 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { -#ifdef __WXOSX__ - wxMessageBox("DATAVIEW_SELECTION_CHANGED"); -#endif //__WXOSX__ object_ctrl_selection_changed(); +#ifdef __WXOSX__ + update_extruder_in_config(g_selected_extruder); +#endif //__WXOSX__ }); m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxEvent& event) @@ -218,39 +222,20 @@ wxBoxSizer* content_objects_list(wxWindow *win) #ifdef __WXMSW__ m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { - if (!*m_config) - return; - - wxString str = event.GetString(); - int extruder = str.size() > 1 ? 0 : atoi(str.c_str()); - (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); - - if (m_event_update_scene > 0) { - wxCommandEvent e(m_event_update_scene); - get_main_frame()->ProcessWindowEvent(e); - } + update_extruder_in_config(event.GetString()); }); #else m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { -#ifdef __WXOSX__ - wxMessageBox("DATAVIEW_ITEM_VALUE_CHANGED"); -#endif //__WXOSX__ - if (!*m_config) - return; if (event.GetColumn() == 3) { wxVariant variant; m_objects_model->GetValue(variant, event.GetItem(), 3); - auto str = variant.GetString(); - int extruder = str.size() > 1 ? 0 : atoi(str.c_str()); - - (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); - - if (m_event_update_scene > 0) { - wxCommandEvent e(m_event_update_scene); - get_main_frame()->ProcessWindowEvent(e); - } +#ifdef __WXOSX__ + g_selected_extruder = variant.GetString(); +#else // --> for Linux + update_extruder_in_config(variant.GetString()); +#endif //__WXOSX__ } }); #endif //__WXMSW__ @@ -1292,5 +1277,19 @@ void set_extruder_column_hidden(bool hide) m_objects_ctrl->GetColumn(3)->SetHidden(hide); } +void update_extruder_in_config(const wxString& selection) +{ + if (!*m_config || selection.empty()) + return; + + int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); + (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); + + if (m_event_update_scene > 0) { + wxCommandEvent e(m_event_update_scene); + get_main_frame()->ProcessWindowEvent(e); + } +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index aaeff7a431..b3feb71106 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -97,6 +97,8 @@ void part_selection_changed(); // show/hide "Extruder" column for Objects List void set_extruder_column_hidden(bool hide); +// update extruder in current config +void update_extruder_in_config(const wxString& selection); } //namespace GUI } //namespace Slic3r #endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file From c64cba8ec2e2f3bad4ddbb116037be642d17e4cb Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 27 Jul 2018 14:38:19 +0200 Subject: [PATCH 079/185] A few fixes on 3D scene toolbar --- lib/Slic3r/GUI/Plater.pm | 37 +++++++++++++++++++++-- xs/src/slic3r/GUI/GLCanvas3D.cpp | 35 ++++++++++++++++------ xs/src/slic3r/GUI/GLToolbar.cpp | 51 +++++++++++++++++++------------- xs/src/slic3r/GUI/GLToolbar.hpp | 14 +++++---- 4 files changed, 101 insertions(+), 36 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index c5b8bd4366..08989089db 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1025,6 +1025,10 @@ sub increase { } else { $self->update; } + +#======================================================================================================================== + $self->selection_changed; # refresh info (size, volume etc.) +#======================================================================================================================== $self->schedule_background_process; } @@ -2223,17 +2227,30 @@ sub selection_changed { if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_sel) - for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); +#=================================================================================================================================================== + for (TB_REMOVE, TB_MORE, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); +# for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); +#=================================================================================================================================================== #=================================================================================================================================================== $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, $layers_height_allowed); + + if ($have_sel) { + my $model_object = $self->{model}->objects->[$obj_idx]; + $self->{htoolbar}->EnableTool(TB_FEWER, $model_object->instances_count > 1); + } else { + $self->{htoolbar}->EnableTool(TB_FEWER, 0); + } #=================================================================================================================================================== } else { # On MSW my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); +#=================================================================================================================================================== + for grep $self->{"btn_$_"}, qw(remove increase rotate45cw rotate45ccw changescale split cut settings); +# for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); +#=================================================================================================================================================== #=================================================================================================================================================== if ($layers_height_allowed) { @@ -2241,6 +2258,17 @@ sub selection_changed { } else { $self->{"btn_layer_editing"}->Disable; } + + if ($have_sel) { + my $model_object = $self->{model}->objects->[$obj_idx]; + if ($model_object->instances_count > 1) { + $self->{"btn_decrease"}->Enable; + } else { + $self->{"btn_decrease"}->Disable; + } + } else { + $self->{"btn_decrease"}->Disable; + } #=================================================================================================================================================== } @@ -2250,6 +2278,11 @@ sub selection_changed { } Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", $layers_height_allowed); + + if ($have_sel) { + my $model_object = $self->{model}->objects->[$obj_idx]; + Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "fewer", $model_object->instances_count > 1); + } #=================================================================================================================================================== if ($self->{object_info_size}) { # have we already loaded the info pane? diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 7c8bf46655..414cad8e50 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1692,6 +1692,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) : m_canvas(canvas) , m_context(nullptr) , m_timer(nullptr) +//################################################################################################################################### + , m_toolbar(*this) +//################################################################################################################################### , m_config(nullptr) , m_print(nullptr) , m_model(nullptr) @@ -2798,7 +2801,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_layers_editing.last_object_id = layer_editing_object_idx; bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); //################################################################################################################################### - int toolbar_contains_mouse = m_toolbar.contains_mouse(*this, m_mouse.position); + int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position); //################################################################################################################################### if (evt.Entering()) @@ -2823,7 +2826,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (evt.LeftDClick() && (toolbar_contains_mouse != -1)) { m_toolbar_action_running = true; - m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); + m_toolbar.do_action((unsigned int)toolbar_contains_mouse); } //################################################################################################################################### else if (evt.LeftDown() || evt.RightDown()) @@ -2868,7 +2871,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (toolbar_contains_mouse != -1) { m_toolbar_action_running = true; - m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); + m_toolbar.do_action((unsigned int)toolbar_contains_mouse); } //################################################################################################################################### else @@ -2927,9 +2930,20 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.RightDown()) { - // if right clicking on volume, propagate event through callback - if (m_volumes.volumes[volume_idx]->hover) - m_on_right_click_callback.call(pos.x, pos.y); +//################################################################################################################################### + // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while + // the context menu is already shown, ensuring it to disappear if the mouse is outside any volume + m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); + render(); + if (m_hover_volume_id != -1) + { +//################################################################################################################################### + // if right clicking on volume, propagate event through callback (shows context menu) + if (m_volumes.volumes[volume_idx]->hover) + m_on_right_click_callback.call(pos.x, pos.y); +//################################################################################################################################### + } +//################################################################################################################################### } } } @@ -3128,7 +3142,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) _on_move(volume_idxs); } - else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) +//############################################################################################################################################################################# + else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) +// else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) +//############################################################################################################################################################################# { // deselect and propagate event through callback //################################################################################################################################### @@ -3780,7 +3797,7 @@ void GLCanvas3D::_picking_pass() const m_gizmos.reset_all_states(); //################################################################################################################################### - m_toolbar.update_hover_state(const_cast(*this), pos); + m_toolbar.update_hover_state(pos); //################################################################################################################################### } } @@ -4034,7 +4051,7 @@ void GLCanvas3D::_render_gizmo() const //################################################################################################################################### void GLCanvas3D::_render_toolbar() const { - m_toolbar.render(*this, m_mouse.position); + m_toolbar.render(m_mouse.position); } //################################################################################################################################### diff --git a/xs/src/slic3r/GUI/GLToolbar.cpp b/xs/src/slic3r/GUI/GLToolbar.cpp index e6e9eeac86..c425164a2f 100644 --- a/xs/src/slic3r/GUI/GLToolbar.cpp +++ b/xs/src/slic3r/GUI/GLToolbar.cpp @@ -11,7 +11,7 @@ namespace Slic3r { namespace GUI { - GLToolbarItem::GLToolbarItem(EType type, const std::string& name, const std::string& tooltip, bool is_toggable, PerlCallback* action_callback) +GLToolbarItem::GLToolbarItem(EType type, const std::string& name, const std::string& tooltip, bool is_toggable, PerlCallback* action_callback) : m_type(type) , m_state(Disabled) , m_name(name) @@ -82,6 +82,16 @@ bool GLToolbarItem::is_enabled() const return m_state != Disabled; } +bool GLToolbarItem::is_hovered() const +{ + return (m_state == Hover) || (m_state == HoverPressed); +} + +bool GLToolbarItem::is_pressed() const +{ + return (m_state == Pressed) || (m_state == HoverPressed); +} + bool GLToolbarItem::is_toggable() const { return m_is_toggable; @@ -92,8 +102,9 @@ bool GLToolbarItem::is_separator() const return m_type == Separator; } -GLToolbar::GLToolbar() - : m_enabled(false) +GLToolbar::GLToolbar(GLCanvas3D& parent) + : m_parent(parent) + , m_enabled(false) , m_textures_scale(1.0f) , m_offset_y(5.0f) , m_gap_x(2.0f) @@ -156,7 +167,7 @@ void GLToolbar::enable_item(const std::string& name) { for (GLToolbarItem* item : m_items) { - if (item->get_name() == name) + if ((item->get_name() == name) && (item->get_state() == GLToolbarItem::Disabled)) { item->set_state(GLToolbarItem::Normal); return; @@ -181,18 +192,18 @@ bool GLToolbar::is_item_pressed(const std::string& name) const for (GLToolbarItem* item : m_items) { if (item->get_name() == name) - return (item->get_state() == GLToolbarItem::Pressed) || (item->get_state() == GLToolbarItem::HoverPressed); + return item->is_pressed(); } return false; } -void GLToolbar::update_hover_state(GLCanvas3D& canvas, const Pointf& mouse_pos) +void GLToolbar::update_hover_state(const Pointf& mouse_pos) { if (!m_enabled) return; - float cnv_w = (float)canvas.get_canvas_size().get_width(); + float cnv_w = (float)m_parent.get_canvas_size().get_width(); float width = _get_total_width(); float left = 0.5f * (cnv_w - width); float top = m_offset_y; @@ -256,15 +267,15 @@ void GLToolbar::update_hover_state(GLCanvas3D& canvas, const Pointf& mouse_pos) } } - canvas.set_tooltip(tooltip); + m_parent.set_tooltip(tooltip); } -int GLToolbar::contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const +int GLToolbar::contains_mouse(const Pointf& mouse_pos) const { if (!m_enabled) return -1; - float cnv_w = (float)canvas.get_canvas_size().get_width(); + float cnv_w = (float)m_parent.get_canvas_size().get_width(); float width = _get_total_width(); float left = 0.5f * (cnv_w - width); float top = m_offset_y; @@ -293,12 +304,12 @@ int GLToolbar::contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) return -1; } -void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& canvas) +void GLToolbar::do_action(unsigned int item_id) { if (item_id < (unsigned int)m_items.size()) { GLToolbarItem* item = m_items[item_id]; - if ((item != nullptr) && !item->is_separator() && item->is_enabled()) + if ((item != nullptr) && !item->is_separator() && item->is_hovered()) { if (item->is_toggable()) { @@ -308,26 +319,26 @@ void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& canvas) else if (state == GLToolbarItem::HoverPressed) item->set_state(GLToolbarItem::Hover); - canvas.render(); + m_parent.render(); item->do_action(); } else { item->set_state(GLToolbarItem::HoverPressed); - canvas.render(); + m_parent.render(); item->do_action(); if (item->get_state() != GLToolbarItem::Disabled) { - // the item may get disabled during the action, if not, set it to normal state + // the item may get disabled during the action, if not, set it back to hover state item->set_state(GLToolbarItem::Hover); - canvas.render(); + m_parent.render(); } } } } } -void GLToolbar::render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const +void GLToolbar::render(const Pointf& mouse_pos) const { if (!m_enabled || m_items.empty()) return; @@ -337,9 +348,9 @@ void GLToolbar::render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const ::glPushMatrix(); ::glLoadIdentity(); - float cnv_w = (float)canvas.get_canvas_size().get_width(); - float cnv_h = (float)canvas.get_canvas_size().get_height(); - float zoom = canvas.get_camera_zoom(); + float cnv_w = (float)m_parent.get_canvas_size().get_width(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); + float zoom = m_parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float width = _get_total_width(); diff --git a/xs/src/slic3r/GUI/GLToolbar.hpp b/xs/src/slic3r/GUI/GLToolbar.hpp index e5c8f9fb85..d157f2a3b4 100644 --- a/xs/src/slic3r/GUI/GLToolbar.hpp +++ b/xs/src/slic3r/GUI/GLToolbar.hpp @@ -66,6 +66,9 @@ public: void do_action(); bool is_enabled() const; + bool is_hovered() const; + bool is_pressed() const; + bool is_toggable() const; bool is_separator() const; }; @@ -85,6 +88,7 @@ public: private: typedef std::vector ItemsList; + GLCanvas3D& m_parent; bool m_enabled; ItemsList m_items; @@ -94,7 +98,7 @@ private: float m_separator_x; public: - GLToolbar(); + explicit GLToolbar(GLCanvas3D& parent); bool is_enabled() const; void set_enabled(bool enable); @@ -112,14 +116,14 @@ public: bool is_item_pressed(const std::string& name) const; - void update_hover_state(GLCanvas3D& canvas, const Pointf& mouse_pos); + void update_hover_state(const Pointf& mouse_pos); // returns the id of the item under the given mouse position or -1 if none - int contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const; + int contains_mouse(const Pointf& mouse_pos) const; - void do_action(unsigned int item_id, GLCanvas3D& canvas); + void do_action(unsigned int item_id); - void render(const GLCanvas3D& canvas, const Pointf& mouse_pos) const; + void render(const Pointf& mouse_pos) const; private: float _get_total_width() const; From c2e09b5bcbf876b4b0b872d2f0ffb3b1d0afb979 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 27 Jul 2018 15:57:53 +0200 Subject: [PATCH 080/185] Added "printf"s to find of an crashing place --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 2061a5581e..a1b76cf9f3 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -693,6 +693,8 @@ void update_settings_list() m_option_sizer->Clear(true); + printf("update_settings_list\n"); + if (m_config) { auto extra_column = [](wxWindow* parent, const Line& line) @@ -1129,6 +1131,7 @@ void parts_changed(int obj_idx) void update_settings_value() { + printf("update_settings_value\n"); auto og = get_optgroup(ogFrequentlyObjectSettings); if (m_selected_object_id < 0 || m_objects->size() <= m_selected_object_id) { og->set_value("scale_x", 0); @@ -1144,6 +1147,7 @@ void update_settings_value() void part_selection_changed() { + printf("part_selection_changed\n"); auto item = m_objects_ctrl->GetSelection(); int obj_idx = -1; if (item) From 54f14e7ebb60936879cd23159729bc7e2b858749 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 27 Jul 2018 16:55:42 +0200 Subject: [PATCH 081/185] +1 message --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index a1b76cf9f3..a0dc827cfd 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -619,6 +619,7 @@ void set_object_scale(int idx, int scale) void unselect_objects() { + printf("UNSELECT OBJECTS\n"); m_objects_ctrl->UnselectAll(); part_selection_changed(); @@ -1133,10 +1134,12 @@ void update_settings_value() { printf("update_settings_value\n"); auto og = get_optgroup(ogFrequentlyObjectSettings); - if (m_selected_object_id < 0 || m_objects->size() <= m_selected_object_id) { + printf("selected_object_id = %d\n", m_selected_object_id); + if (m_selected_object_id < 0 || m_objects->size() <= m_selected_object_id) { og->set_value("scale_x", 0); og->set_value("scale_y", 0); og->set_value("scale_z", 0); + printf("return because of unselect\n"); return; } auto bb_size = (*m_objects)[m_selected_object_id]->instance_bounding_box(0).size(); From c2993de6e025b671543833fea8c78f6ef492bd3b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Sun, 29 Jul 2018 12:19:08 +0200 Subject: [PATCH 082/185] Scale and rotation from scene to value updates correctly --- lib/Slic3r/GUI/Plater.pm | 1 + xs/src/slic3r/GUI/3DScene.cpp | 5 ++ xs/src/slic3r/GUI/3DScene.hpp | 1 + xs/src/slic3r/GUI/Field.cpp | 4 +- xs/src/slic3r/GUI/Field.hpp | 4 +- xs/src/slic3r/GUI/GLCanvas3D.cpp | 9 +++ xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 80 +++++++++++++++++++++++---- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 6 ++ xs/xsp/GUI.xsp | 3 + 9 files changed, 98 insertions(+), 15 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 221b1cc2e2..6d6e0591f2 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1099,6 +1099,7 @@ sub rotate { $model_object->center_around_origin; $self->reset_thumbnail($obj_idx); } + Slic3r::GUI::update_rotation_value(deg2rad($angle), $axis == X ? "x" : ($axis == Y ? "y" : "z")); # update print and start background processing $self->{print}->add_model_object($model_object, $obj_idx); diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 62659033ad..0c1c89b079 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -261,6 +261,11 @@ const Pointf3& GLVolume::get_origin() const return m_origin; } +float GLVolume::get_angle_z() +{ + return m_angle_z; +} + void GLVolume::set_origin(const Pointf3& origin) { m_origin = origin; diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 5409b95883..3522feeeb5 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -318,6 +318,7 @@ public: void set_render_color(); const Pointf3& get_origin() const; + float get_angle_z(); void set_origin(const Pointf3& origin); void set_angle_z(float angle_z); void set_scale_factor(float scale_factor); diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 2109331a43..f862679af1 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -333,9 +333,7 @@ void SpinCtrl::BUILD() { break; } - const int min_val = m_opt_id == "standby_temperature_delta" ? - -500 : m_opt.min > 0 ? - m_opt.min : 0; + const int min_val = m_opt.min == INT_MIN ? 0: m_opt.min; const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647; auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 08b9936c76..905a24364c 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -414,8 +414,8 @@ public: boost::any& get_value()override { return m_value; } - void enable() override { dynamic_cast(window)->Enable(); }; - void disable() override{ dynamic_cast(window)->Disable(); }; + void enable() override { dynamic_cast(window)->Enable(); }; + void disable() override{ dynamic_cast(window)->Disable(); }; wxWindow* getWindow() override { return window; } }; diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 578a17b7af..95822a1ffb 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -3243,6 +3243,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) const BoundingBoxf3& bb = volumes[0]->transformed_bounding_box(); const Pointf3& size = bb.size(); m_on_update_geometry_info_callback.call(size.x, size.y, size.z, m_gizmos.get_scale()); + update_scale_values(size, m_gizmos.get_scale()); + update_rotation_value(volumes[0]->get_angle_z(), "z"); } if ((m_gizmos.get_current_type() != Gizmos::Rotate) && (volumes.size() > 1)) @@ -3341,6 +3343,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) case Gizmos::Scale: { m_on_gizmo_scale_uniformly_callback.call((double)m_gizmos.get_scale()); + Slic3r::GUI::update_settings_value(); break; } case Gizmos::Rotate: @@ -3387,7 +3390,13 @@ void GLCanvas3D::on_key_down(wxKeyEvent& evt) if (key == WXK_DELETE) m_on_remove_object_callback.call(); else + { +#ifdef __WXOSX__ + if (key == WXK_BACK) + m_on_remove_object_callback.call(); +#endif evt.Skip(); + } } } diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index a0dc827cfd..f573ae0d3a 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -11,6 +11,7 @@ #include #include #include +#include "Geometry.hpp" namespace Slic3r { @@ -40,6 +41,9 @@ int m_selected_object_id = -1; bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler // calls this method again and again and again +bool g_is_percent_scale = false; // It indicates if scale unit is percentage +int g_rotation_x = 0; // Last value of the rotation around the X axis +int g_rotation_y = 0; // Last value of the rotation around the Y axis ModelObjectPtrs* m_objects; std::shared_ptr m_config; std::shared_ptr m_default_config; @@ -435,6 +439,9 @@ Line add_og_to_object_settings(const std::string& option_name, const std::string def.sidetext = sidetext; def.width = 70; + if (option_name == "Rotation") + def.min = -360; + const std::string lower_name = boost::algorithm::to_lower_copy(option_name); std::vector axes{ "x", "y", "z" }; @@ -458,6 +465,7 @@ Line add_og_to_object_settings(const std::string& option_name, const std::string Option option = Option(def, lower_name + "_unit"); line.append_option(option); } + return line; } @@ -475,6 +483,9 @@ void add_object_settings(wxWindow* parent, wxBoxSizer* sizer) std::string key = "scale_" + axis; get_optgroup(ogFrequentlyObjectSettings)->set_side_text(key, selection); } + + g_is_percent_scale = selection == _("%"); + update_scale_values(); } }; @@ -485,16 +496,25 @@ void add_object_settings(wxWindow* parent, wxBoxSizer* sizer) // def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; // optgroup->append_single_option_line(Option(def, "object_name")); + ConfigOptionDef def; + + def.label = L("Name"); +// def.type = coString; + def.gui_type = "legend"; + def.tooltip = L("Object name"); + def.full_width = true; + def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; + optgroup->append_single_option_line(Option(def, "object_name")); + optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL); optgroup->sidetext_width = 25; optgroup->append_line(add_og_to_object_settings(L("Position"), L("mm"))); - optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°", 1)); - optgroup->append_line(add_og_to_object_settings(L("Scale"), "%", 2)); + optgroup->append_line(add_og_to_object_settings(L("Rotation"), "°")); + optgroup->append_line(add_og_to_object_settings(L("Scale"), "%")); optgroup->set_flag(ogDEFAULT); - ConfigOptionDef def; def.label = L("Place on bed"); def.type = coBool; def.tooltip = L("Automatic placing of models on printing bed in Y axis"); @@ -1142,10 +1162,8 @@ void update_settings_value() printf("return because of unselect\n"); return; } - auto bb_size = (*m_objects)[m_selected_object_id]->instance_bounding_box(0).size(); - og->set_value("scale_x", int(bb_size.x+0.5)); - og->set_value("scale_y", int(bb_size.y+0.5)); - og->set_value("scale_z", int(bb_size.z+0.5)); + g_is_percent_scale = boost::any_cast(og->get_value("scale_unit")) == _("%"); + update_scale_values(); } void part_selection_changed() @@ -1153,9 +1171,9 @@ void part_selection_changed() printf("part_selection_changed\n"); auto item = m_objects_ctrl->GetSelection(); int obj_idx = -1; + auto og = get_optgroup(ogFrequentlyObjectSettings); if (item) { - auto og = get_optgroup(ogFrequentlyObjectSettings); bool is_part = false; if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { obj_idx = m_objects_model->GetIdByItem(item); @@ -1173,10 +1191,14 @@ void part_selection_changed() } auto config = m_config; + og->set_value("object_name", m_objects_model->GetName(item)); m_default_config = std::make_shared(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part))); } - else - m_config = nullptr; + else { + wxString empty_str = wxEmptyString; + og->set_value("object_name", empty_str); + m_config = nullptr; + } update_settings_list(); @@ -1298,5 +1320,43 @@ void update_extruder_in_config(const wxString& selection) } } +void update_scale_values() +{ + update_scale_values((*m_objects)[m_selected_object_id]->instance_bounding_box(0).size(), + (*m_objects)[m_selected_object_id]->instances[0]->scaling_factor); +} + +void update_scale_values(const Pointf3& size, float scaling_factor) +{ + auto og = get_optgroup(ogFrequentlyObjectSettings); + + if (g_is_percent_scale) { + auto scale = scaling_factor * 100; + og->set_value("scale_x", int(scale)); + og->set_value("scale_y", int(scale)); + og->set_value("scale_z", int(scale)); + } + else { + og->set_value("scale_x", int(size.x + 0.5)); + og->set_value("scale_y", int(size.y + 0.5)); + og->set_value("scale_z", int(size.z + 0.5)); + } +} + +void update_rotation_value() +{ +// update_rotation_values(0, 0, (*m_objects)[m_selected_object_id]->volumes[0]->get_angle_z()); +} + +void update_rotation_value(const double angle, const std::string& axis) +{ + auto og = get_optgroup(ogFrequentlyObjectSettings); + + int deg = int(Geometry::rad2deg(angle)); + if (deg>180) deg -= 360; + + og->set_value("rotation_"+axis, deg); +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index b3feb71106..95a8180910 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -95,10 +95,16 @@ void on_btn_move_down(); void parts_changed(int obj_idx); void part_selection_changed(); +void update_settings_value(); // show/hide "Extruder" column for Objects List void set_extruder_column_hidden(bool hide); // update extruder in current config void update_extruder_in_config(const wxString& selection); +// update scale values after scale unit changing or "gizmos" +void update_scale_values(); +void update_scale_values(const Pointf3& size, float scale); +// update rotation values after "gizmos" +void update_rotation_value(const double angle, const std::string& axis); } //namespace GUI } //namespace Slic3r #endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 3d919a663a..5bee1b3185 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -158,6 +158,9 @@ void select_current_object(int idx) void remove_obj() %code%{ Slic3r::GUI::remove(); %}; +void update_rotation_value(double angle, const char *axis) + %code%{ Slic3r::GUI::update_rotation_value(angle, axis); %}; + std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; From b6d1d10502b645f690bf375526155fe402e85e06 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 30 Jul 2018 10:48:40 +0200 Subject: [PATCH 083/185] Added updating for the Z_rotation after object selection changing --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 16 +++++++++++++--- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 4 +++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index f573ae0d3a..39e49bcba4 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -503,7 +503,7 @@ void add_object_settings(wxWindow* parent, wxBoxSizer* sizer) def.gui_type = "legend"; def.tooltip = L("Object name"); def.full_width = true; - def.default_value = new ConfigOptionString{ "BlaBla_object.stl" }; + def.default_value = new ConfigOptionString{ " " }; optgroup->append_single_option_line(Option(def, "object_name")); optgroup->set_flag(ogSIDE_OPTIONS_VERTICAL); @@ -1164,6 +1164,7 @@ void update_settings_value() } g_is_percent_scale = boost::any_cast(og->get_value("scale_unit")) == _("%"); update_scale_values(); + update_rotation_values(); } void part_selection_changed() @@ -1343,9 +1344,18 @@ void update_scale_values(const Pointf3& size, float scaling_factor) } } -void update_rotation_value() +void update_rotation_values() { -// update_rotation_values(0, 0, (*m_objects)[m_selected_object_id]->volumes[0]->get_angle_z()); + auto og = get_optgroup(ogFrequentlyObjectSettings); + + og->set_value("rotation_x", 0); + og->set_value("rotation_y", 0); + + auto rotation_z = (*m_objects)[m_selected_object_id]->instances[0]->rotation; + auto deg = int(Geometry::rad2deg(rotation_z)); + if (deg > 180) deg -= 360; + + og->set_value("rotation_z", deg); } void update_rotation_value(const double angle, const std::string& axis) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 95a8180910..9728f77d80 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -103,7 +103,9 @@ void update_extruder_in_config(const wxString& selection); // update scale values after scale unit changing or "gizmos" void update_scale_values(); void update_scale_values(const Pointf3& size, float scale); -// update rotation values after "gizmos" +// update rotation values object selection changing +void update_rotation_values(); +// update rotation value after "gizmos" void update_rotation_value(const double angle, const std::string& axis); } //namespace GUI } //namespace Slic3r From 460021f7d088645df079a3650b99f0937d1abf6d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 30 Jul 2018 12:17:32 +0200 Subject: [PATCH 084/185] EXPERIMENTS for Linux --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 117 +++++++++++ xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 4 + xs/src/slic3r/GUI/wxExtensions.cpp | 284 +++++++++++++++++++++++++- xs/src/slic3r/GUI/wxExtensions.hpp | 188 ++++++++++++++++- 4 files changed, 589 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 39e49bcba4..31c6742c6b 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -427,6 +427,8 @@ void add_objects_list(wxWindow* parent, wxBoxSizer* sizer) const auto ol_sizer = content_objects_list(parent); sizer->Add(ol_sizer, 1, wxEXPAND | wxTOP, 20); set_objects_list_sizer(ol_sizer); + + sizer->Add(get_experimental_sizer(parent), 0, wxEXPAND | wxTOP, 20); } Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value = 0) @@ -1368,5 +1370,120 @@ void update_rotation_value(const double angle, const std::string& axis) og->set_value("rotation_"+axis, deg); } + +// **************************************** EXPERIMENRS **************************************** +// wxDataViewCtrl *tmp_ctrl = nullptr; +// MyMusicTreeModel *m_music_model = nullptr; + +wxSizer* get_experimental_sizer(wxWindow* parent) +{ + auto tmp_ctrl = new wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize); + tmp_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects +// tmp_ctrl->Connect(wxEVT_CHAR, +// wxKeyEventHandler(MyFrame::OnDataViewChar), +// NULL, this); + + auto m_music_model = new MyMusicTreeModel; + tmp_ctrl->AssociateModel(m_music_model); + +#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + tmp_ctrl->EnableDragSource(wxDF_UNICODETEXT); + tmp_ctrl->EnableDropTarget(wxDF_UNICODETEXT); +#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE + + // column 0 of the view control: + + wxDataViewTextRenderer *tr = + new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); + wxDataViewColumn *column0 = + new wxDataViewColumn("title", tr, 0, 200, wxALIGN_LEFT, + wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); + tmp_ctrl->AppendColumn(column0); +#if 0 + // Call this and sorting is enabled + // immediately upon start up. + column0->SetAsSortKey(); +#endif + + // column 1 of the view control: + + tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE); + wxDataViewColumn *column1 = + new wxDataViewColumn("artist", tr, 1, 150, wxALIGN_LEFT, + wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE | + wxDATAVIEW_COL_RESIZABLE); + column1->SetMinWidth(150); // this column can't be resized to be smaller + tmp_ctrl->AppendColumn(column1); + + // column 2 of the view control: + + wxDataViewSpinRenderer *sr = + new wxDataViewSpinRenderer(0, 2010, wxDATAVIEW_CELL_EDITABLE, + wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); + wxDataViewColumn *column2 = + new wxDataViewColumn("year", sr, 2, 60, wxALIGN_LEFT, + wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE); + tmp_ctrl->AppendColumn(column2); + + // column 3 of the view control: + + wxArrayString choices; + choices.Add("good"); + choices.Add("bad"); + choices.Add("lousy"); + wxDataViewChoiceRenderer *c = + new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, + wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); + wxDataViewColumn *column3 = + new wxDataViewColumn("rating", c, 3, 100, wxALIGN_LEFT, + wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE); + tmp_ctrl->AppendColumn(column3); + + // column 4 of the view control: + + tmp_ctrl->AppendProgressColumn("popularity", 4, wxDATAVIEW_CELL_INERT, 80); + + // column 5 of the view control: + + MyCustomRenderer *cr = new MyCustomRenderer(wxDATAVIEW_CELL_ACTIVATABLE); + wxDataViewColumn *column5 = + new wxDataViewColumn("custom", cr, 5, -1, wxALIGN_LEFT, + wxDATAVIEW_COL_RESIZABLE); + tmp_ctrl->AppendColumn(column5); + + + // select initially the ninth symphony: + tmp_ctrl->Select(m_music_model->GetNinthItem()); + + + const wxSizerFlags border = wxSizerFlags().DoubleBorder(); + + wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL); + auto add_btn = new wxButton(parent, wxID_ANY, "Add Mozart"); + auto del_btn = new wxButton(parent, wxID_ANY, "Delete selected"); + button_sizer->Add(add_btn, border); + button_sizer->Add(del_btn, border); + + add_btn->Bind(wxEVT_BUTTON, [m_music_model](wxCommandEvent&) + { + m_music_model->AddToClassical("Eine kleine Nachtmusik", "Wolfgang Mozart", 1787); + }); + + del_btn->Bind(wxEVT_BUTTON, [tmp_ctrl, m_music_model](wxCommandEvent&){ + wxDataViewItemArray items; + int len = tmp_ctrl->GetSelections(items); + for (int i = 0; i < len; i++) + if (items[i].IsOk()) + m_music_model->Delete(items[i]); + }); + + auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(tmp_ctrl, 0, wxEXPAND | wxLEFT, 20); + sizer->Add(button_sizer); + return sizer; +} + +// **************************************** EXPERIMENRS **************************************** + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 9728f77d80..f8d7821c1d 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -107,6 +107,10 @@ void update_scale_values(const Pointf3& size, float scale); void update_rotation_values(); // update rotation value after "gizmos" void update_rotation_value(const double angle, const std::string& axis); + + +wxSizer* get_experimental_sizer(wxWindow* parent); + } //namespace GUI } //namespace Slic3r #endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 422c999d19..0690ecee29 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -682,7 +682,285 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, } +// ************************************** EXPERIMENTS *************************************** +// ---------------------------------------------------------------------------- +// MyMusicTreeModel +// ---------------------------------------------------------------------------- + +MyMusicTreeModel::MyMusicTreeModel() +{ + m_root = new MyMusicTreeModelNode(NULL, "My Music"); + + // setup pop music + m_pop = new MyMusicTreeModelNode(m_root, "Pop music"); + m_pop->Append( + new MyMusicTreeModelNode(m_pop, "You are not alone", "Michael Jackson", 1995)); + m_pop->Append( + new MyMusicTreeModelNode(m_pop, "Take a bow", "Madonna", 1994)); + m_root->Append(m_pop); + + // setup classical music + m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); + m_ninth = new MyMusicTreeModelNode(m_classical, "Ninth symphony", + "Ludwig van Beethoven", 1824); + m_classical->Append(m_ninth); + m_classical->Append(new MyMusicTreeModelNode(m_classical, "German Requiem", + "Johannes Brahms", 1868)); + m_root->Append(m_classical); + + m_classicalMusicIsKnownToControl = false; +} + +wxString MyMusicTreeModel::GetTitle(const wxDataViewItem &item) const +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_title; +} + +wxString MyMusicTreeModel::GetArtist(const wxDataViewItem &item) const +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return wxEmptyString; + + return node->m_artist; +} + +int MyMusicTreeModel::GetYear(const wxDataViewItem &item) const +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return 2000; + + return node->m_year; +} + +void MyMusicTreeModel::AddToClassical(const wxString &title, const wxString &artist, + unsigned int year) +{ + if (!m_classical) + { + wxASSERT(m_root); + + // it was removed: restore it + m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); + m_root->Append(m_classical); + + // notify control + wxDataViewItem child((void*)m_classical); + wxDataViewItem parent((void*)m_root); + ItemAdded(parent, child); + } + + // add to the classical music node a new node: + MyMusicTreeModelNode *child_node = + new MyMusicTreeModelNode(m_classical, title, artist, year); + m_classical->Append(child_node); + + // FIXME: what's m_classicalMusicIsKnownToControl for? + if (m_classicalMusicIsKnownToControl) + { + // notify control + wxDataViewItem child((void*)child_node); + wxDataViewItem parent((void*)m_classical); + ItemAdded(parent, child); + } +} + +void MyMusicTreeModel::Delete(const wxDataViewItem &item) +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + if (!node) // happens if item.IsOk()==false + return; + + wxDataViewItem parent(node->GetParent()); + if (!parent.IsOk()) + { + wxASSERT(node == m_root); + + // don't make the control completely empty: + printf("Cannot remove the root item!"); + return; + } + + // is the node one of those we keep stored in special pointers? + if (node == m_pop) + m_pop = NULL; + else if (node == m_classical) + m_classical = NULL; + else if (node == m_ninth) + m_ninth = NULL; + + // first remove the node from the parent's array of children; + // NOTE: MyMusicTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + node->GetParent()->GetChildren().Remove(node); + + // free the node + delete node; + + // notify control + ItemDeleted(parent, item); +} + +int MyMusicTreeModel::Compare(const wxDataViewItem &item1, const wxDataViewItem &item2, + unsigned int column, bool ascending) const +{ + wxASSERT(item1.IsOk() && item2.IsOk()); + // should never happen + + if (IsContainer(item1) && IsContainer(item2)) + { + wxVariant value1, value2; + GetValue(value1, item1, 0); + GetValue(value2, item2, 0); + + wxString str1 = value1.GetString(); + wxString str2 = value2.GetString(); + int res = str1.Cmp(str2); + if (res) return res; + + // items must be different + wxUIntPtr litem1 = (wxUIntPtr)item1.GetID(); + wxUIntPtr litem2 = (wxUIntPtr)item2.GetID(); + + return litem1 - litem2; + } + + return wxDataViewModel::Compare(item1, item2, column, ascending); +} + +void MyMusicTreeModel::GetValue(wxVariant &variant, + const wxDataViewItem &item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + switch (col) + { + case 0: + variant = node->m_title; + break; + case 1: + variant = node->m_artist; + break; + case 2: + variant = (long)node->m_year; + break; + case 3: + variant = node->m_quality; + break; + case 4: + variant = 80L; // all music is very 80% popular + break; + case 5: + if (GetYear(item) < 1900) + variant = "old"; + else + variant = "new"; + break; + + default: + printf("MyMusicTreeModel::GetValue: wrong column %d", col); + } +} + +bool MyMusicTreeModel::SetValue(const wxVariant &variant, + const wxDataViewItem &item, unsigned int col) +{ + wxASSERT(item.IsOk()); + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + switch (col) + { + case 0: + node->m_title = variant.GetString(); + return true; + case 1: + node->m_artist = variant.GetString(); + return true; + case 2: + node->m_year = variant.GetLong(); + return true; + case 3: + node->m_quality = variant.GetString(); + return true; + + default: + printf("MyMusicTreeModel::SetValue: wrong column"); + } + return false; +} + +bool MyMusicTreeModel::IsEnabled(const wxDataViewItem &item, + unsigned int col) const +{ + wxASSERT(item.IsOk()); + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + + // disable Beethoven's ratings, his pieces can only be good + return !(col == 3 && node->m_artist.EndsWith("Beethoven")); +} + +wxDataViewItem MyMusicTreeModel::GetParent(const wxDataViewItem &item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(0); + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + + // "MyMusic" also has no parent + if (node == m_root) + return wxDataViewItem(0); + + return wxDataViewItem((void*)node->GetParent()); +} + +bool MyMusicTreeModel::IsContainer(const wxDataViewItem &item) const +{ + // the invisble root node can have children + // (in our model always "MyMusic") + if (!item.IsOk()) + return true; + + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); + return node->IsContainer(); +} + +unsigned int MyMusicTreeModel::GetChildren(const wxDataViewItem &parent, + wxDataViewItemArray &array) const +{ + MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)parent.GetID(); + if (!node) + { + array.Add(wxDataViewItem((void*)m_root)); + return 1; + } + + if (node == m_classical) + { + MyMusicTreeModel *model = (MyMusicTreeModel*)(const MyMusicTreeModel*) this; + model->m_classicalMusicIsKnownToControl = true; + } + + if (node->GetChildCount() == 0) + { + return 0; + } + + unsigned int count = node->GetChildren().GetCount(); + for (unsigned int pos = 0; pos < count; pos++) + { + MyMusicTreeModelNode *child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + + return count; +} + // ***************************************************************************** - - - diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 3d0ca85640..8b4000d556 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -477,8 +477,194 @@ public: private: wxString m_value; }; -// ***************************************************************************** +// ******************************* EXPERIMENTS ********************************************** +// ---------------------------------------------------------------------------- +// MyMusicTreeModelNode: a node inside MyMusicTreeModel +// ---------------------------------------------------------------------------- +class MyMusicTreeModelNode; +WX_DEFINE_ARRAY_PTR(MyMusicTreeModelNode*, MyMusicTreeModelNodePtrArray); +class MyMusicTreeModelNode +{ +public: + MyMusicTreeModelNode(MyMusicTreeModelNode* parent, + const wxString &title, const wxString &artist, + unsigned int year) + { + m_parent = parent; + + m_title = title; + m_artist = artist; + m_year = year; + m_quality = "good"; + + m_container = false; + } + + MyMusicTreeModelNode(MyMusicTreeModelNode* parent, + const wxString &branch) + { + m_parent = parent; + + m_title = branch; + m_year = -1; + + m_container = true; + } + + ~MyMusicTreeModelNode() + { + // free all our children nodes + size_t count = m_children.GetCount(); + for (size_t i = 0; i < count; i++) + { + MyMusicTreeModelNode *child = m_children[i]; + delete child; + } + } + + bool IsContainer() const + { + return m_container; + } + + MyMusicTreeModelNode* GetParent() + { + return m_parent; + } + MyMusicTreeModelNodePtrArray& GetChildren() + { + return m_children; + } + MyMusicTreeModelNode* GetNthChild(unsigned int n) + { + return m_children.Item(n); + } + void Insert(MyMusicTreeModelNode* child, unsigned int n) + { + m_children.Insert(child, n); + } + void Append(MyMusicTreeModelNode* child) + { + m_children.Add(child); + } + unsigned int GetChildCount() const + { + return m_children.GetCount(); + } + +public: // public to avoid getters/setters + wxString m_title; + wxString m_artist; + int m_year; + wxString m_quality; + + // TODO/FIXME: + // the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded) + // needs to know in advance if a node is or _will be_ a container. + // Thus implementing: + // bool IsContainer() const + // { return m_children.GetCount()>0; } + // doesn't work with wxGTK when MyMusicTreeModel::AddToClassical is called + // AND the classical node was removed (a new node temporary without children + // would be added to the control) + bool m_container; + +private: + MyMusicTreeModelNode *m_parent; + MyMusicTreeModelNodePtrArray m_children; +}; + + +// ---------------------------------------------------------------------------- +// MyMusicTreeModel +// ---------------------------------------------------------------------------- + +/* +Implement this data model +Title Artist Year Judgement +-------------------------------------------------------------------------- +1: My Music: +2: Pop music +3: You are not alone Michael Jackson 1995 good +4: Take a bow Madonna 1994 good +5: Classical music +6: Ninth Symphony Ludwig v. Beethoven 1824 good +7: German Requiem Johannes Brahms 1868 good +*/ + +class MyMusicTreeModel : public wxDataViewModel +{ +public: + MyMusicTreeModel(); + ~MyMusicTreeModel() + { + delete m_root; + } + + // helper method for wxLog + + wxString GetTitle(const wxDataViewItem &item) const; + wxString GetArtist(const wxDataViewItem &item) const; + int GetYear(const wxDataViewItem &item) const; + + // helper methods to change the model + + void AddToClassical(const wxString &title, const wxString &artist, + unsigned int year); + void Delete(const wxDataViewItem &item); + + wxDataViewItem GetNinthItem() const + { + return wxDataViewItem(m_ninth); + } + + // override sorting to always sort branches ascendingly + + int Compare(const wxDataViewItem &item1, const wxDataViewItem &item2, + unsigned int column, bool ascending) const override;//; + + // implementation of base class virtuals to define model + + virtual unsigned int GetColumnCount() const override//wxOVERRIDE + { + return 6; + } + + virtual wxString GetColumnType(unsigned int col) const override//wxOVERRIDE + { + if (col == 2) + return wxT("long"); + + return wxT("string"); + } + + virtual void GetValue(wxVariant &variant, + const wxDataViewItem &item, unsigned int col) const override;//wxOVERRIDE; + virtual bool SetValue(const wxVariant &variant, + const wxDataViewItem &item, unsigned int col) override;//wxOVERRIDE; + + virtual bool IsEnabled(const wxDataViewItem &item, + unsigned int col) const override;//wxOVERRIDE; + + virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override;//wxOVERRIDE; + virtual bool IsContainer(const wxDataViewItem &item) const override;//wxOVERRIDE; + virtual unsigned int GetChildren(const wxDataViewItem &parent, + wxDataViewItemArray &array) const override;//wxOVERRIDE; + +private: + MyMusicTreeModelNode* m_root; + + // pointers to some "special" nodes of the tree: + MyMusicTreeModelNode* m_pop; + MyMusicTreeModelNode* m_classical; + MyMusicTreeModelNode* m_ninth; + + // ?? + bool m_classicalMusicIsKnownToControl; +}; + +// ****************************************************************************************** #endif // slic3r_GUI_wxExtensions_hpp_ From 085ae68e6db7959b8dcb6299b29ee609abcedf3b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 30 Jul 2018 13:19:31 +0200 Subject: [PATCH 085/185] +1 experiment --- xs/src/slic3r/GUI/wxExtensions.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 0690ecee29..ae381366dc 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -692,21 +692,21 @@ MyMusicTreeModel::MyMusicTreeModel() m_root = new MyMusicTreeModelNode(NULL, "My Music"); // setup pop music - m_pop = new MyMusicTreeModelNode(m_root, "Pop music"); - m_pop->Append( - new MyMusicTreeModelNode(m_pop, "You are not alone", "Michael Jackson", 1995)); - m_pop->Append( - new MyMusicTreeModelNode(m_pop, "Take a bow", "Madonna", 1994)); - m_root->Append(m_pop); - - // setup classical music - m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); - m_ninth = new MyMusicTreeModelNode(m_classical, "Ninth symphony", - "Ludwig van Beethoven", 1824); - m_classical->Append(m_ninth); - m_classical->Append(new MyMusicTreeModelNode(m_classical, "German Requiem", - "Johannes Brahms", 1868)); - m_root->Append(m_classical); +// m_pop = new MyMusicTreeModelNode(m_root, "Pop music"); +// m_pop->Append( +// new MyMusicTreeModelNode(m_pop, "You are not alone", "Michael Jackson", 1995)); +// m_pop->Append( +// new MyMusicTreeModelNode(m_pop, "Take a bow", "Madonna", 1994)); +// m_root->Append(m_pop); +// +// // setup classical music +// m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); +// m_ninth = new MyMusicTreeModelNode(m_classical, "Ninth symphony", +// "Ludwig van Beethoven", 1824); +// m_classical->Append(m_ninth); +// m_classical->Append(new MyMusicTreeModelNode(m_classical, "German Requiem", +// "Johannes Brahms", 1868)); +// m_root->Append(m_classical); m_classicalMusicIsKnownToControl = false; } From 2142070331459cf25afab0d5011ae5d15a24a4e6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 30 Jul 2018 14:25:09 +0200 Subject: [PATCH 086/185] Fixed Linux-bug : "Add part" => segmentation fault. Deleted experimental code --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 117 ----------- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 3 - xs/src/slic3r/GUI/wxExtensions.cpp | 281 -------------------------- xs/src/slic3r/GUI/wxExtensions.hpp | 191 +---------------- 4 files changed, 6 insertions(+), 586 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 31c6742c6b..39e49bcba4 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -427,8 +427,6 @@ void add_objects_list(wxWindow* parent, wxBoxSizer* sizer) const auto ol_sizer = content_objects_list(parent); sizer->Add(ol_sizer, 1, wxEXPAND | wxTOP, 20); set_objects_list_sizer(ol_sizer); - - sizer->Add(get_experimental_sizer(parent), 0, wxEXPAND | wxTOP, 20); } Line add_og_to_object_settings(const std::string& option_name, const std::string& sidetext, int def_value = 0) @@ -1370,120 +1368,5 @@ void update_rotation_value(const double angle, const std::string& axis) og->set_value("rotation_"+axis, deg); } - -// **************************************** EXPERIMENRS **************************************** -// wxDataViewCtrl *tmp_ctrl = nullptr; -// MyMusicTreeModel *m_music_model = nullptr; - -wxSizer* get_experimental_sizer(wxWindow* parent) -{ - auto tmp_ctrl = new wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize); - tmp_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects -// tmp_ctrl->Connect(wxEVT_CHAR, -// wxKeyEventHandler(MyFrame::OnDataViewChar), -// NULL, this); - - auto m_music_model = new MyMusicTreeModel; - tmp_ctrl->AssociateModel(m_music_model); - -#if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - tmp_ctrl->EnableDragSource(wxDF_UNICODETEXT); - tmp_ctrl->EnableDropTarget(wxDF_UNICODETEXT); -#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - - // column 0 of the view control: - - wxDataViewTextRenderer *tr = - new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_INERT); - wxDataViewColumn *column0 = - new wxDataViewColumn("title", tr, 0, 200, wxALIGN_LEFT, - wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_RESIZABLE); - tmp_ctrl->AppendColumn(column0); -#if 0 - // Call this and sorting is enabled - // immediately upon start up. - column0->SetAsSortKey(); -#endif - - // column 1 of the view control: - - tr = new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE); - wxDataViewColumn *column1 = - new wxDataViewColumn("artist", tr, 1, 150, wxALIGN_LEFT, - wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE | - wxDATAVIEW_COL_RESIZABLE); - column1->SetMinWidth(150); // this column can't be resized to be smaller - tmp_ctrl->AppendColumn(column1); - - // column 2 of the view control: - - wxDataViewSpinRenderer *sr = - new wxDataViewSpinRenderer(0, 2010, wxDATAVIEW_CELL_EDITABLE, - wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); - wxDataViewColumn *column2 = - new wxDataViewColumn("year", sr, 2, 60, wxALIGN_LEFT, - wxDATAVIEW_COL_SORTABLE | wxDATAVIEW_COL_REORDERABLE); - tmp_ctrl->AppendColumn(column2); - - // column 3 of the view control: - - wxArrayString choices; - choices.Add("good"); - choices.Add("bad"); - choices.Add("lousy"); - wxDataViewChoiceRenderer *c = - new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, - wxALIGN_RIGHT | wxALIGN_CENTRE_VERTICAL); - wxDataViewColumn *column3 = - new wxDataViewColumn("rating", c, 3, 100, wxALIGN_LEFT, - wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_RESIZABLE); - tmp_ctrl->AppendColumn(column3); - - // column 4 of the view control: - - tmp_ctrl->AppendProgressColumn("popularity", 4, wxDATAVIEW_CELL_INERT, 80); - - // column 5 of the view control: - - MyCustomRenderer *cr = new MyCustomRenderer(wxDATAVIEW_CELL_ACTIVATABLE); - wxDataViewColumn *column5 = - new wxDataViewColumn("custom", cr, 5, -1, wxALIGN_LEFT, - wxDATAVIEW_COL_RESIZABLE); - tmp_ctrl->AppendColumn(column5); - - - // select initially the ninth symphony: - tmp_ctrl->Select(m_music_model->GetNinthItem()); - - - const wxSizerFlags border = wxSizerFlags().DoubleBorder(); - - wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL); - auto add_btn = new wxButton(parent, wxID_ANY, "Add Mozart"); - auto del_btn = new wxButton(parent, wxID_ANY, "Delete selected"); - button_sizer->Add(add_btn, border); - button_sizer->Add(del_btn, border); - - add_btn->Bind(wxEVT_BUTTON, [m_music_model](wxCommandEvent&) - { - m_music_model->AddToClassical("Eine kleine Nachtmusik", "Wolfgang Mozart", 1787); - }); - - del_btn->Bind(wxEVT_BUTTON, [tmp_ctrl, m_music_model](wxCommandEvent&){ - wxDataViewItemArray items; - int len = tmp_ctrl->GetSelections(items); - for (int i = 0; i < len; i++) - if (items[i].IsOk()) - m_music_model->Delete(items[i]); - }); - - auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(tmp_ctrl, 0, wxEXPAND | wxLEFT, 20); - sizer->Add(button_sizer); - return sizer; -} - -// **************************************** EXPERIMENRS **************************************** - } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index f8d7821c1d..e4484e72a1 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -108,9 +108,6 @@ void update_rotation_values(); // update rotation value after "gizmos" void update_rotation_value(const double angle, const std::string& axis); - -wxSizer* get_experimental_sizer(wxWindow* parent); - } //namespace GUI } //namespace Slic3r #endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index ae381366dc..c52448ed60 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -680,287 +680,6 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, return count; } - - // ************************************** EXPERIMENTS *************************************** -// ---------------------------------------------------------------------------- -// MyMusicTreeModel -// ---------------------------------------------------------------------------- - -MyMusicTreeModel::MyMusicTreeModel() -{ - m_root = new MyMusicTreeModelNode(NULL, "My Music"); - - // setup pop music -// m_pop = new MyMusicTreeModelNode(m_root, "Pop music"); -// m_pop->Append( -// new MyMusicTreeModelNode(m_pop, "You are not alone", "Michael Jackson", 1995)); -// m_pop->Append( -// new MyMusicTreeModelNode(m_pop, "Take a bow", "Madonna", 1994)); -// m_root->Append(m_pop); -// -// // setup classical music -// m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); -// m_ninth = new MyMusicTreeModelNode(m_classical, "Ninth symphony", -// "Ludwig van Beethoven", 1824); -// m_classical->Append(m_ninth); -// m_classical->Append(new MyMusicTreeModelNode(m_classical, "German Requiem", -// "Johannes Brahms", 1868)); -// m_root->Append(m_classical); - - m_classicalMusicIsKnownToControl = false; -} - -wxString MyMusicTreeModel::GetTitle(const wxDataViewItem &item) const -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return wxEmptyString; - - return node->m_title; -} - -wxString MyMusicTreeModel::GetArtist(const wxDataViewItem &item) const -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return wxEmptyString; - - return node->m_artist; -} - -int MyMusicTreeModel::GetYear(const wxDataViewItem &item) const -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return 2000; - - return node->m_year; -} - -void MyMusicTreeModel::AddToClassical(const wxString &title, const wxString &artist, - unsigned int year) -{ - if (!m_classical) - { - wxASSERT(m_root); - - // it was removed: restore it - m_classical = new MyMusicTreeModelNode(m_root, "Classical music"); - m_root->Append(m_classical); - - // notify control - wxDataViewItem child((void*)m_classical); - wxDataViewItem parent((void*)m_root); - ItemAdded(parent, child); - } - - // add to the classical music node a new node: - MyMusicTreeModelNode *child_node = - new MyMusicTreeModelNode(m_classical, title, artist, year); - m_classical->Append(child_node); - - // FIXME: what's m_classicalMusicIsKnownToControl for? - if (m_classicalMusicIsKnownToControl) - { - // notify control - wxDataViewItem child((void*)child_node); - wxDataViewItem parent((void*)m_classical); - ItemAdded(parent, child); - } -} - -void MyMusicTreeModel::Delete(const wxDataViewItem &item) -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - if (!node) // happens if item.IsOk()==false - return; - - wxDataViewItem parent(node->GetParent()); - if (!parent.IsOk()) - { - wxASSERT(node == m_root); - - // don't make the control completely empty: - printf("Cannot remove the root item!"); - return; - } - - // is the node one of those we keep stored in special pointers? - if (node == m_pop) - m_pop = NULL; - else if (node == m_classical) - m_classical = NULL; - else if (node == m_ninth) - m_ninth = NULL; - - // first remove the node from the parent's array of children; - // NOTE: MyMusicTreeModelNodePtrArray is only an array of _pointers_ - // thus removing the node from it doesn't result in freeing it - node->GetParent()->GetChildren().Remove(node); - - // free the node - delete node; - - // notify control - ItemDeleted(parent, item); -} - -int MyMusicTreeModel::Compare(const wxDataViewItem &item1, const wxDataViewItem &item2, - unsigned int column, bool ascending) const -{ - wxASSERT(item1.IsOk() && item2.IsOk()); - // should never happen - - if (IsContainer(item1) && IsContainer(item2)) - { - wxVariant value1, value2; - GetValue(value1, item1, 0); - GetValue(value2, item2, 0); - - wxString str1 = value1.GetString(); - wxString str2 = value2.GetString(); - int res = str1.Cmp(str2); - if (res) return res; - - // items must be different - wxUIntPtr litem1 = (wxUIntPtr)item1.GetID(); - wxUIntPtr litem2 = (wxUIntPtr)item2.GetID(); - - return litem1 - litem2; - } - - return wxDataViewModel::Compare(item1, item2, column, ascending); -} - -void MyMusicTreeModel::GetValue(wxVariant &variant, - const wxDataViewItem &item, unsigned int col) const -{ - wxASSERT(item.IsOk()); - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - switch (col) - { - case 0: - variant = node->m_title; - break; - case 1: - variant = node->m_artist; - break; - case 2: - variant = (long)node->m_year; - break; - case 3: - variant = node->m_quality; - break; - case 4: - variant = 80L; // all music is very 80% popular - break; - case 5: - if (GetYear(item) < 1900) - variant = "old"; - else - variant = "new"; - break; - - default: - printf("MyMusicTreeModel::GetValue: wrong column %d", col); - } -} - -bool MyMusicTreeModel::SetValue(const wxVariant &variant, - const wxDataViewItem &item, unsigned int col) -{ - wxASSERT(item.IsOk()); - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - switch (col) - { - case 0: - node->m_title = variant.GetString(); - return true; - case 1: - node->m_artist = variant.GetString(); - return true; - case 2: - node->m_year = variant.GetLong(); - return true; - case 3: - node->m_quality = variant.GetString(); - return true; - - default: - printf("MyMusicTreeModel::SetValue: wrong column"); - } - return false; -} - -bool MyMusicTreeModel::IsEnabled(const wxDataViewItem &item, - unsigned int col) const -{ - wxASSERT(item.IsOk()); - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - - // disable Beethoven's ratings, his pieces can only be good - return !(col == 3 && node->m_artist.EndsWith("Beethoven")); -} - -wxDataViewItem MyMusicTreeModel::GetParent(const wxDataViewItem &item) const -{ - // the invisible root node has no parent - if (!item.IsOk()) - return wxDataViewItem(0); - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - - // "MyMusic" also has no parent - if (node == m_root) - return wxDataViewItem(0); - - return wxDataViewItem((void*)node->GetParent()); -} - -bool MyMusicTreeModel::IsContainer(const wxDataViewItem &item) const -{ - // the invisble root node can have children - // (in our model always "MyMusic") - if (!item.IsOk()) - return true; - - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)item.GetID(); - return node->IsContainer(); -} - -unsigned int MyMusicTreeModel::GetChildren(const wxDataViewItem &parent, - wxDataViewItemArray &array) const -{ - MyMusicTreeModelNode *node = (MyMusicTreeModelNode*)parent.GetID(); - if (!node) - { - array.Add(wxDataViewItem((void*)m_root)); - return 1; - } - - if (node == m_classical) - { - MyMusicTreeModel *model = (MyMusicTreeModel*)(const MyMusicTreeModel*) this; - model->m_classicalMusicIsKnownToControl = true; - } - - if (node->GetChildCount() == 0) - { - return 0; - } - - unsigned int count = node->GetChildren().GetCount(); - for (unsigned int pos = 0; pos < count; pos++) - { - MyMusicTreeModelNode *child = node->GetChildren().Item(pos); - array.Add(wxDataViewItem((void*)child)); - } - - return count; -} // ***************************************************************************** diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 8b4000d556..12bb156cbb 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -163,6 +163,12 @@ public: m_scale = wxString::Format("%d%%", scale); m_type = "object"; m_volume_id = -1; +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + m_container = true; +#endif //__WXGTK__ set_object_action_icon(); } @@ -478,191 +484,6 @@ private: wxString m_value; }; // ******************************* EXPERIMENTS ********************************************** -// ---------------------------------------------------------------------------- -// MyMusicTreeModelNode: a node inside MyMusicTreeModel -// ---------------------------------------------------------------------------- -class MyMusicTreeModelNode; -WX_DEFINE_ARRAY_PTR(MyMusicTreeModelNode*, MyMusicTreeModelNodePtrArray); - -class MyMusicTreeModelNode -{ -public: - MyMusicTreeModelNode(MyMusicTreeModelNode* parent, - const wxString &title, const wxString &artist, - unsigned int year) - { - m_parent = parent; - - m_title = title; - m_artist = artist; - m_year = year; - m_quality = "good"; - - m_container = false; - } - - MyMusicTreeModelNode(MyMusicTreeModelNode* parent, - const wxString &branch) - { - m_parent = parent; - - m_title = branch; - m_year = -1; - - m_container = true; - } - - ~MyMusicTreeModelNode() - { - // free all our children nodes - size_t count = m_children.GetCount(); - for (size_t i = 0; i < count; i++) - { - MyMusicTreeModelNode *child = m_children[i]; - delete child; - } - } - - bool IsContainer() const - { - return m_container; - } - - MyMusicTreeModelNode* GetParent() - { - return m_parent; - } - MyMusicTreeModelNodePtrArray& GetChildren() - { - return m_children; - } - MyMusicTreeModelNode* GetNthChild(unsigned int n) - { - return m_children.Item(n); - } - void Insert(MyMusicTreeModelNode* child, unsigned int n) - { - m_children.Insert(child, n); - } - void Append(MyMusicTreeModelNode* child) - { - m_children.Add(child); - } - unsigned int GetChildCount() const - { - return m_children.GetCount(); - } - -public: // public to avoid getters/setters - wxString m_title; - wxString m_artist; - int m_year; - wxString m_quality; - - // TODO/FIXME: - // the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded) - // needs to know in advance if a node is or _will be_ a container. - // Thus implementing: - // bool IsContainer() const - // { return m_children.GetCount()>0; } - // doesn't work with wxGTK when MyMusicTreeModel::AddToClassical is called - // AND the classical node was removed (a new node temporary without children - // would be added to the control) - bool m_container; - -private: - MyMusicTreeModelNode *m_parent; - MyMusicTreeModelNodePtrArray m_children; -}; - - -// ---------------------------------------------------------------------------- -// MyMusicTreeModel -// ---------------------------------------------------------------------------- - -/* -Implement this data model -Title Artist Year Judgement --------------------------------------------------------------------------- -1: My Music: -2: Pop music -3: You are not alone Michael Jackson 1995 good -4: Take a bow Madonna 1994 good -5: Classical music -6: Ninth Symphony Ludwig v. Beethoven 1824 good -7: German Requiem Johannes Brahms 1868 good -*/ - -class MyMusicTreeModel : public wxDataViewModel -{ -public: - MyMusicTreeModel(); - ~MyMusicTreeModel() - { - delete m_root; - } - - // helper method for wxLog - - wxString GetTitle(const wxDataViewItem &item) const; - wxString GetArtist(const wxDataViewItem &item) const; - int GetYear(const wxDataViewItem &item) const; - - // helper methods to change the model - - void AddToClassical(const wxString &title, const wxString &artist, - unsigned int year); - void Delete(const wxDataViewItem &item); - - wxDataViewItem GetNinthItem() const - { - return wxDataViewItem(m_ninth); - } - - // override sorting to always sort branches ascendingly - - int Compare(const wxDataViewItem &item1, const wxDataViewItem &item2, - unsigned int column, bool ascending) const override;//; - - // implementation of base class virtuals to define model - - virtual unsigned int GetColumnCount() const override//wxOVERRIDE - { - return 6; - } - - virtual wxString GetColumnType(unsigned int col) const override//wxOVERRIDE - { - if (col == 2) - return wxT("long"); - - return wxT("string"); - } - - virtual void GetValue(wxVariant &variant, - const wxDataViewItem &item, unsigned int col) const override;//wxOVERRIDE; - virtual bool SetValue(const wxVariant &variant, - const wxDataViewItem &item, unsigned int col) override;//wxOVERRIDE; - - virtual bool IsEnabled(const wxDataViewItem &item, - unsigned int col) const override;//wxOVERRIDE; - - virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override;//wxOVERRIDE; - virtual bool IsContainer(const wxDataViewItem &item) const override;//wxOVERRIDE; - virtual unsigned int GetChildren(const wxDataViewItem &parent, - wxDataViewItemArray &array) const override;//wxOVERRIDE; - -private: - MyMusicTreeModelNode* m_root; - - // pointers to some "special" nodes of the tree: - MyMusicTreeModelNode* m_pop; - MyMusicTreeModelNode* m_classical; - MyMusicTreeModelNode* m_ninth; - - // ?? - bool m_classicalMusicIsKnownToControl; -}; // ****************************************************************************************** From 814d255c7785dbc7ccd7cb5a8e9a850a563fa257 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 30 Jul 2018 17:03:14 +0200 Subject: [PATCH 087/185] Added split-function for the object in list. Updated adding of amf-objects. --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 42 +++++++++++++++++++-------- xs/src/slic3r/GUI/wxExtensions.cpp | 5 ++-- xs/src/slic3r/GUI/wxExtensions.hpp | 3 +- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 39e49bcba4..3bfbfe7368 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -28,6 +28,7 @@ wxIcon m_icon_modifiermesh; wxIcon m_icon_solidmesh; wxIcon m_icon_manifold_warning; wxBitmap m_bmp_cog; +wxBitmap m_bmp_split; wxSlider* m_mover_x = nullptr; wxSlider* m_mover_y = nullptr; @@ -140,6 +141,9 @@ void init_mesh_icons(){ // init icon for manifold warning m_icon_manifold_warning = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); + // init bitmap for "Split to sub-objects" context menu + m_bmp_split = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("split.png")), wxBITMAP_TYPE_PNG); + // init bitmap for "Add Settings" context menu m_bmp_cog = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); } @@ -600,6 +604,10 @@ void add_object_to_list(const std::string &name, ModelObject* model_object) m_objects_model->SetValue(variant, item, 0); } + if (model_object->volumes.size() > 1) + for (auto id = 0; id < model_object->volumes.size(); id++) + m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false); + ModelObjectPtrs* objects = m_objects; // part_selection_changed(); #ifdef __WXMSW__ @@ -836,16 +844,22 @@ void get_settings_choice(wxMenu *menu, int id, bool is_part) wxMenu *create_add_part_popupmenu() { wxMenu *menu = new wxMenu; - wxWindowID config_id_base = wxWindow::NewControlId(4); - std::vector menu_items = { L("Add part"), L("Add modifier"), L("Add generic") }; + + wxWindowID config_id_base = wxWindow::NewControlId(menu_items.size()+2); + int i = 0; for (auto& item : menu_items) { auto menu_item = new wxMenuItem(menu, config_id_base + i, _(item)); menu_item->SetBitmap(i == 0 ? m_icon_solidmesh : m_icon_modifiermesh); menu->Append(menu_item); i++; - } + } + + menu->AppendSeparator(); + auto menu_item = new wxMenuItem(menu, config_id_base + 3, _(L("Split to sub-objects"))); + menu_item->SetBitmap(m_bmp_split); + menu->Append(menu_item); wxWindow* win = get_tab_panel()->GetPage(0); @@ -860,6 +874,9 @@ wxMenu *create_add_part_popupmenu() case 2: on_btn_load(win, true, true); break; + case 3: + on_btn_split(); + break; default:{ get_settings_choice(menu, event.GetId(), false); break;} @@ -868,7 +885,7 @@ wxMenu *create_add_part_popupmenu() menu->AppendSeparator(); // Append settings popupmenu - auto menu_item = new wxMenuItem(menu, config_id_base + 3, _(L("Add settings"))); + menu_item = new wxMenuItem(menu, config_id_base + 4, _(L("Add settings"))); menu_item->SetBitmap(m_bmp_cog); auto sub_menu = create_add_settings_popupmenu(false); @@ -1087,16 +1104,17 @@ void on_btn_split() if (!item) return; auto volume_id = m_objects_model->GetVolumeIdByItem(item); + ModelVolume* volume; if (volume_id < 0) - return; - - auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; - DynamicPrintConfig& config = get_preset_bundle()->prints.get_edited_preset().config; - auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size(); + volume = (*m_objects)[m_selected_object_id]->volumes[0];//return; + else + volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; + DynamicPrintConfig& config = get_preset_bundle()->printers.get_edited_preset().config; + auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size(); if (volume->split(nozzle_dmrs_cnt) > 1) { - // TODO update model - m_parts_changed = true; - parts_changed(m_selected_object_id); + auto model_object = (*m_objects)[m_selected_object_id]; + for (auto id = 0; id < model_object->volumes.size(); id++) + m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false); } } diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index c52448ed60..dd6655e3c5 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -384,12 +384,13 @@ wxDataViewItem PrusaObjectDataViewModel::Add(wxString &name, int instances_count wxDataViewItem PrusaObjectDataViewModel::AddChild( const wxDataViewItem &parent_item, const wxString &name, - const wxIcon& icon) + const wxIcon& icon, + bool create_frst_child/* = true*/) { PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (!root) return wxDataViewItem(0); - if (root->GetChildren().Count() == 0) + if (root->GetChildren().Count() == 0 && create_frst_child) { auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, 0); diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 12bb156cbb..257b5e47cd 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -350,7 +350,8 @@ public: wxDataViewItem Add(wxString &name, int instances_count, int scale); wxDataViewItem AddChild(const wxDataViewItem &parent_item, const wxString &name, - const wxIcon& icon); + const wxIcon& icon, + bool create_frst_child = true); wxDataViewItem Delete(const wxDataViewItem &item); void DeleteAll(); wxDataViewItem GetItemById(int obj_idx); From 128d0f77081712d92e5478e1a8ad04e29d026fe3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 31 Jul 2018 12:04:01 +0200 Subject: [PATCH 088/185] Correct object splitting to parts (sub-objects) --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 3bfbfe7368..07bc115b71 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -608,7 +608,6 @@ void add_object_to_list(const std::string &name, ModelObject* model_object) for (auto id = 0; id < model_object->volumes.size(); id++) m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false); - ModelObjectPtrs* objects = m_objects; // part_selection_changed(); #ifdef __WXMSW__ object_ctrl_selection_changed(); @@ -841,6 +840,14 @@ void get_settings_choice(wxMenu *menu, int id, bool is_part) update_settings_list(); } +bool cur_item_hase_children() +{ + wxDataViewItemArray children; + if (m_objects_model->GetChildren(m_objects_ctrl->GetSelection(), children) > 0) + return true; + return false; +} + wxMenu *create_add_part_popupmenu() { wxMenu *menu = new wxMenu; @@ -860,6 +867,7 @@ wxMenu *create_add_part_popupmenu() auto menu_item = new wxMenuItem(menu, config_id_base + 3, _(L("Split to sub-objects"))); menu_item->SetBitmap(m_bmp_split); menu->Append(menu_item); + menu_item->Enable(!cur_item_hase_children()); wxWindow* win = get_tab_panel()->GetPage(0); @@ -1111,11 +1119,15 @@ void on_btn_split() volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; DynamicPrintConfig& config = get_preset_bundle()->printers.get_edited_preset().config; auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size(); - if (volume->split(nozzle_dmrs_cnt) > 1) { - auto model_object = (*m_objects)[m_selected_object_id]; - for (auto id = 0; id < model_object->volumes.size(); id++) - m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false); - } + auto split_rez = volume->split(nozzle_dmrs_cnt); + if (split_rez == 1) { + wxMessageBox(_(L("The selected object couldn't be split because it contains only one part."))); + return; + } + + auto model_object = (*m_objects)[m_selected_object_id]; + for (auto id = 0; id < model_object->volumes.size(); id++) + m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false); } void on_btn_move_up(){ From 1c0fa198245eb27554b85b82e1b4fa43c7f18a3e Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 31 Jul 2018 12:25:00 +0200 Subject: [PATCH 089/185] 3D scene toolbar uses a single texture --- resources/icons/add_disabled_36.png | Bin 831 -> 0 bytes resources/icons/add_hover_36.png | Bin 956 -> 0 bytes resources/icons/add_hover_pressed_36.png | Bin 953 -> 0 bytes resources/icons/add_normal_36.png | Bin 934 -> 0 bytes resources/icons/add_pressed_36.png | Bin 929 -> 0 bytes resources/icons/arrow_out_disabled_36.png | Bin 723 -> 0 bytes resources/icons/arrow_out_hover_36.png | Bin 826 -> 0 bytes .../icons/arrow_out_hover_pressed_36.png | Bin 824 -> 0 bytes resources/icons/arrow_out_normal_36.png | Bin 804 -> 0 bytes resources/icons/arrow_out_pressed_36.png | Bin 800 -> 0 bytes ...arrow_rotate_anticlockwise_disabled_36.png | Bin 691 -> 0 bytes .../arrow_rotate_anticlockwise_hover_36.png | Bin 799 -> 0 bytes ..._rotate_anticlockwise_hover_pressed_36.png | Bin 795 -> 0 bytes .../arrow_rotate_anticlockwise_normal_36.png | Bin 780 -> 0 bytes .../arrow_rotate_anticlockwise_pressed_36.png | Bin 777 -> 0 bytes .../arrow_rotate_clockwise_disabled_36.png | Bin 709 -> 0 bytes .../icons/arrow_rotate_clockwise_hover_36.png | Bin 796 -> 0 bytes ...rrow_rotate_clockwise_hover_pressed_36.png | Bin 791 -> 0 bytes .../arrow_rotate_clockwise_normal_36.png | Bin 773 -> 0 bytes .../arrow_rotate_clockwise_pressed_36.png | Bin 770 -> 0 bytes resources/icons/brick_add_disabled_36.png | Bin 947 -> 0 bytes resources/icons/brick_add_hover_36.png | Bin 1024 -> 0 bytes .../icons/brick_add_hover_pressed_36.png | Bin 1019 -> 0 bytes resources/icons/brick_add_normal_36.png | Bin 1004 -> 0 bytes resources/icons/brick_add_pressed_36.png | Bin 999 -> 0 bytes resources/icons/brick_delete_disabled_36.png | Bin 953 -> 0 bytes resources/icons/brick_delete_hover_36.png | Bin 1022 -> 0 bytes .../icons/brick_delete_hover_pressed_36.png | Bin 1018 -> 0 bytes resources/icons/brick_delete_normal_36.png | Bin 1002 -> 0 bytes resources/icons/brick_delete_pressed_36.png | Bin 1000 -> 0 bytes resources/icons/bricks_disabled_36.png | Bin 880 -> 0 bytes resources/icons/bricks_hover_36.png | Bin 1013 -> 0 bytes resources/icons/bricks_hover_pressed_36.png | Bin 1006 -> 0 bytes resources/icons/bricks_normal_36.png | Bin 998 -> 0 bytes resources/icons/bricks_pressed_36.png | Bin 990 -> 0 bytes resources/icons/cog_disabled_36.png | Bin 942 -> 0 bytes resources/icons/cog_hover_36.png | Bin 1007 -> 0 bytes resources/icons/cog_hover_pressed_36.png | Bin 1005 -> 0 bytes resources/icons/cog_normal_36.png | Bin 984 -> 0 bytes resources/icons/cog_pressed_36.png | Bin 981 -> 0 bytes resources/icons/cross_disabled_36.png | Bin 800 -> 0 bytes resources/icons/cross_hover_36.png | Bin 875 -> 0 bytes resources/icons/cross_hover_pressed_36.png | Bin 875 -> 0 bytes resources/icons/cross_normal_36.png | Bin 845 -> 0 bytes resources/icons/cross_pressed_36.png | Bin 843 -> 0 bytes resources/icons/delete_disabled_36.png | Bin 839 -> 0 bytes resources/icons/delete_hover_36.png | Bin 913 -> 0 bytes resources/icons/delete_hover_pressed_36.png | Bin 911 -> 0 bytes resources/icons/delete_normal_36.png | Bin 895 -> 0 bytes resources/icons/delete_pressed_36.png | Bin 893 -> 0 bytes resources/icons/package_disabled_36.png | Bin 935 -> 0 bytes resources/icons/package_hover_36.png | Bin 1038 -> 0 bytes resources/icons/package_hover_pressed_36.png | Bin 1034 -> 0 bytes resources/icons/package_normal_36.png | Bin 1021 -> 0 bytes resources/icons/package_pressed_36.png | Bin 1015 -> 0 bytes resources/icons/shape_ungroup_disabled_36.png | Bin 770 -> 0 bytes resources/icons/shape_ungroup_hover_36.png | Bin 873 -> 0 bytes .../icons/shape_ungroup_hover_pressed_36.png | Bin 866 -> 0 bytes resources/icons/shape_ungroup_normal_36.png | Bin 841 -> 0 bytes resources/icons/shape_ungroup_pressed_36.png | Bin 836 -> 0 bytes resources/icons/toolbar.png | Bin 0 -> 25416 bytes .../variable_layer_height_disabled_36.png | Bin 450 -> 0 bytes .../icons/variable_layer_height_hover_36.png | Bin 473 -> 0 bytes ...variable_layer_height_hover_pressed_36.png | Bin 471 -> 0 bytes .../icons/variable_layer_height_normal_36.png | Bin 443 -> 0 bytes .../variable_layer_height_pressed_36.png | Bin 446 -> 0 bytes xs/src/slic3r/GUI/GLCanvas3D.cpp | 128 ++-- xs/src/slic3r/GUI/GLCanvas3D.hpp | 4 + xs/src/slic3r/GUI/GLTexture.cpp | 41 +- xs/src/slic3r/GUI/GLTexture.hpp | 22 + xs/src/slic3r/GUI/GLToolbar.cpp | 650 +++++++++++++----- xs/src/slic3r/GUI/GLToolbar.hpp | 116 +++- 72 files changed, 690 insertions(+), 271 deletions(-) delete mode 100644 resources/icons/add_disabled_36.png delete mode 100644 resources/icons/add_hover_36.png delete mode 100644 resources/icons/add_hover_pressed_36.png delete mode 100644 resources/icons/add_normal_36.png delete mode 100644 resources/icons/add_pressed_36.png delete mode 100644 resources/icons/arrow_out_disabled_36.png delete mode 100644 resources/icons/arrow_out_hover_36.png delete mode 100644 resources/icons/arrow_out_hover_pressed_36.png delete mode 100644 resources/icons/arrow_out_normal_36.png delete mode 100644 resources/icons/arrow_out_pressed_36.png delete mode 100644 resources/icons/arrow_rotate_anticlockwise_disabled_36.png delete mode 100644 resources/icons/arrow_rotate_anticlockwise_hover_36.png delete mode 100644 resources/icons/arrow_rotate_anticlockwise_hover_pressed_36.png delete mode 100644 resources/icons/arrow_rotate_anticlockwise_normal_36.png delete mode 100644 resources/icons/arrow_rotate_anticlockwise_pressed_36.png delete mode 100644 resources/icons/arrow_rotate_clockwise_disabled_36.png delete mode 100644 resources/icons/arrow_rotate_clockwise_hover_36.png delete mode 100644 resources/icons/arrow_rotate_clockwise_hover_pressed_36.png delete mode 100644 resources/icons/arrow_rotate_clockwise_normal_36.png delete mode 100644 resources/icons/arrow_rotate_clockwise_pressed_36.png delete mode 100644 resources/icons/brick_add_disabled_36.png delete mode 100644 resources/icons/brick_add_hover_36.png delete mode 100644 resources/icons/brick_add_hover_pressed_36.png delete mode 100644 resources/icons/brick_add_normal_36.png delete mode 100644 resources/icons/brick_add_pressed_36.png delete mode 100644 resources/icons/brick_delete_disabled_36.png delete mode 100644 resources/icons/brick_delete_hover_36.png delete mode 100644 resources/icons/brick_delete_hover_pressed_36.png delete mode 100644 resources/icons/brick_delete_normal_36.png delete mode 100644 resources/icons/brick_delete_pressed_36.png delete mode 100644 resources/icons/bricks_disabled_36.png delete mode 100644 resources/icons/bricks_hover_36.png delete mode 100644 resources/icons/bricks_hover_pressed_36.png delete mode 100644 resources/icons/bricks_normal_36.png delete mode 100644 resources/icons/bricks_pressed_36.png delete mode 100644 resources/icons/cog_disabled_36.png delete mode 100644 resources/icons/cog_hover_36.png delete mode 100644 resources/icons/cog_hover_pressed_36.png delete mode 100644 resources/icons/cog_normal_36.png delete mode 100644 resources/icons/cog_pressed_36.png delete mode 100644 resources/icons/cross_disabled_36.png delete mode 100644 resources/icons/cross_hover_36.png delete mode 100644 resources/icons/cross_hover_pressed_36.png delete mode 100644 resources/icons/cross_normal_36.png delete mode 100644 resources/icons/cross_pressed_36.png delete mode 100644 resources/icons/delete_disabled_36.png delete mode 100644 resources/icons/delete_hover_36.png delete mode 100644 resources/icons/delete_hover_pressed_36.png delete mode 100644 resources/icons/delete_normal_36.png delete mode 100644 resources/icons/delete_pressed_36.png delete mode 100644 resources/icons/package_disabled_36.png delete mode 100644 resources/icons/package_hover_36.png delete mode 100644 resources/icons/package_hover_pressed_36.png delete mode 100644 resources/icons/package_normal_36.png delete mode 100644 resources/icons/package_pressed_36.png delete mode 100644 resources/icons/shape_ungroup_disabled_36.png delete mode 100644 resources/icons/shape_ungroup_hover_36.png delete mode 100644 resources/icons/shape_ungroup_hover_pressed_36.png delete mode 100644 resources/icons/shape_ungroup_normal_36.png delete mode 100644 resources/icons/shape_ungroup_pressed_36.png create mode 100644 resources/icons/toolbar.png delete mode 100644 resources/icons/variable_layer_height_disabled_36.png delete mode 100644 resources/icons/variable_layer_height_hover_36.png delete mode 100644 resources/icons/variable_layer_height_hover_pressed_36.png delete mode 100644 resources/icons/variable_layer_height_normal_36.png delete mode 100644 resources/icons/variable_layer_height_pressed_36.png diff --git a/resources/icons/add_disabled_36.png b/resources/icons/add_disabled_36.png deleted file mode 100644 index bbeeef5a8d33f6ddcd2e57ca20bf804f3b5bfca4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 831 zcmV-F1Hk-=P)9NH`o9G0?bK7 zK~z}7?U=nz>qr!UKbr(20}jCs4aNdFDG(h>LBU%+%2fCvzV z;g{c?8H4!UF7*;<3G}}M%KgbR9S({2$FOvvZ+^m;wwIR0&1IXgQ8pjNBV?RMGO*N=H_o^(lkX;6doTR+1=gE&d~PuHnwf^^76v|{yxoS zlO#!~)oSw;iK2*Un$+uc;yA`IjO>6UNy0D;8jS`~6j7;E==FN^dOgxKrC2PYC<^s@ zoo2I1wOVCkVA^ChBRaLpVx)S(~@i&CcPm*YZ9vJ`t002ov JPDHLkV1jNyflB}Y diff --git a/resources/icons/add_hover_36.png b/resources/icons/add_hover_36.png deleted file mode 100644 index 1ff71b73381dbe5a3beb69ab0a96af9933bc49af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 956 zcmV;t14I0YP)`R525d_M^xw3d(oQ?$VTEKb9!#cPO@G(-`;sTm zn|$A!SK#mHW9U>c!ZZRv?Gel;=RPJB3X#v}%L-yPoAG!&=)9o|(+Gk-hVJk3V={C+K-3FkK1$*hx6WlEYxjZnv#6F$854(xS~@&QE;w^>cj(bVW>(EBo0(MCZl zu$PH2w{eHS<~#$gmpJ=ym+>u&9&Qszd`EYCFN0@?k+n3-$whum{zA^AaN)^Onq5s? z-};K*$Ej>u$EfoKt4cLWRls$aXlQ|LCt5$relQUAOjr(+>QKfT+8G_3X53Jb~;Ew%kxg( zd-v5p9sLr3iQ5+mC%1UQ`7|+Q#yCTzQjx5hCS8zGYyNpc&`TNuI%=tCoFP%L5Ke4k zGuue!;!J%18reOW&tJWIYzDw2m~h)$+1U$X6B~>()MoP$2+q@5??qSh2&Ez?9VhNP zqm^__n!sthmmAA7^q%TB&d?(dbrRTHV18qk{`Mh$+`dIoD>8BWB6iMX%iC@_iti1Xml&6p(ruP0!^_k4bXox?UZ)f84oK>xR{vDP!jvD@5`58 zelPjn`<}o*VO+qVjv>q<05tEzd~)VvVzC&dQt41ZY&IJ{pAUmK3}F^QFfL$R8MuPo zX2aXnRTn#3$s~KF5|{tDj0IqKXQys30N$=Hc6WBDl}yvLTGAf~)CKBpCKmi?8YMdjR+ z({y?}xW4fff2NnsHKbM)ma}&mltz%m0Kvo!)Pjm;RIuA^6tp}MWrM+<5tO3L&GoMl z9bQg2JmwgR71waIIXTtt=Z9pFqPB;s6`48nF0ViSCfQPkbS_C~YmT#zy+A?8u$Wvh zuVkC~ReC%6S;;N4EAJw!GTB0=mXAj^(MkmhN{W~qVXS|QpA$csS2DSr!)|RQszfO3 zCEgi%??C?3=`R78xqY5^c7rE8Pm@xE<{7G1D-<*Zr7WX0Y5#iGsOkt9Xw`~&hOB~} zcqWR|<|JE8GxPmx6t-nPfAwn341h(j(CzABb3205+Gd`iUZPOuJOiI&iZKe6*&Sw-=l|cJb42VPI6qygNeZ@!#ZL{TK3NYo7`7z`qc;{C3_p*AKQ bk(J)xmZnh-dV-V!00000NkvXXu0mjfKkBiH diff --git a/resources/icons/add_normal_36.png b/resources/icons/add_normal_36.png deleted file mode 100644 index 10de9a22fb8801fd196de672f2391d0dee88cbe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 934 zcmV;X16lluP)$hApmUJ0=qskdT-WZe2}cV&eQJGC`vv42B{sMg*ENZD|<#_g?!-``Y(hLBhs`$!pQ{u5NPfO}>2R z=AM%Sr)&vFo4L6;Jsb{`&*#euU^biadcBN~j|;~@pZfi}#cXC`Vxl5eW@l$P%;)*= z#toqs;NZy<{C+>{>+2N(a<{bbaAgGm6M(9!6@NQN45riesH;F#p#L3E?Z3UK6lEg0 zZFV!8WK@Zgrcoo*ap6oSK6?lDI!F0{Vuv9%Qj0V-dKmJ(gH^Oq&j}w03R<3z`ad}l|6=SL04DEW zC6e0V1?MZom3iX~l}bgjs*GHaP;36VL(oea0y=7`Xq+KYuna$JT?Ph5=?mPt?cdxv55`F8EUh25(qBQTJJ+w^9ZFPXB}rwoY6`;CQabH-N&7^ zdHT)`7-#62r@9F2FSE3@z(D&jzl82l)QU{rzlvT0(5W-k@J9O;?gefksI?4uhl~TV z*EtyST;}%nO;q&%C5&_fmOkS*NoHSG}9n*fLA&izIRj zl(ZsNv4OYFkMgSbHJWWL<*oKkYa_$HQHFh^)dsB!R0aC)0g0kmagZa1S^$^J#mvkM z=RBT@z(k@^TrStifz|Z%v>uPg%RUJaMG>dd$<)-8z-e561A$*iLO~so?f?J)07*qo IM6N<$f*g^yW&i*H diff --git a/resources/icons/add_pressed_36.png b/resources/icons/add_pressed_36.png deleted file mode 100644 index 54272e42ce4482a1e85cb300cae6cdb9caa01bad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 929 zcmV;S177@zP)#)WouJXFpoXF>Mg*ENZE1kM?rm>r@5|MYurV={OG)g%I>~=>^5y$a z&f|v@7Q&;!!oq@?OeU#TtH%o9a5xAA0?f?J2!}wQT)SpE9S;0mU3IatlgV&Ut@6Ru ztAY(+e{ZjDFaZ9pF825K0B8nKRrN3#3Ww_gbw3&fz;@C$bpxmY)ByV50X0AF0*WS+ zs%)}dTBoE+=!QX)(8AeMA%dQMJT2bi1IipESSv*7Xzyk`_%<%Po0?H$x0EKjagW&M z5@Y_iIQQg$^(D(3B#7mIV6bnP@pF?%hRkZ=H~uL6PQ|8h;e}~B{Tu3r(Hg43?(ZYxZ2#D zZ4dENCPGC$Kv65qzVs3AJ@-C^YMxv%Lv&}6^UuCPS;(`JS+=fZ+og4SJNkK0Tw`C_ zM^YpT<@{kj72ig!mMP0wl2V+p{xN<{|6*Oqq*@WDy_JLR*6_=NY^#Ws*GHdP@B|$oong_0w$`iS!c*DI7#IbxE*c^l^nA_zDs#m z;>&lgADRKM2{yVtJ#6pBaogLhGt}!25sNR;(;CE7s|dQrY452ca}3?YW(b_|1i7^u zVfd+0>kK{pM3~s_GD{najP_0NbK(w~p)q^+5~dDd(qe1k^}dVTiQPa@n;8v^TLPy25ba{PEmnA>SZktDlw3GE09($yFB7 z4GovQjW^Fs@k-!TI^F;6d-d42z0cLoL~x3U;8f#*)&M#=Kz6&m?jVmCYyhGtl1iuR z1`~-y5JmB5!)kth-pppRM>-+LWya_8F*i3Sa1!Gma+O5pmX1Gq00000NkvXXu0mjf D(1^2n diff --git a/resources/icons/arrow_out_disabled_36.png b/resources/icons/arrow_out_disabled_36.png deleted file mode 100644 index 0c9e5ef322ba9c47dd30b01babf5c5e19b93c795..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 723 zcmV;^0xbQBP)`dIt*xzzRORgKjJA~Q?d}>D!1L1++uPgR+}unAsQv!VbG;6L2@rWsnH)N~wV#nwpv-2m%_7 z2BvAUv9U3pBU@Nlz_KhdnGC+~qqQcJ$@J+K7Z?8|Hcgh7mjT$^+#LO1)_cLSSXx^8 zQWG_GJQ!o3&*Jv>_RGs$sZ^p+DE!lPcxa$fsnmm!QWAzC*Vos5y35N;q?9C+NzTvD zky3JUax!|LZnsOZSVT&R5CX$6Xf~Vu2~88NHPh462q7pG3gfd5&+{-%6W{k~Hk+)j zuJ-9xR#vcWo0pfDp8t)-IwJSBZ5!YB$>nk+5{ZG@TrS7!>npD7Vi?AFdMNU)TCGMH zhFn}+e5yS@J_g|K?(VO>Z0Je7zP>(m(tJK255^cM2KoSG+xEmkA_fb<^E^&ZPYJ$x zHxxy}aqv9v5#V*tX5g%nbYc`vyNTegpc$8m%^+@=5>z002ovPDHLk FV1lTCOBMhC diff --git a/resources/icons/arrow_out_hover_36.png b/resources/icons/arrow_out_hover_36.png deleted file mode 100644 index f62796c96fe010579c66d783c31e2d81b1bc5daf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 826 zcmV-A1I7G_P)?>2 zK~z}7?U-LkQ&AkpKX>kObDL?dO=s#;R%j1VTJ+j;FJV~FKlLG~&f_gO)Ee9b{pt;$^=D$sI-raM0SPC%}Bjn=W@k4Pl4svw%Ck)NNBq(&+p6h&4d-NkO!X&SY)wMj{*t*woj zNQAo2b+Lqgj*Q@TyYYIxNda=@=JKJd3jh;s?3@Nz3o0XLP&%V2qX5sP;_PG6Dr%Vm(r{~OTqwDyUvh?nVrs3%JOz246*V{ zCZ!}QqJ;G@PR+%e(5nS~)5a8@!TaQ-=P)@lfgX(_L?VEM_~UP4RmC zWt{Jk?*R_06Nkk~aC(gQ-#al9k&uv*gVtjVg@^D@`w@mfH{u&US9LL?N9Z5z$0jnk zT+)!3C9BS?A>Cx5BI`JTPyjWmvEN=E=R1&h2raq}n_N$M;b8!-?zpk?yKGJtla(sx z=_NfCg-27xm;zc-FMd3y!}oO2?@zvXX#CJXXf~8MpgRN2^DyJaC}9zzW8hhQ5A{7{ zR1cD6&7!5NiO_6_i|@`Spoa{>?XQiD8>8e{a!FIn42B2dx2w#0 zK~z}7?U+qS6k#03e>3hf?MG>Dv+jm@s;uk~rA61yUBa-Svh)yCNCoxy{IQX~yh37{x)53+exGxIbJzt5MGbovd0 zu|$HSpN^se3=IzE3t7gaN4cAD{pmYrU0J zEnbhXMCP|lN=Z^AiJNh}nxCh!$5Z-7tq}sfHz;wG@MCNkGnqt)Yyb(#`|s}5YT5=w4xq#}j?Esuwb1w6z zY(h@eMV)v5jGmgZo%v$S1I?(1{SRmh-kWy)?&kpK-<%;f9?Krk)t-x!Fi|T)T*PSW zxu4!cUAKssVTxQuw7hL1HXh^1^TS!_Axm)i%Xy+!gc7HZ0>wseyeIwmDmE-3D0X=$ zc6(@gbv8RqEi6Gr@oF?hV<2uYFg(EC<>~j?j`H1j)J25CArw`z)V~ZVWafb^A&?Rr zHYdgpgMH-(aogQfdTT4{Fro(YwL%IM6(MCd5|VWEzvH9PNx~ebrSrnH-o|H#kV4Yg z{gIBZAO0$#e{SmIt4{y5w`J3nd@=qfKz6%5=O7adDuAx*g!=k&2Gic&j;`ye!PUgT z>e7mfgu~%KCLyzxjK|~QSl4ls0E#mCuRKRadcOeCK`dfmt-FE%0000 zBCx1l?4{@>f_gO+E%y>pptj&9c7~DY2!YuE!Vu_2V#DXFZsznDgW*B!B8%&#jmep^nrsgi zlaGr;*5fBzZs&hFX?jCXFyr5ut17Rbmf)drDXjkz>oDwYQnb ze1xl?t|Xy{48i>$O-veL@~rt}C}xJELy6l~F=GfpuFXNN-9dBr?c_AIFa)K!`_L4P zv1pL7sWHy&Nj%R^m7K+4+Cd-`Kru;nyNi)RZak171X6;u~@`sn}p^`8=2cT+d)y|wP2 zwjOCq7h@VI4YUkMRn?S(EHan@oK7cgZEfrkxhC-oDzX?%Q i6^Fw?LqmhYR@7e!tRt40kv&EL0000@e2R|03hfy1S;i?Yb-KDKoP}gce;pcL~FS%Fshlu~cAYP$ZFQVo(?n zL|{=}>{4`zux@D-T6&2n(A?D#TmS89uCw!A2T_n!QE*+M{opYJ?;H5>y?Nh*2b)aF z{7Xwqi}d^bL?V$51<*8&+}vDzKA*Axbi1iZrfM2qk0&9i4Cy*EkqDP-Y7`aV&&Wu^ zU;w-x4}V5R0I&cUhOscYv#2N`P#-%w08lsEPn`rx0wsasEz4Rzcslg}04e3%5R%Fa z-;&<@{w9Qw2q6&W+_Spi>iQ>Cy7d4hnfq6S0jTnxBLxe4-6fRf>_eDh{g+HiNmN7$ z8)4jQ=M01}d~V_i%b6Xd1k@mHPV?fB~$-xL2M zTy{4uo15VD7@vN3V#XsOAtgubCm9S65||DkOo3qT9C2jua%g#Uh(MtX2IzqFd_yOJPZ(M*GH%AGJ5N-W0VteTOV@8c2nf6SY zzcdh<4RPti#W?hkDY)~qo^f-OY@3G^#lk?iKX&^n7EB??vOCFgIBDp-5uc_Orl2@$ zH=3d`9M&1080Pej*z@do;VGQzHUhx_iYnReEkFvn{y?S>NC{~c8~UWqnZolptd1qU z6~&e4<2vbD1}Tb)kTM<#NxFx+=+%3O7_&6@+*;OK{r&<{NP7Bu>HhU?RSB)RscUy# zU$wXS;N4^~{wF|It2N;u^9(A0%jF_4IG8Y)_V#vMF4y7p&EM;Xlo!sR&9<0tF3pC*8Sp(Y>$GwR^W-AEDQ+ParPc34MXmC#bq8 zNU2r`w5Ap}Nt&6-T{RD&HZ7MP2xJxmADJ_ACI>bwjOEMC%}w_H{!Wr4g#v8b=IH2% zi;D{*2mQUi&MLM|v)NqBSLyY7#7V-R%S%H5d`zdbS}k5)Ue*GNqlk|%1fT?vrs>*q zXK5&HT2gO8ThJD?>JUGfJfF|`{QM+I5~P#}A#fZA&-01}Nht|}03igf>tb0JS(XvU zF<}@I1Odm#$Jn-A%#sB`K)GC|TCHLj23l*BQW%DT=Xvbx?C|vTR7?)d=W|TcWN&Ye zEX&YZGo4PEOeQF$$g&LAbumqo*Vot8gJ!cCySuwcDOoHQ3r4&l3)q|GHqP5PSWef%b&d$z?J%@x4 zL{UVVrtI(UGa8L}e0CckMk9v9Az>Jzl*02owAPHrW6sadi?3z%dYyW`o{w*DZ&6Bp8}sYc{->B<2mKVY zy`XJD|1Ze0thGay1_9uC9^G!2MZvwHWt43@|1roLR_%5>3&XJRPKae$IF7^B)s?{} Z#y{!A7}HWnNiqNc002ovPDHLkV1l&8Gj0F? diff --git a/resources/icons/arrow_rotate_anticlockwise_hover_36.png b/resources/icons/arrow_rotate_anticlockwise_hover_36.png deleted file mode 100644 index ebc68791b88397136b061fcf939cf62cfc26188e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 799 zcmV+)1K|9LP)0<1|y zK~z}7?bzK*Q*j)}@%PzwoJpE7mu^}Maw{r&l8_*K{2PKEs0@07$e@erPpDmqb)OMR z=$&9tXpeN2JB8(P2rAFK*yrx>}G5V z7^58nz%z@v(r}sa@p1C`yrU4W*GpYp9X6+JqaDMro3W#-qm+8Rw6wGoWOv%y+Q{Ye zoEthfvqQ;5f@m~KEEX#WC{RbuENDtkay#9FS>hp56=s3C9;G$uT$D+fj3AO$@?vLJ? z`x;8+ldN&CMcaz^$#*m^-paP>-7{aOT-47L?W~5wzC7)_UVT zT-w>{Y#%D8l5bPr$V_Lbt_*PZQy({muLB^KaHjX9v!D$X4SYy`z-_oO4A{0|I|=oL zv$swWs0tET668ep5$8age2q+}h4@4q(L!cALwzXB?k&5Btcb8Gypmub$l+`Iox3{r zh7U2B$};$NkZd-GD8bSS(H5cv5%pIE{nwqkqGS=L8_x3Z<2@b>-DCXg7$QP2P>WKE z(RZVC9PM)cE^7+ypeeND*Ym-v`%t3jJl5r04gIMl{0rv4gF26Q7k|)-pd#qEAk#Dp z4w*H00Q`PGt*x!p27?8`e4d!V@ApH3nW>fCjD2a-OnJnpjL1LDMBBrAHnuX(041M zh`tpRl*SeH!L_#5+NgD@wU9(;Y?`EV?j0X$3qFZUCk1ofh6BTI&OMwv9D^TWH)4~= zFxoKyiY75<8cs7fI7lv+a}?tBda0?Y!KTSJ+A$2f5xZ?k8zo*ZW+0H4>U0d`Hp0cu8-z zi|+JOzK$nRq9`*eSU$UfRh4U;1&!%ZZlpT!6c-Vx3Ny>I0;M&nY>HQjZl3i&X8Xe3 zlogjdb7a?39h8=oQd=3uFx-d|lvcQ0E+W+tX3Z|)Lf=_udgx`Qiz1Jk`DF_bt4&x#9P+RSAY}>47L?W~5wzA* zxvQ}bTDP2Xb`F(M$;j9U>G3qxl_s}7c5bDyDuP1eRaRp5c&D{HNo4dWY8T|SQ5g};K zLn+0;+W{^dxa|C0wjs2c4WZ3b&wI}9M2X_!!OPC|(62_qpJ1j9bn(!Y!VlVi2J(13 zd526ITmXK*pU*=>dBa4bQT%@Y#NujlVbxl9l4LUZ(2Q&Akp@%P%_M5nfIVb8Pb|N(xHt`DJ~HUWy8f zs4WX(7*0e9N-G==2cgOk>xv8M8t=3xLvKj9#1}8{S3u3F}6E* zptFjPu@BU5IKaWmqrcuxIk-R6i&zQ~K`gcUAY}=YH;t-v&S|;p)B1_JVeo)-e^E!f7}$3^=&^5VPtV*ZQvDEBE88 z@N?8Z(L!=DNo^p=(fvmVZ3(e0xD~(8&)Hii?b|wz2hT7cPciy_ zlvFB>D8bSS(H5cv5%o`j{^w3zTCkp`x@$cBG{}>&L8j-vAR_pE)hMNy{4hz^`5ycC zvW7q-4S~kx=hK}hP@?F*&|_bQ{?-y!g8A>D?u&Qx2edpW5Bej>G|iktmJBWcuh&ak zTN~AWe@-x8XJ+tvy}w^rwYIiq2Q&Akp@%P=FGf6Y%baz?{YAY$ql8_+n*WVEIgNmSE5EAsD{1dcCfq#HR z7?IBe1H<*hC?E7Yt;}UwnhD#2mUEl;d(L_w3O)qjNcMdEIkAe9z&)GAm<# z(AL(LotT&)lgZc$aJ$`9S69>2)MPAxTCZHm7P;M+K3`5&=ij0dOq0Q0GB;P#%;A<+=*~#Y^6%Uont;&bQe( zN)#o=Dz>hyV|&?7dqFdLnmdUuT!jUM%7d(MZ9{2IBAwt}Y=9TTPdT{mC?$oZ_WZJ* zL>I+H#cU`GVi-i~!)TYd@j&AvOg!;y3-AxOT+8t>aMeIMeYI zgC7P-rP7EJEUgf2AxaQYzZK}e?$o7)t7xjb$iq+f>3e&hiErbG2!68)r4*whqja3Q zW&d8*5ZFsYVDG~7vG&6#QQSCv%f1Z#swMmhX30P|&UEGvX#W|=<#Od5GH-AIc)eb} zOitzu6Ap**dcBJ;tXf)HvhjF)(M^b5E93EaXl`ycSdR4*Wd9bLG*rFa00000NkvXX Hu0mjfG{axz diff --git a/resources/icons/arrow_rotate_clockwise_disabled_36.png b/resources/icons/arrow_rotate_clockwise_disabled_36.png deleted file mode 100644 index 0733c40051c015454798ba719560f6b58008ce73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 709 zcmV;$0y_PPP);f4kYu-k?OnW}~QOK*7Qn5X9EbLNE`J)|*Gjwe>;l#M;#d5VTN4 z@N*$?H5OralKHq*7H)Mx$tFeq)l4z)os9aoJkP5-?!$Zpfc<**OI5^<`{(d4rX_D|B#{k#>!{Kn^ z-Wf62(`M=!&G;TRRIo#{@`274t2!WIm$8qp|pCAbG1(H${MG=nUP_0&RT^C~v zX_^wpF;Nt;wzh`ndHGyf6h#z^MJkmFmSv%}Mk$45Sp-2qsZ`?n`Z^yE^?E%Dg#zVr z8Dk7uYr5SoolXa(6vh~;)hdNTfyc+k@dJH)d@wgRhcN~r1h==h*tU)1INaRaAcQ~& zf$#e~JUoowLrIeG^z?+35-BCi%gd~+tYqHmbUM^(HC)%_>+5U$KpPty|LiX>FEgN# zmy9terM}fg{HP9vVThCxfUB!3?(XjJeIFqN{eGWPsgz$G3d4|vg$0ySoSmIfsZ`k9 z+yuZF!|UrS^?E%YpfC(6m&+_JE@F&fX=w?qHN)W$+qQXrekMs0*4NkbD~wV~wAN^? zv-D5Xl()Ax(lljzdpo~My|c5!>FFth!63UHr4;k?^VIA0Z$;+E9@^dA<>cf9DJA>+ z`_u8-@9phnSvEE8N2Af0HE1)S8PI=$T-TjA$cVuK2!ep~^K%9{&xS@SdS3Q1$OcyJ rcH6{pockoibzOYlr`2j%Or!n+>@geAPeKZ400000NkvXXu0mjfN{>rI diff --git a/resources/icons/arrow_rotate_clockwise_hover_36.png b/resources/icons/arrow_rotate_clockwise_hover_36.png deleted file mode 100644 index 92aa3b7c86689060d977b938de03efae77dcdc28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 796 zcmV+%1LOROP)>y6WwJoB9Lg}#EW9Xy9S8Zs8GSC#kSAgJr010lLPiCLx0=l^5psC?()COUt=p~ zBPMB#)`tdQ-^TP-_prRYOd^r66yk6=@caFkG@DusIvljLw4`Nqy1ToH zCla(ywQjD^>dFd%K!9K{m==)N<6&fU6aX85VHl)4+XfrEEE!M+lmVqW_MLn@6`vp& z8|Ozdj1-c5Z7)aisySA0+*;6viZT%CN6)rXRai-meiW$`k$8mZ)gbTYM!8USiTvz6 z)@<3!NI$vGTq+7G(X=dt6i6kp*=$r5SCNzFKYtGQ*uL@AvF@|ObaTy*8rI*3?L&TCBM3XT}+H;p7=> zK!za@LLj6-mV7B zj)vN^*575Fr+a96ei>m%?wx;P`Jo35@dN_@VwV7`PxxZQ48VY6#xHe%jAbcb*_Yl#kPcoG-md zZss0qj_g&Whiq3i{=9NDEdwD1Qb}w!8x@5WWaYScHu>0^J2dup1iK@HeYyJ)N}{A< zCN#tJ;xt06!Ac5BFrA8b-`?&#=yP-kZ?=w9l4v4EV5$$d;U!<{^nD5tORf@4#whmq zcr)9-^LHqmSmM|GPo$6tLsFYxPmQM*0Fe~T2ES5TREEptBDfOVdC+BF^Oo`L;hUQv zB^8Dc7|K8?6Ok0w)}dQNtr+W@J|BBR@0XVp73f5hQI;dixLx_y)uFb*R`fhCLP(nX zub{h&sH>?*nG(BlFg`m$oxi~vP}_$V3iEUh7W)xOQRX{y}QhCm2`kOE;Ul#)nO5nqinJ~xT^2d>s#v#wI#Io`qb*G4UgiH5OCA|#2A10GuC!Nnw>-O_{eq`r!D4AQ~`{FmG zkO*DUQq@Ycy9EG|7tAL3CP+zzE(E&LQOZE%g}HU; z=2#c{-%X!QK4$Ra3w+gH()l#2sa0H#D)Z`4ceD$y(}NI_&`1YfmybYmE6R}Al*ZKj zG=YXTb3omrozytJ9I9(ceZGQV zz9bU3-R`X)Ry{pEMlzW+{U=1zG+Zthot>Q)c4Pbo_3a!?w0i2%00000NkvXXu0mjf D1T0X& diff --git a/resources/icons/arrow_rotate_clockwise_pressed_36.png b/resources/icons/arrow_rotate_clockwise_pressed_36.png deleted file mode 100644 index 043d1dfa2972a2554a52314fcf7929c73cd87a70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 770 zcmV+d1O5DoP)0Vu_H6<@aLHp!-3!LClm@jK-cw+!2^wrc|pA$90b6+(~`OXDu4>00w~{M+lomhGNX*8NBEgbAcdsN zvYUgYM>t%5)LhV-O3|0>#a?Wq!BbC({SZlD)W` zE&}ECSS&>dDUeEHwOVPYY@npn$&;yv=H$@$?@?^_BKDN+MJS1qirM%qGfOiF@fTK8 zQG?-7y!rBa`#~R5!}wepQb|(TG|}mP+`12s)ad^ZC7oL%l}l6Y_48_OVEc0@kzMA; z!gr*Q2wl?bX`#v641mZ9=3<|zt*XQ6bP`*QZ9k~ZAKuizIdXjiq@+R@0$u4SWgv3G z+&Xk)xC8y)qEE*k(f8>&RTUbkT#A+C3T~&zygJl5)PYv+LkLND;1ZgjEJJLJEYT zP)Z^VMP@C-#QYS-FSs1MVqT@bb*zW0FGFN98H7|UEX*T>pwgvrA`qmt`Ly|WSIL3X=6?~rwa6~OED@--IA8zvf! z;`Mqre^_;Obs337V$)BESv%u)yJ>H4x3ClA4;G6U@bQ_!C;$Ke07*qoM6N<$f&w3B ANdN!< diff --git a/resources/icons/brick_add_disabled_36.png b/resources/icons/brick_add_disabled_36.png deleted file mode 100644 index 5d9ddb12b0db539930132fd870b188c10545a002..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 947 zcmV;k15EshP)6>@@f=@pB}Ic$Vq}kQR^ulaAv~H8PRL-CY=7;OK|^FuSSg%HF4m?XrkC4 zhJ%8L3Q?p;4|_xt_l*4`nrzpk{hF^?Pv5M!YvqG)7DDG{dwbg{7K^l6t-b;%io)2~ z7#kZKf(!Kb@86E9D6Fike2FSYM@JZ~7JvQvCCC8PhX>Zz*U4luUjk$_n^Yek0T2P& z?e>?~PKQDKZbkhA^aJ##A^(wG4TD;(My*x@AQTD_3WZQrwf{h-X>xme%i7x7hv&Py zyJ(t*EX$t{WEckZdY$LzX8;BV2f4YqLDMt1u7K|D?pR%2 zb=Rd*iHnO1Ow$A)pU;y>Bp4YParf-)?J+$)-TR`hR;x&oL^hknwry+5T`LDuVaTCJArGRJY;cRvNFlR!}vVzC&_W)sJ8sMqTl zhJn}XMV4g*LBO&sY}@`EAj`5`>07N9mStg@CW0Vf+cu_YGB7YerBdPk{vN|Hcz=H< z9*?^&>?x3{s+^ynqiGsGpU+L$`Fh*7DVNKbrpfa1GU0HTbUKZ$>)5v4d)tqWjv|U8 zr>Cb}U0ng-^ZDFbv2B}NF2~f=6oMcSkH-O+o13FtF83Zt6h%Uz5Spf87zW43$K>;Q zyk0MXKmfnr&&$gTk|eRTv;;sZl_DCAcBN3)4M&z`VzC&(V310sLN=RaVPSzrqd}w5 zpj0ZkJ#J@b2U(W8vi*M^KU7s^Vq${H$w_j#91jl<%+Ah|PNxw?k+ZWiBuV1(@{&X% z(Vy`HAP@);2m}C_pP%RC7eB*NzAroeZMe*pbi VcGTxd?^^%>002ovPDHLkV1i{Ow+#RQ diff --git a/resources/icons/brick_add_hover_36.png b/resources/icons/brick_add_hover_36.png deleted file mode 100644 index 961f1c39578694597abf19fc90c0505f828f3a04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1024 zcmV+b1poVqP)U zK~z}7?U>C=Tvr%}pL6Hl`MB-qI3H@_hzqAWZA@*k5-GHhmTU^mMwBcq*)_yXAkCkU zx)NKu=%%g}DJ8T>HzgoRrPHD_j-w+33Oa-1jPyq5i~H4ub->U~l2HTow>yV(IM4a< zp7*@Gf`4U?VRMcW(+B|h80Oo7Z`j`6rdF$+DTvGEA{-85bJrHr2w{(5PrNgM+vQ?> zeEeM8nVOoSTB~t?_5P7Vg}pr{CMHNE66XSRslA;)VleM*N zl}gdu+ly`6q*5u|Za2wf@}(VnA)u|TEk;I0PK;x*SUstw(`g0=2kGkSs;`-yoh2HL zHa@6#cXyFxnbp-*EX%^OEONOV9*>83JWf0wM^#mFxg4fxVi*RAL;_is$z(E(cSzGT z`uh4Pl}aoxFXM1H*xA`(czF24y1BVIR8>XSbxNfYKA(?FCPR03cVmHs5Oj2OP%f7_ zJUm1QL2GL(8yg$-!!nr+ZEbBholf%kd_9gNNou@9wtZB;x!rCQMd9G!07X&IG>w&& zmHG#{zrRnlTCKk_+qUa_PX)-fZ7j>eRmI_OU|ClE_LgN4&pst*$NA*rA-=qIhxb2-5G(#jeE)IdfsS*_!omVe zOG^kLXliOgmXAy}O_SKxZ@d@n$COO^FWtZtIzv}KB$0pGcpyoV2nK`n^z=|Fm6)HO zC!J2?^?LF9{j{{SPZg;b0Te|c91i36`^jdrB$G)7 z1_sbHjlU1}NoJq0oL%6-jURaU>le5kE@X$&c!&NgrXrCD(P)(Q^>wn@EFZn~8NbJ$ z;6&z8;yau|W?|_u*RR|>oALv|=kww7`2gsrpSeXRKmGEEn*K8`#l!V0pEG>z_SpeF zui9LNNjffHJ{OpsY?e?c1bZBHt?V)EucHqs u7K>-z2~iYeg@tW)k)B-u0000C=BWE1PKhI<)FVjY2UVOob2UCq*x3*YWDYURHdnt4;qU>?Y-dgrl*zTXO z-D^t^z0|WJr7SH{kg_0J!4{3iXe6MZlQkxxvzj;Ntp{tsrH5`wbSZvsGxN+mpZU)3 zndi$-@VA`1I9#H{6aqj#gZXCUYxei|snu#13gYp2h(@D0+;_wjLO6GE9>4h*ug61I zXXmB3lYRD#YOThD%?C&T`J?D75ME#Q)hEcy59~`_CsKuovfK!KFC0zks z0bKz#TXG{FQmIrZ6bj_?c>u!UFriQgpU-#kK&EN3ySvNeY)6>($Vlfm&X+F@4 zjNI`ai&--Qmt0&tITnn`n^p7avTTSw(B;yD?S@KaeOsZ9LG~ zzhz-zfz{Pjgb=i}v>?l;W?PoU%HFTM9UsP$EQY&oVF`n=>+i9iTWUOzBuRwBVfy>~ zDV0jh&CQWcr}6v!1cO1^+S_A*6Lh(@CXgF!S+ zBbiJxGBSd$>->3KB&jX3rY-RB*7rR6C=Tvr&zf9KA<^KskJaX!?<5f@H%N=$9B5-GHhmTU^mMwD(#b}hOIr2Yxk zl_+%4P4|kF5?Z925|E_QY0(+Si6a9FdIu*n(i@#G?pGGY0Yf)Q#t*2^>fFmY+~0k8 z&U;=CTr!E1!TkKZv$wZLywR)Xz zA3P8;K=Ii#W@ctcrBW9Hbgi?KKNAT6Bml!OE<8IY4ANx_^%c++(3?trlf~6)l~Soh zu~-Bk91ar-h4A@&=MQ9>CObPjjE|3>dcUx+Kr9wRQIxX>dfAcI)>bl^4E_E6IF3Um zlfmotl1`^z+p$*y+TGn{Y;5e*b0U#w6t!$N%kc0pJv}{*Ig5*n#N+Yihx*{)09lsV z+}y;rZEV{npU>m-`A8;{B$G*0RVAO#V_6oaX_88%kY$-%F4ugAbX{j)V1RPD%=-E| zE|-h_{e4D9M^DXLT3SL?RSd(RTrLv`1jyxb^!D~P7f1*}S63I6N`>R&V}ua2x3{ys zz1{d&E|;UDqXW0wO`%X|_%KGI^b^U$N6iO1sVyrjE3B=pA%vi%r3G33 z*JR7GNbLU3`|&|6$zt%@9V}rma`Ping(uAik|c?6I80w(ALVkH<>h6v*(`p)pI|UZ zTU#5&=Laa_ZSKB10l@vwexSGgJxt3un@SC!C<@VNlwdH3rfHL)4zR%*X2QWDb082zcLkz#fZn_Y;A3!X&N8D^ErPcALB;mVd{I_LS|*{ z5w~yLJ)iaiAP@);2m}BaWRRs*H$VUSkh<{;9>vG)8(%Pb>)!bRy{yq&_g)F$s)fA* z`hNhuZbRNooiNA%kw}Eu*;%@-U%wETeN7`0iJZQ$nx39^N~O}d--IZNLMRksYHCVw a8TA5JZg^U03R89f00006;V^9}dHBGH3sa4ZQ(LTz6k3>;*%Ue(QMxU&YjIVW`X|`g z3SD&5y&|OyEmDv&5VV3V8jTqv0R_DoVOkK-dE$6I9(uaFuf>(zix*UDHNJlQ zSV#bc)6;8%0qE)Orf_-+fDB+5M!hpWJbW!st7~fjNH;C0Z-H)sZh@LDxe<$0Diw;w zB85T$fN(fWC=|ly^R*txG))c;4w#;vzVd!yVS!jIhN39V2YS_!_V#wt=`=$_LpY8@ zI-SPr^^!`ZUfZ$%1ax?K$kf!-mG;KQMm+^(G8x9l$La6yua8+=TqGWkH$JFOPEL?z zncdx8Y}>}RZF0FBKA(?dGD$L-L{(LCxg3^dVVWk}+uO*pOg5WsyhFOKGcqzlsZ?TX zYYUgl#qsemlarHI#w{%^p{go|VNfcS2m}ITvsnfP1{w<_grK*#mvXtx#l;0e2s%4E z+27xAnkJGY z;W!S4VPIJnhG8Itz_x8n)5PQP@I3!J&+~t9QGH3ZR^e{%1aBDKjX!0os^x1YdmiCGWl$W25*3$+KsT2fF;XBoYZWH#ZSN(AL(5 zEdOn`Wm#+-{>nS?Q7p+~wC4eqFqpXeKHK@V#sf)`L^vE~aBz@Psl@W~GMP*Uzu!+V z7^I`4gTl)b6!9hxhi3rz>f`Si=zJT~GMbmm6h$E#jS>t7(KL-zD#h5?7`m?W=lK~a zZIvx8!R&+Ynfv8)ye<#2OKH4A{}fZPSd4f)&feZ0nx^r=Tc7Y-auqi+Pq)9tEo2g# z&$xf*VQb0{fIuKXAP@jxlu?$}-Te6TQ)t2GF=$Qf1_hFH0CBzD^j5#L_82W?M&(#x`|HTuS@8APrHV0qKF`Zprfz| z6Srv_=GEM!Ig_*bjvDAYi&w3K{wFhQSc~;rf1YQahXvnE!u!kq{=VI4G|+W@ssNHC zvAn#@&d!eD0R8*>w=GK&8yg#6;>y|C8Je#1&#zyC2+;iVhwbfcipAoW0BOS^%~lHl z7rF70+30I%0erBWf0NB~eSmysmN`N0N*LHvIIryTnf(8I$6 zo12@?`0??Po0}U9!vNs!?v7L{#p2?kGw0~&h?SL<@dtIY*~IO3Q!baWEDOuBXf~V3 zvP_{+pin3v%QDSo6U(wNO_O4=h}-R^Uaybep?<&5>gp=P;gCY1fZOdxRaLUt?1y>B z$H&OB%xE-1(=_Jh=4dn;#A31W0ttdZBoaZ>G+tj{5d?wx`FU!!nzLEGUMCa^A&Mf; z&(BWgxm>RCJ7n9o)4zzK$Y3yVo~2UBDUjW67hTt#EVFIfSvwJ+_YEXTA{vb{91gK< zn{Ky@rfJO1&LWB;f*@d;CYEJQ2FNr`$9i4YF-;T0Fc1U*%d#*GgPEBb%H=YpQi*oE zjiM;5t*s#l!uWw?S?22MibNuTq9|yZ#@pMQb9&3Ns8*|VIvujvEDH+@RI62Txf~vk zXZ&fuw6uiF<>K=4lH1!`02D=WV#Ts7^7%ZOOa@g|IXOAu>FJ3`B!a4{<9EpAauEmw z@Or(pS}o4c&xyri#N%;fS!OsK((m^P27|1xuRH4w4i0#Ed6`@-6Gf3|G>YHvM^#nI zNaXz0Xm&?)X_3(PVq|<3KnauQn{;y;*Niv!IX<>hW zet{hEa^_vin$3rL-Vt02} b;5+I+zw3FB^5@cY00000NkvXXu0mjfv_Q#& diff --git a/resources/icons/brick_delete_hover_36.png b/resources/icons/brick_delete_hover_36.png deleted file mode 100644 index eb1687858bc32a34fd3a78ee94035fd704400406..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1022 zcmVC^V^tW3pL2UJZSP3i=?5@F8Jy4p8A)bANX%pwEL^!1Y19=9SCb427o$s; z%8HfP!PAUADv9vwSW+}~$nVuDmEbtynMIy!i|v;=?xV45bEtrG@?=S){XS3u7y z^;v>zwHl>TiDIz`KsX#G6bj+>dM_TxvMe??H%TNCr@qh3%n*yk&@}D*f&S@;-|r`z z&C=J`hm?|RHjBsOA)QX2*|Glww6(Rx=;-LF@zT;#<562*UuS4&i0ZB*|nFUDwIy^VqhHWm%+BDO6P@m&-NZp>ny* zz`y|2YL%6h6=|@X)cfug3iuP48!2? z@DL#cZEbCAY-}_Z%jI&kx3|;M(!$=}UgI1oiqd?Cq&%+QJRT34rctR>&@_#5xy1SB^pK-2-5HG)udF@W~fplGG zadDA&JdV%j!!Qi$^?GCXj^m&#KgPH79ldWozbcLyDph4u|pk{S*oX z=I7^$$K&+&_M+=LhGFpAuLV3Ex8c=y{$Aky8&E2qUoO)$jc7DVFc@Tacb9ZJ&EVi5 z<#L%nUhrcqKjzvO52)9E!2l}1yO7r-=bv14T_+Zc5e|o0TU*1nZEoFvgY@@b(-rih zx-=^L7Mpu6^x?$Clpg?rK!89Xa6GtsmwIb}%#&#-N@TDDeR!1CSKhujpnv`N-AmAR zFLCDhs}lAK=>G|H)`Gm5I$=-&B9RD_laqAbym=`wJG;9?A`#f}C#56)YZ!Nt)0U88;420vPO#lD@07*qoM6N<$f~+9iivR!s diff --git a/resources/icons/brick_delete_hover_pressed_36.png b/resources/icons/brick_delete_hover_pressed_36.png deleted file mode 100644 index 08575fd7678abf99ccfc52eb24a80c2e68798ab9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1018 zcmVC^V^tW3pL2UJZSP3i=?9ch1}C)88A)bANX%pwEL^!1Y1Gv$TutJ}#pu$d z&Q|{bT$w=7L^BCdnYbV`5{ZFATY=Po0ZO4oYw0cZejFD_4Z6a#FebdabI&>VJm+^l z-uJwMzf9goa)~A;5di8b%qJrsv%S4dqtUof5VzY+EEYrZjuev!A@3s}-F}3}?WQLh zy%co{dwU!=8a&KCL;)xr99$XEM9ol9{I2tW4gzbtzC!pFIOOQRsQHE1)Z& zE1-6p+KQjl>vhWIGNn=pfN(fWC=|l$^=suQNJ2N`HTUv(5DM zG|6PL^_#j_ETXC^*=!caac~@mLZN`y>m{8|lTN46b)7i`0*?3A3nz_{DN~mgn0RN%xkw> z52Wil3kwS*5(#`hABJJjXf&F$cN_<0=`p^YrwqRJ0Qb$8sHeXsJO3?RzAA99wa5PS zZkeB-XK85(Ap}034^=&xn{C_F=RYGc_8Ol2cZg5lMF05_kw`E&IEb$67>2=bzZUUCZ^EnZ{yoqKJy0&S&zEVMMl2R17!0zzyGtgM zVPs^4N~OXdFZeN*9&_!>2Q=z?7(n%R7xH?f{mxa_b>i_j;c%F>wKZ(p=El7@$bA0| zeL*j(OQU*VvAOR;A4^?K_yG_I1PBBICxttAXmke1KAD7)LI`V^tK#Klk-L+P;zYO@F}*WpF|ZL`h~rNX%pwEL^!1Y1Fl=Nnqh(Wa$#S zBJdCB$^?QYnn{S7iHpWah8Som4UifzKrOUrEq$fFUoIxppevd3BZjlO_vW75&;91y zd(ORZ%}ks&W@l%e-Q8UpjmD({xZQ4|(J15Nr z@bawK5c^Lo+z%Yy}kDXHn>AD5=4bTnH%bNT$&h>hoa=A>Y zR01Fr3K0wj@p`?N4`iAq+uPeD5{Wb8b8~aVVlfm&xp<&|D&qJ1$>nnN_xIyC4!K+o zkHlt3wz;{<@bEA_Jw44i^Yilz3=FhBsEfrSvMjT{ zzK(6%*tSifP{8Z;l1`^dr_-paN}*7|vMfx~B$LS?%QE?VzV!-KDisC?2dUL+tgf!& za=F;s+hcTe^vt}4g#}bq#V`zNwHlhHk)5tUsZ^p~uhZV%jx5UvA+Rh9%d##8$TUqPNy2d) z48y>(EDXay2!U^;<0ko}Wtyf5rBtoGOe!rh$vB=`$BJp^fzP>(G zRYlize*dkAr}I9%_2J(Oe0&GWrHjjDilPvWMhOH0?CY>T@feUpc zaXIA&fTn3^n)age-~o+xjrC`rLdn4ibfS)o(f;NKmk0E(hu^~lk%x(M``?tXH$eYS zpz{{wt+Z1H86X@EGd(>`*WJ5U0<*WjPdFSt`(ZUTHRY7cFv#TOq~JQ* YU+CU&`9iR8mjD0&07*qoM6N<$f}|(sjsO4v diff --git a/resources/icons/brick_delete_pressed_36.png b/resources/icons/brick_delete_pressed_36.png deleted file mode 100644 index e6fd3ab74e04e8c8fdce82cf8a817ce3ff6558ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1000 zcmV>P)I`V^tK#Klk-L+P;zYO@BZMWpF|ZjFQZPkeJCVSh#X2(x_`!ldy3yvUG{F zU18dS31{{0d-vY+x!;`o z&bb$^nTd;s+1XiVe}A7^t=3imx7$rL8f9u~O4NZqd-BBbxZU(bB3GhH?&yf~T8&R0 zKNb=|;pF7XU;ug|5eg?K0LTD_Vbnhk-@A7uP|shy06@BKL45;s19St_YLlBWNwr#~ zSS(T~6aWZ?LIi_Byk2knflSk6cXyXWBGK@EZf=fPEQX>etq1z2B7VQ0OeVw7&=8K} zkjZ55cs!)j=}Rm2pMduE_L!WUY-q2ntkhG`_VzYoV`KF9_t*Q(&(AYFJly=C&gb*U zvdre@Cbn&3+cvpe4zJfsDwQIYN};MMxm*s*vM^1P_4RdRStgs!HeaDqsl>?02$f2O zwY4=|E*A#}2TV*%H1u0oSU^=(48x#Osi0{Z*=&}9fq~`%2_fk1?M2sh&d$ydLeSOK z#m>%7-B~u9rMtVEj*bqFkB{qdBuP^96>=O00FTFmq9~NhWfVoBR4TEtvB5<-b9#Ep z`T2Q$mpP78AKMZj$8oT28?V<(Utb@(u4CIag+hU9wMu7aC$cOfgut>aEX!&QkZGDo zl7!0pmSr~?a5CYq_F-;S<+l~0;TdbeI!p!}KeLIMF>phHHZ!{lBRaKUkmx;&Y z_5+8pTPxeQ|7aySh`ZcfL383vfYraB~BoPXQ@caGb^LZ8*7m3H? z3=R&Wsw%p!^T+RbJdwNb&c}Z{`m_g%h1TUVMNx=GqXYs04i67Wr_+p%j#4U>`13VC z`s!0|ee;M~^#~m(zjWc;4!1tJs;WvX79$i2v9+~@Wm(*L_&({MzNatXMRqBaPfT`? zU8v)U_LLt0nx>&?+N;L>`_wu$HlKYC1qUY(K^>o@^X-q?2lTHa@`D6j4-%IicT>Xt zKY=b=X12mE7$ksjILyJ};g!KGEiDlahZ`@fW@cubVzJn86Vfhag25ou)6;_MXnz56 Wt!sT0vpPrl~+7>Le!4_k?=t58zZloYC-1r^*5^nGK5uCgA+qe|Bf>?@B zq!pYcj3*QR`1UO$0e4$2ha27W(?Kc4&dv@1&(F^|j)PJPAp}X1%m=95ZX=~6 zj$?!n2qDPja@e+w<2by$y!@fcm#DV0h*Jw1^m2?qxUAM211f;f&*N+G4hvMe?>HZV;S-}iZV zc)<64;y5Ok%TXv4sMTs1hC#R6{dk9xB*F7Mlu{%~LLA2=NkSNg93LOk>2!E|d!tsX z&8p3HUBWO#*Y%GF+TPx#)oLMxz_x7+!$3-jrfC?4LA6>X2m%I!0YMNDMG=`yW`3Qz zP$^n*D*~KDJ4-9F&d4~b)Cb*!}$YERV)@c zIXS^~UGDGi5kkzI+TGoyUav1^+W%9wZDZT^^2N9WS_1tm(0gXhyfkHy0G4HOeSJ;1 z;N8%y$(kmXW&OIay12MV2ZO=FHzB&Nv%bF0`T2Q;=!aypSsceXP)H~gqNk?^%{y95BZMA9PoyUZheC{xk2j<` zQ&Uq^9fxzb&ebZknaeRTF~Rckazj9gt}d=EEC3(@JkO)i*)vEUupEL8LH|3D`CmWO z%17PH;}>>F%v?a1wreXO+QRzj(^xGJVYIa&4D(<>YHpTCvUkx=l|ToCj`oA^Hoqh~v7UeNV18Nb#&zuG7Gi@VU|SS_zC$sa0ox)rFpM2-Cc5x7eqp;l74ie0 zd@)ajRpE*7G#n$H9O7hZl=)YNSv5R1zr09wH(eEj%XF0N^2u4eYX zhlG&$zK82Ogsc#;REn9WHVH4C;)V4qbYHoQ3N(L2BFyzY$FE(3oH*8jf3M79#^cO> zgJdAsDE^7k3Z;A;vzf2H&teMk_ap{E9al$xa)AEi09Ch&X_%bPtZ_GA zLL&&2qUr@GrLimntpiHMDqf{h|6LZo-xqH@-N7eUZ%_zY7)d9HMQm)#q~f>~w|8l? zO1$>W(fWh-l8m?V_DjRu%4~4q+A6NqiXSK}x5$~1Bb-Y2AI!G@XVSeLq;_*0>laq8OlMTWAzP?U89)}#Y(@u|}KNvXA*4Ea6zl2zpMSFWY j@2P?Md%1h}3ZKvfmCq00000NkvXXu0mjf2)fPN diff --git a/resources/icons/bricks_hover_pressed_36.png b/resources/icons/bricks_hover_pressed_36.png deleted file mode 100644 index 4e98ca751fc9c3372a7fa1c3dfbf8fc2484e0cf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1006 zcmVC^oK+OYfA_v0Gs6s^(197s7)u*IhJc~exG=ggMq_NWZrs?Ux-llYt*%|w zZuJjf>B@yPu~;BcQ}C-as4YRtiAxI=RC3c9>q56fs9YiG}5wLOB-(Sb0`!vRv$Q#_VkM>{nF9S}M(0KSh? z$k8`FgRpJxCcCj-eED!n7Q1-^yS0tj@EF(@rQh#R%4Wc}hz*WnM_Y;hIFDc4Zj3{I z;8Q3SC{}m)uJ9G_)N087ek$L8i|`c3o_-#H;;kE$eqKO%Ra%6k;`@yOnq9eu6D{F* zF2WE9DY4o+5b0rde_5oGSq7o;%ezQRB834$Hg3vRtH07>w{WWaB&M_whNw3;lE}~q zW^W3=P(WG|QC|W<2aN-AwZjv=<7|}+_*&sglj79pfItTUD23L#?hN4V7O5zgyl3L8 z;NXD_VGs?+NcW6zIyKJhYojb19{I1YQd_!(8EL^VZ9K=Jyp!inw4G0%JkQlt&E#C> z;Co02iSK*3zC*|g5lf|*d?rtL@eD7mUFZ1q8C0Ozh(ws~e}O*|!wjA5!oOExA>(oG zpn+r{*ev~x(h8+~9OV({fJKqy^xGdYwe%<1fO;$2FYL16thY#MNSNmS) z%Y`qfhAWJuMu>)@n1+p%f>=02`qXh!6^GSanPSCd*YPlgK$-?pHel?#A5C!nsW(|! zTV{6Y8X_8?b$~$Ps{mI8M8X!2cenD*PgzVM_9rn28WtBS{#sjW8qCbh4Doopo|StCtB(dRv9-1JuS>{bIn&wM$@|M6ASMvv cK|AUF14!m3$gbxF)?AX*(|Q>9w;Cb3enNg!QkMacnJDnbku}HA%=#A4kat&J25@Ca5@6T-3&2BfE_cr1G#<5mbvKE{8+-@Q#?VGiw;sS}ct@7D%2vwRIVTEO+H2umQOL^U=c z(!K0_H%}?E1j67KcaTaVg#|*^uFF=+KTzk?ak}*swp0jfcXKgFB4_%rJ5u<$98yWd z-d1CR+JUOZ<%y1SY!-9)MpKnG`KgZqfe8Z88e`0EW&m#|Pf1tV@N9e?>_5m77SV8w zbh3}LsdLP{*3XjVvGMs;DvNiqBXwA|gXg*ww>P*KZREozFL8CnFnKex|1~6p#P>a_ zzDr1jh^10YKC?l1{yZji#k>*dU;7W@Y#<}w}^_ZuVw!CK)D zw9#no<7$t<1k8&BXWxFGsl{K(=3LTU38DZ$uCbh1r{a28PI&(_^lInpd^-0D<#36< zR3Fh$6x(u;QVOE=A&|C(lr?Dk)<@g8^wgUyuP!mOcnuK^ zFeX4C@pV8|2Smb($6M?9;@d2?5Dz;s2x>Swx|2P0Cwr(=E7+FJNM?omxgrKZpfwdQ zKx>0i7RCe=3l+R_x%RUx{8wGP^=u0t-Mm9SXkZ|nAQo|Olug;KQrOy|NfmkHxs$aA z?L{))$h)uf^L=KGE4P-Ysz&@kqpAfi4xHe8y8B?J{XZk!*+ROr<#=N}1|5U`6{M6p zbjY5;28hSwjE#-ao=hGJ=C{>V;_>+3AFM`3M$G2s=7GP2D5Yp_Zf1CRSa1~MPglKT U-QrSKtpET307*qoM6N<$g3kBQr~m)} diff --git a/resources/icons/bricks_pressed_36.png b/resources/icons/bricks_pressed_36.png deleted file mode 100644 index 51c7c201c104f4e04f349533a48b451bc512633d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 990 zcmV<410np0P)HFe0|Q5bnwy^oKpwZEJ^`J8PC$Oojp-P$(5BR=4@4 z@CEPHYRKMRD&KyM@D-sLfzcY2!Wgrg8NlBuQc-pCzD=O?!GkPe5e>&k zcMWkqb%EJ8hFP|J@?YMdwsaRe(u8HZ_?}03JI}pnD<407g&V7e$+^tI*N_mBAn>UN z9w8@0ER|yNxjf;;i@dUSi_^DeP};B&i7?&!5`QEH893WPaKFMr#^>@ugQOO0mi|T= zg;D{Y^3g`KD3Y9i_d}+Z{v=!QNcSd*0{ptkN+w6m^Re9U!Ds08o;Ue?;WMh?3PY(O zqM<0ZMT%SgEt)`Pf1rZ3`(I(Dt2=C%E#=+pMfDGrM#X5!D!@ z5l8~1sVhw+?C@k~Ghh9X#TH_}6SZ#O=;-U}r?0D@TD^vC*^FmaSud0^2((hve2r2D z$FVR*Q!dr;tJTKOvhcrk@y_!dd@^^3qHbX%og@}6oTQ8hxJZLYv z;;p>@+Au$7Ho5-YGIghwKr5VjiOVCWxR~xcoN52xNcVJ*?&&z$7#}f^<2XkS*)vFh zcs$NuYimb_nVFd(9*;l#U^PBIZnn0z9(oBmtjx5xw=*_2COD4q4&~|L-lkToeF=~=9Mb7_ z0Z;*a-~V#&yfUcY&D1|YKR|yQ+TR`^guwTGq?D+tieVU^uaJ~-^mv{}Hk&1z&GPv8 zh?J5r4BuT!wD-UWArL~K>pDURJkLW)iKb}?A!xN)xUNe)9w(Q}jZYydC8lXIGc&{F z z-}mu6k7l#U#l;14b93VZ(lm`|G>YrGc%H{_IHce2(`vQQG>t-`KqL|&2m&0(p`2tgQz z0GOspBoZ0D6NVu|h(E5r<2X2u^YNeT`T3cfn;YWsI1394xUNeklcCXQpePDbO78CN zux%UP_fb`q%jI}@ct8k&ZQF!li064Yj>F>O zA}cE^o8?M!9XG#Y&u%!kZFrBdn5?cryH{Q>&_0qMH_pH1airw8^h3^>u0S-oo;NM3x QZ~y=R07*qoM6N<$g4bZTmjD0& diff --git a/resources/icons/cog_hover_36.png b/resources/icons/cog_hover_36.png deleted file mode 100644 index 0626cd123e8beb1ede75e2526aa256e6e3d60892..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1007 zcmV|qBwyF1sLF7?w_;8tZui(j>r`Ll9Qb3__$OimK@TGx zAOR@PFn|2?JEc+y(=^`{MAI~4u^57%g>--O%KqDVMaQ@{B05X7OS@b&33^G4h-htkMzBkJE9^g0*olXbWb@6yS z_M{k!6`e zq0l`;j^j`+mjN(MlV~)GEXyxdHl0pmnkJ@cQY;oZJv}9zPInJTQ4};y!!QgC!=Tx0 zQm@y!ySqbGRT7B=ilSgy7Ohr`OeVwh^mKQC+U+*BZDZRu!C(+cl6ZQ0BA?Hr>pHS5 zb9HrvuIosWL?jX+pU+b&mGF2xWV6}VeM6RIkxV8Dhr`tCbtFlmQmHUAGsDQp2+?R1 zUDv5pD#)@-tyW`UVS%-^HS+oV>pyDCvM@~(+qQ8W2O$Lg{rxD)zmi?o#kOr+*L|r^ z&1REUtM&Rbaa$Lp@1Yw2qCz>z9yMW(&==NWtpR+BRZW9 ze!u_qfplF**LAAZDy>!v+qMxxFg7;E{rx?Tf=7Z=># z-XerxWn~4|bvZmdL{(Mh=jREB!wd}#(Qdc9XDAwtQYw|$+uP&p?2OgbRa8|)(==37 zMF_$A`Z^~kCv0zT=OjRwtT6T>hF1OjYsZc?w; zi9{lA(|&|Pp_l#C)D*|Z$5@ud($W%=B%x{A|AKjyekhenkxHfBTiAD?ccA|SQdPC* zAkPd6KsX#`cXyZ3j~{yi^YHa6;cyrp`8Kr@8;H-T->B7UZ$1f8Rh8l4VSf4RSEK_Z d=|>ys{S7#>mEgkwvmpQg002ovPDHLkV1k?e$jATy diff --git a/resources/icons/cog_hover_pressed_36.png b/resources/icons/cog_hover_pressed_36.png deleted file mode 100644 index e9c67934bdcd6dc93fbe8401821d69fb3d224395..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1005 zcmV2Q52x3qJXap4{BN!Kk8r>KregIc~fVXth?Q1vg+qZjZKY%MY zegYS4T=bc^GA6Y|iK6%l5f)$uW|%*>i&ts7Ya5W4)RWxgCO5hH<#%t+IXM!4hIofy zh#?&!0VpppKY#NRl}ZK6vR)NL(=-x^1cGmcbciI0cZd(m9|&j~(^FGJvGaSiifLKA z|MfjGK>g`yXfOcNQ&ZHRp6ET9ZQH%WOg1|ds9!#P0{CL+Yl=6ZH=sA5L5I?>i(J>G z+wJ0c9zLIsKp^n?hJ+BkWtt|5M1o{8$>ZZALI^z1qucGijv*lguIu7B4t~EM$8oSM z3n2u4zaQ6iDVNKbrpegY7|CREaE62sghC$+glQe z1o?a(S(Yi4N`o`xx-Qjf6#&b!h{xl|vfQhQ*=!ceval?Ra=Fau=_%QNSR?-vkfJDP znuckbn5Kzg7&Mzr?(gqWRh3jKg`z0fwoSX;CYQ^xw6ruBpiZZQ<2X2uLpU5pk|ds= zpD7lL=(>(9%UoYyqw6}7BoT|nC>D!UDiwS_ANhQ~zkkTKZPMv9(P)%rvxy{0)M_=B zmzSBIo+ciTqw6}gS`As2X*3$FudlPayGyZH?Ej~>Z5zw7a2yBMbrC`^Iy#D?{3YA- zJRHZt^SoYuG7N)uyWRgMYcv{MU0o3lhgn%!q0{LwGc!Y}R6>#@gb>`^+>lPE>2|xw zvdr=EG2L#LU@+K!AYIqdb)9;>PP^U4aU6sY%+Aj8@bG}^x}?)-T-U|2EDXb7b#;}E zjg7(Op?bYewOS<-iJ&M7e!rh!Fv#589P{(@ghC-SO(PHpkV>Vvyu9T6{Cu!^C>Dz` zGBU!&#RYeFcL*Wa-rmOZJdTczP*s(+wKbyAC=(MCbUK~E8H&f_R4Nrde*DPU*%>=K zJE*FPrfI0EiV%Xmy*&zr0tW{Nc%H}B*4AKaSuU4jVPSz_Fo>$ExUNg9)xt0gOw%M3 z3bDVxPqW!17K^=3`w@vmdi%x2MNUpmux*>o%}pdpLesQ=1=H7lD3i&M$z zK~z}7?U+ALB3l^7e>2Q52(m%Vz`CM{k`auBAx0ac#1CNQhp^FduZ_Le%0J)1#!p~j zV`btthQbgfisA;cP=Enum_KJLy}8XT?uuIYCMS8TlV6_my&8&394d;NSq$viR%!cS!-L{rbi3?k@R!{#}5E zzkcQ7#}5Ex0Nb|T-8;_=@<%iEC(tL*pO*5c7r3rVx7)?@JbXSMfk5Ex6%s%kwu~_V#LayslsZ;>4 zEQ?qyhAhi3DVxb;uq+G9vM7~GoSmJK$z*y5q$moSreT^UrfJe@wP-XN+~41$sw&B3 z5=Bw4ZJTzxO*WflYHF%CK%Gtp$8m5Rhj2KIBuP9yJy9qWFbo4(mbt#Z#xM*dNg^7J zQYaKCm&^EkK61I->w3txZBnTex~|h`G>{~TYPHJr^fbf6!^C1S48x#Wts=`Z^?IH8 z`FU1XS1A+(9fegdIFbrz7 z8trx)$8iusFg7;E!@~ov>yk>Pa9tP6vS_th%+AiTu&~g(9IDl7R4NrBkqC;S;P?9p z27@FL3C73A357yvnnoZHAel^(&*!2!LhP%IXsTrRV}zt8#kIV&qGsH%#lX{f4-5Q4R}HBL`Y+1}p9 z^E{T8mU{b_WwTi(CMF05gQ%*C>$)_XO2Q52x3qJXao@@BNz)qj5bDzAHd2FVPpH&#@>CMdEddtPhde~ zvCqWHSQw&2QG5##3NSD;%%8i}WAnP*4XDX_lAGM*CO5zQ?#VeP7d}`>KR4gLeG}Dc z70a^T6hPB75{U%+`}1DU zya1p8*tXq2%;s|M0`r$)L z0I)2Jcs!0Q%l)31%jK{v3(K;oR4SaGpOgECJ@P*RDT;!oX_%&oX_|C89a^mxkB^V2 zs!BSYMo|=O+os#?lF#Q^TwELukYN}&j)UVkgu`JZN#gbOl~SpMuItFM%TE*w{Q79A!n?ts3lgVU=Mx(S^EhI^zUazyXw8ZrEH1T*GUDv7C>&UW9 zv)N>AZH=9s9ZIFr;FH?6Z7j>eaU5LNMF_#@=qQTvw`|Yza2yBE^ZNCv)9KLdb_f4t z&1RFEn;XL6Fw4u!7>2>j%napn8A*~5LU4C?M<$b@*Xtq6GN-4f^m;vl!QkM5bX`Z+ zbsCKZ-EJ4haS%c4n#p7|R)hfrw$6Q=ou)V#Fs;X$3hN`LvA=usBrC2O- zczB5Cd2DWO4!4)(^Lggy=LrUbsH%$Vy0qJEI-L%tX%Y&B*xTEq)oKxo#olK8h(sd& z{ldZmXJ=>Fw#~-I29hM9Y1+Sn8R$He&1T7Fv!5gE{}f17)prN^$shwnqfwrJ{CGE* zVzEdx8vXUd>gec5G@H#|enQ^L%;e-G2L}fdA2I#{(yxVak=Guq00000NkvXXu0mjf D<8Rb8 diff --git a/resources/icons/cross_disabled_36.png b/resources/icons/cross_disabled_36.png deleted file mode 100644 index b6a138a7c1c699921cb44aa533109a350b3c8df1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 800 zcmV+*1K<3KP){&029@$Y2LnKz7yBoPa3EN!(3h_$Vtkjlo&!gjBJf_Gd04zcndkobav zg)SU?0Jx0?l}d&C`}(C_y@y>|wN%vWpb31|ZPvYiY0M@kHXxx2d~ zO;eVZmT(;B7tGbw6+#Fc$H6cRvf1otc4&Z+Qeqeex3{;nTCI1O%gakF%VK(Znji?M z*XyKdIvS9ak~of0N+G30O3BU54N(-mVQkyRwryP3<@NQI#l=N5O&iT4({-Jdl@%^7 zF6j6B*tU&QitFoZ;yA{#tT#*$1Z-?+ z7$L-XPMNOjtgWpPh9OERT-T-1Xpkfc&1Q3G%pc`YyWK|9G?Y>(rAU&5BuUV9ohXXN z8;oHXa(;e}VHhMy!pzJJ`FtLw6prI?a&khm*&Gij3`5S&&M*uE+qUsMkH^PH9v&We zd3hn5%`!DL#qsg+P{Ek_D5hGiVwxt7y8fe^ACXMc?3bg*47q62!s%9Z*SxKKDAnnOeTY-X>>ZB(SeG^BFoFm1VQi)^A>7C zu(Pv+>$)xp6*>UrMSCm~Ww3WWmu e`}-PSvHk&DJ2KfS&4H%?0000Oc7>GIlGWYK!=Bd*!-q5X zoO?J5e}^1J(#B9`Pyn<#=Ial>!f_nR<#J0QrfHH+r;(hM$_$DkN0FJs87$LeY;3G8 z*_oP}VzXT4{G;==4CQ>EOeVv^!a`d>g98KHo1X_j0EA&kyQ6Lp?6By7I-ng3QW~t> zhCq6;h*>E7=ed!~)BE5d*7N0Ue_Lw=GyKc1NGW;p@kf;7@qb`)?7umOVj8R`6I5ar zVSj&fGE~Q4nqXNR{P`}%`ubMP>@C!uZj3~N__Blb+wV}VHU|{NVpM|w8AT|u7+4ku z?)-$2%hxb7HxYIZn!St7l@&^>t85(m5QJ#XFO#auqmfS-o}NVn0b*A#G6>jz>qlf5 zA#5AnwriMd7H8rzsA_XBwbV2gPn{$z6o~va)b1|K!4y_Hjc)h6$&52`8FalhRVFo! zCudF*%K}0HDiMSsL=i+0LMUjy-`d7#_&$4o zm`06tK@>tN1*t)ZBFx@i2F52)9k(?g-S^r1{WMyv3*ELUc^>NzACS9$pFf{3W9^P( z#^dyV_BraxN^?QF@3Zf_I;NYF>r%>Qaj#wF#pHF0p2x=1CAt!Q#1aV-7cU{U*{!z) zQj0}owF0tA$$i1=wJh!znc4-`b=JQ9h7Hf-)gMn0LZAqNc*_&>&Y;bqA)FJ(+4OU~ zUd!Trd2MSo83y^;8@zH|imprU+^0lGk2Lo!D<3?7H#$NvH1zMKZ5X_~J%{#qaod=N zKmfs^L;q8TMnjm+#Ml9KKrI954UP)$GwKEnAel@uIXTImRH`i)*Yijwli*X^TFFu5 z)!{4T^ZAxLiS4A&P24;^X=>i2v10BRX}pP~(FgW90~ zJxKZ69oKM>UMyl33jern34Zqwh?wu2e`^*5xHJpJ?t<#_xJOpblGrcg|S zrDTFi%p&aVZA^yh7)%o^ivz#h#aLR}h?$r~?drrxB#6&BSdX7Twb~d^6pK*}0%R1S z#A0As?Em==MlN5&jNd}oU1;_$R_Es_WiqUM@-Ya}m@Sj4%A*s<862BH1OZ}KH!=v= zH+dTwMhM$Rx9u7xo5dM92ddiGLoGFpna@8XEEI_RMbyp?%)WlCbQ<06TF;C#at?I8 zIaMY#ji;wh66Es`R^a(_n4N`6g;FMqbAEVBm>s!?^p^#M0#qUhLx>`XB7{)T)|#%S zfehbg&&@H^SO-KQ^!G!5A4CymcQ>iw5md)*4oLTX_S_gli*=yeHYLwv>EQ!%_wV!S z#T?e|IA%Of?>FC~&d)a%r29U5f2d(81s1Mcrs#RBJe#E>(L*eeAaV9< z#3pa+{{&KtMP#)CvP#Ka;Pqk__q);B0oHXEuYb>q=dt|fGlUQ*LLlDp#B3Y1Ixv89 z>?o^#j@OG>yh|51R+C|npZJkw*QMyX`GMTLX6x0V+ z7Y5Ih&*wKxLYkGCo}M1g%$!9TM^V;$oA+d-_ZO(ODPHRro6i6M002ovPDHLkV1iL; Bi!J~F diff --git a/resources/icons/cross_normal_36.png b/resources/icons/cross_normal_36.png deleted file mode 100644 index 93eaef6699dabf8f6de9d5e0580e7790695b4794..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 845 zcmV-T1G4;yP)mkVnwq{_$0 z$Vg~e7R6$*t^i%vNhXu@^z=wSK<84a(9m^ydwW+UE5pOXEEbEL=0KV_9dhJvUVyzW*1JnSmS&&p^=T-$$^Lg}K?zih&CQIwH=NKQS zSNw0K8qBswPYA=1w|jO|j7ERLWY~1;E|RV>ACFOr82Ih&waHKggRX;Nu;t-nwE6kv zn6cZ)EzM}L7}049&6b6-%!>p2*)}?c@I1u2RzlBX)9pKiet0>(gbSqVV?U5xi^`{1SZl08>*?Dp8nC zV-23I3iD5MNd25cNI)q7--jT8AV3HS#dYgD7&X_W>-s2iqzM8a5(!9jLJ*+0w$gES z0NJwZ15#a=u4|(xktS5rq~JKrKYvDMVuG(9r!m$?(W6n?Pn|}dnW-&EbzL@Itzeod z*fxc98vFb?J`G(W?>H>HpQ0(&MkE#^)_)SQ!fCxCketsGmP-(pDcGO*HkZb}kXkBW zRb}qVWfmNVFYn$Xgg_Dk@rNVkpFxY=-B|nfvgl^`HkZb^cz$^|X&TwF8+@^C^0rOp z_%VVV+iQE46}N20IdlN8yZhIzt!aF|cNgX5s}*Ca0s(kixBjIKwW=_U8)F010M!ko zR(VzUpHVR=0P%R7p`jr*BoeEFu^oqaJih$FYH)Bc%x1H7e+f}lmA1Av`uqDO)?$1I XUMwJzgOAyB00000NkvXXu0mjfSUi1L diff --git a/resources/icons/cross_pressed_36.png b/resources/icons/cross_pressed_36.png deleted file mode 100644 index 9b80bc5998d1e57b76a681b60db80fe55c3e0b34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 843 zcmV-R1GM~!P)Hq-gP=7p02}LO~F< zT3k@@py;(%J&GROid|=>NhhhnM$^npG9GG+6_?hUv_jr#;KAd=Z+^?;!5UlA{6}A3 zUuant#bU9l0A1HfCX;k@bVv)Jp5ETj&~;i8iB*ZpWGcmMvB=32CnN!oaottJ0JJ0$ zWLy^j1;F?Hh0XSD+g1hj?D=y5#9B-08mI=Uf&P1t^j(juI7rRs(Q~<9?rWJWjW1te zjEyh(Ol2jQ_Qy{N!;qmpyD3JaKVUL!x_u8x*O-pSC`AnX=H|*oXdZ*EgJH1c(G#@k z>BX3VJID?7Xt5a4aSP+!dnlJH0}3J$%AQ9U1W1tx7zUdkJVeW6=PCsR@YgO<;o^? zs3^QWd=NjEBXFmX>+8^4S}~GIRI_0rGFI1FP}S;GS*R!s9X*1V&4OQo(NP!~fl`S= zDvi~BZdsUr(udTKIfMk10`PqZ0tf=1n7;8 zB+hjqTXuCos_U}hdOu2}4%IX%I1baVUXpq7g3n{)80(|x(J0NQ&md1sR2HPVE*r1S zW9li`HidK=`+N_RmoJfb9A<_`sEaibiN%O@o`A^%rLiyc z&Lyy_GIjL|GmgWj55ou{kc2?|X2h%*G~3pOwQn!8ZiX*YX`GAa7gv*}ksY|nC)*}( z+hmR(BiOOMva_tXWi!s919)w1KQC=fi4 diff --git a/resources/icons/delete_disabled_36.png b/resources/icons/delete_disabled_36.png deleted file mode 100644 index 665a56aadf80d993ca5347559f4f827c54b1a48c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 839 zcmV-N1GxN&P)O{>R1$pKe1306pUg7iBxVtYX~NKqdthQ;Dxtmd<5rQ;v1>=)hI@YQ4myW zX*40Wr7i7TOgzctn3J4Iiq3>}wX^f@op1kZC2MEF7mKm?#nI7`-)^_*_xnQy5Jiz} zHp~A0{@4$oKfizXqoT;p&d#T}a(a4-)$jAiw{K$tKv&b)-QDHp=H^p?EYqZ`>i~oR z9LM?e+IeRPeYK(<0gZtEcR<3wJ!qOHs;ctz^n_`e0EEL~(&;qmbedQ!Has9r)2P*I zEG#Urv9ZC##00ME^7{J9#l;18cX!CLOeT{FK4qGwadmY?u~?*BF5@^3tyYV6yN%;G zY;SLqOeRqjg@=cSU>!0|lX|^Qp->=~%TcXX>G%71o`)a^SeAvZ>lBMcOw;82{G3E0 zK|CH0#-UE9Lo60!X=#aCt%l<`*tU&r+Zcv{VHnu9&Gq#)Yinz~y}faNe;=Gfolb{B zp}^zgBfVaaR;%@)E}2a7{QOKlpQq7iu)4Y$43K46L?RJXRmF8(_V)HZ)Kw}K48tIw z&;Rxh;3w+wD@RR6c%XD8$^{9IC1! ziehjMWilD+^*S>%Gnl4HG#dR$<9QyQ=P^G&&+Y9k%gf8b0j1Mv;_*0_mzR`EB?f~5 zq0rw~_`Z+lc>t8lWzNpd7#|-epU(#e6pO`JSy`di>!BzLrBaD(HcKQDK@bFTxg6{3 z>nMuC%gYO!o108cO$FOJGMNm%@1rORCnqOJl7u8l0O-0-wOU0G1h%%ee(VR{%N7?G zNhA_9n@yU{CXGe|*L9hkoJ5voBuQd=dV09sJ|2%F%QCVok3MK4pb^l24=5ZCe>%uJ zg8-09r8qu5#vbx)=shB$NGg^3=fLXl@X*(Fedv>ra5&8D>?{Wd2V;E2_zOH&FBCV* R?}z{Z002ovPDHLkV1lykwo)9m)kP6n+&Ffoo3iXimeoH& zvtm$JZdm)ea5~zdSjVV1Se;5#XgZLVQdB}pbCV{yxo<8I7rrf=xk+{Mte?f_{LYzk z&OCzO${ok0i6y2H0P+szjf1bVwzh_07!8G}s!DfvH!d%^Vj3abaokigML<=Vn3!lv zI#W|q)C_}jKc3r8XfvNDl}eGxWSRo%ipTl-@?`)d0Mj&ScI_A>?y+itTA=?ONdDI= z(RGw-*O0C*LQ%)T!40aIy-D2tNr<*L4y3H)@PD2rx+g(+bQlqhK&^)P%R09&US#Xr zuW+9kM%w?7_gki{K(+DFAxiJsCRV|r(Pi5(T;WgZS19tw+TtQ zj+#!>Idq6ne-itX52#dY2-`&UD~P)#+?!e2o*1P5afw?O-Y2lT3#q-+n}p(=%wtjuF_*Q(u_(E+IL)MELOmoW(0twl=Xc z%K%jV(C3f0Vda+TIxxi5@8$?RJ>m_>DHoALA;@OIFo;dR^Ot=ar^irACV_*4Sl_3; zJJfF7!qIh{QW3vh`^R3(a=-;gSl(@!9S%~xah+f|g1uT`jv*m@ zzHJBs+jhZ`i433M#_V}2s!H(DCp`p3+YwJ4=jT#^{Dm2U$4{ap`ymuT$YBEgj}RFd z?pqk;D$2lPsD}(KBW9VKvdwDm!2 zfm)#d9!OD?rbBiNGJvLOOioVnKqAo;%&qlxG);p%+fyrd9QU=JS1A+<4WEQ4ib5%Q+rm$P zZh;k>todA^3aZo;VW?3gP)UKLt`gHUO+y?v_1LvNo-Y<5k@%EQHE|?-w)ZTa`<|Hhj$i_Hino)0LYs#?;LoWrKKfI)7(}NRaNQl@5kdcPfQ|&cNXt_<~*9Jl1wDp zqRyR_6`H2Wxu4G=0j!luZG!(^!O zUb@8k_ut~ZFp9MAG5<@ZEELhQS@gq)h`lt1)VPPZ`VC?xk9yyr@6;DH09}OyuQ6S6%*xQ<*(45hvNlM9^K30PnXg5ANH{ir3a|(>ZSbUWt7oz zTvI{>0vjL*9LED!COUeYo7oH0Rh7__&-w@y>qb0xjFoDc(#0u4$4;PR1|b|p$Pu)` zCy0(saBJ#Q7S|VuzI+C`XSctdT1pKNIDV2lc8%E&-@~jmP==mDJ$Q(Et;TmBy~|>2 zmDs6Q2@ec!&n%NugNR*8teI)%X0G7O=b>fcc0~!jcmjQB1UdfSzE=-C?E^hJfg|H+ zN5(q~S_f$704a*nc92a52|(9%ZZ9vl4JMn-qU-vGakXh!y_I^Ca=H8mCuF;niO1u- lKKBM<5+Sx$iXCF<{RV~JCzbY0Y`Opd002ovPDHLkV1iE&p)3FZ diff --git a/resources/icons/delete_normal_36.png b/resources/icons/delete_normal_36.png deleted file mode 100644 index c9f5d69c200deebee68e8bf4dbbee3d5a2850e0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 895 zcmV-_1AzRAP)V+$pj70zrJ)D|L7|`$gOa*ROw%;9N!-+H*YN_w%GA^pRm0%? zxpR^Nu(i2KDwQIW$utDi5s&lTl`8yA~AHKzXc@X)) zLGQOrtmFvH&Y~SXLiFSia^)V<)vu8j=1}kJB+k4>ysZ`M!h6_D8E+GkbsaUGrhVWr zq246+=O0t9RFP~G)i03l7jbW9X?eb%+Gj;>UHpi^-VWr}c5f0=vRNL91_?fy#QySA zY@>o>lo4ee=UyILzl&2XW2R@&jvptmwMlJZ-n)d9>=NPU`fwJnQr_Oe$}9s={zIKV z-h!1|rsL26*M6KM@Y0YsAg5G7356h=1;Zfr?)!gPw{dm^Q8WoW(~tF2+PkK9YZi{K z;}i?{?dm_)T9yMYIFjYvmf7JTl^eeh3`ek6^K6_Q`I}|R_#fX#A(ul6MZ8PM>gpyx zKaZQ(k6kbjkBUE|T^HN+A)eUJ>d%)696IXZIn>=lX-_-(FD@enhj9!U$>;kWf`o0m z;3z}}PjO@R0%cVt`1A`Nf}*WRFP`9bG0*138GjlJ@Y1^p8opW zWlC2s(w+{?g?W}1zQJCegPMuc5+V4?X|%oplvupJx4o}T!*^_$z_H=x1FZ>ag8q9T zAw&QD002ovPDHLkV1n#rqCNls diff --git a/resources/icons/delete_pressed_36.png b/resources/icons/delete_pressed_36.png deleted file mode 100644 index 99c04f5cf5a8cc74357a8a80583a13ef82fc0bc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 893 zcmV-@1A_dCP)3pAvnKsgW+5-wGdxNzl?E96f| zxkf$p(sNtr0Uy3+^Wa zh&y+AKV|ZK4mFiR+rN*ek0b759Q>$R6C(#ZcMqSHOpPuqAAt^gc=)+!|ncGy>*Rbx)0Z{o{ zoj=xsm762JyPvziWl#?edINIGMWkR5vRN<;qF;UUuXU@ZMvzM;>IZ#Tzo)%h)Nb9v z(RG|s5x-q~Wvyj7;DRGq-c6Yu3Q&D;pFk*#y-;BF)X0AfSH%C$Hj23%LNM%ILRMFT z!qgOQ#}4eGf&8}oPqgb|yFTRYJ6QPR2I}tp9_~YlZpvHQDg1B)d0+^~5D|R7zaR+M zwhN9#c;Gk>QkSVH3W4_zcnFHLB7A(5l~RHHwMha;hmnDX(oA=IAt2oH|%aPkrh z>+^(9oJMNf>g`S~c6H-B{u#?wnVE0D#3)yhd-tI1-AAQd=GO~fuuxwma`Ftp?!JvV z%cQOzge`H*=_zKXZ(`47pl;%{gb5rOM(gcIivGXftJgm5eQg@PLqn*ChME_&CTR13 zWLa)Fsl1R@qf}htc^)&Q`;0D6fGz!ICA6%@k=;8*N@;mQ@@V`aV*Y!(1DgJ z#nZ;NvCTXCjx6_b;lklo>%awpl|kT_wO6t?3%*!L@5$NOndo#ngkkup0A1HvSy|!u z_*j~P{`~nLesSQfYLN&JRUP1kEz$|gkeax+r=;pOw&YB zl+OVPA@F@4&-1Wto1L8L z*+f-U!Z76i{vO}=sn_dlY-|t&0qu61(P;EB6&enQ_`c8T>ME9H;dvhIcAGek34(xp zJ`cd#+ZziD3pATe`u#qgPKVdmSJv0pXIr81c+A1U0j6ov>-888hlF8>=XpFmJ+Za5 zMYUSRwrz5`9Fxfe+qT)>-o|y^kLe*H1iG$cnkIw60Mj&CT3SMqB%&xH%QB)UB8nnh z*X8;78OL#uBx&{_Ap}{L0bm#gnx^5pF23(0%Q8U_kjv%h^?FPu6I4}2Q54cNojx~n z(BE@emMN7=c%DZb$GEObk|ZdKLN1ph3`4RkBaY*b0i|g=&9f|ZrI=7XE+@4@bEyPP+)g= zciKOnw%h0Pc^ZudrBaEzyE}vs)BCxehb+s&vaIhT>^JBe^q(MARp$r^V0e-5lpPfta^-~aSUh^nfT%VkbZP9(l!`~`k?qR-dm;Ozha002ov JPDHLkV1jJLxKaQB diff --git a/resources/icons/package_hover_36.png b/resources/icons/package_hover_36.png deleted file mode 100644 index c853281fed3dca81d6c361530884aa4f9a0d4535..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1038 zcmV+p1o8WcP)^QFN*lrxhO**GaOVSo7QzazSqACkE6;d}yk=THMY!HP# z8;FF6k}Oz;EfV7fi6FHgL<_iqs%ofO7e$$z#2KBJ=Qm##ZCJ9Y9e0u6U+~en_nv!n zBpyceBWPfxF(d$W8#DUk2(z=Z*tWf+5M9^F=ko~O5z-ivB>EA9-GhX6oq>UYhE!*G zco@sJxjcFKL4}sfWd;WaxpnJSLqKiWEPoUV0LTEY>(c0K8)P1{?1FYdj~exnJFK&a zvi<|Yv+z@Wptm=uLcW2rK8~{R8R@nzTGH*v)1ydB-+{ZagB21!^40`O2O#yICq;Wxe3*2=3T=K3-cPhFr=y31nmYplg>S-3c6XwtdywC%yIhC z2wEfxTT_UwB2wipsay~77MS?{%i4p4P!LFj!aA{*4yxrznvw|;$p{flu%^Kefi(x! z8O(*>Fs6Pby#FFXXth5wAp`_~6hez1LDOScO9cXB2~7dXybrR#S@@C7$~s;o0U;gX zdI%BJ0pte{M@k=r4QiC|-cxv?2w`i5U}2mMcM~t%jIYNCOb0wc;CKi>sC^$2zW?8| zCvYJ!LD;Cx?ZmQ2F*R`XFrMw(&Y6*~pp(N(6)jfenELeigzwJORS? z2}}piuyG9wEq)wXfbgt3bn3_f{`q-`>e@8uIZ&FwcR;v6fN%qR%fT}plu!rpu2%{C z2*tuRSnAUXA?bOVvQ>m9C{5mGWo`m(-#MC3zl|J8 z)%P!xv^}Vu7m?G?P@4Ldncw?4^y2GOmu8v&`z9pvM4$T*C2??P#}7H0Cv^NRwqb#LCLbj&DLVO(T^`@&26;kj9Xt$8DweFR7T12zVTs%m4rY07*qo IM6N<$f>DU$Gynhq diff --git a/resources/icons/package_hover_pressed_36.png b/resources/icons/package_hover_pressed_36.png deleted file mode 100644 index 57b7a97b1a23f8d723942b0dcef7c2895328a5b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1034 zcmV+l1oiugP)@&BFicZT8*i=Z}AVp#W0#=B^ zo()t&ASDZyVT;6bgG7j05NZla16}l?)2swzn(tCx7uG5ywH6)#4 zsf1qT-V(`>^XL@+*5< zpP}{45K7=<6emdLdRZ)7W3BKRWL|;viwGrLKaj8rgx0STjiqQk^FEQ(5s(hv>H|uD zf5pDum&hG>p48q$EEI0CQNDrFb^#@Ivi6|R>bEGyO|pkC5ovh}b8U&@gWvHT19!8C z-rPojMzC_5aC0YJr!G(_-e;lk9oE7)TF=|H1&NKnIC^dvcVh{oc#XiRpqpjPN|DOU z9H%c0p+#e`IfdLTAS(AsxA&1~fytk~sXa(a6^S4m(TTToQZ3)1DU~FdiW0>HYa0A8 zSaVRF!7TlOF?F5D{)f*#B)b6HE{F@p6%gV4!&vOnl^#!)(Pax%uUHzPvd_VcV0n~Umm<6yl@oksDixJ;{9GsHAEo^D2ehAfmG+YZ2t7|zu8Q;|#XGlIo|{D5caG-M z@1jK0^?l2PwueyHMU>2Q6sLY*=FdS6z5FKC#aZU>-hyPF*b5({CJ*lH_@Tt|gpa?2 z(t4JMli!2ypq+RFE%$6==b=!hpHQZMcZB_)f;3HQIAqJ90A#aS=H}-ch8Y_hBb&`W zYA0I@t517AVR?D^Kbw%9N+z97^WnXZ5F-fjq^|hHvj+t07*qoM6N<$ Eg4qDtUjP6A diff --git a/resources/icons/package_normal_36.png b/resources/icons/package_normal_36.png deleted file mode 100644 index 9cc9e43082486ad963c0aa564264fb8807f9cea0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1021 zcmVV zONfMsk`An_NIVxLg4BW#E#L;Ks0Fnyit@4(=jnX;{Jk#&N+lMS+Hr=Q>5f00d+xbM z2X>z<~4!^x?>e2_+@@@^~YJGkKTe=f@<{Hw{H{fpUV1|T`yfukZ{egY$PtksE969hYN(BPuwMKK(l0^kc_ca1IFMu&Q8%s=i6&`3_b%b&F%S;k+5^gee9D0nFOWU-EXn;x zSS;RVvvLc$;}UZ6bp1i<+84;iZ8Ar%5N>-CbA6f8gP-sn19z*0-r7NcMzDI9P-_?6 zXD(4K-Dk1*CD!6iv?t!GFG$+_o#Pj-;chNtlx`3>Rdln0SuIhWo#*V8akNMjwx$tV zMWpI|Qn_B@Z7})m=k*5(p&*b5g>_LI?-|DTEe3hNj1`mI?&M5}E>%`4D7*Q~sXK>N;K|0U;gXdI%9T0OSXV zf8RR@8`LP_{b%q(5yI9AL3x4=cM~t%im%5AOb0wc;CKi>sDB?4zW?ai6SxqVAZ*mu zPGZ^Pm>M{G7|-_bEeGGUa7~-QbsGfoY-CLXB?7{Nz=psAzXstPo&aI{1g3*$*tmv; z7C(tBKzLRII(6g_fB!g2ZEXhh94IZ|J0M&jK)33pJ!na)lFG}p-MZB8D?41IJIgft$6{6i2>kpD- z6)AllDc;NK!UUDi-XnMFWpbzck@P$**&4zVlVrXbcVmHP=c!7cS2Ff1L00000NkvXXu0mjfB;VM1 diff --git a/resources/icons/package_pressed_36.png b/resources/icons/package_pressed_36.png deleted file mode 100644 index ed49516f243c5d7456c25fdd1697783c77c0f4f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1015 zcmV#HD0TS^vgiqMT9xKT7Kv1ack z7DORkxOFS|+z7_dD$-O*qg}L0JC%^vOfq>+?%cW0b6iLR-OpqK=35;&n;+jf-*-L^ z?66RtEJjC1<=osHj^k`Az%UF7g#trEL&_HD5!2rx^N zB=aX&D&Atf_z7fRh0IGRYPfkI#V!)sxJfjYqT}5AL{djUy7+64DF68d`%b(}{=f^Q z_8wxfc$dw}9n{XtsHxM92Zh$YMm6t}JA9Q$`WdYCWlE2JC2&o=S_z}Q6M>Fcy-&Ek zhu$-nsg@qHSo{WiaRU9=JBb1Dp=JL)!BK@UL8e`#-KKh ztQAqJ56N`(lSsqV4_`GNB&CK#AsjJ?r+cVZ9?+Ibl1xR3Vu3vaK^W|LsLx`Rf5V)< zO=SNSq|_UKWKv2HfD%Se96>kY*h`ZLa|vAo#d-{?#4Z2CW_2AunuM@{^n9cgO#lS~ z`Cq+*bRZNXviA&rI7-A`L6pbX@HX)y?F2>~VY%Q-gzF;%(fB?jgJ7%d5grH&q(i8^ zn|S^xmJY5F!FPND+a<7UJj+3NUXwt+gR1MGMM2si91u1Hb%=D~OOQ^0uv~o8!82|2 z#7R^M(zlz?siOz@=aA`(g=BLp2U7-E!d#KS&bKf#W-$SVP z3TpOwO4Hvl`}><5dgU$ZOLHvzbq|sSVlRG#mOQw<z5ka!6xDW)FU5hKhjSELm_fEx){sTb}T)1+r zh>G|NT!>w?`6x-0l=zX++{~&$$C&|Pl9r(dF1eQzxDW5W=iI}A4zpBivb(z**6VeA z-%k`kmSsjpMp#-}N<~25E0r)K%c!c_7FEvA&w25EzOAgJdI4<9qFgR>dwbg!pqJ-o zY{vnh2jKPfwe5GO#n97fm%0no1^VxRdcX4X_4PHr?~}=77#|=1fhdV0e1JUq|CbzNN7#V`z}rlzngD;b5d*(|50rzna-Utb>pLI}bz z1YlrbfTN=$1_uX|0U8?{!}B~8MIj7BgbRIAna6>8Nnhlhtql7ylt$g&(MB!nQB%dxYw^X23F641%X3ASyM%jGDQO6a;y zKA-;p(i(Gfb2J)_-=>Fuc4Uo4gPEBbOw+`5-DvY52nd4U!&(Tz+uK{bc}P(d4h{~; z=kpYc#Xmv>K@fdojk4L<*-z2-9_rT-JJ5UE-rmM>9QynFDVNKHVHicA5Q6>veX7+e z$H&K1DwX&jSKGE(TwKI7O-$27*L4iTK+`ldP2=wFj*X2CYPDK23Ka?kwzjqy9v)_5 zV&aqk^74}P^>s!^N0R|sUS9sIe{F58TcCA;x znfIA_`5PQd51^@|j0Fq;&k4-KZ>Gs+vs5aTnu2(}ULuhQnwT~gFbq9_j$e<{?DaA@ zI9L~VrlzJSS1R1!xbHZ$yR$<)9%pTBtu8?AZEgJV{5b$k0M%-hdh3Lti8H1KPy^_* z1M>WzhgVluIXXJRvMjo~x<09|tgMjBG; z>dxXHo0}sP3e^Uvv$K<} zBb(L>O(@MrLuAWhXG*T&ko%oKwHlFe9@f8*q7v2B4=JR>R#>OaQ{v`3b z#AfCLo0$?Df4}Eds>JsC@66B7lT0RS<4`ypCKiif+cw{Keww81CtnkdM(OD2s14A> z#KeErXJ=;{2ebxI1L*7mnWkBHkP`+EKrk3&a&nTd!r{8W^U^oM8X}0`Eyg zK~z}7?U+wUTS*+pKhK&*`qiYQmwJE*`)dNc8zYOyD=g2r-!EjDfF=Ll0x%?;mz>lF(1A& z-X>z$7)oQh&AYQMRSS*Gnp^XI$L*GF^y7Gt)ua`(T z+!S?+woRp4<^JY<+5k$syG?@uh=jwGc6X^SS*=#9Cogw*HwEhD`Z|E4(6?}h=L+t&PW8*`K*O`4@r%TxXeB}9?bEHx!#>U1-CX?Gc%kzZ4&aeqVzHQeK+DU^*tU%s2y=7r0dF$< zM9-f_DUFm0sc^nhbL;LOZ0F0|y-{(`_PJb+csx!qyG>zV96>lRLVyqoAv8j2q}0@E z+TC-=@Aor3Jxw4G;94k6O=_f22&oZLA*6D~Lf7G3b=mOn@X7ro91wY=NPQ0-^@Wh` z+x|F4MnCMGCm-;w-K zW-I$2TiG(3fBwtcbeWxvb>`>iNu^SaQ79UXl1L=5EQ?=yzjV^hiyw){MFrt&?#IU7p#82{E)r9y%Uv? sMk&+P)x~dH4=@%mjQR`k8!73122(|L($Py68vpk*`2YX_ diff --git a/resources/icons/shape_ungroup_normal_36.png b/resources/icons/shape_ungroup_normal_36.png deleted file mode 100644 index 3578265fd8c8adb321c5256ab59da6389468bbf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 841 zcmV-P1GfB$P)Gi zI2>kmb+sy>)|M7tzj*^dJwT~cs(S4l80t@2QP)5<(0>PV{L975%ggNT?O~cG9UUFV z=9iY1NT<`dTrRr1yX{Y?P$`fkwBF3uo$4fL5S1XbtY>%goNs;`8|`18Q$?XJgNz>1;hpb7a#Ru65oc^Fwj9 zd9yMgDBoBqc=3Fiov$ez4KA*Cg)kbcsx#PYbzroBldNflI29Ua!Go7 zd)fYyV*T?LD~TOe;#=gt|KQo$IbyLG!^6WwqfvVk5*nliqtV0k^faeVoA_J~oQ(!j zX_VARr7_R@85|rW5D3@@T3lQtm&;*#TDaLW#9Cqp|M}A>rIAu073ICublsU`Bc12Y zje>n`pGu_&27_di8)SCGKB9~f0)$Wqp%GFerKVKU_P&SQZa0xg1dqqVHD8>P)JUNa zQX`~7NL4-;`VePpm-Y4a9jzx(hRFRy4r^%tSO{r<+aHX9fdO*49Igv~Zry*3lI2IG zltwDW(}~-BeEW)5PcAV(KX3oXHIvCOIyy==`GM%?Jnxggd7sSl_R}xk#q(^gzhrK1 zj#w;KnS}g)KcP?v%d)uJe!P=5pI;#u4AR!tRvFO5#Kd3cXJ%$U3<_V#wgS3(TKpsA^ep`oEVPU83jHmYt0 T=|%Z=00000NkvXXu0mjfw9tNe diff --git a/resources/icons/shape_ungroup_pressed_36.png b/resources/icons/shape_ungroup_pressed_36.png deleted file mode 100644 index c89c88b558ac353fcde4dfac775efc0dfa8db66b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 836 zcmV-K1H1f*P)hNmDR*d?(h z=pYu0=}aa=xm>O(z~yog4u^?GqfN)4rxO#p#pNOx2-GE&yk$`;mwEE|agzg}u(MM) z3_vgtps=$8Kr=w4QaLVO?dYfr>h;aeweKPFiE9S^!4?T&*y8C zkYN}sEG*FK@zZwBfdaGwr9o@(T)M*S>@0r2zc!$*t}eC?Oxn*oP?{5))^MZeA-V60 z>zB4`1A^*}m4eqx)9iiC(A?tY=A9_cmh;t`Q~*p)PLfO}2?m3Vj*i;bWlB~PDOhRx z`}^7bl41RGfw!qW-X;rJ-@fze-31bf1S2CO#N%;$6A~Jv24}06>FH_CoHg*fn{l-| zkxHYaMk)gcmv$-8|GbVkI==lD5a57Ar;lV)ZDrMjIC^u z`?pH=wS6X&Args@Pj8Xi6NiW@MhFl>A%sRqjg*>7Mcexx@_0PNVllj4FE{*2DpDhb zLP(8}3L#bXTp}y#Mr*jbxGS^)=?^ z=13$GwMi%x3K5M)F-?;PU4M4c_R=*XkqDigowWf?O-=o^J~K1Z_(5xcY7cTcoppyC z85{sUpO0)VS2xVc$_hT8@8p8j`1rWq-Q7L05>l&V+S}V19v*JuG>+ddG-|4BoARUp O0000Ouxlq5My5D+D2$%ueR z&RKAgb6oJo#J%@D`_z5!-hc1;zh2e9Se8QRSaZ%XX79bV-dYcQq99Fxe+?f7gAvHe zNGii%nBcFNu!}h0j~$o(Bk%`?y|VOuSYbEKFYpbv$-T$-V6ft_ODE4Sfba2aWHjwz zuuG)SZw%PGL`v{O9DO-yN!S_mE4B806!;0QgY4snxU;y_gja4Bs_~_RA6++7ed_SQ z+S1a<$^m=}gFUb_dhTFkc*EJu!Ssf-?BgefTt>Jsn0}b7+m z+ThWbk{8GV!eKtI<+!ep*wuF%o-Vq2_89sZR>@B%3K@A#DJm|06R~F^THM{fyKet8 zk?#gQefrYekFXu*Zf5}xH-38UFhaC-M<5R|aj*YNoc13-l&2yXq`!}i95Vx(F|2h)WHp=wM)&Tt%`?^ zc9(k%;6b>TF8MdVO^VLaT2Y(&euX#Ns59mlxx3|LjSvfyY{{Cdm0;RBzB5JMYidfB z%;WA5Spibwh@W(nm+8gMx!!=|8J6jqoC;YHBIbZ^2hp zQv<6FsA|l)s6>e~$(H%fU7CJfXD>j0%nT!mB0Yi@BtBnIqeypEtK3RQGO9i16Ytx% zZ?RiS96Cr!lM`a!)2^01Z}PjzQ=nbRn@$SLwd#t4kK7HfyQZ*I?a0Y^is#)t|GJLP z;KyT0$;_4|X?lA4Ao@hu6=LFtHx*z30Rib(m|>EVl1c%jFz|3$97ajK*Ve2YKgX_) zmFGVk%zE>3cV{OxEe#_&I@&zK(JUCgcb!;iN4NmbyKU~?VDuP;AFI2%6~mjWZf=g5MhB56B2UZ=J3+?cm-uMRMgWnG@P za~2+vicB6zRGy&~_o68-E@qoaIF=oqw_cy9*5LfaWoi7v4lYq9)#bf|_8d#+@i_L+gGKz{C9o^;%xi%ug z!ZzJPi#dodQHI=jJHy2W54thJ@3>m_B#W6^STyA*W%QqZf*xT2Cb(*wO6|DYW|2p^ z13U(;Ij{MjH)5}hmG>ajz8-;PBf`T|Nn{WRy4KL^!EtdP>n$$^N3m&-D1Jg1if3kJ z<<^T>jSEJ9{5EVT#=!6Zjo#aHax`_{S?Y~hWd>VId;#CWaR1N)r9{~BNI(3+;O6FL zUe()2kI8cl6A}^zZa#B;Og_>*KFFq!XVZnuPnf_- zop^joXB`7QJx@BGx9Fp2CbH4DtSYJxSvwTiG^ygUs@^U}8opMwRA(k;R;%zimL(sd z*m71;QBhM~MWK|BALTs@Y#CQ1$xu6Hy~PAwmoLBD*E9_(8>5H^gS;0zGcHf_%5*MA zisrp@p8iGT6^gL-yh#1k%Ugggx%6bkl22ABtuZ>&Y>jz@`6vCc>}kvgIr^xl^H26f z)@xpU`o!zVY8r@?MvYTqd5m1YCLkqwk5sZPAivfQ-FTsf~u=!$UYeTd3zsHqWk zbe1@L)PN5iH~g!E*m|QSW+>b3pIsu7Z~ z#JR+*S-*xw=k7mkktMTIPzViruot1ad!O(OoW9wJD45B&xU|`*rm6dDmu^JC*gviZ z1h71LN(!S&<>TX1OOK2i*7ewY=op`%YHSx%O;aZ}>2!6o@U{n>8)3OUV1e|DC++rc zvsjf_&BWK(i%LvolGVub5UN2geZNT-3)dMzC=Wae*&UmK4$YgJUx0NF!{v)fDxmUKDM;>WUCWZ%U!!ea*di=Exn8cI^oS`M%1~3#$E5S zA_HkqG3%w=DYZ1#tK%=Rn`cJ0q{F9!ntrY#zYJ`PrUjtPt>=zDG=BB6qbLgVaxj%nC^O0xa=fW@b;}_!_d1ri74i+t6?C$_>$k2h>3|g3Z)Txshgl^BPTDfDvlYIk|J){U@ZUY zehWNsZ^kz(z)*g_K7sD7O%f$3F)_gwQK-d)6(W+yrtn$mqw--3^wI7T4@t`_43qH$ z9wQSYC}j%VejQ>q^2rF?-d4h)507lRV~>3ko~H(-4VgF9zbF;YZ#4((Csh{@i(`bx zC4PsVRbW8C?X!jow_Wq8D#vB|camjqqth}o;Vju%axpo(GTL%ncU}zNf@epwYe+d@ z-WVvLih_^f{?Gu|R$iFZ{P&e6@#DZw}bQcUui&Qfe)kSeaETB5!x0;m6I|hTX*^6pkGAFxWa@TCU3{ukl{L+I9N&)iEAHy>BJ&#w*2O2n*d7sE_-NJpY+2-D( z@32a)y%m3K)78{7`$^>PtGBO^Ew34@^FgKzxkt+wDx+Qz**-5~E=2WY(0@$P&R0TC z?owgx-m`J!8M@*KmEQ}Z++G10^xj-$`GD6@V%FC`_p>WuAnU2J*jMNAEp%x(My31q zI?r*Q;tjJiY8$aVvrHNn{J!_0502`F`rSXwX5tGlGfTJJJeKL+ps+ib0#!;_;(*te z3lur_FkK|t(a}OrXoa(bO~@XhTh%ZzNrd)jQD$E7WQzTWr_S`3_I+BJ?v^2 zBD_)fnZz`2C`F$D)I^_B=CS!@tL*f(G&SRe$GL4~e?+4hrEv|B9Sbk)BF9$CrwS+8 z2MT}RjH#r_laBo&yGC^@tHP?+%en89l}$`LVUw|UaCF8}uDW+bex#i#oO}6;gwvoZUHTwK4$h9CT~}9JZ|5k z=4?*wJ}1s#Jac_mXCf@g`q(Cw@sg~DCvisMagA4bY2wqFcbR!fV(xc-b|(e(+& z=3x(#mRFB81a=TeQ`|Jn;$^afC|c4#T)rxgyRtSs@{isY)FkQFUv+ zzP?SZZ!P)=>@Y3=OIez^UWOU#eG**B(9lq?>mC0C9P`%OEUNO6HbXzja}lH=s`5nY zg?Nuj6hc6{!m+gxm6K^4$S3B}{++r3(vwsQCi|iJQX1wrOAMn=|NalK*5fBsmFlA#Ph*}_P7{Mt!%9o#`<_V$|(4=eNh3BOMNjkFyeR~R;_=FbKoN; zHaqnxPEBoCbgEX;)hz^QTT{uWV5}^yqC$`*yCs$N z^I@`)mNN&Km7+Yeo{Lr-dx|hP6Z1|IRg@K|MY_aq73$WGmRn6!1u#W$8X2*$vf>4q z{fcBX7b6*p3?`tyb2-p#;cGN;5=_R?q7~k;=d&|lbAPJ9f_3w6E4?Yl1SG*fW#+#_ zX2W0D4My8x4JWl*E~0K-Lgy!?oLCVyD5Vyta z&2%E!2wOrCHf`C{YNyr4wTWu~By5>2WZSSfE)lJyqa*+5*w`-n;B)W>L|k;o)ML5= zXTp?~joaP%EMsalScJmf8n(WY5EJzJn|z3v1GcRD9)RIT*9YzI`&uv|4=s;gzs6n} zuOtPC?r4B0>P*YJ)@Y>>9zrX2jZ>`Th`Od|HqFj;^zJSc$sw={>TkF@8UPt6IEwgubusT7)+j`;q&_H>K70utkFzb<&O5UxBo0WvmTXl0$2a=G%dN) z@ysB^#qh9+?liTV=;Jw_lz*-W(-#q*rru~g5NGh|&sdI&kf61r9%0nwY&%^hE};(0 zv^>Me&=2(cHKFL^wh8t;JJQyYF*uF6MEh*t-4yv z`Z>X$ufWc?I3EV~yS~Y-b8J5S=$wNlZ2ds53fKlEG-uk{MSYCjvNudPMr9QXLY zBn{}0fiu&dB^BUF3Kldb-Fp7Ail`yUc~WR4%j?Hp7Pn_d%)B*}_aZ2Ju_bo8;`ux< zd8h)LyRqp(x->R2B1kxSy!B3}V+uO)=^#8Me$6H0LQg>-w)ZYn~o z`zw^%h|!jR10;Why>@YofHN&--I4iuO3J&dQZC0u%}*{9 zKhe_gKRxxjN={BltzxB{Y*pIR`iA;JYd?ieK0<^M&VuMFfLO;7+nsdBy0mE1Vm589+}O@Bkk*=d)U@Yneo z4Ehz4%gq2|L0{?Q?3tgj!(f+N7y#Lsc7Y%~D=VvxK{^y09aDRqgp`!Wb<1#magm7X zBUNAO696eaU7oYjQ}vfv=sNj={BT>}($$$L?IEpulY@ap|Q;bMX2Fo3#Xy<>cjo&&ta!Y~9ReLdr+J zxYS0`z7R;fF5+xZjKsXbBa{Z^QGlL79<|*?4b(X<4?esd_=1)Ffvl|T6M4B<4yEOY ziWuz|!yli^$g^t{hAd%ALN=hER{duEiYu4X7 zRNv0?v^z;eNl7U!*~G}``N_eix##+1?Qp3@?CQ|Q=ReEIACpK5ybLJ@%Tr&8`=X;3 zd+F$QjMC&7_q!y@(l;Hos6;I}-7Xyq<~Bwj&oDDH8vq_DAUIe>8WZqQ16AXnb=P!| z8`Jk3oSaacAKJic6J!Odc~LA{8VfPMJU`Ea)wMIJ`-(*RKfm2uOtz~ys%@`XaVrhj z+uHxGsrBlr#$^xMW87tcN`Brv%a9J!ZwtM?w7#yPhSbqXas!=@tTiG30#q2M2i(F;PpzipLtq zZ_6~RKdVMhe0Ruue??RdLsZabZuiO`Z;%%EnL*UmSBBg9-iGBJ;q z0Ig%Ww6izkf}|1EwTObs>s%D`<9C^LeX;195V=9SP)UOl81tpU+o0_DP*l|Oi2?JE zD`vT7ak(Z})+{d8o|fy~rz=ICvmUCgdvgZ}grs5%S2N@L8+W)c(6zNoredMBxds~t zv(YZaqy8r4G~a&%Ddo-1K+mn(desw%(Obl5A33$VpGtE*;LRH~Swtvyg*KO=v2l8K zHVz={lK+v&uL#NOockr6@}44aGf*ah_d<_uvL69oQQPSxVWNvYL)B=4Eu7HBqx+4^ zNexn>N~t9a-ZEF)gg#RFFaxMnCRpTn*1OoIlP$FdK+O+iLUo@GAq!|{2npN}6T9A* z{aA$*tCHLMFGMQw&HGZ@XjuA?480h7Vrh5JpdqK(sp3=bhA*$)S{PoV&W>E&WRm8-~eD)gYd9(#;fw0MBP*@119K)5FX1#bMIrt z@ms{4Dc_5Sx6e!Hmg7Uk4Cr2zDAFYOM#ql7VsAtEuab!r8%-scuD0QQhKnemYiI0{cNms{N=dl`a}w-pEy_m)#&uL&zejk zl{{NoT4yHNnNn8@_BXd;*lttdHL=h}T%mi9^J}hcG-7<}HL~iCoV5`Fvqr^F%`-by ziM2y+XWZM?$Lt7#c1Z~)96JZSUq7<)Cd|Pmq%S#1q24EM8rYmk_q4T|fl&^z2( zr~uW6>W})>vawtCKjqQphEPi(y;JjyAn)I>u#!D~^q=1?eG7hfULp|uJx2h>I)CjR zU1*eno_v#m{QFiso-4#c1OCn`Jb#o1c6IAO)UK`Ybi@hxQ@v8R!yJ;Dcg41k5`+&l}WW#_|zsB795}fL(uw)Z<`b z*{Gbb+I8QJc=GRpVL(|(sHuTwwZ>yd{!jt_LxV^|Ex|eoF2xX+sY>c#G$|~w1JBc> zH8TOQHrqrY`5Hxg0RC{L%NojQQayU~=%xO%D#O5MVa=uU`b~ZW5A>f^+8IeQ!;~l9 z@yLGiyy3|Yfco;YM_@z$VWqhOZ$K;jQ_J`d8szT>LBRs^`+`|p-QMc((=+jdzO=Je zItwp9@#`K}MMCR+%j!svbl~-V&a=&U{r&xVQ+1v>A3iis*1Fs7EGdnxN1B{dI1q}y z=0>RBxw-f~Y243faUviLVMlb=uHz9$va?X$QRim=DIzV;sNBnW2eq;yX=%x+udg2- z8|xp;RVBmu*`!Cx-M#wV<#xrNqk&M$VgQXk%n`3yZo&1$mZx`?nMw#s45T~nth{ig znBdW=c>ZNIE=~-i=lTKG-fd)R`KQ=(Jfpmzm?TWbXVU$;ZSKqFT$@@W9VKPM$>H|E z`e3ILhFLil6yGF=K@Tob+;rsSwy|~y)!g>S;x*TmyH%vpT4Cw=^`wqTr7p+AOo;-! z`ty{5^S)@sj7pIsHf^Dmso~+ANTjhb6Ctf=04S77N=wseV8yr9#@XTubj*K6-#P}!@sPCj#*SE>gPPpjJ2yA2>o(08BlA2msN@${~D;&B9 zmK>5AjtSP63JMnAf=IglJ~K0Co(qA|{#qgwrtS7C3&rl8oJ`A46^`hP%D~bsq5#F2 zn}r1w1lq6eVNXA4)0I!SMG;*Ejd8Gh+mz9;}&2#%78}WXhm?+5eih{exUzHi)CiT($Y@FNS9eoaRzjUD#>C|fdz24IL*Uk;WqcK?R*Z3|x*4gu@hgz21Yhhtw z8hE;S?uUod)K(Su>hZ_Dj#n!-Td4;sVRpU^OT8JKXZYUGhG&0dhUpgJCZShN*DW3( zU({P&Pwo+cQFtr~loh`M@l19_d8Tt8%{4T2$|LMr6kc@kHz%MnOzQ2TL1L~*Bl%4G zn%i;JMgu}T40$>wvYX$4#qBA@$x}!MeJIVWIk?M6~m_kdEsoMhuG zd(kxVY##~!IcPh+Cy%EAOsiDVt+=;uGZCdY(1C%#X?E6vzWeT2+&f;Zx>MbX5PaM5 zVO2EBAE)MS^W}kSyl6^}NYX9|9TyN^6dd*I)n=9#-;M8(f082>1tXHjK+ zq}E-43Di`G;=fil4()jG$>j26p&OQ*UEzb55ged&RTC=oI^}DfUU_g|o}*B7(YU4p z-D9)c_Pk@>2<$oFMz0H4CAx{)OnX=LE6Oy+=ADh0q^K?1y_G109ZbI>>UYJo>qz}; z18)gG=ZIMt_Z}U^c`>rhRv70*Y8;jjrevFOi|sC%D`|I|+9F3=ut;)?yZioh-{Pb% zo*C)x;2_982_U$V92Fb1JyBEhrQ$QrNjPl}^ufc!ixad>7eDOed`ZFa)VrkQ_CK8a z8^&Qt{t3q8p+`r?Cnu$6_nkE;ws^T4YBnj-N^1$u4j1V%wi<{%*3!B4Hs|I3By12? zhwRRWVPOy5yapV)?19}J-QhKfwLNB1pZb#pZj=CQ zKBqxVL&r#KO90}wO`Az!5fO=j%f=jSL)>RXq7F@GO;rh}m&7afZhFA?N#o*TvZjo< znbj-oq$MhQ!{EnSR+SfkRw|(ix1q*$n?rns4l1C2!=zk*=YQlv1mDQq2T+-V|K?y0 zy-$N87Ux=?tJnS}>G=nmn$R)A$?kH9nM=s8e!-hK^z`qy&J+s9R`)u*g;<%T%ZKyJ z3Kh^ER{y$ga`@QKLh#DBOZ*|gS{ogwm*maHWgo(3eD)hx3koJP!e>we0hC<76Ez@k z({ozze{pK6wdWbD>xax6OT61!8aue&0!L_SwK#xVl z&G8U!P<1wOlKT1__nU?zZM2u2jw3^S><987=6HFiNIEQnh4!-G+++x=2s`gSvdG%> zB30ifqliP!qyJ=9ej6LROzy6xuBx|OX34_EMHpnZnf~xd)9^AyUl18PILnY?OhxC- z+~dcmK#WBm${pTG$}s7?)~bJoEun9BAL2Iv1@PkpYYsJ#@3}l!5Nt{^qO9&085x-x z7sI8QKFDCpSqb2JQ+S+up*B@7p0{ebF~I(GG6+yHOjN~?{Cy=Cc0KUSSpMBi{I}L} zTHiZZb4lsV%?am-lj^A(hs?51E*S|sdWK8 zy&f*~+S0)AaDek0$>mL#kEXr!<2FqM6+hou*i5=Erq$KaYL&gX2e7bLxfpbSZv(9m zJUsIaHIG+Ei%h+%kbafc^yn#*wn%6A@Q7ccq9=*wTx;X@_*sJ%ReVC9AW-*#GD<$-EZBc=BpH`*FK=6=m*X z&#V2?_utiuu_oR~^L(H1@yYO?cHLGBsa#Q(rx7Epkz9;~M zYynNfj}}8*(Fa;uTD^UJ8MF(IAhuuQ#}{#0kpS>`a`YO6tayRkEr2gl{h1qYWDg%n zI|AWdUQUfws}rzp-XtMMT4UKox%BJ)S+>TlQ&fJ0Lk+Q~$W*VERu3zg)q@9E$|@>6 zjwscYkB+(|B>kchw2>%~bDy5wKD1dG z=1H{g7D7qhJMZ>Esb4QOgUptdRmzVqZ(vf_Q1od2quhR)!BYNc7@Psm?Zvg6B-NR( z&rd zFYxfl#PJdo3=G(0g$!zGLA6`+2_EbPc6-q0XS62KN7c5%o zY6==D2X@9(vY8Z{8MC|WuWF?Dmv>v}y^c6&Mt6lHxeoqFwNRfoX+hj+{jlw|{O#IJ;aTgd3s|*0HZ84ru{AbdmZKwt z2s5m!$Z!Sj`g3LEJ+51_+pQV^C6cj;2?Hl50iDMNMz{ExhhrQf&-14LG2`Z?O7Vuh z)iGj=l?Ot^-<6@K@S`yWmKo-|97Nswee!i{tnR(7H|00ICJ()ND640cBOFeBDQiy{ z1W&$x7Ic_}Ng;RWhy|?DCx@RwoB+wW7EM28d`X%KBZ^s3of)UKr6t!LOWS3}7gb8i zte^)ZODHc`%sGEdeu~|2CCZ&k-@2A`Hl3dEdLB7^OB7N`qG7Sz+HLreC8t(> z9eT^*QnxX#*j6qw{G!|Du^HgZ5mgV|pE|*sM51j~Z+mzTTUa+8aC?nSV$;(+ol$sq z8&5;Wkqoe@?-lUhHlfGLELE7%T`A&p0+TKomjA|QORiLG%J4?ESEx<39axLL+?+xS zF?lUWQ+jqWUx|(QGsALu!;cp_W9LFe)*9Z6qs=^aGC+^}QV8w!`1RruaDBb2Kv1Kw z(2gIgfCrLpJFUqAwuE4DW0uOfa%)mk1QP3{fB#!x{XNn|C%)5d;q)K2g8>mJ z{jbZj|8mtwzxLSSGZs(fpEd)xo=@NYF(@75aM(r)nIb8r#}q0T*oqP;tECz+==MLk?NX+^eFYNkG^)j!IPi6K|j(8&1o zi3CIeAGa4TUi249LFGG(8))Et=l|~WUdziLESdF96Q$cS3>1S&Cdm8G! zQ|whw%cF_j&biI$jhUt;khVd%16qsFcs>*T+2%mNueSi6+_GNdE>zb_iDJ0qu2N;f zv-H%DUQ}ooZ@7qU5V}Czvk>Xx4Ixgv`ht@zFK0uLRv|s~{ri|eLu|3aOabEF^sd{$ z(ZS|iL}KEH(2K)=V(Assq))}cQ+XF|cMGft!@7X@`9UC7H>F-yKetLX#^wFe; zmh6GHHjUHjs4s+TiJ_On2Mc#ceell)S!(XSAc;ZL6@$y)`5*IF7_RVY^K3rTj`qA> z*j-6WU0xSta(cv}>we7NU)ukxU-J+&x`g|khBw&RFR5h9f9cOv8K9p!s|P}o z;X2Q1VY&uFC|HvKj=|;l&6nXtrB@|GlyOF>cf`uqsjlj%z4R~Se%R3^{-ufTFy5<$ z7E(|Ha+t;c%!sF>*Bc;NjLul@Hp}t}zc9}ggCnwYX$+*gxrP7Bbk+Q!34)m^+hlI{ zLDB5yS494hsMO2D*u}b>L544~WQ8=S*bEpNji%%?zHDx8ej+}?9`({B*;T4w?0moU zB}u(6NpVYdZC-1>hE>{mLEH9AwCT>BX?AW;s*!l1_T=za+@h{wUgfnQKZQ~YC|o}h z05*nPxzqE0u$s5W{@Hw#B1&WR%Xxo09k1abRcvZe*DFuQeaate2u)*va&m%2x>~Mtxp&%FG@afXn`*xAC3=RsXnv#*@qMU_9VjoY?ee_E`60SI78BIfA=zyeom z6##L=D-^Npe0H@G^ktvT;6xT-(8r(nLzyLr*2DvIqZ(PZ|6#8lQcZW5bs)}(*fy#Z z*guMe$<#<;fNQu)3m#Z+JoFuaOhb2-9wSP|L2>u2o;s6Yl5rr!24cQ*8i+ax*7RjrD3<&9c%BTOam77-*xf~a9@^U z%RZj!B6?KuTkv#<+g55-&fP&Ns8R3?;IL9JwA&74;OrM~zO}BnPoF;l^7+5O#Qq;J zU(`u5^8aos_K!N{y%@&u#AhtU+h`K;T;xR}I^E#5(37j{f?@64hYoHa9oxbvFyiD9ANhvmJJdNM z8i;2eN?SAES#u?$LTk3~7|#u?QumGw`G(dh$X^quLgsos{+;Ezy1HZ@KUNFWCEQ*I z;H~8f1%*E_IOX2avFfHFvt}HX`=we2l1urh@{9X7g5- zZAO~em2{k8#jVpxZ{NPIiV?1j76Wnl>;5Uw&MF5Y0cYOc(eVY?YHk6X2$}_;_g04= z0GQE5tYIH0U_k-@n3-Z0k&Prk#P2@7`qPuTh}P>~Jtn{DKJ_cdGl_boTGyFh+e;cy zhI0npB*FEXZJQZi3VnbGa&mKfizts}Q!d?*#+@&&uSeADbN-IeDcGh*`_A=#UP&xo z<;y%Z+Pj!`Oor50j<^aXNfJ`Z*=}823I#R-mZO<@Pr=p^t^mi>^`qD#n6QY*K!>-J z0YFXVw<^#&?Y7(`OVx@XPq~ZLOa%z z3{b<=`nec%$51&owXWn}vcO^4)8HS5mfBkSh5E9$m9!*0`f5M}sa8Mc1*}{HcX`RT zMUeIp88A?vqUP&?#96t1j25a3u5DsZyUWqt?~1tgcKsN(;C{DZyQt24QP+*oy?njx z(>L~w39b6D;@r8N>Q#W!_fh#pt-uX8fam>JT1t7!s4zcte-=ptq%fdpi&Y?iU=yUB1!k52zrQzzKtt(JcJx1?mOVZAS7xPa z&i}fs1e$~Qin^F97@3^(AdwS@2_4u;yr2)0i@kXOE;5Yv#C;jCp{W~B6{>1~e zMFw-%4|8WTy?g$forB8guTO~RYz@$9r7XFJH&5-)h&I00t^d;K_JRUk=a*NwN-8QD zF;74}pVd7%RcX%*G$WyqaJ1ge7&^i%PllyoJhQI3t5m)4QZa*g+d)4b zWeP9GV;$y=y|nV_(zGB4Ad>Ca1Nmubd0C}HhXi1`0x!oWMn+j~V*LDMK%9%Rz$zNb zM~Hof!oazi!8>C3ZhRGq<8{ZJ)jYLV*7fCxdSMp!yVPRn+8ndOd!r2?!k#*F&_Sf# zBaSVD+n~Q_9??U`RSeWm`Lg%z?fF3W)TVEwP;;E8n{Q-b9j|ROjc(5P3x+PMoyWvj^1k8GfSPI=ZG}mI#fx+ z_4Zaq;&_dMtf(F-VBU_Z|BFAep$Ec>opNaNSDXjsVe%ks{pyFJ(u)1yxki8K?9MD+ zLkjAQLXq~c!0S%G_1@i}b!#-?=<%qdoI_q=p%f_FyzaamzhDC~+{qYDP)kJ#+%O#- zhjLh?#Iu^NB}&t_C|uuH&)4gH!9=4{0g5F8USC1m+1GAd_1ABVJv*-rfAxtFCfde8ZZ; zi)c!dQaDi5;W3_{+s-MfT2(L;n zqnrfmI!#UpfOD3QzZ|owe*9N>0b9pxZ|5tAq=qt^mq{0PLkG~#T0&{>IEnupe+fJO zO)LwTz#Bs!q(?h=Ze`E#WUI`*rVI#3{EA$p`e*Z|aoxv{Hp6jt+glF#o_DOqDS5N+ zQX^}apWN5ie7sNTW#!&Bp9^_4>D8DXJpnY}+`q@1RD9Bgb`!26f$)29&0 z0xHapz&m>ri~o09tir-R_)5orLm-lxo?YGH+{^I}Nct9X=*f?4) znmb%fX+xZDQB07m3}_%)48p|xpfU^CL24oTsKku4%xWS}>t*fkpw_uUwr6nkT+RmR z7sSNGpjisvM@NJ5CgX?4_(|PEveG@7oT#AWXuFu?oL*O_YfYRqzgKQo-T+#44vvm# z85s{aKTR1%pzS9dhti6RmHwz@p=L~GcJ}PA_bF08ayBVrNnY7zgfQ+lwS|;WhIA(e zL6Zt7fe2u-@h)JfRFsuBTB)sb+UDmCOQ*g`YyRl#+XS}n=ilOZ+%7FcA^2P*62x98 zF-d^<(zv|V=uJdP39D(%sML1r^sLyS8=UJ)IW76U9tD?nKua)1e#_$am*w3&X&$|m zk}SE{S@4wFx&)>^jINxUC;9>uFyD$YbgkJvtquImDsgR^sfpf|_XyMuuuFJHQoY21VLvs3Hc z@kh{03vqOpUBu_H+X9i zy;{#Y&BzKhcLn(QXZ)zGB!KfEPj4B8dZMKj3}`6e$ejfoYy~Jvlyr0o%1a2LxC|)? zXvJ9CnBUJuNYi5IN8Ng^O2d@Z#^CM=9P7 zjEkdG`xOcrihwFr4^G6o(X+tp2(10PRz+PvoqZUu_2 zF8FmOk5r3@9>-mm5a}=zSUK7rudr%84E5f8br#5f6wKWZ7h(rGEQWO5Il1aj>>RI3 zFk#s(Awmh`I@|8<@pu3aOB)uEXli-cUrG|l#f?1hM@pd6YI7|1JmLgHl(cBYXfqmb zNBXz1@QF$BM!fzpWgW6~$L^6Y7$%{_0VZBRn4|fQ?2%8P_(r9uo8h8n2!u+Z6KywK zRZ^m=)9Uo=wfT}h%)Yam<#qnT?NvAQ(gYm_?{)!&6 zq^QdH1FWcep_{i5pRYk%BhMg=_k9N(4KgHK6R`a% zIWKX3Sm?*!mli*YsOkq9>$LRIj&Y+v)R{H%SnFh#qF&f z+;@DsU$foar#0LrOST5qRnpe}pzy4EK2ok<2e#mhSqMbH+Mc@u?Ok0h0`-TFza@(S z_X-4Sl^jIC7#Et+7(@v>D{C+37q6qf6iB3w+9;m@kI-v@9q}a$n3}@<59&AHSWgEs zCpXle9oH|DpTGC#rp?4}f2G3Zq*9P5|18WtI1iprm|?m7?+u#y-}93hCm)0U(KiVi zWw90u|D+UMULD}vdn6-&Ij93n0&3YFXBz>ALVPER#$6U|=C-RHfA*6u5QIE@_^>WJ zn~C3MRF>o+aBBhkDxYEM?r64I5 zi*V8;cEgy@aE!*4u?-bNO;AN7gXOiyQ()&hjB7zZe$}))aJD}T$`CP8^$KQ{Z_*;R^7w-=ajz*uDfMASoxQ4 ziSyX#S5J;g5f|DlobChlA$%IVz)e8uF|gh*cML?Zy%Q5}5))}a`KD33YYy?8gay0Hm!<)=~rY%9F5;jt-P3fQ66` zCJHP5a(l!}z=q}xb^n3aEHJ0dk#zQjo|I;DcD*WRI5_XhZxmZ_e?Q?G%`4OY+K9aW zwLc+(*Nl~9*6qsF9v82jv0it}r5T~4nA5Avf?18LD=Vu0$I@Z61(8+q7c;kF10{fJ z*UsLa$97iI@vgTA!3_jyyPK|Vvaq?{Uw)YB?UqJd3X(_?9`kWkcysFw&lkdW*-5WD zR9Z2`{6juDiGQ~gb)u8fXc`kNn|r^6ddye$26EEB2b#%xF4m9&B(irpRu-t>LW^Pif zZ5eO*D;xN~lYjYs2+LZLu0{&; z*E))Sa@6_(iLHV!dvuK~NmVmb7%M9)rHIo9;cwhgn0&uFGvqNj#4yjt8Ng_A+^t|`x(kf1*-84_DEutu*IRw?)*Bg8qN^2mI5=LpZPr3d%91_~j z+sPnEYT*L*5qm%{RLUg)i)^;`x_$#e7WWTe^M)%9;Ylg>1T!H{mpdSNs+EBw@8$bxqCOI{J^;=gGt(mVyr@!ay?*T* zx_OTnQg(p#CMrm&&srd_+}_ru?N0y7#O0w9BbnNrvETk40TB@`wKLElqy?0ZY69Fz zi0ww^QLLk8hHgOyX;3r=MO87R+gk4ibl4L+=$eh7RiY zMlIMnItjcD>2D03bS`+?QBQAe*$pr`UwS)+-6`v%4ylurQa9=_ziV%15DTn2HFF;b zqlkuPS5qe5%;A=$m+oC;n{uHo@YK!ZIwXRpxD)9|@_{b7oxU$SS-ti#cloiH(+Xv> z>rblwfq~ovtPTxqJHm#_#pGZ}u3JR#yHtlpe6cb)@{;^SQsGgE2B6n1r6#^$1&BK{ zDwC7Z-$By=<)C0?WAs-<1O00S%@s ze=9kFV>MiBQ0kwtcoyA^Q?)0rp2F9TUXVcx@>TZRxH&jz?qrYGh)$TNyrCBG3&|}i z*C-!mzsbYH!ZbCpLDplRZ%Fa0j(u3?;!uW75{(M{&(VM6w{MY?ksShiReysSv+PwW zDypZTEfMbXj)wrBEDAneTKN#nJNmE9XGl4bw1hb44(^%9$)Z&MJf9)YOIJ{tXX4Dt z>q+D98BOitf`yLNp0ceCmF5F%z;v>^Je1KUbCn1u(bl^4Kb&ibRfu`ZAjsiQEA9!F z!M%GvJ*!R`H8t7*#(zr3)GD`pU~6mpL_U~;^G6T8K}8(!VEg3dL7(*wWlm#lzSRcr zD6{eK(N3n^{C_mqkT%ewqs5Pa+ON69ZsBWy0xYwNA2?i&C63I&%*Dadw+DY4k=Uw* z0qn2;{yf7!*k@q|QdrSYFqmuz%$(`(-%FSO=m3*wP9r>x_PcEFL(Jd9_n<53aW@4S z(R|ZcH-Xm(-`v7NZLFaIc64;KQ=OqP?G=fJa#Sg(QI7VbChB7-D9_ES!f#A5Z^+(f zlg`yQnwZy)g5f|Qff9VsZE#=*4?t0P|+=|r9Zo*UBJmZr?t9jZshLCERcYp=#MfC?mK zR;7<>8sU{l*xK5<<-Yf#)C<^LPXN|^2;4GmR=Bbxpz*q}P9yH6jUHos4qgO>RrWrh zd4xKRrMd`27{tCmCi%-L0TXiItS@64i09zs-T#K)xruST?b;vNBG@x$rM3 zEf8S(JSB;TUzBCyij@_>=T`9H0_?qD!jJX|n$c>bg1vuE$F)qrVpUzfL7 z)+3sxlnFW;EExgsxwRha$%vHOdlgQKxnSzuF)sdz$Vg|>no0cMQb{iU*~y}K{(3QE zn(X~}Fc4DMft?IY{M+KIR91Sl75-?egE>Xg%#2l8S$XIE(g^CHgAZuK}3I-S!K@dn<1PKu%OHqMvi3GwXA%PIcI``7) zbbIFMOy`$5f57*g^F8OD@0|C0-}g>t?$y(?!u1wH?eCx5i*|Z}f_Cc<9jXhRD@ytf zUAH(CoSW=%&@`~%dygH1(`@$S*7IvurRu?ulDbo*;T3(yTIkSE1MIk4YG-(8RAiYot`UFELmb^w$ZnC z^CPxHMRwilBW+{=KApA!9YM72EROF~#uW&_;|{xHnMJ`h+_Uu{j|+=gr=F=&YwX}x zV=GTKg-1Tog20SIh+WXh!O7zIQ6Ct2Eo540`(r#oXT>=VhS#$<2tzG==dwXf1G&UW zSr+>NXW+^!&#I!lUR%46Tu#%}F)W-!Uh6WdQvO0U-Lu4n+1r<}j|#-jSI=>*;(6_l zJ{-umc6pm8`2>)hk78W;nePr#$M%#v8{S zp*G7=IbO3rvFAF@-`{^O027{byOC<1=NGVH2MTt+cUm4~&=Xf19<$5h3@~^J?(3j$ z^g$y{-W2V}so;8g2_&>qFMZLYNC^bv{Ysmn2ok$(n9%9!#wHOV*V%siKcM6V=T7~3 zWk$v8{Zg5ATBgxXwh12qO{lzUPoLht6(7}k7EQ0t@hlhBct`1aWd3j^+%|jRpNh(J zFDb50PORLxLE1)P4xb<)s+7$PP)UtkoKjU7a7-_8S|CO5x#B%dWrX zp2Qko$^LuH^7QRn`}$kiEzD~j`r95WQ}B8N8`{q68`@4KB_;FbQ3CjpfR(p5T{(o& zw7sjV>u-k*y&5esH}FiK9Dq~%e{Cc2Tk4w0T?9-rjUt0@Y&o=cCN3L@M-_B*n9J@N z?BBmXmiF>RHv)ECCSDH0fX{!K&*DSoDuXPYC(jw4JjOu4+}=dB?b^AkAIJo`ge1x_ zSlRCJElWd?)uQ?c2+2YpQmRu%vhyUBW`tX}Y4^YQRgnCp`}$*pg>q=g%Am9gDaNjDRsTGizAg&AP0=Pnbh_ufj{uuo0V@|WfydX;jh1xRaHe`W9d<`PXu zy0rN>A;nSwS*6M^?O{+~WQ@U~L&NT=kx(?zV#LRCE-PA)TB{S$uSih_;{`=4PW&=e z%-Z)VsntF58V%vO&%id78x}rNSR_|6t1O5(AXi@vztK|S!7QRraY2B&|38l#JWPT& zeT39#NJkccol0nxj%rCrPiEN*#S;fdm>q*VNJ51I$D@-{XpXClSvxt>%rWRzor(gl z`|m@uTzZdEIk|y`fq7nh!ZBZ2UESe+v(U!dUnCWA=6@Y@A`K(H@Dj*LxL{snGod9v zRh2BGK|vRu43^5d_D4a-!gef}MtvwOb61k%Gf-gu<;AZUnJ}wwL@o4$9XaI~e&rda z8gWfAtex*N7ClMW&=P@ow(s?9kR9%Y-)humk3D4vJyq-JZYWrm%`7YR2wBRn1r1!! zOn_MCUMW~LCo2lPeh98@PyM3U?9THCE}&6ZtaVy5)f~ceL`&>HRXMMN3Mei&)-e2c z-mFqY`R{e^48~77cU4sGo5x@K_S_Q2#({<}s;X+BJ52GS`Y#WP8S*ZB?>`({!9hU| zKU==_RC}lWx4x_(HT2((yT)rnGHFNYEUwei+-S&*ndtJE^Bq}d!Ts(Ga-kO1>ph{g zIDFZUyv zI<7uq#JK4Jm`at#=}(-t-5EJ~RE?t|K$6re*PfHP0x_VsOCmw6zL#XY>ApxUi72HT zb7mDj&`cRV);$ntfJM;hHzVN|c=kM@>!NU}CA+O75RJKDaGJnB1J49Z^8A%uKEO`%!H}jwlBwxY#ub(k0Nvu zv$^2QN5g8=(hugzJyBR^Wd2p2STGa7=VU!&`l!~2HLASHRJ~DWX*;EQFU*A`ZNBCc zFU^u=;9aM8&tFMP-WQXZ4bPowX-rFZlod-7;_kmz2LS#?hPG(hYa8d+Zfe@%OL-mC z$ktJCnCg#~HZ{`X@a(9tJ+nt-d*qGrNUh0;J}YJdK)=sR zbYpQeXs0v1GD z@pRJA8A)y0w@6mvHazv>SvBS520Xh->~7)hKEd8Bjq-D zCnq32S(3C;IkF(6Igqo)YVp!~%MzptZ6{?0GEUyNckL&<_po7_DP8B~NAYZjsNN@ND#X1%)9ukG~q zwfhukN4*OIdM1L<mHLF&ngTZw}V0@MmgB(3P1}DUSYr!^lrMLq*+lp z1c{NcWEt1$r1$GWO!E6$_54aYC8dss$0CzMyMcy%9l6BKpRGT#CkCbuZEUK~VvLP9 zefvaV6koOSIs62^!bY3G-dZkUy|d%DaMyt&=HGR>&f)nu@{KpuT2wt$A i1kCLJFlI=b#kd#tRnbGpfKuq66;`M0ENVXYzWNXSMIht= literal 0 HcmV?d00001 diff --git a/resources/icons/variable_layer_height_disabled_36.png b/resources/icons/variable_layer_height_disabled_36.png deleted file mode 100644 index ad75a278a5e9a733955c3e780dd9b7645ead7ecc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)W?&6k5$rCs^#?@UXNt4aZG}$6J>gej^14tetqg&#f*gH)p zB@Jmqq3S{J;NTpDAAjx?4&ZD^?IH+*Yf%)as%lUGAq3Ou6t-_S?0Ht*Fz0(>dylrX$5+DKkcOdz< zFJxJUrfE>ubvGGfpp=3!)=f$&D5Y>+w_lL1>wFbT)3mGds7PC?U3;F_FUT+qz6vEt z@}VMf(5%0cJ@= zK~z}7?bxwu!cZ87;rB>Fx=X-XFQ9bo?KlW-LLF`J0wm28(&8pMxOxL#fJ+4jq4WZ* zi*@hG5l;sj39W4r8WU)K_{b0f4(4Z$eDdq*yF+`|KbF2oc@L%1ZAYX`nb;a` P00000NkvXXu0mjf6P3wT diff --git a/resources/icons/variable_layer_height_hover_pressed_36.png b/resources/icons/variable_layer_height_hover_pressed_36.png deleted file mode 100644 index e04634f08264493229efe06a68686ee9dd516c6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 471 zcmV;|0Vw{7P)8>;{S0c1%; zK~z}7?U+An!ax{@pA#kBB@n?6pk4cQ90WI^jyCuK#5##BZlZ&$UqEn;qg&|*uvUof zO-{THHX*h44~3Wv_ri}GUJf3fJMQ2We3Yu9NU+2h0gw$~9&a94tyXxRcTo_-Feny_ zC~lP)BZR7=y4PK<41pGqSmC8%x1HAfm*Frx(wO2 z{i~g59LI?lsMG1B%TT>u$Md|i_a#X56<;ow@d6D71O3};APw|qpj+MYa`RvD5WThf-T5(UA`J}9Or5~)0Ab&7UcUrUkzST2`v97h6=>k~MO6K^X&+&lmP002ovPDHLkV1mFjwQT?Z diff --git a/resources/icons/variable_layer_height_pressed_36.png b/resources/icons/variable_layer_height_pressed_36.png deleted file mode 100644 index 6bc8018f945e3026346e0439aad8b43a97ef012d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 446 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB*pj^6T^Rm@;DWu&Cj&(|3p^r= z85p>QL70(Y)*K0-AbW|YuPgf_b^#tMQ4{`%00ss|Z%-G;kch)?Z*0tJc93EFa9&{J zZW)Oa%qH)r=*~_!7E;qta8^XuWBa$IS4HL7^o>&aU5W?t)Es`4}z)=dLvMXf^?&7g@jl*BW*y zg@m?l(wWVnIIG~?Z0+YVr=JGtW!T8Az8WCWc0Vf9ylC#*GHr2_nLbytGPXwDy*oX+ z^Kpb;hSl6vS1;7q{ogpTEZd1g#ZyUKgrzZx_vgO<+177UBGtrQG(%S_UN1c;v3sZf z^ey`z{r(%Lm$CWgi~W1qGB3<`T$WkJw^O2u!GYu8?T _parse_colors(const std::vector& colors); }; diff --git a/xs/src/slic3r/GUI/GLTexture.cpp b/xs/src/slic3r/GUI/GLTexture.cpp index 18c9f5dea0..03890d7807 100644 --- a/xs/src/slic3r/GUI/GLTexture.cpp +++ b/xs/src/slic3r/GUI/GLTexture.cpp @@ -12,6 +12,10 @@ namespace Slic3r { namespace GUI { +//################################################################################################################################### +GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; +//################################################################################################################################### + GLTexture::GLTexture() : m_id(0) , m_width(0) @@ -128,6 +132,34 @@ const std::string& GLTexture::get_source() const } void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top) +{ +//################################################################################################################################### + render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs); + +// ::glEnable(GL_BLEND); +// ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +// +// ::glEnable(GL_TEXTURE_2D); +// ::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +// +// ::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id); +// +// ::glBegin(GL_QUADS); +// ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(left, bottom); +// ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(right, bottom); +// ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(right, top); +// ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(left, top); +// ::glEnd(); +// +// ::glBindTexture(GL_TEXTURE_2D, 0); +// +// ::glDisable(GL_TEXTURE_2D); +// ::glDisable(GL_BLEND); +//################################################################################################################################### +} + +//################################################################################################################################### +void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs) { ::glEnable(GL_BLEND); ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -138,10 +170,10 @@ void GLTexture::render_texture(unsigned int tex_id, float left, float right, flo ::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id); ::glBegin(GL_QUADS); - ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(left, bottom); - ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(right, bottom); - ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(right, top); - ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(left, top); + ::glTexCoord2f(uvs.left_bottom.u, uvs.left_bottom.v); ::glVertex2f(left, bottom); + ::glTexCoord2f(uvs.right_bottom.u, uvs.right_bottom.v); ::glVertex2f(right, bottom); + ::glTexCoord2f(uvs.right_top.u, uvs.right_top.v); ::glVertex2f(right, top); + ::glTexCoord2f(uvs.left_top.u, uvs.left_top.v); ::glVertex2f(left, top); ::glEnd(); ::glBindTexture(GL_TEXTURE_2D, 0); @@ -149,6 +181,7 @@ void GLTexture::render_texture(unsigned int tex_id, float left, float right, flo ::glDisable(GL_TEXTURE_2D); ::glDisable(GL_BLEND); } +//################################################################################################################################### unsigned int GLTexture::_generate_mipmaps(wxImage& image) { diff --git a/xs/src/slic3r/GUI/GLTexture.hpp b/xs/src/slic3r/GUI/GLTexture.hpp index 3113fcab20..a7d610200c 100644 --- a/xs/src/slic3r/GUI/GLTexture.hpp +++ b/xs/src/slic3r/GUI/GLTexture.hpp @@ -10,6 +10,25 @@ namespace GUI { class GLTexture { +//################################################################################################################################### + public: + struct UV + { + float u; + float v; + }; + + struct Quad_UVs + { + UV left_bottom; + UV right_bottom; + UV right_top; + UV left_top; + }; + + static Quad_UVs FullTextureUVs; +//################################################################################################################################### + protected: unsigned int m_id; int m_width; @@ -30,6 +49,9 @@ namespace GUI { const std::string& get_source() const; static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); +//################################################################################################################################### + static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); +//################################################################################################################################### protected: unsigned int _generate_mipmaps(wxImage& image); diff --git a/xs/src/slic3r/GUI/GLToolbar.cpp b/xs/src/slic3r/GUI/GLToolbar.cpp index c425164a2f..fd993f5a2e 100644 --- a/xs/src/slic3r/GUI/GLToolbar.cpp +++ b/xs/src/slic3r/GUI/GLToolbar.cpp @@ -11,34 +11,20 @@ namespace Slic3r { namespace GUI { -GLToolbarItem::GLToolbarItem(EType type, const std::string& name, const std::string& tooltip, bool is_toggable, PerlCallback* action_callback) - : m_type(type) - , m_state(Disabled) - , m_name(name) - , m_tooltip(tooltip) - , m_is_toggable(is_toggable) - , m_action_callback(action_callback) +GLToolbarItem::Data::Data() + : name("") + , tooltip("") + , sprite_id(-1) + , is_toggable(false) + , action_callback(nullptr) { } -bool GLToolbarItem::load_textures(const std::string* filenames) +GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Data& data) + : m_type(type) + , m_state(Disabled) + , m_data(data) { - if (filenames == nullptr) - return false; - - std::string path = resources_dir() + "/icons/"; - - for (unsigned int i = (unsigned int)Normal; i < (unsigned int)Num_States; ++i) - { - if (!filenames[i].empty()) - { - std::string filename = path + filenames[i]; - if (!m_icon_textures[i].load_from_file(filename, false)) - return false; - } - } - - return true; } GLToolbarItem::EState GLToolbarItem::get_state() const @@ -53,28 +39,18 @@ void GLToolbarItem::set_state(GLToolbarItem::EState state) const std::string& GLToolbarItem::get_name() const { - return m_name; + return m_data.name; } const std::string& GLToolbarItem::get_tooltip() const { - return m_tooltip; -} - -unsigned int GLToolbarItem::get_icon_texture_id() const -{ - return m_icon_textures[m_state].get_id(); -} - -int GLToolbarItem::get_icon_textures_size() const -{ - return m_icon_textures[Normal].get_width(); + return m_data.tooltip; } void GLToolbarItem::do_action() { - if (m_action_callback != nullptr) - m_action_callback->call(); + if (m_data.action_callback != nullptr) + m_data.action_callback->call(); } bool GLToolbarItem::is_enabled() const @@ -94,7 +70,7 @@ bool GLToolbarItem::is_pressed() const bool GLToolbarItem::is_toggable() const { - return m_is_toggable; + return m_data.is_toggable; } bool GLToolbarItem::is_separator() const @@ -102,16 +78,97 @@ bool GLToolbarItem::is_separator() const return m_type == Separator; } +void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const +{ + GLTexture::render_sub_texture(tex_id, left, right, bottom, top, _get_uvs(texture_size, border_size, icon_size, gap_size)); +} + +GLTexture::Quad_UVs GLToolbarItem::_get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const +{ + GLTexture::Quad_UVs uvs; + + float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f; + + float scaled_icon_size = (float)icon_size * inv_texture_size; + float scaled_border_size = (float)border_size * inv_texture_size; + float scaled_gap_size = (float)gap_size * inv_texture_size; + float stride = scaled_icon_size + scaled_gap_size; + + float left = scaled_border_size + (float)m_state * stride; + float right = left + scaled_icon_size; + float top = scaled_border_size + (float)m_data.sprite_id * stride; + float bottom = top + scaled_icon_size; + + uvs.left_top = { left, top }; + uvs.left_bottom = { left, bottom }; + uvs.right_bottom = { right, bottom }; + uvs.right_top = { right, top }; + + return uvs; +} + +GLToolbar::ItemsIconsTexture::ItemsIconsTexture() + : items_icon_size(0) + , items_icon_border_size(0) + , items_icon_gap_size(0) +{ +} + +GLToolbar::Layout::Layout() + : type(Horizontal) + , top(0.0f) + , left(0.0f) + , separator_size(0.0f) + , gap_size(0.0f) +{ +} + GLToolbar::GLToolbar(GLCanvas3D& parent) : m_parent(parent) , m_enabled(false) - , m_textures_scale(1.0f) - , m_offset_y(5.0f) - , m_gap_x(2.0f) - , m_separator_x(5.0f) { } +bool GLToolbar::init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size) +{ + std::string path = resources_dir() + "/icons/"; + bool res = !icons_texture_filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture_filename, false); + if (res) + { + m_icons_texture.items_icon_size = items_icon_size; + m_icons_texture.items_icon_border_size = items_icon_border_size; + m_icons_texture.items_icon_gap_size = items_icon_gap_size; + } + + return res; +} + +GLToolbar::Layout::Type GLToolbar::get_layout_type() const +{ + return m_layout.type; +} + +void GLToolbar::set_layout_type(GLToolbar::Layout::Type type) +{ + m_layout.type = type; +} + +void GLToolbar::set_position(float top, float left) +{ + m_layout.top = top; + m_layout.left = left; +} + +void GLToolbar::set_separator_size(float size) +{ + m_layout.separator_size = size; +} + +void GLToolbar::set_gap_size(float size) +{ + m_layout.gap_size = size; +} + bool GLToolbar::is_enabled() const { return m_enabled; @@ -122,40 +179,9 @@ void GLToolbar::set_enabled(bool enable) m_enabled = true; } -void GLToolbar::set_textures_scale(float scale) +bool GLToolbar::add_item(const GLToolbarItem::Data& data) { - m_textures_scale = scale; -} - -void GLToolbar::set_offset_y(float offset) -{ - m_offset_y = offset; -} - -void GLToolbar::set_gap_x(float gap) -{ - m_gap_x = gap; -} - -void GLToolbar::set_separator_x(float separator) -{ - m_separator_x = separator; -} - -bool GLToolbar::add_item(const GLToolbar::ItemCreationData& data) -{ - GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data.name, data.tooltip, data.is_toggable, data.action_callback); - if ((item == nullptr) || !item->load_textures(data.textures)) - return false; - - m_items.push_back(item); - - return true; -} - -bool GLToolbar::add_separator() -{ - GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Separator, "", "", false, nullptr); + GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data); if (item == nullptr) return false; @@ -163,6 +189,49 @@ bool GLToolbar::add_separator() return true; } +bool GLToolbar::add_separator() +{ + GLToolbarItem::Data data; + GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Separator, data); + if (item == nullptr) + return false; + + m_items.push_back(item); + return true; +} + +float GLToolbar::get_width() const +{ + switch (m_layout.type) + { + default: + case Layout::Horizontal: + { + return _get_width_horizontal(); + } + case Layout::Vertical: + { + return _get_width_vertical(); + } + } +} + +float GLToolbar::get_height() const +{ + switch (m_layout.type) + { + default: + case Layout::Horizontal: + { + return _get_height_horizontal(); + } + case Layout::Vertical: + { + return _get_height_vertical(); + } + } +} + void GLToolbar::enable_item(const std::string& name) { for (GLToolbarItem* item : m_items) @@ -203,25 +272,252 @@ void GLToolbar::update_hover_state(const Pointf& mouse_pos) if (!m_enabled) return; - float cnv_w = (float)m_parent.get_canvas_size().get_width(); - float width = _get_total_width(); - float left = 0.5f * (cnv_w - width); - float top = m_offset_y; + switch (m_layout.type) + { + default: + case Layout::Horizontal: + { + _update_hover_state_horizontal(mouse_pos); + break; + } + case Layout::Vertical: + { + _update_hover_state_vertical(mouse_pos); + break; + } + } +} + +int GLToolbar::contains_mouse(const Pointf& mouse_pos) const +{ + if (!m_enabled) + return -1; + + switch (m_layout.type) + { + default: + case Layout::Horizontal: + { + return _contains_mouse_horizontal(mouse_pos); + } + case Layout::Vertical: + { + return _contains_mouse_vertical(mouse_pos); + } + } +} + +void GLToolbar::do_action(unsigned int item_id) +{ + if (item_id < (unsigned int)m_items.size()) + { + GLToolbarItem* item = m_items[item_id]; + if ((item != nullptr) && !item->is_separator() && item->is_hovered()) + { + if (item->is_toggable()) + { + GLToolbarItem::EState state = item->get_state(); + if (state == GLToolbarItem::Hover) + item->set_state(GLToolbarItem::HoverPressed); + else if (state == GLToolbarItem::HoverPressed) + item->set_state(GLToolbarItem::Hover); + + m_parent.render(); + item->do_action(); + } + else + { + item->set_state(GLToolbarItem::HoverPressed); + m_parent.render(); + item->do_action(); + if (item->get_state() != GLToolbarItem::Disabled) + { + // the item may get disabled during the action, if not, set it back to hover state + item->set_state(GLToolbarItem::Hover); + m_parent.render(); + } + } + } + } +} + +void GLToolbar::render() const +//void GLToolbar::render(const Pointf& mouse_pos) const +{ + if (!m_enabled || m_items.empty()) + return; + + ::glDisable(GL_DEPTH_TEST); + + ::glPushMatrix(); + ::glLoadIdentity(); + + switch (m_layout.type) + { + default: + case Layout::Horizontal: + { + _render_horizontal(); + break; + } + case Layout::Vertical: + { + _render_vertical(); + break; + } + } + + ::glPopMatrix(); +} + +float GLToolbar::_get_width_horizontal() const +{ + return _get_main_size(); +} + +float GLToolbar::_get_width_vertical() const +{ + return m_icons_texture.items_icon_size; +} + +float GLToolbar::_get_height_horizontal() const +{ + return m_icons_texture.items_icon_size; +} + +float GLToolbar::_get_height_vertical() const +{ + return _get_main_size(); +} + +float GLToolbar::_get_main_size() const +{ + float size = 0.0f; + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) + { + if (m_items[i]->is_separator()) + size += m_layout.separator_size; + else + size += (float)m_icons_texture.items_icon_size; + } + + if (m_items.size() > 1) + size += ((float)m_items.size() - 1.0f) * m_layout.gap_size; + + return size; +} + +void GLToolbar::_update_hover_state_horizontal(const Pointf& mouse_pos) +{ + float zoom = m_parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + Size cnv_size = m_parent.get_canvas_size(); + Pointf scaled_mouse_pos((mouse_pos.x - 0.5f * (float)cnv_size.get_width()) * inv_zoom, (0.5f * (float)cnv_size.get_height() - mouse_pos.y) * inv_zoom); + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + float scaled_separator_size = m_layout.separator_size * inv_zoom; + float scaled_gap_size = m_layout.gap_size * inv_zoom; + + float separator_stride = scaled_separator_size + scaled_gap_size; + float icon_stride = scaled_icons_size + scaled_gap_size; + + float left = m_layout.left; + float top = m_layout.top; + + std::string tooltip = ""; + + for (GLToolbarItem* item : m_items) + { + if (item->is_separator()) + left += separator_stride; + else + { + float right = left + scaled_icons_size; + float bottom = top - scaled_icons_size; + + GLToolbarItem::EState state = item->get_state(); + bool inside = (left <= scaled_mouse_pos.x) && (scaled_mouse_pos.x <= right) && (bottom <= scaled_mouse_pos.y) && (scaled_mouse_pos.y <= top); + + switch (state) + { + case GLToolbarItem::Normal: + { + if (inside) + item->set_state(GLToolbarItem::Hover); + + break; + } + case GLToolbarItem::Hover: + { + if (inside) + tooltip = item->get_tooltip(); + else + item->set_state(GLToolbarItem::Normal); + + break; + } + case GLToolbarItem::Pressed: + { + if (inside) + item->set_state(GLToolbarItem::HoverPressed); + + break; + } + case GLToolbarItem::HoverPressed: + { + if (inside) + tooltip = item->get_tooltip(); + else + item->set_state(GLToolbarItem::Pressed); + + break; + } + default: + case GLToolbarItem::Disabled: + { + break; + } + } + + left += icon_stride; + } + } + + m_parent.set_tooltip(tooltip); +} + +void GLToolbar::_update_hover_state_vertical(const Pointf& mouse_pos) +{ + float zoom = m_parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + Size cnv_size = m_parent.get_canvas_size(); + Pointf scaled_mouse_pos((mouse_pos.x - 0.5f * (float)cnv_size.get_width()) * inv_zoom, (0.5f * (float)cnv_size.get_height() - mouse_pos.y) * inv_zoom); + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + float scaled_separator_size = m_layout.separator_size * inv_zoom; + float scaled_gap_size = m_layout.gap_size * inv_zoom; + + float separator_stride = scaled_separator_size + scaled_gap_size; + float icon_stride = scaled_icons_size + scaled_gap_size; + + float left = m_layout.left; + float top = m_layout.top; std::string tooltip = ""; for (GLToolbarItem* item : m_items) { if (item->is_separator()) - left += (m_separator_x + m_gap_x); + top -= separator_stride; else { - float tex_size = (float)item->get_icon_textures_size() * m_textures_scale; - float right = left + tex_size; - float bottom = top + tex_size; + float right = left + scaled_icons_size; + float bottom = top - scaled_icons_size; GLToolbarItem::EState state = item->get_state(); - bool inside = (left <= mouse_pos.x) && (mouse_pos.x <= right) && (top <= mouse_pos.y) && (mouse_pos.y <= bottom); + bool inside = (left <= scaled_mouse_pos.x) && (scaled_mouse_pos.x <= right) && (bottom <= scaled_mouse_pos.y) && (scaled_mouse_pos.y <= top); switch (state) { @@ -263,22 +559,72 @@ void GLToolbar::update_hover_state(const Pointf& mouse_pos) break; } } - left += (tex_size + m_gap_x); + + top -= icon_stride; } } m_parent.set_tooltip(tooltip); } -int GLToolbar::contains_mouse(const Pointf& mouse_pos) const +int GLToolbar::_contains_mouse_horizontal(const Pointf& mouse_pos) const { - if (!m_enabled) - return -1; + float zoom = m_parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float cnv_w = (float)m_parent.get_canvas_size().get_width(); - float width = _get_total_width(); - float left = 0.5f * (cnv_w - width); - float top = m_offset_y; + Size cnv_size = m_parent.get_canvas_size(); + Pointf scaled_mouse_pos((mouse_pos.x - 0.5f * (float)cnv_size.get_width()) * inv_zoom, (0.5f * (float)cnv_size.get_height() - mouse_pos.y) * inv_zoom); + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + float scaled_separator_size = m_layout.separator_size * inv_zoom; + float scaled_gap_size = m_layout.gap_size * inv_zoom; + + float separator_stride = scaled_separator_size + scaled_gap_size; + float icon_stride = scaled_icons_size + scaled_gap_size; + + float left = m_layout.left; + float top = m_layout.top; + + int id = -1; + + for (GLToolbarItem* item : m_items) + { + ++id; + + if (item->is_separator()) + left += separator_stride; + else + { + float right = left + scaled_icons_size; + float bottom = top - scaled_icons_size; + + if ((left <= scaled_mouse_pos.x) && (scaled_mouse_pos.x <= right) && (bottom <= scaled_mouse_pos.y) && (scaled_mouse_pos.y <= top)) + return id; + + left += icon_stride; + } + } + + return -1; +} + +int GLToolbar::_contains_mouse_vertical(const Pointf& mouse_pos) const +{ + float zoom = m_parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + Size cnv_size = m_parent.get_canvas_size(); + Pointf scaled_mouse_pos((mouse_pos.x - 0.5f * (float)cnv_size.get_width()) * inv_zoom, (0.5f * (float)cnv_size.get_height() - mouse_pos.y) * inv_zoom); + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + float scaled_separator_size = m_layout.separator_size * inv_zoom; + float scaled_gap_size = m_layout.gap_size * inv_zoom; + + float separator_stride = scaled_separator_size + scaled_gap_size; + float icon_stride = scaled_icons_size + scaled_gap_size; + + float left = m_layout.left; + float top = m_layout.top; int id = -1; @@ -287,110 +633,88 @@ int GLToolbar::contains_mouse(const Pointf& mouse_pos) const ++id; if (item->is_separator()) - left += (m_separator_x + m_gap_x); + top -= separator_stride; else { - float tex_size = (float)item->get_icon_textures_size() * m_textures_scale; - float right = left + tex_size; - float bottom = top + tex_size; + float right = left + scaled_icons_size; + float bottom = top - scaled_icons_size; - if ((left <= mouse_pos.x) && (mouse_pos.x <= right) && (top <= mouse_pos.y) && (mouse_pos.y <= bottom)) + if ((left <= scaled_mouse_pos.x) && (scaled_mouse_pos.x <= right) && (bottom <= scaled_mouse_pos.y) && (scaled_mouse_pos.y <= top)) return id; - left += (tex_size + m_gap_x); + top -= icon_stride; } } return -1; } -void GLToolbar::do_action(unsigned int item_id) +void GLToolbar::_render_horizontal() const { - if (item_id < (unsigned int)m_items.size()) - { - GLToolbarItem* item = m_items[item_id]; - if ((item != nullptr) && !item->is_separator() && item->is_hovered()) - { - if (item->is_toggable()) - { - GLToolbarItem::EState state = item->get_state(); - if (state == GLToolbarItem::Hover) - item->set_state(GLToolbarItem::HoverPressed); - else if (state == GLToolbarItem::HoverPressed) - item->set_state(GLToolbarItem::Hover); + unsigned int tex_id = m_icons_texture.texture.get_id(); + int tex_size = m_icons_texture.texture.get_width(); - m_parent.render(); - item->do_action(); - } - else - { - item->set_state(GLToolbarItem::HoverPressed); - m_parent.render(); - item->do_action(); - if (item->get_state() != GLToolbarItem::Disabled) - { - // the item may get disabled during the action, if not, set it back to hover state - item->set_state(GLToolbarItem::Hover); - m_parent.render(); - } - } - } - } -} - -void GLToolbar::render(const Pointf& mouse_pos) const -{ - if (!m_enabled || m_items.empty()) + if ((tex_id == 0) || (tex_size <= 0)) return; - ::glDisable(GL_DEPTH_TEST); - - ::glPushMatrix(); - ::glLoadIdentity(); - - float cnv_w = (float)m_parent.get_canvas_size().get_width(); - float cnv_h = (float)m_parent.get_canvas_size().get_height(); float zoom = m_parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float width = _get_total_width(); - float top_x = -0.5f * width * inv_zoom; - float top_y = (0.5f * cnv_h - m_offset_y * m_textures_scale) * inv_zoom; - float scaled_gap_x = m_gap_x * inv_zoom; - float scaled_separator_x = m_separator_x * inv_zoom; + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + float scaled_separator_size = m_layout.separator_size * inv_zoom; + float scaled_gap_size = m_layout.gap_size * inv_zoom; + + float separator_stride = scaled_separator_size + scaled_gap_size; + float icon_stride = scaled_icons_size + scaled_gap_size; + + float left = m_layout.left; + float top = m_layout.top; // renders icons for (const GLToolbarItem* item : m_items) { if (item->is_separator()) - top_x += (scaled_separator_x + scaled_gap_x); + left += separator_stride; else { - float tex_size = (float)item->get_icon_textures_size() * m_textures_scale * inv_zoom; - GLTexture::render_texture(item->get_icon_texture_id(), top_x, top_x + tex_size, top_y - tex_size, top_y); - top_x += (tex_size + scaled_gap_x); + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size); + left += icon_stride; } } - - ::glPopMatrix(); } -float GLToolbar::_get_total_width() const +void GLToolbar::_render_vertical() const { - float width = 0.0f; + unsigned int tex_id = m_icons_texture.texture.get_id(); + int tex_size = m_icons_texture.texture.get_width(); - for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) + if ((tex_id == 0) || (tex_size <= 0)) + return; + + float zoom = m_parent.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; + float scaled_separator_size = m_layout.separator_size * inv_zoom; + float scaled_gap_size = m_layout.gap_size * inv_zoom; + + float separator_stride = scaled_separator_size + scaled_gap_size; + float icon_stride = scaled_icons_size + scaled_gap_size; + + float left = m_layout.left; + float top = m_layout.top; + + // renders icons + for (const GLToolbarItem* item : m_items) { - if (m_items[i]->is_separator()) - width += m_separator_x; + if (item->is_separator()) + top -= separator_stride; else - width += m_items[i]->get_icon_textures_size(); - - if (i < (unsigned int)m_items.size() - 1) - width += m_gap_x; + { + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size); + top -= icon_stride; + } } - - return width; } } // namespace GUI diff --git a/xs/src/slic3r/GUI/GLToolbar.hpp b/xs/src/slic3r/GUI/GLToolbar.hpp index d157f2a3b4..b6eb6b19c4 100644 --- a/xs/src/slic3r/GUI/GLToolbar.hpp +++ b/xs/src/slic3r/GUI/GLToolbar.hpp @@ -28,41 +28,38 @@ public: enum EState : unsigned char { Normal, - Hover, Pressed, - HoverPressed, Disabled, + Hover, + HoverPressed, Num_States }; -private: - // icon textures are assumed to be square and all with the same size in pixels, no internal check is done - GLTexture m_icon_textures[Num_States]; + struct Data + { + std::string name; + std::string tooltip; + unsigned int sprite_id; + bool is_toggable; + PerlCallback* action_callback; + Data(); + }; + +private: EType m_type; EState m_state; - - std::string m_name; - std::string m_tooltip; - - bool m_is_toggable; - PerlCallback* m_action_callback; + Data m_data; public: - GLToolbarItem(EType type, const std::string& name, const std::string& tooltip, bool is_toggable, PerlCallback* action_callback); - - bool load_textures(const std::string* filenames); + GLToolbarItem(EType type, const Data& data); EState get_state() const; void set_state(EState state); const std::string& get_name() const; - const std::string& get_tooltip() const; - unsigned int get_icon_texture_id() const; - int get_icon_textures_size() const; - void do_action(); bool is_enabled() const; @@ -71,18 +68,48 @@ public: bool is_toggable() const; bool is_separator() const; + + void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; + +private: + GLTexture::Quad_UVs _get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; }; class GLToolbar { public: - struct ItemCreationData + // items icon textures are assumed to be square and all with the same size in pixels, no internal check is done + // icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState + // from left to right + struct ItemsIconsTexture { - std::string name; - std::string tooltip; - bool is_toggable; - PerlCallback* action_callback; - std::string textures[GLToolbarItem::Num_States]; + GLTexture texture; + // size of the square icons, in pixels + unsigned int items_icon_size; + // distance from the border, in pixels + unsigned int items_icon_border_size; + // distance between two adjacent icons (to avoid filtering artifacts), in pixels + unsigned int items_icon_gap_size; + + ItemsIconsTexture(); + }; + + struct Layout + { + enum Type : unsigned char + { + Horizontal, + Vertical, + Num_Types + }; + + Type type; + float top; + float left; + float separator_size; + float gap_size; + + Layout(); }; private: @@ -90,27 +117,32 @@ private: GLCanvas3D& m_parent; bool m_enabled; - ItemsList m_items; + ItemsIconsTexture m_icons_texture; + Layout m_layout; - float m_textures_scale; - float m_offset_y; - float m_gap_x; - float m_separator_x; + ItemsList m_items; public: explicit GLToolbar(GLCanvas3D& parent); + bool init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size); + + Layout::Type get_layout_type() const; + void set_layout_type(Layout::Type type); + + void set_position(float top, float left); + void set_separator_size(float size); + void set_gap_size(float size); + bool is_enabled() const; void set_enabled(bool enable); - void set_textures_scale(float scale); - void set_offset_y(float offset); - void set_gap_x(float gap); - void set_separator_x(float separator); - - bool add_item(const ItemCreationData& data); + bool add_item(const GLToolbarItem::Data& data); bool add_separator(); + float get_width() const; + float get_height() const; + void enable_item(const std::string& name); void disable_item(const std::string& name); @@ -123,10 +155,20 @@ public: void do_action(unsigned int item_id); - void render(const Pointf& mouse_pos) const; + void render() const; private: - float _get_total_width() const; + float _get_width_horizontal() const; + float _get_width_vertical() const; + float _get_height_horizontal() const; + float _get_height_vertical() const; + float _get_main_size() const; + void _update_hover_state_horizontal(const Pointf& mouse_pos); + void _update_hover_state_vertical(const Pointf& mouse_pos); + int _contains_mouse_horizontal(const Pointf& mouse_pos) const; + int _contains_mouse_vertical(const Pointf& mouse_pos) const; + void _render_horizontal() const; + void _render_vertical() const; }; } // namespace GUI From e234973ab449bfcc6414c34a597122bd1cf0a168 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 31 Jul 2018 14:20:16 +0200 Subject: [PATCH 090/185] Warning texture use square power of two image and bigger font size --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 132 ++++++++++++++++++++++++------- xs/src/slic3r/GUI/GLCanvas3D.hpp | 16 ++++ 2 files changed, 120 insertions(+), 28 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 736f6431d8..f60fead3dc 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1467,6 +1467,25 @@ float GLCanvas3D::Gizmos::_get_total_overlay_height() const const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 9, 91, 134 }; const unsigned char GLCanvas3D::WarningTexture::Opacity = 255; +//############################################################################################################################################ +GLCanvas3D::WarningTexture::WarningTexture() + : GUI::GLTexture() + , m_original_width(0) + , m_original_height(0) +{ +} + +int GLCanvas3D::WarningTexture::get_original_width() const +{ + return m_original_width; +} + +int GLCanvas3D::WarningTexture::get_original_height() const +{ + return m_original_height; +} +//############################################################################################################################################ + bool GLCanvas3D::WarningTexture::generate(const std::string& msg) { reset(); @@ -1476,13 +1495,30 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg) wxMemoryDC memDC; // select default font - memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +//############################################################################################################################################ + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + font.MakeLarger(); + memDC.SetFont(font); + +// memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +//############################################################################################################################################ // calculates texture size wxCoord w, h; memDC.GetTextExtent(msg, &w, &h); - m_width = (int)w; - m_height = (int)h; + +//############################################################################################################################################ + int pow_of_two_size = next_highest_power_of_2((int)std::max(w, h)); +//############################################################################################################################################ + +//############################################################################################################################################ + m_original_width = (int)w; + m_original_height = (int)h; + m_width = pow_of_two_size; + m_height = pow_of_two_size; +// m_width = (int)w; +// m_height = (int)h; +//############################################################################################################################################ // generates bitmap wxBitmap bitmap(m_width, m_height); @@ -1534,6 +1570,42 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg) return true; } +//############################################################################################################################################ +void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const +{ + if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0)) + { + ::glDisable(GL_DEPTH_TEST); + ::glPushMatrix(); + ::glLoadIdentity(); + + const Size& cnv_size = canvas.get_canvas_size(); + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float left = (-0.5f * (float)m_original_width) * inv_zoom; + float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom; + float right = left + (float)m_original_width * inv_zoom; + float bottom = top - (float)m_original_height * inv_zoom; + + float uv_left = 0.0f; + float uv_top = 0.0f; + float uv_right = (float)m_original_width / (float)m_width; + float uv_bottom = (float)m_original_height / (float)m_height; + + GLTexture::Quad_UVs uvs; + uvs.left_top = { uv_left, uv_top }; + uvs.left_bottom = { uv_left, uv_bottom }; + uvs.right_bottom = { uv_right, uv_bottom }; + uvs.right_top = { uv_right, uv_top }; + + GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs); + + ::glPopMatrix(); + ::glEnable(GL_DEPTH_TEST); + } +} +//############################################################################################################################################ + const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 }; const unsigned char GLCanvas3D::LegendTexture::Background_Color[3] = { 9, 91, 134 }; const unsigned char GLCanvas3D::LegendTexture::Opacity = 255; @@ -3879,32 +3951,36 @@ void GLCanvas3D::_render_warning_texture() const if (!m_warning_texture_enabled) return; - // If the warning texture has not been loaded into the GPU, do it now. - unsigned int tex_id = m_warning_texture.get_id(); - if (tex_id > 0) - { - int w = m_warning_texture.get_width(); - int h = m_warning_texture.get_height(); - if ((w > 0) && (h > 0)) - { - ::glDisable(GL_DEPTH_TEST); - ::glPushMatrix(); - ::glLoadIdentity(); +//############################################################################################################################################ + m_warning_texture.render(*this); - const Size& cnv_size = get_canvas_size(); - float zoom = get_camera_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float l = (-0.5f * (float)w) * inv_zoom; - float t = (-0.5f * (float)cnv_size.get_height() + (float)h) * inv_zoom; - float r = l + (float)w * inv_zoom; - float b = t - (float)h * inv_zoom; - - GLTexture::render_texture(tex_id, l, r, b, t); - - ::glPopMatrix(); - ::glEnable(GL_DEPTH_TEST); - } - } +// // If the warning texture has not been loaded into the GPU, do it now. +// unsigned int tex_id = m_warning_texture.get_id(); +// if (tex_id > 0) +// { +// int w = m_warning_texture.get_width(); +// int h = m_warning_texture.get_height(); +// if ((w > 0) && (h > 0)) +// { +// ::glDisable(GL_DEPTH_TEST); +// ::glPushMatrix(); +// ::glLoadIdentity(); +// +// const Size& cnv_size = get_canvas_size(); +// float zoom = get_camera_zoom(); +// float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +// float l = (-0.5f * (float)w) * inv_zoom; +// float t = (-0.5f * (float)cnv_size.get_height() + (float)h) * inv_zoom; +// float r = l + (float)w * inv_zoom; +// float b = t - (float)h * inv_zoom; +// +// GLTexture::render_texture(tex_id, l, r, b, t); +// +// ::glPopMatrix(); +// ::glEnable(GL_DEPTH_TEST); +// } +// } +//############################################################################################################################################ } void GLCanvas3D::_render_legend_texture() const diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index b026acd13e..51aaca1071 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -402,8 +402,24 @@ public: static const unsigned char Background_Color[3]; static const unsigned char Opacity; +//############################################################################################################################################ + int m_original_width; + int m_original_height; +//############################################################################################################################################ + public: +//############################################################################################################################################ + WarningTexture(); + + int get_original_width() const; + int get_original_height() const; +//############################################################################################################################################ + bool generate(const std::string& msg); + +//############################################################################################################################################ + void render(const GLCanvas3D& canvas) const; +//############################################################################################################################################ }; class LegendTexture : public GUI::GLTexture From a03d5178ebea0140bc11a353054906bf353e76de Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 31 Jul 2018 14:32:59 +0200 Subject: [PATCH 091/185] Legend texture use square power of two image --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 131 +++++++++++++++++++++++-------- xs/src/slic3r/GUI/GLCanvas3D.hpp | 16 ++++ 2 files changed, 116 insertions(+), 31 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index f60fead3dc..f82dc262fa 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1509,9 +1509,7 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg) //############################################################################################################################################ int pow_of_two_size = next_highest_power_of_2((int)std::max(w, h)); -//############################################################################################################################################ -//############################################################################################################################################ m_original_width = (int)w; m_original_height = (int)h; m_width = pow_of_two_size; @@ -1573,7 +1571,7 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg) //############################################################################################################################################ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const { - if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0)) + if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { ::glDisable(GL_DEPTH_TEST); ::glPushMatrix(); @@ -1610,6 +1608,25 @@ const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 6 const unsigned char GLCanvas3D::LegendTexture::Background_Color[3] = { 9, 91, 134 }; const unsigned char GLCanvas3D::LegendTexture::Opacity = 255; +//############################################################################################################################################ +GLCanvas3D::LegendTexture::LegendTexture() + : GUI::GLTexture() + , m_original_width(0) + , m_original_height(0) +{ +} + +int GLCanvas3D::LegendTexture::get_original_width() const +{ + return m_original_width; +} + +int GLCanvas3D::LegendTexture::get_original_height() const +{ + return m_original_height; +} +//############################################################################################################################################ + bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors) { reset(); @@ -1642,10 +1659,22 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c max_text_height = std::max(max_text_height, (int)h); } - m_width = std::max(2 * Px_Border + title_width, 2 * (Px_Border + Px_Square_Contour) + Px_Square + Px_Text_Offset + max_text_width); - m_height = 2 * (Px_Border + Px_Square_Contour) + title_height + Px_Title_Offset + items_count * Px_Square; +//############################################################################################################################################ + m_original_width = std::max(2 * Px_Border + title_width, 2 * (Px_Border + Px_Square_Contour) + Px_Square + Px_Text_Offset + max_text_width); + m_original_height = 2 * (Px_Border + Px_Square_Contour) + title_height + Px_Title_Offset + items_count * Px_Square; if (items_count > 1) - m_height += (items_count - 1) * Px_Square_Contour; + m_original_height += (items_count - 1) * Px_Square_Contour; + + int pow_of_two_size = next_highest_power_of_2(std::max(m_original_width, m_original_height)); + + m_width = pow_of_two_size; + m_height = pow_of_two_size; + +// m_width = std::max(2 * Px_Border + title_width, 2 * (Px_Border + Px_Square_Contour) + Px_Square + Px_Text_Offset + max_text_width); +// m_height = 2 * (Px_Border + Px_Square_Contour) + title_height + Px_Title_Offset + items_count * Px_Square; +// if (items_count > 1) +// m_height += (items_count - 1) * Px_Square_Contour; +//############################################################################################################################################ // generates bitmap wxBitmap bitmap(m_width, m_height); @@ -1754,6 +1783,42 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c return true; } +//############################################################################################################################################ +void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const +{ + if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) + { + ::glDisable(GL_DEPTH_TEST); + ::glPushMatrix(); + ::glLoadIdentity(); + + const Size& cnv_size = canvas.get_canvas_size(); + float zoom = canvas.get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; + float top = (0.5f * (float)cnv_size.get_height()) * inv_zoom; + float right = left + (float)m_original_width * inv_zoom; + float bottom = top - (float)m_original_height * inv_zoom; + + float uv_left = 0.0f; + float uv_top = 0.0f; + float uv_right = (float)m_original_width / (float)m_width; + float uv_bottom = (float)m_original_height / (float)m_height; + + GLTexture::Quad_UVs uvs; + uvs.left_top = { uv_left, uv_top }; + uvs.left_bottom = { uv_left, uv_bottom }; + uvs.right_bottom = { uv_right, uv_bottom }; + uvs.right_top = { uv_right, uv_top }; + + GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs); + + ::glPopMatrix(); + ::glEnable(GL_DEPTH_TEST); + } +} +//############################################################################################################################################ + GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const { GizmosMap::const_iterator it = m_gizmos.find(m_current); @@ -3988,32 +4053,36 @@ void GLCanvas3D::_render_legend_texture() const if (!m_legend_texture_enabled) return; - // If the legend texture has not been loaded into the GPU, do it now. - unsigned int tex_id = m_legend_texture.get_id(); - if (tex_id > 0) - { - int w = m_legend_texture.get_width(); - int h = m_legend_texture.get_height(); - if ((w > 0) && (h > 0)) - { - ::glDisable(GL_DEPTH_TEST); - ::glPushMatrix(); - ::glLoadIdentity(); +//############################################################################################################################################ + m_legend_texture.render(*this); - const Size& cnv_size = get_canvas_size(); - float zoom = get_camera_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float l = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; - float t = (0.5f * (float)cnv_size.get_height()) * inv_zoom; - float r = l + (float)w * inv_zoom; - float b = t - (float)h * inv_zoom; - - GLTexture::render_texture(tex_id, l, r, b, t); - - ::glPopMatrix(); - ::glEnable(GL_DEPTH_TEST); - } - } +// // If the legend texture has not been loaded into the GPU, do it now. +// unsigned int tex_id = m_legend_texture.get_id(); +// if (tex_id > 0) +// { +// int w = m_legend_texture.get_width(); +// int h = m_legend_texture.get_height(); +// if ((w > 0) && (h > 0)) +// { +// ::glDisable(GL_DEPTH_TEST); +// ::glPushMatrix(); +// ::glLoadIdentity(); +// +// const Size& cnv_size = get_canvas_size(); +// float zoom = get_camera_zoom(); +// float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +// float l = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; +// float t = (0.5f * (float)cnv_size.get_height()) * inv_zoom; +// float r = l + (float)w * inv_zoom; +// float b = t - (float)h * inv_zoom; +// +// GLTexture::render_texture(tex_id, l, r, b, t); +// +// ::glPopMatrix(); +// ::glEnable(GL_DEPTH_TEST); +// } +// } +//############################################################################################################################################ } void GLCanvas3D::_render_layer_editing_overlay() const diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index 51aaca1071..97b44ca929 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -433,8 +433,24 @@ public: static const unsigned char Background_Color[3]; static const unsigned char Opacity; +//############################################################################################################################################ + int m_original_width; + int m_original_height; +//############################################################################################################################################ + public: +//############################################################################################################################################ + LegendTexture(); + + int get_original_width() const; + int get_original_height() const; +//############################################################################################################################################ + bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors); + +//############################################################################################################################################ + void render(const GLCanvas3D& canvas) const; +//############################################################################################################################################ }; private: From f0c1c15b62ff41256973c495669364d79cefb40b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 31 Jul 2018 15:09:57 +0200 Subject: [PATCH 092/185] Integration of SLA parameters, WIP --- xs/src/libslic3r/Config.hpp | 29 ++- xs/src/libslic3r/PrintConfig.cpp | 151 +++++++++++-- xs/src/libslic3r/PrintConfig.hpp | 99 ++++++++- xs/src/slic3r/GUI/AppConfig.cpp | 1 + xs/src/slic3r/GUI/Field.cpp | 8 +- xs/src/slic3r/GUI/Preset.cpp | 37 +++- xs/src/slic3r/GUI/Preset.hpp | 12 + xs/src/slic3r/GUI/PresetBundle.cpp | 308 +++++++++++++++++--------- xs/src/slic3r/GUI/PresetBundle.hpp | 5 + xs/src/slic3r/Utils/PresetUpdater.cpp | 1 + 10 files changed, 506 insertions(+), 145 deletions(-) diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 377bdbea47..f938be86a0 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -49,9 +49,9 @@ enum ConfigOptionType { coPercents = coPercent + coVectorType, // a fraction or an absolute value coFloatOrPercent = 5, - // single 2d point. Currently not used. + // single 2d point (Point2f). Currently not used. coPoint = 6, - // vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets. + // vector of 2d points (Point2f). Currently used for the definition of the print bed and for the extruder offsets. coPoints = coPoint + coVectorType, // single boolean value coBool = 7, @@ -821,12 +821,7 @@ public: bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); - const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); - auto it = enum_keys_map.find(str); - if (it == enum_keys_map.end()) - return false; - this->value = static_cast(it->second); - return true; + return from_string(str, this->value); } static bool has(T value) @@ -838,7 +833,7 @@ public: } // Map from an enum name to an enum integer value. - static t_config_enum_names& get_enum_names() + static const t_config_enum_names& get_enum_names() { static t_config_enum_names names; if (names.empty()) { @@ -855,7 +850,17 @@ public: return names; } // Map from an enum name to an enum integer value. - static t_config_enum_values& get_enum_values(); + static const t_config_enum_values& get_enum_values(); + + static bool from_string(const std::string &str, T &value) + { + const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); + auto it = enum_keys_map.find(str); + if (it == enum_keys_map.end()) + return false; + value = static_cast(it->second); + return true; + } }; // Generic enum configuration value. @@ -900,7 +905,7 @@ public: // What type? bool, int, string etc. ConfigOptionType type = coNone; // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. - ConfigOption *default_value = nullptr; + const ConfigOption *default_value = nullptr; // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, @@ -958,7 +963,7 @@ public: std::vector enum_labels; // For enums (when type == coEnum). Maps enum_values to enums. // Initialized by ConfigOptionEnum::get_enum_values() - t_config_enum_values *enum_keys_map = nullptr; + const t_config_enum_values *enum_keys_map = nullptr; bool has_enum_value(const std::string &value) const { for (const std::string &v : enum_values) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 7064e19fec..1e2c13d07c 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -17,9 +17,51 @@ namespace Slic3r { #define L(s) Slic3r::I18N::translate(s) PrintConfigDef::PrintConfigDef() +{ + this->init_common_params(); + this->init_fff_params(); + this->init_sla_params(); +} + +void PrintConfigDef::init_common_params() { t_optiondef_map &Options = this->options; + ConfigOptionDef* def; + + def = this->add("printer_technology", coEnum); + def->label = L("Printer technology"); + def->tooltip = L("Printer technology"); + def->cli = "printer-technology=s"; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("FFF"); + def->enum_values.push_back("SLA"); + def->default_value = new ConfigOptionEnum(ptFFF); + + def = this->add("bed_shape", coPoints); + def->label = L("Bed shape"); + def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) }; + def = this->add("layer_height", coFloat); + def->label = L("Layer height"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("This setting controls the height (and thus the total number) of the slices/layers. " + "Thinner layers give better accuracy but take more time to print."); + def->sidetext = L("mm"); + def->cli = "layer-height=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.3); + + def = this->add("max_print_height", coFloat); + def->label = L("Max print height"); + def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing."); + def->sidetext = L("mm"); + def->cli = "max-print-height=f"; + def->default_value = new ConfigOptionFloat(200.0); +} + +void PrintConfigDef::init_fff_params() +{ + t_optiondef_map &Options = this->options; ConfigOptionDef* def; // Maximum extruder temperature, bumped to 1500 to support printing of glass. @@ -33,10 +75,6 @@ PrintConfigDef::PrintConfigDef() def->cli = "avoid-crossing-perimeters!"; def->default_value = new ConfigOptionBool(false); - def = this->add("bed_shape", coPoints); - def->label = L("Bed shape"); - def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) }; - def = this->add("bed_temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Bed temperature for layers after the first one. " @@ -882,16 +920,6 @@ PrintConfigDef::PrintConfigDef() def->height = 50; def->default_value = new ConfigOptionString(""); - def = this->add("layer_height", coFloat); - def->label = L("Layer height"); - def->category = L("Layers and Perimeters"); - def->tooltip = L("This setting controls the height (and thus the total number) of the slices/layers. " - "Thinner layers give better accuracy but take more time to print."); - def->sidetext = L("mm"); - def->cli = "layer-height=f"; - def->min = 0; - def->default_value = new ConfigOptionFloat(0.3); - def = this->add("silent_mode", coBool); def->label = L("Support silent mode"); def->tooltip = L("Set silent mode for the G-code flavor"); @@ -1004,13 +1032,6 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; - def = this->add("max_print_height", coFloat); - def->label = L("Max print height"); - def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing."); - def->sidetext = L("mm"); - def->cli = "max-print-height=f"; - def->default_value = new ConfigOptionFloat(200.0); - def = this->add("max_print_speed", coFloat); def->label = L("Max print speed"); def->tooltip = L("When setting other speed settings to 0 Slic3r will autocalculate the optimal speed " @@ -2037,6 +2058,90 @@ PrintConfigDef::PrintConfigDef() def->default_value = new ConfigOptionFloat(0); } +void PrintConfigDef::init_sla_params() +{ + t_optiondef_map &Options = this->options; + ConfigOptionDef* def; + + // SLA Printer settings + def = this->add("display_width", coFloat); + def->label = L("Display width"); + def->tooltip = L("Width of the display"); + def->cli = "display-width=f"; + def->min = 1; + def->default_value = new ConfigOptionFloat(150.); + + def = this->add("display_height", coFloat); + def->label = L("Display height"); + def->tooltip = L("Height of the display"); + def->cli = "display-height=f"; + def->min = 1; + def->default_value = new ConfigOptionFloat(100.); + + def = this->add("display_pixels_x", coInt); + def->label = L("Number of pixels in X"); + def->tooltip = L("Number of pixels in X"); + def->cli = "display-pixels-x=i"; + def->min = 100; + def->default_value = new ConfigOptionInt(2000); + + def = this->add("display_pixels_y", coInt); + def->label = L("Number of pixels in Y"); + def->tooltip = L("Number of pixels in Y"); + def->cli = "display-pixels-y=i"; + def->min = 100; + def->default_value = new ConfigOptionInt(1000); + + def = this->add("printer_correction", coFloats); + def->label = L("Printer scaling correction"); + def->tooltip = L("Printer scaling correction"); + def->min = 0; + def->default_value = new ConfigOptionFloats( { 1., 1., 1. } ); + + // SLA Material settings. + def = this->add("initial_layer_height", coFloat); + def->label = L("Initial layer height"); + def->tooltip = L("Initial layer height"); + def->sidetext = L("mm"); + def->cli = "initial-layer-height=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.3); + + def = this->add("exposure_time", coFloat); + def->label = L("Exposure time"); + def->tooltip = L("Exposure time"); + def->sidetext = L("s"); + def->cli = "exposure-time=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(10); + + def = this->add("initial_exposure_time", coFloat); + def->label = L("Initial exposure time"); + def->tooltip = L("Initial exposure time"); + def->sidetext = L("s"); + def->cli = "initial-exposure-time=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(15); + + def = this->add("material_correction_printing", coFloats); + def->label = L("Correction for expansion when printing"); + def->tooltip = L("Correction for expansion when printing"); + def->min = 0; + def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } ); + + def = this->add("material_correction_curing", coFloats); + def->label = L("Correction for expansion after curing"); + def->tooltip = L("Correction for expansion after curing"); + def->min = 0; + def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } ); + + def = this->add("default_sla_material_profile", coString); + def->label = L("Default SLA material profile"); + def->tooltip = L("Default print profile associated with the current printer profile. " + "On selection of the current printer profile, this print profile will be activated."); + def->default_value = new ConfigOptionString(); +} + void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) { // handle legacy options @@ -2362,4 +2467,8 @@ StaticPrintConfig::StaticCache PrintConfig::s_c StaticPrintConfig::StaticCache HostConfig::s_cache_HostConfig; StaticPrintConfig::StaticCache FullPrintConfig::s_cache_FullPrintConfig; +StaticPrintConfig::StaticCache SLAMaterialConfig::s_cache_SLAMaterialConfig; +StaticPrintConfig::StaticCache SLAPrinterConfig::s_cache_SLAPrinterConfig; +StaticPrintConfig::StaticCache SLAFullPrintConfig::s_cache_SLAFullPrintConfig; + } diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index c530868a12..347eebf92a 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -22,6 +22,14 @@ namespace Slic3r { +enum PrinterTechnology +{ + // Fused Filament Fabrication + ptFFF, + // Stereolitography + ptSLA, +}; + enum GCodeFlavor { gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, @@ -44,7 +52,16 @@ enum FilamentType { ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA }; -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["FFF"] = ptFFF; + keys_map["SLA"] = ptSLA; + } + return keys_map; +} + +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["reprap"] = gcfRepRap; @@ -61,7 +78,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_ return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["rectilinear"] = ipRectilinear; @@ -81,7 +98,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enu return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["rectilinear"] = smpRectilinear; @@ -91,7 +108,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["random"] = spRandom; @@ -102,7 +119,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enum return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["PLA"] = ftPLA; @@ -126,6 +143,11 @@ public: PrintConfigDef(); static void handle_legacy(t_config_option_key &opt_key, std::string &value); + +private: + void init_common_params(); + void init_fff_params(); + void init_sla_params(); }; // The one and only global definition of SLic3r configuration options. @@ -822,6 +844,73 @@ protected: } }; +class SLAMaterialConfig : public StaticPrintConfig +{ + STATIC_PRINT_CONFIG_CACHE(SLAMaterialConfig) +public: + ConfigOptionFloat layer_height; + ConfigOptionFloat initial_layer_height; + ConfigOptionFloat exposure_time; + ConfigOptionFloat initial_exposure_time; + ConfigOptionFloats material_correction_printing; + ConfigOptionFloats material_correction_curing; +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + OPT_PTR(layer_height); + OPT_PTR(initial_layer_height); + OPT_PTR(exposure_time); + OPT_PTR(initial_exposure_time); + OPT_PTR(material_correction_printing); + OPT_PTR(material_correction_curing); + } +}; + +class SLAPrinterConfig : public StaticPrintConfig +{ + STATIC_PRINT_CONFIG_CACHE(SLAPrinterConfig) +public: + ConfigOptionEnum printer_technology; + ConfigOptionPoints bed_shape; + ConfigOptionFloat max_print_height; + ConfigOptionFloat display_width; + ConfigOptionFloat display_height; + ConfigOptionInt display_pixels_x; + ConfigOptionInt display_pixels_y; + ConfigOptionFloats printer_correction; +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + OPT_PTR(printer_technology); + OPT_PTR(bed_shape); + OPT_PTR(max_print_height); + OPT_PTR(display_width); + OPT_PTR(display_height); + OPT_PTR(display_pixels_x); + OPT_PTR(display_pixels_y); + OPT_PTR(printer_correction); + } +}; + +class SLAFullPrintConfig : public SLAPrinterConfig, public SLAMaterialConfig +{ + STATIC_PRINT_CONFIG_CACHE_DERIVED(SLAFullPrintConfig) + SLAFullPrintConfig() : SLAPrinterConfig(0), SLAMaterialConfig(0) { initialize_cache(); *this = s_cache_SLAFullPrintConfig.defaults(); } + +public: + // Validate the SLAFullPrintConfig. Returns an empty string on success, otherwise an error message is returned. +// std::string validate(); + +protected: + // Protected constructor to be called to initialize ConfigCache::m_default. + SLAFullPrintConfig(int) : SLAPrinterConfig(0), SLAMaterialConfig(0) {} + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + this->SLAPrinterConfig ::initialize(cache, base_ptr); + this->SLAMaterialConfig::initialize(cache, base_ptr); + } +}; + #undef STATIC_PRINT_CONFIG_CACHE #undef STATIC_PRINT_CONFIG_CACHE_BASE #undef STATIC_PRINT_CONFIG_CACHE_DERIVED diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 2a33cd7331..0f77b1953c 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -233,6 +233,7 @@ void AppConfig::reset_selections() if (it != m_storage.end()) { it->second.erase("print"); it->second.erase("filament"); + it->second.erase("sla_material"); it->second.erase("printer"); m_dirty = true; } diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 36a1c396f9..42177605a0 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -273,7 +273,7 @@ void CheckBox::BUILD() { bool check_value = m_opt.type == coBool ? m_opt.default_value->getBool() : m_opt.type == coBools ? - static_cast(m_opt.default_value)->get_at(m_opt_idx) : + static_cast(m_opt.default_value)->get_at(m_opt_idx) : false; auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); @@ -596,7 +596,7 @@ void ColourPicker::BUILD() if (m_opt.height >= 0) size.SetHeight(m_opt.height); if (m_opt.width >= 0) size.SetWidth(m_opt.width); - wxString clr(static_cast(m_opt.default_value)->get_at(m_opt_idx)); + wxString clr(static_cast(m_opt.default_value)->get_at(m_opt_idx)); auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size); // // recast as a wxWindow to fit the calling convention @@ -628,7 +628,7 @@ void PointCtrl::BUILD() // wxSize field_size(40, -1); - auto default_pt = static_cast(m_opt.default_value)->values.at(0); + auto default_pt = static_cast(m_opt.default_value)->values.at(0); double val = default_pt.x; wxString X = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None); val = default_pt.y; @@ -695,7 +695,7 @@ void StaticText::BUILD() if (m_opt.height >= 0) size.SetHeight(m_opt.height); if (m_opt.width >= 0) size.SetWidth(m_opt.width); - wxString legend(static_cast(m_opt.default_value)->value); + wxString legend(static_cast(m_opt.default_value)->value); auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size); temp->SetFont(bold_font()); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 49e235146c..34ab225e74 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -120,6 +120,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem VendorProfile::PrinterModel model; model.id = section.first.substr(printer_model_key.size()); model.name = section.second.get("name", model.id); + auto technology_field = section.second.get("technology", "FFF"); + if (! ConfigOptionEnum::from_string(technology_field, model.technology)) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Invalid printer technology field: `%2%`") % id % technology_field; + model.technology = ptFFF; + } section.second.get("variants", ""); const auto variants_field = section.second.get("variants", ""); std::vector variants; @@ -177,7 +182,7 @@ void Preset::normalize(DynamicPrintConfig &config) { auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter")); if (nozzle_diameter != nullptr) - // Loaded the Printer settings. Verify, that all extruder dependent values have enough values. + // Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values. set_num_extruders(config, (unsigned int)nozzle_diameter->values.size()); if (config.option("filament_diameter") != nullptr) { // This config contains single or multiple filament presets. @@ -327,6 +332,7 @@ const std::vector& Preset::printer_options() static std::vector s_opts; if (s_opts.empty()) { s_opts = { + "printer_technology", "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", @@ -358,6 +364,35 @@ const std::vector& Preset::nozzle_options() return s_opts; } +const std::vector& Preset::sla_printer_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "printer_technology", + "bed_shape", "max_print_height", + "display_width", "display_height", "display_pixels_x", "display_pixels_y", + "printer_correction", + "inherits" + }; + } + return s_opts; +} + +const std::vector& Preset::sla_material_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "layer_height", "initial_layer_height", + "exposure_time", "initial_exposure_time", + "material_correction_printing", "material_correction_curing", + "compatible_printers_condition", "inherits" + }; + } + return s_opts; +} + PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys) : m_type(type), m_edited_preset(type, "", false), diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 0d00cae485..f302ec74a2 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -52,6 +52,7 @@ public: PrinterModel() {} std::string id; std::string name; + PrinterTechnology technology; std::vector variants; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) @@ -83,6 +84,7 @@ public: TYPE_INVALID, TYPE_PRINT, TYPE_FILAMENT, + TYPE_SLA_MATERIAL, TYPE_PRINTER, }; @@ -149,6 +151,10 @@ public: std::string& compatible_printers_condition() { return Preset::compatible_printers_condition(this->config); } const std::string& compatible_printers_condition() const { return Preset::compatible_printers_condition(const_cast(this)->config); } + static PrinterTechnology& printer_technology(DynamicPrintConfig &cfg) { return cfg.option>("printer_technology", true)->value; } + PrinterTechnology& printer_technology() { return Preset::printer_technology(this->config); } + const PrinterTechnology& printer_technology() const { return Preset::printer_technology(const_cast(this)->config); } + // Mark this preset as compatible if it is compatible with active_printer. bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config); @@ -167,6 +173,10 @@ public: static const std::vector& printer_options(); // Nozzle options of the printer options. static const std::vector& nozzle_options(); + + static const std::vector& sla_printer_options(); + static const std::vector& sla_material_options(); + static void update_suffix_modified(); protected: @@ -247,6 +257,8 @@ public: Preset& get_selected_preset() { return m_presets[m_idx_selected]; } const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; } int get_selected_idx() const { return m_idx_selected; } + // Returns the name of the selected preset, or an empty string if no preset is selected. + std::string get_selected_preset_name() const { return (m_idx_selected == -1) ? std::string() : this->get_selected_preset().name; } // For the current edited preset, return the parent preset if there is one. // If there is no parent preset, nullptr is returned. // The parent preset may be a system preset or a user preset, which will be diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 94baa0e0ef..85885ea5e5 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -42,6 +42,7 @@ static std::vector s_project_options { PresetBundle::PresetBundle() : prints(Preset::TYPE_PRINT, Preset::print_options()), filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), + sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options()), printers(Preset::TYPE_PRINTER, Preset::printer_options()), m_bitmapCompatible(new wxBitmap), m_bitmapIncompatible(new wxBitmap), @@ -69,6 +70,10 @@ PresetBundle::PresetBundle() : this->filaments.default_preset().compatible_printers_condition(); this->filaments.default_preset().inherits(); + this->sla_materials.default_preset().config.optptr("sla_material_settings_id", true); + this->sla_materials.default_preset().compatible_printers_condition(); + this->sla_materials.default_preset().inherits(); + this->printers.default_preset().config.optptr("printer_settings_id", true); this->printers.default_preset().config.optptr("printer_vendor", true); this->printers.default_preset().config.optptr("printer_model", true); @@ -78,15 +83,17 @@ PresetBundle::PresetBundle() : this->printers.default_preset().inherits(); // Load the default preset bitmaps. - this->prints .load_bitmap_default("cog.png"); - this->filaments.load_bitmap_default("spool.png"); - this->printers .load_bitmap_default("printer_empty.png"); + this->prints .load_bitmap_default("cog.png"); + this->filaments .load_bitmap_default("spool.png"); + this->sla_materials.load_bitmap_default("spool.png"); + this->printers .load_bitmap_default("printer_empty.png"); this->load_compatible_bitmaps(); // Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above. - this->prints .select_preset(0); - this->filaments.select_preset(0); - this->printers .select_preset(0); + this->prints .select_preset(0); + this->filaments .select_preset(0); + this->sla_materials.select_preset(0); + this->printers .select_preset(0); this->project_config.apply_only(FullPrintConfig::defaults(), s_project_options); } @@ -113,13 +120,15 @@ void PresetBundle::reset(bool delete_files) { // Clear the existing presets, delete their respective files. this->vendors.clear(); - this->prints .reset(delete_files); - this->filaments.reset(delete_files); - this->printers .reset(delete_files); + this->prints .reset(delete_files); + this->filaments .reset(delete_files); + this->sla_materials.reset(delete_files); + this->printers .reset(delete_files); this->filament_presets.clear(); - this->filament_presets.emplace_back(this->filaments.get_selected_preset().name); + this->filament_presets.emplace_back(this->filaments.get_selected_preset_name()); this->obsolete_presets.prints.clear(); this->obsolete_presets.filaments.clear(); + this->obsolete_presets.sla_materials.clear(); this->obsolete_presets.printers.clear(); } @@ -135,11 +144,13 @@ void PresetBundle::setup_directories() data_dir / "presets", data_dir / "presets" / "print", data_dir / "presets" / "filament", + data_dir / "presets" / "sla_material", data_dir / "presets" / "printer" #else // Store the print/filament/printer presets at the same location as the upstream Slic3r. data_dir / "print", data_dir / "filament", + data_dir / "sla_material", data_dir / "printer" #endif }; @@ -175,6 +186,11 @@ void PresetBundle::load_presets(const AppConfig &config) } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } + try { + this->sla_materials.load_presets(dir_user_presets, "sla_material"); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + } try { this->printers.load_presets(dir_user_presets, "printer"); } catch (const std::runtime_error &err) { @@ -238,13 +254,16 @@ std::string PresetBundle::load_system_presets() std::vector PresetBundle::merge_presets(PresetBundle &&other) { this->vendors.insert(other.vendors.begin(), other.vendors.end()); - std::vector duplicate_prints = this->prints .merge_presets(std::move(other.prints), this->vendors); - std::vector duplicate_filaments = this->filaments.merge_presets(std::move(other.filaments), this->vendors); - std::vector duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors); - append(this->obsolete_presets.prints, std::move(other.obsolete_presets.prints)); - append(this->obsolete_presets.filaments, std::move(other.obsolete_presets.filaments)); - append(this->obsolete_presets.printers, std::move(other.obsolete_presets.printers)); + std::vector duplicate_prints = this->prints .merge_presets(std::move(other.prints), this->vendors); + std::vector duplicate_filaments = this->filaments .merge_presets(std::move(other.filaments), this->vendors); + std::vector duplicate_sla_materials = this->sla_materials.merge_presets(std::move(other.sla_materials), this->vendors); + std::vector duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors); + append(this->obsolete_presets.prints, std::move(other.obsolete_presets.prints)); + append(this->obsolete_presets.filaments, std::move(other.obsolete_presets.filaments)); + append(this->obsolete_presets.sla_materials, std::move(other.obsolete_presets.sla_materials)); + append(this->obsolete_presets.printers, std::move(other.obsolete_presets.printers)); append(duplicate_prints, std::move(duplicate_filaments)); + append(duplicate_prints, std::move(duplicate_sla_materials)); append(duplicate_prints, std::move(duplicate_printers)); return duplicate_prints; } @@ -275,9 +294,10 @@ void PresetBundle::load_selections(const AppConfig &config) this->load_installed_printers(config); // Parse the initial print / filament / printer profile names. - std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); - std::string initial_filament_profile_name = remove_ini_suffix(config.get("presets", "filament")); - std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); + std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print")); + std::string initial_filament_profile_name = remove_ini_suffix(config.get("presets", "filament")); + std::string initial_sla_material_profile_name = remove_ini_suffix(config.get("presets", "sla_material")); + std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer")); // Activate print / filament / printer profiles from the config. // If the printer profile enumerated by the config are not visible, select an alternate preset. @@ -285,6 +305,7 @@ void PresetBundle::load_selections(const AppConfig &config) // will be selected by the following call of this->update_compatible_with_printer(true). prints.select_preset_by_name_strict(initial_print_profile_name); filaments.select_preset_by_name_strict(initial_filament_profile_name); + sla_materials.select_preset_by_name_strict(initial_sla_material_profile_name); printers.select_preset_by_name(initial_printer_profile_name, true); // Load the names of the other filament profiles selected for a multi-material printer. @@ -313,25 +334,33 @@ void PresetBundle::load_selections(const AppConfig &config) void PresetBundle::export_selections(AppConfig &config) { assert(filament_presets.size() >= 1); - assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front()); + assert(filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front()); config.clear_section("presets"); - config.set("presets", "print", prints.get_selected_preset().name); - config.set("presets", "filament", filament_presets.front()); + config.set("presets", "print", prints.get_selected_preset_name()); + config.set("presets", "filament", filament_presets.front()); for (int i = 1; i < filament_presets.size(); ++i) { char name[64]; sprintf(name, "filament_%d", i); config.set("presets", name, filament_presets[i]); } - config.set("presets", "printer", printers.get_selected_preset().name); + config.set("presets", "sla_material", sla_materials.get_selected_preset_name()); + config.set("presets", "printer", printers.get_selected_preset_name()); } void PresetBundle::export_selections(PlaceholderParser &pp) { assert(filament_presets.size() >= 1); - assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front()); - pp.set("print_preset", prints.get_selected_preset().name); - pp.set("filament_preset", filament_presets); - pp.set("printer_preset", printers.get_selected_preset().name); + assert(filament_presets.size() > 1 || filaments.get_selected_preset_name() == filament_presets.front()); + switch (printers.get_edited_preset().printer_technology()) { + case ptFFF: + pp.set("print_preset", prints.get_selected_preset().name); + pp.set("filament_preset", filament_presets); + break; + case ptSLA: + pp.set("sla_material_preset", sla_materials.get_selected_preset().name); + break; + } + pp.set("printer_preset", printers.get_selected_preset().name); } bool PresetBundle::load_compatible_bitmaps() @@ -349,29 +378,40 @@ bool PresetBundle::load_compatible_bitmaps() bool loaded_lock_open = m_bitmapLockOpen->LoadFile( wxString::FromUTF8(Slic3r::var(path_bitmap_lock_open).c_str()), wxBITMAP_TYPE_PNG); if (loaded_compatible) { - prints .set_bitmap_compatible(m_bitmapCompatible); - filaments.set_bitmap_compatible(m_bitmapCompatible); + prints .set_bitmap_compatible(m_bitmapCompatible); + filaments .set_bitmap_compatible(m_bitmapCompatible); + sla_materials.set_bitmap_compatible(m_bitmapCompatible); // printers .set_bitmap_compatible(m_bitmapCompatible); } if (loaded_incompatible) { - prints .set_bitmap_incompatible(m_bitmapIncompatible); - filaments.set_bitmap_incompatible(m_bitmapIncompatible); -// printers .set_bitmap_incompatible(m_bitmapIncompatible); + prints .set_bitmap_incompatible(m_bitmapIncompatible); + filaments .set_bitmap_incompatible(m_bitmapIncompatible); + sla_materials.set_bitmap_incompatible(m_bitmapIncompatible); +// printers .set_bitmap_incompatible(m_bitmapIncompatible); } if (loaded_lock) { - prints .set_bitmap_lock(m_bitmapLock); - filaments.set_bitmap_lock(m_bitmapLock); - printers .set_bitmap_lock(m_bitmapLock); + prints .set_bitmap_lock(m_bitmapLock); + filaments .set_bitmap_lock(m_bitmapLock); + sla_materials.set_bitmap_lock(m_bitmapLock); + printers .set_bitmap_lock(m_bitmapLock); } if (loaded_lock_open) { - prints .set_bitmap_lock_open(m_bitmapLock); - filaments.set_bitmap_lock_open(m_bitmapLock); - printers .set_bitmap_lock_open(m_bitmapLock); + prints .set_bitmap_lock_open(m_bitmapLock); + filaments .set_bitmap_lock_open(m_bitmapLock); + sla_materials.set_bitmap_lock_open(m_bitmapLock); + printers .set_bitmap_lock_open(m_bitmapLock); } return loaded_compatible && loaded_incompatible && loaded_lock && loaded_lock_open; } DynamicPrintConfig PresetBundle::full_config() const +{ + return (this->printers.get_edited_preset().printer_technology() == ptFFF) ? + this->full_fff_config() : + this->full_sla_config(); +} + +DynamicPrintConfig PresetBundle::full_fff_config() const { DynamicPrintConfig out; out.apply(FullPrintConfig()); @@ -466,6 +506,48 @@ DynamicPrintConfig PresetBundle::full_config() const return out; } +DynamicPrintConfig PresetBundle::full_sla_config() const +{ + DynamicPrintConfig out; + out.apply(SLAFullPrintConfig()); + out.apply(this->sla_materials.get_edited_preset().config); + out.apply(this->printers.get_edited_preset().config); + // There are no project configuration values as of now, the project_config is reserved for FFF printers. +// out.apply(this->project_config); + + // Collect the "compatible_printers_condition" and "inherits" values over all presets (sla_materials, printers) into a single vector. + std::vector compatible_printers_condition; + std::vector inherits; + compatible_printers_condition.emplace_back(this->prints.get_edited_preset().compatible_printers_condition()); + inherits .emplace_back(this->prints.get_edited_preset().inherits()); + inherits .emplace_back(this->printers.get_edited_preset().inherits()); + + // These two value types clash between the print and filament profiles. They should be renamed. + out.erase("compatible_printers"); + out.erase("compatible_printers_condition"); + out.erase("inherits"); + + out.option("sla_material_settings_id", true)->value = this->sla_materials.get_selected_preset().name; + out.option("printer_settings_id", true)->value = this->printers.get_selected_preset().name; + + // Serialize the collected "compatible_printers_condition" and "inherits" fields. + // There will be 1 + num_exturders fields for "inherits" and 2 + num_extruders for "compatible_printers_condition" stored. + // The vector will not be stored if all fields are empty strings. + auto add_if_some_non_empty = [&out](std::vector &&values, const std::string &key) { + bool nonempty = false; + for (const std::string &v : values) + if (! v.empty()) { + nonempty = true; + break; + } + if (nonempty) + out.set_key_value(key, new ConfigOptionStrings(std::move(values))); + }; + add_if_some_non_empty(std::move(compatible_printers_condition), "compatible_printers_condition_cummulative"); + add_if_some_non_empty(std::move(inherits), "inherits_cummulative"); + return out; +} + // Load an external config file containing the print, filament and printer presets. // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. @@ -530,6 +612,8 @@ void PresetBundle::load_config_string(const char* str, const char* source_filena // Load a config file from a boost property_tree. This is a private method called from load_config_file. void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config) { + PrinterTechnology printer_technology = Preset::printer_technology(config); + // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway, // but some of the alpha versions of Slic3r did. { @@ -541,8 +625,10 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool } } - size_t num_extruders = std::min(config.option("nozzle_diameter" )->values.size(), - config.option("filament_diameter")->values.size()); + size_t num_extruders = (printer_technology == ptFFF) ? + std::min(config.option("nozzle_diameter" )->values.size(), + config.option("filament_diameter")->values.size()) : + 0; // Make a copy of the "compatible_printers_condition_cummulative" and "inherits_cummulative" vectors, which // accumulate values over all presets (print, filaments, printers). // These values will be distributed into their particular presets when loading. @@ -553,7 +639,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool compatible_printers_condition_values.resize(num_extruders + 2, std::string()); inherits_values.resize(num_extruders + 2, std::string()); // The "default_filament_profile" will be later extracted into the printer profile. - config.option("default_filament_profile", true)->values.resize(num_extruders, std::string()); + if (printer_technology == ptFFF) + config.option("default_filament_profile", true)->values.resize(num_extruders, std::string()); // 1) Create a name from the file name. // Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles. @@ -562,81 +649,83 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // 2) If the loading succeeded, split and load the config into print / filament / printer settings. // First load the print and printer presets. for (size_t i_group = 0; i_group < 2; ++ i_group) { - PresetCollection &presets = (i_group == 0) ? this->prints : this->printers; + PresetCollection &presets = (i_group == 0) ? ((printer_technology == ptFFF) ? this->prints : this->sla_materials) : this->printers; // Split the "compatible_printers_condition" and "inherits" values one by one from a single vector to the print & printer profiles. size_t idx = (i_group == 0) ? 0 : num_extruders + 1; inherits = inherits_values[idx]; compatible_printers_condition = compatible_printers_condition_values[idx]; if (is_external) presets.load_external_preset(name_or_path, name, - config.opt_string((i_group == 0) ? "print_settings_id" : "printer_settings_id", true), + config.opt_string((i_group == 0) ? ((printer_technology == ptFFF) ? "print_settings_id" : "sla_material_id") : "printer_settings_id", true), config); else presets.load_preset(presets.path_from_name(name), name, config).save(); } - // 3) Now load the filaments. If there are multiple filament presets, split them and load them. - auto old_filament_profile_names = config.option("filament_settings_id", true); - old_filament_profile_names->values.resize(num_extruders, std::string()); + if (Preset::printer_technology(config) == ptFFF) { + // 3) Now load the filaments. If there are multiple filament presets, split them and load them. + auto old_filament_profile_names = config.option("filament_settings_id", true); + old_filament_profile_names->values.resize(num_extruders, std::string()); - if (num_extruders <= 1) { - // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. - inherits = inherits_values[1]; - compatible_printers_condition = compatible_printers_condition_values[1]; - if (is_external) - this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config); - else - this->filaments.load_preset(this->filaments.path_from_name(name), name, config).save(); - this->filament_presets.clear(); - this->filament_presets.emplace_back(name); - } else { - // Split the filament presets, load each of them separately. - std::vector configs(num_extruders, this->filaments.default_preset().config); - // loop through options and scatter them into configs. - for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { - const ConfigOption *other_opt = config.option(key); - if (other_opt == nullptr) - continue; - if (other_opt->is_scalar()) { - for (size_t i = 0; i < configs.size(); ++ i) - configs[i].option(key, false)->set(other_opt); - } else if (key != "compatible_printers") { - for (size_t i = 0; i < configs.size(); ++ i) - static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i); - } - } - // Load the configs into this->filaments and make them active. - this->filament_presets.clear(); - for (size_t i = 0; i < configs.size(); ++ i) { - DynamicPrintConfig &cfg = configs[i]; + if (num_extruders <= 1) { // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. - cfg.opt_string("compatible_printers_condition", true) = compatible_printers_condition_values[i + 1]; - cfg.opt_string("inherits", true) = inherits_values[i + 1]; - // Load all filament presets, but only select the first one in the preset dialog. - Preset *loaded = nullptr; + inherits = inherits_values[1]; + compatible_printers_condition = compatible_printers_condition_values[1]; if (is_external) - loaded = &this->filaments.load_external_preset(name_or_path, name, - (i < old_filament_profile_names->values.size()) ? old_filament_profile_names->values[i] : "", - std::move(cfg), i == 0); - else { - // Used by the config wizard when creating a custom setup. - // Therefore this block should only be called for a single extruder. - char suffix[64]; - if (i == 0) - suffix[0] = 0; - else - sprintf(suffix, "%d", i); - std::string new_name = name + suffix; - loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name), - new_name, std::move(cfg), i == 0); - loaded->save(); + this->filaments.load_external_preset(name_or_path, name, old_filament_profile_names->values.front(), config); + else + this->filaments.load_preset(this->filaments.path_from_name(name), name, config).save(); + this->filament_presets.clear(); + this->filament_presets.emplace_back(name); + } else { + // Split the filament presets, load each of them separately. + std::vector configs(num_extruders, this->filaments.default_preset().config); + // loop through options and scatter them into configs. + for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { + const ConfigOption *other_opt = config.option(key); + if (other_opt == nullptr) + continue; + if (other_opt->is_scalar()) { + for (size_t i = 0; i < configs.size(); ++ i) + configs[i].option(key, false)->set(other_opt); + } else if (key != "compatible_printers") { + for (size_t i = 0; i < configs.size(); ++ i) + static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i); + } + } + // Load the configs into this->filaments and make them active. + this->filament_presets.clear(); + for (size_t i = 0; i < configs.size(); ++ i) { + DynamicPrintConfig &cfg = configs[i]; + // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. + cfg.opt_string("compatible_printers_condition", true) = compatible_printers_condition_values[i + 1]; + cfg.opt_string("inherits", true) = inherits_values[i + 1]; + // Load all filament presets, but only select the first one in the preset dialog. + Preset *loaded = nullptr; + if (is_external) + loaded = &this->filaments.load_external_preset(name_or_path, name, + (i < old_filament_profile_names->values.size()) ? old_filament_profile_names->values[i] : "", + std::move(cfg), i == 0); + else { + // Used by the config wizard when creating a custom setup. + // Therefore this block should only be called for a single extruder. + char suffix[64]; + if (i == 0) + suffix[0] = 0; + else + sprintf(suffix, "%d", i); + std::string new_name = name + suffix; + loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name), + new_name, std::move(cfg), i == 0); + loaded->save(); + } + this->filament_presets.emplace_back(loaded->name); } - this->filament_presets.emplace_back(loaded->name); } - } - // 4) Load the project config values (the per extruder wipe matrix etc). - this->project_config.apply_only(config, s_project_options); + // 4) Load the project config values (the per extruder wipe matrix etc). + this->project_config.apply_only(config, s_project_options); + } this->update_compatible_with_printer(false); } @@ -691,9 +780,10 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true; return preset_name_dst; }; - load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset().name, true); - load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments.get_selected_preset().name, true); - load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset().name, true); + load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset().name, true); + load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments .get_selected_preset().name, true); + load_one(this->sla_materials, tmp_bundle.sla_materials, tmp_bundle.sla_materials.get_selected_preset().name, true); + load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset().name, true); this->update_multi_material_filament_presets(); for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i) this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false); @@ -817,6 +907,7 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree) { flatten_configbundle_hierarchy(tree, "print"); flatten_configbundle_hierarchy(tree, "filament"); + flatten_configbundle_hierarchy(tree, "sla_material"); flatten_configbundle_hierarchy(tree, "printer"); } @@ -853,9 +944,11 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla // Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure. std::vector loaded_prints; std::vector loaded_filaments; + std::vector loaded_sla_materials; std::vector loaded_printers; std::string active_print; std::vector active_filaments; + std::string active_sla_material; std::string active_printer; size_t presets_loaded = 0; for (const auto §ion : tree) { @@ -870,6 +963,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla presets = &this->filaments; loaded = &loaded_filaments; preset_name = section.first.substr(9); + } else if (boost::starts_with(section.first, "sla_material:")) { + presets = &this->sla_materials; + loaded = &loaded_sla_materials; + preset_name = section.first.substr(9); } else if (boost::starts_with(section.first, "printer:")) { presets = &this->printers; loaded = &loaded_printers; @@ -886,6 +983,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla active_filaments.resize(idx + 1, std::string()); active_filaments[idx] = kvp.second.data(); } + } else if (kvp.first == "sla_material") { + active_sla_material = kvp.second.data(); } else if (kvp.first == "printer") { active_printer = kvp.second.data(); } @@ -899,6 +998,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla dst = &this->obsolete_presets.prints; else if (kvp.first == "filament") dst = &this->obsolete_presets.filaments; + else if (kvp.first == "sla_material") + dst = &this->obsolete_presets.sla_materials; else if (kvp.first == "printer") dst = &this->obsolete_presets.printers; if (dst) @@ -999,6 +1100,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { if (! active_print.empty()) prints.select_preset_by_name(active_print, true); + if (! active_sla_material.empty()) + sla_materials.select_preset_by_name(active_sla_material, true); if (! active_printer.empty()) printers.select_preset_by_name(active_printer, true); // Activate the first filament preset. @@ -1111,6 +1214,7 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami // Export the names of the active presets. c << std::endl << "[presets]" << std::endl; c << "print = " << this->prints.get_selected_preset().name << std::endl; + c << "sla_material = " << this->sla_materials.get_selected_preset().name << std::endl; c << "printer = " << this->printers.get_selected_preset().name << std::endl; for (size_t i = 0; i < this->filament_presets.size(); ++ i) { char suffix[64]; diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index a5c5682f94..fed8385193 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -40,6 +40,7 @@ public: PresetCollection prints; PresetCollection filaments; + PresetCollection sla_materials; PresetCollection printers; // Filament preset names for a multi-extruder or multi-material print. // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() @@ -57,6 +58,7 @@ public: struct ObsoletePresets { std::vector prints; std::vector filaments; + std::vector sla_materials; std::vector printers; }; ObsoletePresets obsolete_presets; @@ -146,6 +148,9 @@ private: void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); bool load_compatible_bitmaps(); + DynamicPrintConfig full_fff_config() const; + DynamicPrintConfig full_sla_config() const; + // Indicator, that the preset is compatible with the selected printer. wxBitmap *m_bitmapCompatible; // Indicator, that the preset is NOT compatible with the selected printer. diff --git a/xs/src/slic3r/Utils/PresetUpdater.cpp b/xs/src/slic3r/Utils/PresetUpdater.cpp index 6e23ab4219..2e423dc5ee 100644 --- a/xs/src/slic3r/Utils/PresetUpdater.cpp +++ b/xs/src/slic3r/Utils/PresetUpdater.cpp @@ -447,6 +447,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.prints) { obsolete_remover("print", name); } for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("filament", name); } + for (const auto &name : bundle.obsolete_presets.filaments) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } } } From 19411df0e4add66308af34e72c8232b6b4de0aaf Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 31 Jul 2018 15:31:12 +0200 Subject: [PATCH 093/185] Correct split for the parts too --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 95 ++++++++++++++++++++------- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 3 +- xs/src/slic3r/GUI/wxExtensions.cpp | 31 +++++++++ xs/src/slic3r/GUI/wxExtensions.hpp | 1 + 4 files changed, 105 insertions(+), 25 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 07bc115b71..59688606e7 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -277,7 +277,7 @@ wxBoxSizer* content_edit_object_buttons(wxWindow* win) }); btn_delete ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_del(); }); - btn_split ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_split(); }); + btn_split ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_split(true); }); m_btn_move_up ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_up(); }); m_btn_move_down ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_move_down(); }); //*** @@ -721,11 +721,9 @@ void update_settings_list() m_option_sizer->Clear(true); - printf("update_settings_list\n"); - if (m_config) { - auto extra_column = [](wxWindow* parent, const Line& line) + auto extra_column = [](wxWindow* parent, const Line& line) { auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line @@ -848,6 +846,21 @@ bool cur_item_hase_children() return false; } +wxMenuItem* menu_item_split(wxMenu* menu, int id) { + auto menu_item = new wxMenuItem(menu, id, _(L("Split to parts"))); + menu_item->SetBitmap(m_bmp_split); + return menu_item; +} + +wxMenuItem* menu_item_settings(wxMenu* menu, int id) { + auto menu_item = new wxMenuItem(menu, id, _(L("Add settings"))); + menu_item->SetBitmap(m_bmp_cog); + + auto sub_menu = create_add_settings_popupmenu(false); + menu_item->SetSubMenu(sub_menu); + return menu_item; +} + wxMenu *create_add_part_popupmenu() { wxMenu *menu = new wxMenu; @@ -864,11 +877,14 @@ wxMenu *create_add_part_popupmenu() } menu->AppendSeparator(); - auto menu_item = new wxMenuItem(menu, config_id_base + 3, _(L("Split to sub-objects"))); - menu_item->SetBitmap(m_bmp_split); + auto menu_item = menu_item_split(menu, config_id_base + i); menu->Append(menu_item); menu_item->Enable(!cur_item_hase_children()); + menu->AppendSeparator(); + // Append settings popupmenu + menu->Append(menu_item_settings(menu, config_id_base + i + 1)); + wxWindow* win = get_tab_panel()->GetPage(0); menu->Bind(wxEVT_MENU, [config_id_base, win, menu](wxEvent &event){ @@ -883,7 +899,7 @@ wxMenu *create_add_part_popupmenu() on_btn_load(win, true, true); break; case 3: - on_btn_split(); + on_btn_split(false); break; default:{ get_settings_choice(menu, event.GetId(), false); @@ -891,19 +907,34 @@ wxMenu *create_add_part_popupmenu() } }); - menu->AppendSeparator(); - // Append settings popupmenu - menu_item = new wxMenuItem(menu, config_id_base + 4, _(L("Add settings"))); - menu_item->SetBitmap(m_bmp_cog); - - auto sub_menu = create_add_settings_popupmenu(false); - - menu_item->SetSubMenu(sub_menu); - menu->Append(menu_item); - return menu; } +wxMenu *create_part_settings_popupmenu() +{ + wxMenu *menu = new wxMenu; + wxWindowID config_id_base = wxWindow::NewControlId(2); + + menu->Append(menu_item_split(menu, config_id_base)); + + menu->AppendSeparator(); + // Append settings popupmenu + menu->Append(menu_item_settings(menu, config_id_base + 1)); + + menu->Bind(wxEVT_MENU, [config_id_base, menu](wxEvent &event){ + switch (event.GetId() - config_id_base) { + case 0: + on_btn_split(true); + break; + default:{ + get_settings_choice(menu, event.GetId(), true); + break; } + } + }); + + return menu; +} + wxMenu *create_add_settings_popupmenu(bool is_part) { wxMenu *menu = new wxMenu; @@ -915,7 +946,7 @@ wxMenu *create_add_settings_popupmenu(bool is_part) for (auto cat : settings_menu) { - auto menu_item = new wxMenuItem(menu, wxID_ANY/*config_id_base + inc*/, _(cat.first)); + auto menu_item = new wxMenuItem(menu, wxID_ANY, _(cat.first)); menu_item->SetBitmap(categories.find(cat.first) == categories.end() ? wxNullBitmap : categories.at(cat.first)); menu->Append(menu_item); @@ -947,7 +978,7 @@ void object_ctrl_context_menu() // obj_idx = m_objects_model->GetIdByItem(parent); // auto volume_id = m_objects_model->GetVolumeIdByItem(item); // if (volume_id < 0) return; - auto menu = create_add_settings_popupmenu(true); + auto menu = create_part_settings_popupmenu(); get_tab_panel()->GetPage(0)->PopupMenu(menu); } } @@ -1106,15 +1137,17 @@ void on_btn_del() // #endif //__WXMSW__ } -void on_btn_split() +void on_btn_split(const bool split_part) { auto item = m_objects_ctrl->GetSelection(); if (!item) return; auto volume_id = m_objects_model->GetVolumeIdByItem(item); ModelVolume* volume; - if (volume_id < 0) - volume = (*m_objects)[m_selected_object_id]->volumes[0];//return; + if (volume_id < 0) { + if (split_part) return; + else + volume = (*m_objects)[m_selected_object_id]->volumes[0]; } else volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; DynamicPrintConfig& config = get_preset_bundle()->printers.get_edited_preset().config; @@ -1126,8 +1159,22 @@ void on_btn_split() } auto model_object = (*m_objects)[m_selected_object_id]; - for (auto id = 0; id < model_object->volumes.size(); id++) - m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false); + + if (split_part) { + auto parent = m_objects_model->GetParent(item); + m_objects_model->DeleteChildren(parent); + + for (auto id = 0; id < model_object->volumes.size(); id++) + m_objects_model->AddChild(parent, model_object->volumes[id]->name, + model_object->volumes[id]->modifier ? m_icon_modifiermesh : m_icon_solidmesh, false); + + m_objects_ctrl->Expand(parent); + } + else { + for (auto id = 0; id < model_object->volumes.size(); id++) + m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false); + m_objects_ctrl->Expand(item); + } } void on_btn_move_up(){ diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index e4484e72a1..17c99f65c5 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -48,6 +48,7 @@ void show_collpane_settings(bool expert_mode); wxMenu *create_add_settings_popupmenu(bool is_part); wxMenu *create_add_part_popupmenu(); +wxMenu *create_part_settings_popupmenu(); // Add object to the list //void add_object(const std::string &name); @@ -88,7 +89,7 @@ void load_lambda(wxWindow* parent, ModelObject* model_object, void on_btn_load(wxWindow* parent, bool is_modifier = false, bool is_lambda = false); void on_btn_del(); -void on_btn_split(); +void on_btn_split(const bool split_part); void on_btn_move_up(); void on_btn_move_down(); diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index dd6655e3c5..020ea570c6 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -456,7 +456,9 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) // set m_containet to FALSE if parent has no child if (node_parent && node_parent->GetChildCount() == 0){ +#ifndef __WXGTK__ node_parent->m_container = false; +#endif //__WXGTK__ ret_item = parent; } @@ -475,6 +477,35 @@ void PrusaObjectDataViewModel::DeleteAll() } } +void PrusaObjectDataViewModel::DeleteChildren(wxDataViewItem& parent) +{ + PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!root) // happens if item.IsOk()==false + return; + + // first remove the node from the parent's array of children; + // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ + // thus removing the node from it doesn't result in freeing it + auto& children = root->GetChildren(); + for (int id = root->GetChildCount() - 1; id >= 0; --id) + { + auto node = children[id]; + auto item = wxDataViewItem(node); + children.RemoveAt(id); + + // free the node + delete node; + + // notify control + ItemDeleted(parent, item); + } + + // set m_containet to FALSE if parent has no child +#ifndef __WXGTK__ + root->m_container = false; +#endif //__WXGTK__ +} + wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx) { if (obj_idx >= m_objects.size()) diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 257b5e47cd..ac9fd4da00 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -354,6 +354,7 @@ public: bool create_frst_child = true); wxDataViewItem Delete(const wxDataViewItem &item); void DeleteAll(); + void DeleteChildren(wxDataViewItem& parent); wxDataViewItem GetItemById(int obj_idx); int GetIdByItem(wxDataViewItem& item); int GetVolumeIdByItem(wxDataViewItem& item); From e7cc3c5f4a66aec8b5b946e915f554919c0f46a3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 31 Jul 2018 15:31:24 +0200 Subject: [PATCH 094/185] Code cleanup --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 101 ------------------------------- xs/src/slic3r/GUI/GLCanvas3D.hpp | 18 ------ 2 files changed, 119 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index f82dc262fa..d70981c15b 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1467,7 +1467,6 @@ float GLCanvas3D::Gizmos::_get_total_overlay_height() const const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 9, 91, 134 }; const unsigned char GLCanvas3D::WarningTexture::Opacity = 255; -//############################################################################################################################################ GLCanvas3D::WarningTexture::WarningTexture() : GUI::GLTexture() , m_original_width(0) @@ -1475,17 +1474,6 @@ GLCanvas3D::WarningTexture::WarningTexture() { } -int GLCanvas3D::WarningTexture::get_original_width() const -{ - return m_original_width; -} - -int GLCanvas3D::WarningTexture::get_original_height() const -{ - return m_original_height; -} -//############################################################################################################################################ - bool GLCanvas3D::WarningTexture::generate(const std::string& msg) { reset(); @@ -1495,28 +1483,20 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg) wxMemoryDC memDC; // select default font -//############################################################################################################################################ wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); font.MakeLarger(); memDC.SetFont(font); -// memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); -//############################################################################################################################################ - // calculates texture size wxCoord w, h; memDC.GetTextExtent(msg, &w, &h); -//############################################################################################################################################ int pow_of_two_size = next_highest_power_of_2((int)std::max(w, h)); m_original_width = (int)w; m_original_height = (int)h; m_width = pow_of_two_size; m_height = pow_of_two_size; -// m_width = (int)w; -// m_height = (int)h; -//############################################################################################################################################ // generates bitmap wxBitmap bitmap(m_width, m_height); @@ -1568,7 +1548,6 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg) return true; } -//############################################################################################################################################ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const { if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) @@ -1602,13 +1581,11 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const ::glEnable(GL_DEPTH_TEST); } } -//############################################################################################################################################ const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 }; const unsigned char GLCanvas3D::LegendTexture::Background_Color[3] = { 9, 91, 134 }; const unsigned char GLCanvas3D::LegendTexture::Opacity = 255; -//############################################################################################################################################ GLCanvas3D::LegendTexture::LegendTexture() : GUI::GLTexture() , m_original_width(0) @@ -1616,17 +1593,6 @@ GLCanvas3D::LegendTexture::LegendTexture() { } -int GLCanvas3D::LegendTexture::get_original_width() const -{ - return m_original_width; -} - -int GLCanvas3D::LegendTexture::get_original_height() const -{ - return m_original_height; -} -//############################################################################################################################################ - bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors) { reset(); @@ -1659,7 +1625,6 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c max_text_height = std::max(max_text_height, (int)h); } -//############################################################################################################################################ m_original_width = std::max(2 * Px_Border + title_width, 2 * (Px_Border + Px_Square_Contour) + Px_Square + Px_Text_Offset + max_text_width); m_original_height = 2 * (Px_Border + Px_Square_Contour) + title_height + Px_Title_Offset + items_count * Px_Square; if (items_count > 1) @@ -1670,12 +1635,6 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c m_width = pow_of_two_size; m_height = pow_of_two_size; -// m_width = std::max(2 * Px_Border + title_width, 2 * (Px_Border + Px_Square_Contour) + Px_Square + Px_Text_Offset + max_text_width); -// m_height = 2 * (Px_Border + Px_Square_Contour) + title_height + Px_Title_Offset + items_count * Px_Square; -// if (items_count > 1) -// m_height += (items_count - 1) * Px_Square_Contour; -//############################################################################################################################################ - // generates bitmap wxBitmap bitmap(m_width, m_height); @@ -1783,7 +1742,6 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c return true; } -//############################################################################################################################################ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const { if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) @@ -1817,7 +1775,6 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const ::glEnable(GL_DEPTH_TEST); } } -//############################################################################################################################################ GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const { @@ -4016,36 +3973,7 @@ void GLCanvas3D::_render_warning_texture() const if (!m_warning_texture_enabled) return; -//############################################################################################################################################ m_warning_texture.render(*this); - -// // If the warning texture has not been loaded into the GPU, do it now. -// unsigned int tex_id = m_warning_texture.get_id(); -// if (tex_id > 0) -// { -// int w = m_warning_texture.get_width(); -// int h = m_warning_texture.get_height(); -// if ((w > 0) && (h > 0)) -// { -// ::glDisable(GL_DEPTH_TEST); -// ::glPushMatrix(); -// ::glLoadIdentity(); -// -// const Size& cnv_size = get_canvas_size(); -// float zoom = get_camera_zoom(); -// float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -// float l = (-0.5f * (float)w) * inv_zoom; -// float t = (-0.5f * (float)cnv_size.get_height() + (float)h) * inv_zoom; -// float r = l + (float)w * inv_zoom; -// float b = t - (float)h * inv_zoom; -// -// GLTexture::render_texture(tex_id, l, r, b, t); -// -// ::glPopMatrix(); -// ::glEnable(GL_DEPTH_TEST); -// } -// } -//############################################################################################################################################ } void GLCanvas3D::_render_legend_texture() const @@ -4053,36 +3981,7 @@ void GLCanvas3D::_render_legend_texture() const if (!m_legend_texture_enabled) return; -//############################################################################################################################################ m_legend_texture.render(*this); - -// // If the legend texture has not been loaded into the GPU, do it now. -// unsigned int tex_id = m_legend_texture.get_id(); -// if (tex_id > 0) -// { -// int w = m_legend_texture.get_width(); -// int h = m_legend_texture.get_height(); -// if ((w > 0) && (h > 0)) -// { -// ::glDisable(GL_DEPTH_TEST); -// ::glPushMatrix(); -// ::glLoadIdentity(); -// -// const Size& cnv_size = get_canvas_size(); -// float zoom = get_camera_zoom(); -// float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -// float l = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; -// float t = (0.5f * (float)cnv_size.get_height()) * inv_zoom; -// float r = l + (float)w * inv_zoom; -// float b = t - (float)h * inv_zoom; -// -// GLTexture::render_texture(tex_id, l, r, b, t); -// -// ::glPopMatrix(); -// ::glEnable(GL_DEPTH_TEST); -// } -// } -//############################################################################################################################################ } void GLCanvas3D::_render_layer_editing_overlay() const diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index 97b44ca929..a92aff1a4f 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -402,24 +402,15 @@ public: static const unsigned char Background_Color[3]; static const unsigned char Opacity; -//############################################################################################################################################ int m_original_width; int m_original_height; -//############################################################################################################################################ public: -//############################################################################################################################################ WarningTexture(); - int get_original_width() const; - int get_original_height() const; -//############################################################################################################################################ - bool generate(const std::string& msg); -//############################################################################################################################################ void render(const GLCanvas3D& canvas) const; -//############################################################################################################################################ }; class LegendTexture : public GUI::GLTexture @@ -433,24 +424,15 @@ public: static const unsigned char Background_Color[3]; static const unsigned char Opacity; -//############################################################################################################################################ int m_original_width; int m_original_height; -//############################################################################################################################################ public: -//############################################################################################################################################ LegendTexture(); - int get_original_width() const; - int get_original_height() const; -//############################################################################################################################################ - bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors); -//############################################################################################################################################ void render(const GLCanvas3D& canvas) const; -//############################################################################################################################################ }; private: From c9d23d0ac0f5e8e07d1608a5c453d265bec9ab3b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 31 Jul 2018 15:32:16 +0200 Subject: [PATCH 095/185] Font for warning texture set to bold --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index d70981c15b..5ca7e64112 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1485,6 +1485,7 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg) // select default font wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); font.MakeLarger(); + font.MakeBold(); memDC.SetFont(font); // calculates texture size From 4e193555ae524546bf856a0b868e1532a037da47 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 1 Aug 2018 11:09:51 +0200 Subject: [PATCH 096/185] Adding the SLA printer profiles, WIP --- xs/src/libslic3r/PrintConfig.cpp | 9 +++ xs/src/slic3r/GUI/Preset.cpp | 48 ++++++++++------ xs/src/slic3r/GUI/Preset.hpp | 33 +++++++---- xs/src/slic3r/GUI/PresetBundle.cpp | 91 +++++++++++++++++++----------- xs/src/slic3r/GUI/PresetBundle.hpp | 2 +- xs/src/slic3r/GUI/Tab.cpp | 77 +++++++++++++------------ xs/src/slic3r/GUI/Tab.hpp | 2 +- xs/xsp/GUI_Preset.xsp | 1 - 8 files changed, 161 insertions(+), 102 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 1e2c13d07c..698156d59d 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -2135,6 +2135,15 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } ); + def = this->add("material_notes", coString); + def->label = L("SLA print material notes"); + def->tooltip = L("You can put your notes regarding the SLA print material here."); + def->cli = "material-notes=s"; + def->multiline = true; + def->full_width = true; + def->height = 130; + def->default_value = new ConfigOptionString(""); + def = this->add("default_sla_material_profile", coString); def->label = L("Default SLA material profile"); def->tooltip = L("Default print profile associated with the current printer profile. " diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 34ab225e74..71a2bb6a59 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -265,8 +265,9 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer) const { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); - config.set_key_value("num_extruders", new ConfigOptionInt( - (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size())); + const ConfigOption *opt = active_printer.config.option("nozzle_diameter"); + if (opt) + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); return this->is_compatible_with_printer(active_printer, &config); } @@ -373,6 +374,7 @@ const std::vector& Preset::sla_printer_options() "bed_shape", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", "printer_correction", + "printer_notes", "inherits" }; } @@ -387,13 +389,14 @@ const std::vector& Preset::sla_material_options() "layer_height", "initial_layer_height", "exposure_time", "initial_exposure_time", "material_correction_printing", "material_correction_curing", + "material_notes", "compatible_printers_condition", "inherits" }; } return s_opts; } -PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys) : +PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const std::string &default_name) : m_type(type), m_edited_preset(type, "", false), m_idx_selected(0), @@ -401,7 +404,7 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectoradd_default_preset(keys, default_name); m_presets.front().load(keys); m_edited_preset.config.apply(m_presets.front().config); } @@ -416,7 +419,7 @@ PresetCollection::~PresetCollection() void PresetCollection::reset(bool delete_files) { - if (m_presets.size() > 1) { + if (m_presets.size() > m_num_default_presets) { if (delete_files) { // Erase the preset files. for (Preset &preset : m_presets) @@ -424,11 +427,19 @@ void PresetCollection::reset(bool delete_files) boost::nowide::remove(preset.file.c_str()); } // Don't use m_presets.resize() here as it requires a default constructor for Preset. - m_presets.erase(m_presets.begin() + 1, m_presets.end()); + m_presets.erase(m_presets.begin() + m_num_default_presets, m_presets.end()); this->select_preset(0); } } +void PresetCollection::add_default_preset(const std::vector &keys, const std::string &preset_name) +{ + // Insert just the default preset. + m_presets.emplace_back(Preset(this->type(), preset_name, true)); + m_presets.back().load(keys); + ++ m_num_default_presets; +} + // Load all presets found in dir_path. // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) @@ -458,7 +469,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri errors_cummulative += "\n"; } } - std::sort(m_presets.begin() + 1, m_presets.end()); + std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); @@ -676,7 +687,7 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. size_t PresetCollection::first_visible_idx() const { - size_t idx = m_default_suppressed ? 1 : 0; + size_t idx = m_default_suppressed ? m_num_default_presets : 0; for (; idx < this->m_presets.size(); ++ idx) if (m_presets[idx].is_visible) break; @@ -689,7 +700,7 @@ void PresetCollection::set_default_suppressed(bool default_suppressed) { if (m_default_suppressed != default_suppressed) { m_default_suppressed = default_suppressed; - m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > 1 && m_idx_selected > 0); + m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > m_num_default_presets && m_idx_selected > 0); } } @@ -697,9 +708,10 @@ size_t PresetCollection::update_compatible_with_printer_internal(const Preset &a { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); - config.set_key_value("num_extruders", new ConfigOptionInt( - (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size())); - for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) { + const ConfigOption *opt = active_printer.config.option("nozzle_diameter"); + if (opt) + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + for (size_t idx_preset = m_num_default_presets; idx_preset < m_presets.size(); ++ idx_preset) { bool selected = idx_preset == m_idx_selected; Preset &preset_selected = m_presets[idx_preset]; Preset &preset_edited = selected ? m_edited_preset : preset_selected; @@ -740,7 +752,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) wxString selected = ""; if (!this->m_presets.front().is_visible) ui->Append("------- " +_(L("System presets")) + " -------", wxNullBitmap); - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) { + for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) continue; @@ -778,7 +790,7 @@ void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) if (i == m_idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } - if (preset.is_default) + if (i + 1 == m_num_default_presets) ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); } if (!nonsys_presets.empty()) @@ -808,7 +820,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati wxString selected = ""; if (!this->m_presets.front().is_visible) ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++i) { + for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) continue; @@ -838,7 +850,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati if (i == m_idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } - if (preset.is_default) + if (i + 1 == m_num_default_presets) ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); } if (!nonsys_presets.empty()) @@ -930,7 +942,7 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b idx = it - m_presets.begin(); else { // Find the first visible preset. - for (size_t i = m_default_suppressed ? 1 : 0; i < m_presets.size(); ++ i) + for (size_t i = m_default_suppressed ? m_num_default_presets : 0; i < m_presets.size(); ++ i) if (m_presets[i].is_visible) { idx = i; break; @@ -972,7 +984,7 @@ std::vector PresetCollection::merge_presets(PresetCollection &&othe if (preset.is_default || preset.is_external) continue; Preset key(m_type, preset.name); - auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key); + auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key); if (it == m_presets.end() || it->name != preset.name) { if (preset.vendor != nullptr) { // Re-assign a pointer to the vendor structure in the new PresetBundle. diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index f302ec74a2..86356f5b76 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -194,15 +194,15 @@ class PresetCollection { public: // Initialize the PresetCollection with the "- default -" preset. - PresetCollection(Preset::Type type, const std::vector &keys); + PresetCollection(Preset::Type type, const std::vector &keys, const std::string &default_name = "- default -"); ~PresetCollection(); typedef std::deque::iterator Iterator; typedef std::deque::const_iterator ConstIterator; - Iterator begin() { return m_presets.begin() + 1; } - ConstIterator begin() const { return m_presets.begin() + 1; } - Iterator end() { return m_presets.end(); } - ConstIterator end() const { return m_presets.end(); } + Iterator begin() { return m_presets.begin() + m_num_default_presets; } + ConstIterator begin() const { return m_presets.begin() + m_num_default_presets; } + Iterator end() { return m_presets.end(); } + ConstIterator end() const { return m_presets.end(); } void reset(bool delete_files); @@ -210,6 +210,9 @@ public: std::string name() const; const std::deque& operator()() const { return m_presets; } + // Add default preset at the start of the collection, increment the m_default_preset counter. + void add_default_preset(const std::vector &keys, const std::string &preset_name); + // Load ini files of the particular type from the provided directory path. void load_presets(const std::string &dir_path, const std::string &subdir); @@ -295,7 +298,7 @@ public: template size_t first_compatible_idx(PreferedCondition prefered_condition) const { - size_t i = m_default_suppressed ? 1 : 0; + size_t i = m_default_suppressed ? m_num_default_presets : 0; size_t n = this->m_presets.size(); size_t i_compatible = n; for (; i < n; ++ i) @@ -321,7 +324,8 @@ public: const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); } // Return number of presets including the "- default -" preset. - size_t size() const { return this->m_presets.size(); } + size_t size() const { return m_presets.size(); } + bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; } // For Print / Filament presets, disable those, which are not compatible with the printer. template @@ -386,8 +390,16 @@ private: std::deque::iterator find_preset_internal(const std::string &name) { Preset key(m_type, name); - auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key); - return ((it == m_presets.end() || it->name != name) && m_presets.front().name == name) ? m_presets.begin() : it; + auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key); + if (it == m_presets.end() || it->name != name) { + // Preset has not been not found in the sorted list of non-default presets. Try the defaults. + for (size_t i = 0; i < m_num_default_presets; ++ i) + if (m_presets[i].name == name) { + it = m_presets.begin() + i; + break; + } + } + return it; } std::deque::const_iterator find_preset_internal(const std::string &name) const { return const_cast(this)->find_preset_internal(name); } @@ -407,7 +419,8 @@ private: // Selected preset. int m_idx_selected; // Is the "- default -" preset suppressed? - bool m_default_suppressed = true; + bool m_default_suppressed = true; + size_t m_num_default_presets = 0; // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter. // These bitmaps are not owned by PresetCollection, but by a PresetBundle. const wxBitmap *m_bitmap_compatible = nullptr; diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 85885ea5e5..7047841bee 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -43,7 +43,7 @@ PresetBundle::PresetBundle() : prints(Preset::TYPE_PRINT, Preset::print_options()), filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options()), - printers(Preset::TYPE_PRINTER, Preset::printer_options()), + printers(Preset::TYPE_PRINTER, Preset::printer_options(), "- default FFF -"), m_bitmapCompatible(new wxBitmap), m_bitmapIncompatible(new wxBitmap), m_bitmapLock(new wxBitmap), @@ -74,13 +74,18 @@ PresetBundle::PresetBundle() : this->sla_materials.default_preset().compatible_printers_condition(); this->sla_materials.default_preset().inherits(); - this->printers.default_preset().config.optptr("printer_settings_id", true); - this->printers.default_preset().config.optptr("printer_vendor", true); - this->printers.default_preset().config.optptr("printer_model", true); - this->printers.default_preset().config.optptr("printer_variant", true); - this->printers.default_preset().config.optptr("default_print_profile", true); - this->printers.default_preset().config.option("default_filament_profile", true)->values = { "" }; - this->printers.default_preset().inherits(); + this->printers.add_default_preset(Preset::sla_printer_options(), "- default SLA -"); + this->printers.preset(1).printer_technology() = ptSLA; + for (size_t i = 0; i < 2; ++ i) { + Preset &preset = this->printers.preset(i); + preset.config.optptr("printer_settings_id", true); + preset.config.optptr("printer_vendor", true); + preset.config.optptr("printer_model", true); + preset.config.optptr("printer_variant", true); + preset.config.optptr("default_print_profile", true); + preset.config.option("default_filament_profile", true)->values = { "" }; + preset.inherits(); + } // Load the default preset bitmaps. this->prints .load_bitmap_default("cog.png"); @@ -1118,6 +1123,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla void PresetBundle::update_multi_material_filament_presets() { + if (printers.get_edited_preset().printer_technology() != ptFFF) + return; + // Verify and select the filament presets. auto *nozzle_diameter = static_cast(printers.get_edited_preset().config.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); @@ -1158,36 +1166,51 @@ void PresetBundle::update_multi_material_filament_presets() void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible) { const Preset &printer_preset = this->printers.get_edited_preset(); - const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); - const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; - prefered_print_profile.empty() ? - this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : - this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible, - [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); - prefered_filament_profiles.empty() ? - this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : - this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible, - [&prefered_filament_profiles](const std::string& profile_name) - { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); }); - if (select_other_if_incompatible) { - // Verify validity of the current filament presets. - this->filament_presets.front() = this->filaments.get_edited_preset().name; - for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) { - std::string &filament_name = this->filament_presets[idx]; - Preset *preset = this->filaments.find_preset(filament_name, false); - if (preset == nullptr || ! preset->is_compatible) { - // Pick a compatible profile. If there are prefered_filament_profiles, use them. - if (prefered_filament_profiles.empty()) - filament_name = this->filaments.first_compatible().name; - else { - const std::string &preferred = (idx < prefered_filament_profiles.size()) ? - prefered_filament_profiles[idx] : prefered_filament_profiles.front(); - filament_name = this->filaments.first_compatible( - [&preferred](const std::string& profile_name){ return profile_name == preferred; }).name; + + switch (printers.get_edited_preset().printer_technology()) { + case ptFFF: + { + const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); + const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; + prefered_print_profile.empty() ? + this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); + prefered_filament_profiles.empty() ? + this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_filament_profiles](const std::string& profile_name) + { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); }); + if (select_other_if_incompatible) { + // Verify validity of the current filament presets. + this->filament_presets.front() = this->filaments.get_edited_preset().name; + for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) { + std::string &filament_name = this->filament_presets[idx]; + Preset *preset = this->filaments.find_preset(filament_name, false); + if (preset == nullptr || ! preset->is_compatible) { + // Pick a compatible profile. If there are prefered_filament_profiles, use them. + if (prefered_filament_profiles.empty()) + filament_name = this->filaments.first_compatible().name; + else { + const std::string &preferred = (idx < prefered_filament_profiles.size()) ? + prefered_filament_profiles[idx] : prefered_filament_profiles.front(); + filament_name = this->filaments.first_compatible( + [&preferred](const std::string& profile_name){ return profile_name == preferred; }).name; + } } } } } + case ptSLA: + { + const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile"); + const std::vector &prefered_filament_profiles = printer_preset.config.option("default_filament_profile")->values; + prefered_print_profile.empty() ? + this->sla_materials.update_compatible_with_printer(printer_preset, select_other_if_incompatible) : + this->sla_materials.update_compatible_with_printer(printer_preset, select_other_if_incompatible, + [&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; }); + } + } } void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index fed8385193..68ec534dae 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -64,7 +64,7 @@ public: ObsoletePresets obsolete_presets; bool has_defauls_only() const - { return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; } + { return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); } DynamicPrintConfig full_config() const; diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 187030f31b..7a9484db9e 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -2052,48 +2052,52 @@ void Tab::rebuild_page_tree() // Called by the UI combo box when the user switches profiles. // Select a preset by a name.If !defined(name), then the default preset is selected. // If the current profile is modified, user is asked to save the changes. -void Tab::select_preset(const std::string& preset_name /*= ""*/) +void Tab::select_preset(std::string preset_name /*= ""*/) { - std::string name = preset_name; - auto force = false; - auto presets = m_presets; // If no name is provided, select the "-- default --" preset. - if (name.empty()) - name= presets->default_preset().name; - auto current_dirty = presets->current_is_dirty(); - auto canceled = false; - auto printer_tab = presets->name().compare("printer")==0; + if (preset_name.empty()) + preset_name = m_presets->default_preset().name; + auto current_dirty = m_presets->current_is_dirty(); + auto printer_tab = m_presets->name() == "printer"; + auto canceled = false; m_reload_dependent_tabs = {}; - if (!force && current_dirty && !may_discard_current_dirty_preset()) { + if (!current_dirty && !may_discard_current_dirty_preset()) { canceled = true; - } else if(printer_tab) { + } else if (printer_tab) { // Before switching the printer to a new one, verify, whether the currently active print and filament // are compatible with the new printer. // If they are not compatible and the current print or filament are dirty, let user decide // whether to discard the changes or keep the current printer selection. - auto new_printer_preset = presets->find_preset(name, true); - auto print_presets = &m_preset_bundle->prints; - bool print_preset_dirty = print_presets->current_is_dirty(); - bool print_preset_compatible = print_presets->get_edited_preset().is_compatible_with_printer(*new_printer_preset); - canceled = !force && print_preset_dirty && !print_preset_compatible && - !may_discard_current_dirty_preset(print_presets, name); - auto filament_presets = &m_preset_bundle->filaments; - bool filament_preset_dirty = filament_presets->current_is_dirty(); - bool filament_preset_compatible = filament_presets->get_edited_preset().is_compatible_with_printer(*new_printer_preset); - if (!canceled && !force) { - canceled = filament_preset_dirty && !filament_preset_compatible && - !may_discard_current_dirty_preset(filament_presets, name); + // + // With the introduction of the SLA printer types, we need to support switching between + // the FFF and SLA printers. + const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true); + PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); + PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); + struct PresetUpdate { + std::string name; + PresetCollection *presets; + PrinterTechnology technology; + bool old_preset_dirty; + bool new_preset_compatible; + }; + std::vector updates = { + { "print", &m_preset_bundle->prints, ptFFF }, + { "filament", &m_preset_bundle->filaments, ptFFF }, + { "sla_materials", &m_preset_bundle->sla_materials, ptSLA } + }; + for (PresetUpdate &pu : updates) { + pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty(); + pu.new_preset_compatible = (new_printer_technology == pu.technology) && pu.presets->get_edited_preset().is_compatible_with_printer(new_printer_preset); + if (! canceled) + canceled = pu.old_preset_dirty && ! pu.new_preset_compatible && ! may_discard_current_dirty_preset(pu.presets, preset_name); } - if (!canceled) { - if (!print_preset_compatible) { + if (! canceled) { + for (PresetUpdate &pu : updates) { // The preset will be switched to a different, compatible preset, or the '-- default --'. - m_reload_dependent_tabs.push_back("print"); - if (print_preset_dirty) print_presets->discard_current_changes(); - } - if (!filament_preset_compatible) { - // The preset will be switched to a different, compatible preset, or the '-- default --'. - m_reload_dependent_tabs.push_back("filament"); - if (filament_preset_dirty) filament_presets->discard_current_changes(); + m_reload_dependent_tabs.emplace_back(pu.name); + if (pu.old_preset_dirty) + pu.presets->discard_current_changes(); } } } @@ -2102,10 +2106,10 @@ void Tab::select_preset(const std::string& preset_name /*= ""*/) // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the platter. on_presets_changed(); - } - else { - if (current_dirty) presets->discard_current_changes() ; - presets->select_preset_by_name(name, force); + } else { + if (current_dirty) + m_presets->discard_current_changes() ; + m_presets->select_preset_by_name(preset_name, false); // Mark the print & filament enabled if they are compatible with the currently selected preset. // The following method should not discard changes of current print or filament presets on change of a printer profile, // if they are compatible with the current printer. @@ -2114,7 +2118,6 @@ void Tab::select_preset(const std::string& preset_name /*= ""*/) // Initialize the UI from the current preset. load_current_preset(); } - } // If the current preset is dirty, the user is asked whether the changes may be discarded. diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 8b4eae7de5..7e7a7dc6e5 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -219,7 +219,7 @@ public: void create_preset_tab(PresetBundle *preset_bundle); void load_current_preset(); void rebuild_page_tree(); - void select_preset(const std::string& preset_name = ""); + void select_preset(std::string preset_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn); diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 2c63db10c4..9486012468 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -32,7 +32,6 @@ %name{Slic3r::GUI::PresetCollection} class PresetCollection { Ref preset(size_t idx) %code%{ RETVAL = &THIS->preset(idx); %}; - Ref default_preset() %code%{ RETVAL = &THIS->default_preset(); %}; size_t size() const; size_t num_visible() const; std::string name() const; From ed5f5239aa10d32fd48668ae2b0d8cc01d044eef Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 1 Aug 2018 12:49:39 +0200 Subject: [PATCH 097/185] Added tooltips for objects list Updated icons with transparency --- resources/icons/add_object.png | Bin 591 -> 829 bytes resources/icons/exclamation_mark_.png | Bin 327 -> 1052 bytes resources/icons/lambda.png | Bin 0 -> 913 bytes resources/icons/object.png | Bin 618 -> 1017 bytes resources/icons/split.png | Bin 0 -> 1021 bytes xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 31 +++++++++++++++++++++----- xs/src/slic3r/GUI/wxExtensions.cpp | 6 +++++ xs/src/slic3r/GUI/wxExtensions.hpp | 1 + 8 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 resources/icons/lambda.png create mode 100644 resources/icons/split.png diff --git a/resources/icons/add_object.png b/resources/icons/add_object.png index 40059f5ac0de3b5c5db4ab492b4de5952fe6f25d..ffc958edc4fca5a58a8600c0495eba3941038262 100644 GIT binary patch delta 807 zcmV+?1K9k}1ic23BYy#cX+uL$b5ch_AW20-HZeIiHZ3wPF#rH4k#&*JE5l(N$6p(3 znrXzr!C?ufO-zZ4vL%$XX3B8czWb%N`S$&OrRAzzSCqK;b>J`Hs+^S~QgU!|CNAjf z^WD@W`#e3L=k>gw&*#(gd>*hzb4p&X?}L27&?4hO?_6x&>wkQr0UIt_aKsf|9Sw)4 zsA=!F5-WD?w`$dn`_~$iDO~|ugYbx=YKHKH@YJfITD&FPrexzu;S*t>CKnXGvGhcR zpDoT*_|sx78VP~DS=gKT#fe|6WVO7oyqvClZbkXIkM$CFsxUhvYp2W(bRvus#LFaO zgczisVey0b34hQlJ8I-Bk`WaZ^;yAp4)qAs{jB1 z32;bRa{vGf6951U69E94oEQKA0bxl*K~y-6b<;m<6;Tic@Zaux@eh(HmKMgu5J|p( z5Nv{2ilh+i?X(H_0n%xyfuMaLRf<4JBYp@43&B<^g(o6uG|98rvzwdsxp29A&&-^; zb7n477=L8d*&pCL{@@L!3fnFGz-+M2P-88DC)mVFNM097mN3R#Nl*s9#MOd$8W)58 z!eIywbM_&Dui{jeQWu$rx%O%ZR&X6g0lwhoe}YSFIsP|-8DSm!IKuXX;6;2&g7dhB zbFBs=JkAyG;27VqkorFcdz*8O``OPBwAp5`8-HDn@EP+-P?j)@dZ_uTfK}9e6mdtm z8(V9y64SL%VQl{;`|nVwwe}MOR z=d`8I@H1ttITf{YxPraZzEco9)QtHUPjJcE8R`1jj_d8*=2-KP#(0j)CQN7BmQuJ1 l7a*_^?1kph^aZXk{tE!YT?uli7wrH5002ovPDHLkV1n)!jl=)| delta 567 zcmV-70?7Tn2G0bLBYyw^b5ch_0Itp)=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGm zbN~PnbOGLGA9w%&0pv+UK~y+TRg+JvaZwb;_qpT_82B?J1BT2bLng{V$`_!ND4Cd; z%D`NF4&T6gm4pnGWMV=LUdg~r`Lo{hJg<%WtJPlnS!+GEbAR?x_xoLLw;MxqIQ*4( zJT6Kpna}6cbbYy8N+1wmj#>3NxY=wZ91e3Z7K=%>T4mmBHlH0Li^YPPh8YG!p%4o& z*lxEhg4Jpzr_)Jhvl;7lyPd1s?G}f_Au63tQ%EEdvfJ&RWB-r*c{6 zem|J`Hs+^S~QgU!|CNAjf z^WD@W`#e3L=k>gw&*#(gd>*hzb4p&X?}L27&?4hO?_6x&>wkQr0UIt_aKsf|9Sw)4 zsA=!F5-WD?w`$dn`_~$iDO~|ugYbx=YKHKH@YJfITD&FPrexzu;S*t>CKnXGvGhcR zpDoT*_|sx78VP~DS=gKT#fe|6WVO7oyqvClZbkXIkM$CFsxUhvYp2W(bRvus#LFaO zgczisVey0b34hQlJ8IBL_t(IjeSu~XcJ)&o%y~^*mRT4+HA8iX{i;9y-3f3_nwL<6czDcwH5K`p~Z_R zUi6^e(#`U_MO?*J;BNhJaR z$d~{?Dwusi2E4O>jvszq4&vm}e{%y6U$Id4?)_5K$H~^sR+0Dd=~Z&HX}`2BvJv+u z+qZh+=UtRC&v4*DjYrex0DXsSBi3|+q){%ad&Tc^vN3tKM;BDR$b`F38CmxVCs;E+NcukSOfs@r@Bn<$I>Mf?s?^;X!^`w_lF1DTFHsdkgdmC zIlpKo$yT3uS(XmUWc}`$^mfW=cPMW*GLbW>1leld%QCA~k3l9>Ff&rnIFoW2kf8)f iArD_xhL_*BYX1NYIOVS08S7sF0000Px#1ZP1_K>z@;j|==^1(UA<7k>cUNkl*h^V{@S(0;*kBA_5t1YYIG}kA|Q=Cz2rGVdwCR z;H$t)55W7TBt|w;uL!xXK67ASk{kFuvzw410rC^rcAS74=S6++TJilEXi6>McX0lJ z7|iU1P7q7rew1NEzC`jUDDYq(Z-(ne^@!?3Qtc^-u*E7FMv{aja9GIE?RK6WMzX0$ gq8eg&J`Hs+^S~QgU!|CNAjf^WD@W`#e3L=k>gw&*#(gd>*hzb4p&X z?}L27&?4hO?_6x&>wKaC8!lRK#1&l~4Tq)Ez@Q9*n zhVX>&)T*Icyd~VGWaCNU6Jehw7Zkp+^hAZ9EzVT<(_$?e34y&?*qiyqiC?T_wY;#r zoUVLsMftgp^%8fgFgqh_r_2s?B8(Hn%Oqoj7^I(J@q_pY&?`G?jH+;o@=my*6o`UqSAGS-p% z{eBL71)czznPmy~G4L61tRj2w4cGzRt{_M3Ye^4C$wg6EQ4|Ik@Cw+G5a_i(gZDnL zjA`o;+md$Mu`TZGoEuB}4)j~c4-$W_w3A*2*amh}a$?wh-~hM_9ByoMy1-4~7x2)` zvb4cEx32;pfWJTkd;zXSti|2TjIt~hLSQzV#Xa%f?{~Z1JHSt16S!e!dl73bF03I3 zfTn3;N&gAqSl$Bb$n*J}^QJU21JF6-(@2$LIDinsC6?C~02Yfy2qBy*1G%J?D+;K9 nr<@JNX34t&9>u!%K1kvpr}3V-p+x%d00000NkvXXu0mjf8P=-p literal 0 HcmV?d00001 diff --git a/resources/icons/object.png b/resources/icons/object.png index ff4974a532b083cd7e8f741b1a2c81d2d1184045..c85cbaf2fa5905e4738034d7bdb069b9abc4f10f 100644 GIT binary patch delta 996 zcmVlP4z`63!j` z_SQ@fhN@p^oq?uj+kWo>&mnwm+xF+NZGV-)Exugs*6WMAF#Ia|PF)Kefo}`WFY8+J z1TJ=<<55o-iHhWwqUvR?_< zwf0jG1UmrUNJ>cFA$dvCx^r$JNs?B%T>f!{z!5m=Nyg`WWMju)oQiDQ^1FoYPH&6sZ^Q+kOBP=B`uL$ zDwoSof*@GSvTQYuPx#1ZP1_K>z@;j|==^1poj532;bRa{vGm zbN~PnbOGLGA9w%&0sl!vK~y+TjZ?qNfl(Ab@9ReOv6_rTgp_1pmrdb*_c^@fGkkqdz1{2F=Q;PBdmhT;@qZ8!1SpC^s;crH*zfmr zzu(F0^}ZuS&*zg@Xu#(LpQ7b*Nxfc=`u#o=OePauuUE?F^HeMrDHsgCnM6{{nZ#UO&&v)Rm~&sG44zk#;&dOcYz7Fw^@q~exT zDn&>XkH@(JSt7p$lH$Xy-ENm}j7Fn`iW7;%>w<7NEM~JA+F^`FBT+7wh12O2kw`?u zVlm-xI7F#b62sx}%@VAER*$l7w;Puy9x-n*TA>HIW;5GqpB!1xXLdi zxhgx^GDXSWj?1RP3TQxXYDuC(MQ%=Bu~mhw64+cTAR8pCucQE0Qj%?}6yY17;GAES zs$i;Ts%M~N$E9FXl#*r@k>g7FXt#Bv$C=6)S^`fSBQuTAW;zSx}OhpQivaGchT@w8U0PKeRZts8~NOGbJ@A zIk6yLAE++7B)>qvxF9F9M6Vz(T}dD041K6OfF9C^`v}N^he>K4Fd&M6K7M>HO$!*1 z?U~Nb0mVV?P70o`&Sr*|W@frZdWH-P8WT$=?)E+$AkuChz4R!%?3)JBqc2)E#7CU`^WX`8*k-r53J;43=cit|Wpi%+maNvLtnSmVc6v_Xc&y=Le3B)R zO=6<0&x8pUCJOid#>%g{az1cMRA|{;UY=_wCLCQ9eq~b0^(S-1VkfJsOA4Kn{qyab z4}aUL_>8%Y%hGSxy=OeHSd)AE+w+aN^-~Y(?u&VQ?REEVgFjlo{9?If8uPi&>A#Np z#G+TW;Blb8L}%BNX-~bDKYS~4>(PZe=Cj(N_p_c)d7oG)oVGskKf^AS``dr<$}y(9 zW}fjm1x&!41s;*b3=DinK$vl=HlH+5@Uo|iV~9m>@8p+U%?1K3`(=W>7jo48->wt# zdg8x=B|&4Ky_w1DV5OL^kaU32_5!1^K!dBF;~Yn;bk+`~^9Ld(Fd2CAO*~yx zB*haQnY)Vl$DtXU`470J{% literal 0 HcmV?d00001 diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 59688606e7..c1829dada2 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -101,12 +101,15 @@ void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) { auto options = get_options(is_part); + auto extruders_cnt = get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + DynamicPrintConfig config; for (auto& option : options) { auto const opt = config.def()->get(option); auto category = opt->category; - if (category.empty()) continue; + if (category.empty() || + (category == "Extruders" && extruders_cnt == 1)) continue; std::pair option_label(option, opt->label); std::vector< std::pair > new_category; @@ -135,7 +138,7 @@ void set_objects_from_model(Model &model) { } void init_mesh_icons(){ - m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("lambda_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); + m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); // init icon for manifold warning @@ -195,7 +198,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->AppendColumn(column3); // column 4 of the view control: - m_objects_ctrl->AppendBitmapColumn("", 4, wxDATAVIEW_CELL_INERT, 25, + m_objects_ctrl->AppendBitmapColumn(" ", 4, wxDATAVIEW_CELL_INERT, 25, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) @@ -248,6 +251,19 @@ wxBoxSizer* content_objects_list(wxWindow *win) }); #endif //__WXMSW__ + m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { + wxPoint pt = event.GetPosition(); + wxDataViewItem item; + wxDataViewColumn* col; + m_objects_ctrl->HitTest(pt, item, col); + if (col->GetTitle() == " " && item) + m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("For object settings changing click a right button on icon"))); + else + m_objects_ctrl->GetMainWindow()->SetToolTip(""); +// if (m_objects_model->GetIcon(item) == m_icon_manifold_warning) +// m_objects_ctrl->GetMainWindow()->SetToolTip("Tru-lala"); + }); + return objects_sz; } @@ -604,9 +620,11 @@ void add_object_to_list(const std::string &name, ModelObject* model_object) m_objects_model->SetValue(variant, item, 0); } - if (model_object->volumes.size() > 1) + if (model_object->volumes.size() > 1) { for (auto id = 0; id < model_object->volumes.size(); id++) m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false); + m_objects_ctrl->Expand(item); + } // part_selection_changed(); #ifdef __WXMSW__ @@ -741,9 +759,12 @@ void update_settings_list() if (opt_keys.size() == 1 && opt_keys[0] == "extruder") return; + auto extruders_cnt = get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + for (auto& opt_key : opt_keys) { auto category = (*m_config)->def()->get(opt_key)->category; - if (category.empty()) continue; + if (category.empty() || + (category == "Extruders" && extruders_cnt==1)) continue; std::vector< std::string > new_category; diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 020ea570c6..e7bb27b928 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -566,6 +566,12 @@ wxString PrusaObjectDataViewModel::GetScale(const wxDataViewItem &item) const return node->m_scale; } +wxIcon PrusaObjectDataViewModel::GetIcon(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->m_icon; +} + void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const { wxASSERT(item.IsOk()); diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index ac9fd4da00..fbe8129fe1 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -365,6 +365,7 @@ public: wxString GetName(const wxDataViewItem &item) const; wxString GetCopy(const wxDataViewItem &item) const; wxString GetScale(const wxDataViewItem &item) const; + wxIcon GetIcon(const wxDataViewItem &item) const; // helper methods to change the model From 6fb4ced41012096519c24907365887c1b92a629f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 1 Aug 2018 15:54:56 +0200 Subject: [PATCH 098/185] Fix for previous commit --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 43 ++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index c1829dada2..a0dee41172 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -204,16 +204,35 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { object_ctrl_selection_changed(); -#ifdef __WXOSX__ - update_extruder_in_config(g_selected_extruder); -#endif //__WXOSX__ +// #ifdef __WXOSX__ +// update_extruder_in_config(g_selected_extruder); +// #endif //__WXOSX__ }); - m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxEvent& event) - { +// m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) + m_objects_ctrl->GetMainWindow()->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event) { + wxPoint pt = event.GetPosition(); + wxDataViewItem item; + wxDataViewColumn* col; + m_objects_ctrl->HitTest(pt, item, col); + wxString title = col->GetTitle(); + if (item && (title==" " || title == _("Name"))) { + if (item != m_objects_ctrl->GetSelection()) { + m_objects_ctrl->Select(item); + object_ctrl_selection_changed(); + g_prevent_list_events = false; + } + + if (title == " ") + object_ctrl_context_menu(); + else if (title == _("Name") && pt.x >15 && + m_objects_model->GetParent(item) == wxDataViewItem(0)) + { +// auto menu = create_add_settings_popupmenu(true);// create_correction_stl_menu !!! +// get_tab_panel()->GetPage(0)->PopupMenu(menu); + } + } event.Skip(); - object_ctrl_context_menu(); - }); m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) @@ -257,11 +276,11 @@ wxBoxSizer* content_objects_list(wxWindow *win) wxDataViewColumn* col; m_objects_ctrl->HitTest(pt, item, col); if (col->GetTitle() == " " && item) - m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("For object settings changing click a right button on icon"))); + m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("For object settings changing click on icon"))); +// else if (col->GetTitle() == _("Name") && item && m_objects_model->GetIcon(item) == m_icon_manifold_warning ) +// m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("Information about auto-repaired errors\n To fix errors, click on the icon"))); else m_objects_ctrl->GetMainWindow()->SetToolTip(""); -// if (m_objects_model->GetIcon(item) == m_icon_manifold_warning) -// m_objects_ctrl->GetMainWindow()->SetToolTip("Tru-lala"); }); return objects_sz; @@ -717,6 +736,10 @@ void object_ctrl_selection_changed() event.SetId(m_selected_object_id); get_main_frame()->ProcessWindowEvent(event); } + +#ifdef __WXOSX__ + update_extruder_in_config(g_selected_extruder); +#endif //__WXOSX__ } //update_optgroup From cce0e9e5019068c168977ce093aa1a1a99b68ec7 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 3 Aug 2018 13:04:41 +0200 Subject: [PATCH 099/185] First prototype for "SLA materials" Tab --- xs/src/libslic3r/PrintConfig.cpp | 4 +- xs/src/slic3r/GUI/GUI.cpp | 9 ++-- xs/src/slic3r/GUI/Preset.cpp | 18 ++++--- xs/src/slic3r/GUI/Preset.hpp | 2 +- xs/src/slic3r/GUI/Tab.cpp | 86 ++++++++++++++++++++++++++++++++ xs/src/slic3r/GUI/Tab.hpp | 12 +++++ 6 files changed, 118 insertions(+), 13 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 698156d59d..881b2dd11e 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -2124,13 +2124,13 @@ void PrintConfigDef::init_sla_params() def->default_value = new ConfigOptionFloat(15); def = this->add("material_correction_printing", coFloats); - def->label = L("Correction for expansion when printing"); + def->full_label = L("Correction for expansion when printing"); def->tooltip = L("Correction for expansion when printing"); def->min = 0; def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } ); def = this->add("material_correction_curing", coFloats); - def->label = L("Correction for expansion after curing"); + def->full_label = L("Correction for expansion after curing"); def->tooltip = L("Correction for expansion after curing"); def->min = 0; def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } ); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 9c62fe8ebf..b8632d18b7 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -496,12 +496,13 @@ void open_preferences_dialog(int event_preferences) void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) { update_label_colours_from_appconfig(); - add_created_tab(new TabPrint (g_wxTabPanel, no_controller)); - add_created_tab(new TabFilament (g_wxTabPanel, no_controller)); - add_created_tab(new TabPrinter (g_wxTabPanel, no_controller)); + add_created_tab(new TabPrint (g_wxTabPanel, no_controller)); + add_created_tab(new TabFilament (g_wxTabPanel, no_controller)); + add_created_tab(new TabSLAMaterial (g_wxTabPanel, no_controller)); + add_created_tab(new TabPrinter (g_wxTabPanel, no_controller)); for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (! tab) + if (! tab || tab->GetName()=="sla_material") continue; tab->set_event_value_change(wxEventType(event_value_change)); tab->set_event_presets_changed(wxEventType(event_presets_changed)); diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 71a2bb6a59..642346d6b0 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -211,11 +211,17 @@ void Preset::normalize(DynamicPrintConfig &config) // Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file. // In case of a "default" config item, return the default values. -DynamicPrintConfig& Preset::load(const std::vector &keys) +DynamicPrintConfig& Preset::load(const std::vector &keys, Preset::Type& type) { // Set the configuration from the defaults. - Slic3r::FullPrintConfig defaults; - this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); + if (type == TYPE_SLA_MATERIAL) { + Slic3r::SLAFullPrintConfig defaults; + this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); + } + else { + Slic3r::FullPrintConfig defaults; + this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); + } if (! this->is_default) { // Load the preset file, apply preset values on top of defaults. try { @@ -405,7 +411,7 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectoradd_default_preset(keys, default_name); - m_presets.front().load(keys); + m_presets.front().load(keys, m_type); m_edited_preset.config.apply(m_presets.front().config); } @@ -436,7 +442,7 @@ void PresetCollection::add_default_preset(const std::vector &keys, { // Insert just the default preset. m_presets.emplace_back(Preset(this->type(), preset_name, true)); - m_presets.back().load(keys); + m_presets.back().load(keys, m_type); ++ m_num_default_presets; } @@ -462,7 +468,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri try { Preset preset(m_type, name, false); preset.file = dir_entry.path().string(); - preset.load(keys); + preset.load(keys, m_type); m_presets.emplace_back(preset); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 86356f5b76..09ad6fed50 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -126,7 +126,7 @@ public: // Load this profile for the following keys only. // Throws std::runtime_error in case the file cannot be read. - DynamicPrintConfig& load(const std::vector &keys); + DynamicPrintConfig& load(const std::vector &keys, Preset::Type& type); void save(); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 7a9484db9e..f066260435 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -2759,5 +2759,91 @@ void SavePresetWindow::accept() } } +void TabSLAMaterial::build() +{ + m_presets = &m_preset_bundle->sla_materials; + load_initial_data(); + + auto page = add_options_page(_(L("General")), "spool.png"); + +/* auto optgroup = page->new_optgroup(_(L("Display"))); + optgroup->append_single_option_line("display_width"); + optgroup->append_single_option_line("display_height"); + + Line line = { _(L("Number of pixels in axes")), "" }; + line.append_option(optgroup->get_option("display_pixels_x")); + line.append_option(optgroup->get_option("display_pixels_y")); + optgroup->append_line(line); +*/ + auto optgroup = page->new_optgroup(_(L("Layers"))); + optgroup->append_single_option_line("layer_height"); + optgroup->append_single_option_line("initial_layer_height"); + + optgroup = page->new_optgroup(_(L("Exposure"))); + optgroup->append_single_option_line("exposure_time"); + optgroup->append_single_option_line("initial_exposure_time"); + + optgroup = page->new_optgroup(_(L("Corrections"))); + // Legend for OptionsGroups + optgroup->set_show_modified_btns_val(false); + optgroup->label_width = 230; + auto line = Line{ "", "" }; + + ConfigOptionDef def; + def.type = coString; + def.width = 150; + def.gui_type = "legend"; + + std::vector axes{ "X", "Y", "Z" }; + for (auto& axis : axes) { + def.tooltip = L("Values in this column are for ") + axis + L(" axis"); + def.default_value = new ConfigOptionString{ axis }; + std::string opt_key = axis + "power_legend"; + auto option = Option(def, opt_key); + line.append_option(option); + } + optgroup->append_line(line); + + std::vector corrections = { "material_correction_printing", "material_correction_curing" }; + for (auto& opt_key : corrections){ + line = Line{ m_config->def()->get(opt_key)->full_label, "" }; + for( int id = 0; id < 3; ++id) + line.append_option(optgroup->get_option(opt_key, id)); + optgroup->append_line(line); + } + + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + optgroup->label_width = 0; + Option option = optgroup->get_option("material_notes"); + option.opt.full_width = true; + option.opt.height = 250; + optgroup->append_single_option_line(option); + + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); + line = { _(L("Compatible printers")), "" }; + line.widget = [this](wxWindow* parent){ + return compatible_printers_widget(parent, &m_compatible_printers_checkbox, &m_compatible_printers_btn); + }; + optgroup->append_line(line, &m_colored_Label); + + option = optgroup->get_option("compatible_printers_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); + + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); +} + +void TabSLAMaterial::update() +{ + +} + } // GUI } // Slic3r diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 7e7a7dc6e5..40b51ce293 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -342,6 +342,18 @@ public: void init_options_list() override; }; +class TabSLAMaterial : public Tab +{ +public: + TabSLAMaterial() {} + TabSLAMaterial(wxNotebook* parent, bool no_controller) : + Tab(parent, _(L("SLA Material Settings")), "sla_material", no_controller) {} + ~TabSLAMaterial(){} + + void build() override; + void update() override; +}; + class SavePresetWindow :public wxDialog { public: From f65aadebef104a4806f4995bae86af6a5ef211ab Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 3 Aug 2018 14:14:25 +0200 Subject: [PATCH 100/185] Corrected initialization of the SLA presets with their default values. --- xs/src/slic3r/GUI/Preset.cpp | 17 +++++++---------- xs/src/slic3r/GUI/Preset.hpp | 7 +++---- xs/src/slic3r/GUI/PresetBundle.cpp | 14 +++++++------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 71a2bb6a59..412895eeb0 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -209,12 +209,9 @@ void Preset::normalize(DynamicPrintConfig &config) } } -// Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file. -// In case of a "default" config item, return the default values. -DynamicPrintConfig& Preset::load(const std::vector &keys) +DynamicPrintConfig& Preset::load(const std::vector &keys, const StaticPrintConfig &defaults) { // Set the configuration from the defaults. - Slic3r::FullPrintConfig defaults; this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); if (! this->is_default) { // Load the preset file, apply preset values on top of defaults. @@ -396,7 +393,7 @@ const std::vector& Preset::sla_material_options() return s_opts; } -PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const std::string &default_name) : +PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : m_type(type), m_edited_preset(type, "", false), m_idx_selected(0), @@ -404,8 +401,7 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectoradd_default_preset(keys, default_name); - m_presets.front().load(keys); + this->add_default_preset(keys, defaults, default_name); m_edited_preset.config.apply(m_presets.front().config); } @@ -432,11 +428,11 @@ void PresetCollection::reset(bool delete_files) } } -void PresetCollection::add_default_preset(const std::vector &keys, const std::string &preset_name) +void PresetCollection::add_default_preset(const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name) { // Insert just the default preset. m_presets.emplace_back(Preset(this->type(), preset_name, true)); - m_presets.back().load(keys); + m_presets.back().load(keys, defaults); ++ m_num_default_presets; } @@ -462,7 +458,8 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri try { Preset preset(m_type, name, false); preset.file = dir_entry.path().string(); - preset.load(keys); + //FIXME One should initialize with SLAFullPrintConfig for the SLA profiles! + preset.load(keys, static_cast(FullPrintConfig::defaults())); m_presets.emplace_back(preset); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 86356f5b76..d1a4063214 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -125,8 +125,7 @@ public: DynamicPrintConfig config; // Load this profile for the following keys only. - // Throws std::runtime_error in case the file cannot be read. - DynamicPrintConfig& load(const std::vector &keys); + DynamicPrintConfig& load(const std::vector &keys, const StaticPrintConfig &defaults); void save(); @@ -194,7 +193,7 @@ class PresetCollection { public: // Initialize the PresetCollection with the "- default -" preset. - PresetCollection(Preset::Type type, const std::vector &keys, const std::string &default_name = "- default -"); + PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name = "- default -"); ~PresetCollection(); typedef std::deque::iterator Iterator; @@ -211,7 +210,7 @@ public: const std::deque& operator()() const { return m_presets; } // Add default preset at the start of the collection, increment the m_default_preset counter. - void add_default_preset(const std::vector &keys, const std::string &preset_name); + void add_default_preset(const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name); // Load ini files of the particular type from the provided directory path. void load_presets(const std::string &dir_path, const std::string &subdir); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 7047841bee..dc40ced3b2 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -40,10 +40,10 @@ static std::vector s_project_options { }; PresetBundle::PresetBundle() : - prints(Preset::TYPE_PRINT, Preset::print_options()), - filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), - sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options()), - printers(Preset::TYPE_PRINTER, Preset::printer_options(), "- default FFF -"), + prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast(FullPrintConfig::defaults())), + filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults())), + sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast(SLAFullPrintConfig::defaults())), + printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -"), m_bitmapCompatible(new wxBitmap), m_bitmapIncompatible(new wxBitmap), m_bitmapLock(new wxBitmap), @@ -74,7 +74,7 @@ PresetBundle::PresetBundle() : this->sla_materials.default_preset().compatible_printers_condition(); this->sla_materials.default_preset().inherits(); - this->printers.add_default_preset(Preset::sla_printer_options(), "- default SLA -"); + this->printers.add_default_preset(Preset::sla_printer_options(), static_cast(SLAFullPrintConfig::defaults()), "- default SLA -"); this->printers.preset(1).printer_technology() = ptSLA; for (size_t i = 0; i < 2; ++ i) { Preset &preset = this->printers.preset(i); @@ -419,7 +419,7 @@ DynamicPrintConfig PresetBundle::full_config() const DynamicPrintConfig PresetBundle::full_fff_config() const { DynamicPrintConfig out; - out.apply(FullPrintConfig()); + out.apply(FullPrintConfig::defaults()); out.apply(this->prints.get_edited_preset().config); // Add the default filament preset to have the "filament_preset_id" defined. out.apply(this->filaments.default_preset().config); @@ -514,7 +514,7 @@ DynamicPrintConfig PresetBundle::full_fff_config() const DynamicPrintConfig PresetBundle::full_sla_config() const { DynamicPrintConfig out; - out.apply(SLAFullPrintConfig()); + out.apply(SLAFullPrintConfig::defaults()); out.apply(this->sla_materials.get_edited_preset().config); out.apply(this->printers.get_edited_preset().config); // There are no project configuration values as of now, the project_config is reserved for FFF printers. From c30813d9a84b1786a1febc91a287c73823bd3414 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 3 Aug 2018 14:34:52 +0200 Subject: [PATCH 101/185] Added "inside tab" for SLA printer --- xs/src/slic3r/GUI/Tab.cpp | 148 +++++++++++++++++++++++++++----------- xs/src/slic3r/GUI/Tab.hpp | 10 ++- 2 files changed, 117 insertions(+), 41 deletions(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index f066260435..6f21829f19 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -214,7 +214,7 @@ void Tab::load_initial_data() m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; } -PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages/* = false*/) +Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages /*= false*/) { // Index of icon in an icon list $self->{icons}. auto icon_idx = 0; @@ -238,8 +238,10 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo page->SetScrollbars(1, 1, 1, 1); page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); + + std::vector & pages = name() != "printer" ? m_pages : *static_cast(this)->m_current_pages; if (!is_extruder_pages) - m_pages.push_back(page); + pages.push_back(page); page->set_config(m_config); return page; @@ -678,6 +680,36 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) } +void Tab::add_xyz_options_with_legend(ConfigOptionsGroupShp& optgroup, std::vector& options) +{ + // Legend for OptionsGroups + optgroup->set_show_modified_btns_val(false); + optgroup->label_width = 230; + auto line = Line{ "", "" }; + + ConfigOptionDef def; + def.type = coString; + def.width = 150; + def.gui_type = "legend"; + + std::vector axes{ "X", "Y", "Z" }; + for (auto& axis : axes) { + def.tooltip = L("Values in this column are for ") + axis + L(" axis"); + def.default_value = new ConfigOptionString{ axis }; + std::string opt_key = axis + "_power_legend"; + auto option = Option(def, opt_key); + line.append_option(option); + } + optgroup->append_line(line); + + for (auto& opt_key : options){ + line = Line{ m_config->def()->get(opt_key)->full_label, "" }; + for (int id = 0; id < 3; ++id) + line.append_option(optgroup->get_option(opt_key, id)); + optgroup->append_line(line); + } +} + // Show/hide the 'purging volumes' button void Tab::update_wiping_button_visibility() { bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; @@ -1407,6 +1439,8 @@ void TabPrinter::build() m_presets = &m_preset_bundle->printers; load_initial_data(); + m_current_pages = &m_pages; + // to avoid redundant memory allocation / deallocation during extruders count changing m_pages.reserve(30); @@ -1685,6 +1719,72 @@ void TabPrinter::build() if (!m_no_controller) update_serial_ports(); + +// build_sla(); +} + +void TabPrinter::build_sla() +{ + m_presets = &m_preset_bundle->printers; + load_initial_data(); + + m_current_pages = &m_sla_pages; + + auto page = add_options_page(_(L("General")), "printer_empty.png"); + auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); + + Line line{ _(L("Bed shape")), "" }; + line.widget = [this](wxWindow* parent){ + auto btn = new wxButton(parent, wxID_ANY, _(L(" Set ")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); + // btn->SetFont(Slic3r::GUI::small_font); + btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); + + btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) + { + auto dlg = new BedShapeDialog(this); + dlg->build_dialog(m_config->option("bed_shape")); + if (dlg->ShowModal() == wxID_OK){ + load_key_value("bed_shape", dlg->GetValue()); + update_changed_ui(); + } + })); + + return sizer; + }; + optgroup->append_line(line, &m_colored_Label); + optgroup->append_single_option_line("max_print_height"); + + optgroup = page->new_optgroup(_(L("Display"))); + optgroup->append_single_option_line("display_width"); + optgroup->append_single_option_line("display_height"); + + line = { _(L("Number of pixels in axes")), "" }; + line.append_option(optgroup->get_option("display_pixels_x")); + line.append_option(optgroup->get_option("display_pixels_y")); + optgroup->append_line(line); + + optgroup = page->new_optgroup(_(L("Corrections"))); + std::vector corrections = { "printer_correction" }; + add_xyz_options_with_legend(optgroup, corrections); + + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + auto option = optgroup->get_option("printer_notes"); + option.opt.full_width = true; + option.opt.height = 250; + optgroup->append_single_option_line(option); + + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); } void TabPrinter::update_serial_ports(){ @@ -2170,9 +2270,11 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) wxWindowUpdateLocker noUpdates(this); #endif + std::vector & pages = name() != "printer" ? m_pages : *static_cast(this)->m_current_pages; + Page* page = nullptr; auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); - for (auto p : m_pages) + for (auto p : pages) if (p->title() == selection) { page = p.get(); @@ -2182,7 +2284,7 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) } if (page == nullptr) return; - for (auto& el : m_pages) + for (auto& el : pages) el.get()->Hide(); #ifdef __linux__ @@ -2766,15 +2868,6 @@ void TabSLAMaterial::build() auto page = add_options_page(_(L("General")), "spool.png"); -/* auto optgroup = page->new_optgroup(_(L("Display"))); - optgroup->append_single_option_line("display_width"); - optgroup->append_single_option_line("display_height"); - - Line line = { _(L("Number of pixels in axes")), "" }; - line.append_option(optgroup->get_option("display_pixels_x")); - line.append_option(optgroup->get_option("display_pixels_y")); - optgroup->append_line(line); -*/ auto optgroup = page->new_optgroup(_(L("Layers"))); optgroup->append_single_option_line("layer_height"); optgroup->append_single_option_line("initial_layer_height"); @@ -2784,33 +2877,8 @@ void TabSLAMaterial::build() optgroup->append_single_option_line("initial_exposure_time"); optgroup = page->new_optgroup(_(L("Corrections"))); - // Legend for OptionsGroups - optgroup->set_show_modified_btns_val(false); - optgroup->label_width = 230; - auto line = Line{ "", "" }; - - ConfigOptionDef def; - def.type = coString; - def.width = 150; - def.gui_type = "legend"; - - std::vector axes{ "X", "Y", "Z" }; - for (auto& axis : axes) { - def.tooltip = L("Values in this column are for ") + axis + L(" axis"); - def.default_value = new ConfigOptionString{ axis }; - std::string opt_key = axis + "power_legend"; - auto option = Option(def, opt_key); - line.append_option(option); - } - optgroup->append_line(line); - std::vector corrections = { "material_correction_printing", "material_correction_curing" }; - for (auto& opt_key : corrections){ - line = Line{ m_config->def()->get(opt_key)->full_label, "" }; - for( int id = 0; id < 3; ++id) - line.append_option(optgroup->get_option(opt_key, id)); - optgroup->append_line(line); - } + add_xyz_options_with_legend(optgroup, corrections); page = add_options_page(_(L("Notes")), "note.png"); optgroup = page->new_optgroup(_(L("Notes")), 0); @@ -2822,7 +2890,7 @@ void TabSLAMaterial::build() page = add_options_page(_(L("Dependencies")), "wrench.png"); optgroup = page->new_optgroup(_(L("Profile dependencies"))); - line = { _(L("Compatible printers")), "" }; + auto line = Line { _(L("Compatible printers")), "" }; line.widget = [this](wxWindow* parent){ return compatible_printers_widget(parent, &m_compatible_printers_checkbox, &m_compatible_printers_btn); }; diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 40b51ce293..36622d2021 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -267,6 +267,9 @@ public: void on_value_change(const std::string& opt_key, const boost::any& value); + void add_xyz_options_with_legend(ConfigOptionsGroupShp& optgroup, + std::vector& options); + protected: void on_presets_changed(); void update_preset_description_line(); @@ -319,6 +322,8 @@ class TabPrinter : public Tab bool m_use_silent_mode = false; void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key); bool m_rebuild_kinematics_page = false; + + std::vector m_sla_pages; public: wxButton* m_serial_test_btn; wxButton* m_octoprint_host_test_btn; @@ -328,12 +333,15 @@ public: size_t m_initial_extruders_count; size_t m_sys_extruders_count; + std::vector *m_current_pages; + TabPrinter() {} TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {} ~TabPrinter(){} void build() override; - void update() override; + void build_sla() ; + void update() override; void update_serial_ports(); void extruders_count_changed(size_t extruders_count); PageShp build_kinematics_page(); From d4c69a6258ce181ceb526da22bb41e1635d964f6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 3 Aug 2018 16:20:39 +0200 Subject: [PATCH 102/185] Tabs are completed --- xs/src/libslic3r/PrintConfig.cpp | 2 +- xs/src/slic3r/GUI/Tab.cpp | 63 +++++++++++++------------------- xs/src/slic3r/GUI/Tab.hpp | 3 -- 3 files changed, 27 insertions(+), 41 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 881b2dd11e..3bcd8177d8 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -2093,7 +2093,7 @@ void PrintConfigDef::init_sla_params() def->default_value = new ConfigOptionInt(1000); def = this->add("printer_correction", coFloats); - def->label = L("Printer scaling correction"); + def->full_label = L("Printer scaling correction"); def->tooltip = L("Printer scaling correction"); def->min = 0; def->default_value = new ConfigOptionFloats( { 1., 1., 1. } ); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 6f21829f19..a6d24fd759 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -679,37 +679,6 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) update(); } - -void Tab::add_xyz_options_with_legend(ConfigOptionsGroupShp& optgroup, std::vector& options) -{ - // Legend for OptionsGroups - optgroup->set_show_modified_btns_val(false); - optgroup->label_width = 230; - auto line = Line{ "", "" }; - - ConfigOptionDef def; - def.type = coString; - def.width = 150; - def.gui_type = "legend"; - - std::vector axes{ "X", "Y", "Z" }; - for (auto& axis : axes) { - def.tooltip = L("Values in this column are for ") + axis + L(" axis"); - def.default_value = new ConfigOptionString{ axis }; - std::string opt_key = axis + "_power_legend"; - auto option = Option(def, opt_key); - line.append_option(option); - } - optgroup->append_line(line); - - for (auto& opt_key : options){ - line = Line{ m_config->def()->get(opt_key)->full_label, "" }; - for (int id = 0; id < 3; ++id) - line.append_option(optgroup->get_option(opt_key, id)); - optgroup->append_line(line); - } -} - // Show/hide the 'purging volumes' button void Tab::update_wiping_button_visibility() { bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; @@ -1720,13 +1689,13 @@ void TabPrinter::build() if (!m_no_controller) update_serial_ports(); -// build_sla(); + build_sla(); } void TabPrinter::build_sla() { - m_presets = &m_preset_bundle->printers; - load_initial_data(); +// m_presets = &m_preset_bundle->printers; +// load_initial_data(); m_current_pages = &m_sla_pages; @@ -1767,8 +1736,16 @@ void TabPrinter::build_sla() optgroup->append_line(line); optgroup = page->new_optgroup(_(L("Corrections"))); - std::vector corrections = { "printer_correction" }; - add_xyz_options_with_legend(optgroup, corrections); + line = Line{ m_config->def()->get("printer_correction")->full_label, "" }; + std::vector axes{ "X", "Y", "Z" }; + int id = 0; + for (auto& axis : axes) { + auto opt = optgroup->get_option("printer_correction", id); + opt.opt.label = axis; + line.append_option(opt); + ++id; + } + optgroup->append_line(line); page = add_options_page(_(L("Notes")), "note.png"); optgroup = page->new_optgroup(_(L("Notes")), 0); @@ -2878,7 +2855,19 @@ void TabSLAMaterial::build() optgroup = page->new_optgroup(_(L("Corrections"))); std::vector corrections = { "material_correction_printing", "material_correction_curing" }; - add_xyz_options_with_legend(optgroup, corrections); + std::vector axes{ "X", "Y", "Z" }; + for (auto& opt_key : corrections){ + auto line = Line{ m_config->def()->get(opt_key)->full_label, "" }; + int id = 0; + for (auto& axis : axes) { + auto opt = optgroup->get_option(opt_key, id); + opt.opt.label = axis; + opt.opt.width = 60; + line.append_option(opt); + ++id; + } + optgroup->append_line(line); + } page = add_options_page(_(L("Notes")), "note.png"); optgroup = page->new_optgroup(_(L("Notes")), 0); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 36622d2021..4a8f95b986 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -267,9 +267,6 @@ public: void on_value_change(const std::string& opt_key, const boost::any& value); - void add_xyz_options_with_legend(ConfigOptionsGroupShp& optgroup, - std::vector& options); - protected: void on_presets_changed(); void update_preset_description_line(); From 2af2b05bd6c8ac8bae47381405aee235e2eca685 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 6 Aug 2018 17:01:41 +0200 Subject: [PATCH 103/185] Correct switching between printer_technologies (show/hide according tabs) --- lib/Slic3r/GUI/MainFrame.pm | 2 + xs/src/libslic3r/PrintConfig.cpp | 3 ++ xs/src/slic3r/GUI/GUI.cpp | 46 +++++++++++++++++++- xs/src/slic3r/GUI/GUI.hpp | 5 +++ xs/src/slic3r/GUI/PresetBundle.cpp | 30 +++++++------ xs/src/slic3r/GUI/Tab.cpp | 70 ++++++++++++++++++++++++++---- xs/src/slic3r/GUI/Tab.hpp | 2 + 7 files changed, 133 insertions(+), 25 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 77d7956c93..91f0c57f24 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -201,6 +201,8 @@ sub _init_tabpanel { # load initial config my $full_config = wxTheApp->{preset_bundle}->full_config; $self->{plater}->on_config_change($full_config); + #return if $num_extruders is undefined because of SLA printer is selected + return if (!defined $full_config->nozzle_diameter); # ys_FIXME # Show a correct number of filament fields. $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 3bcd8177d8..933feb229f 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -2149,6 +2149,9 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Default print profile associated with the current printer profile. " "On selection of the current printer profile, this print profile will be activated."); def->default_value = new ConfigOptionString(); + + def = this->add("sla_material_settings_id", coString); + def->default_value = new ConfigOptionString(""); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index b8632d18b7..c4851b26c8 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -127,6 +127,11 @@ std::shared_ptr m_optgroup; double m_brim_width = 0.0; wxButton* g_wiping_dialog_button = nullptr; +// Windows, associated with Print, Filament & Material Tabs accordingly +wxWindow *g_PrintTab = nullptr; +wxWindow *g_FilamentTab = nullptr; +wxWindow *g_MaterialTab = nullptr; + static void init_label_colours() { auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -502,7 +507,8 @@ void create_preset_tabs(bool no_controller, int event_value_change, int event_pr add_created_tab(new TabPrinter (g_wxTabPanel, no_controller)); for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (! tab || tab->GetName()=="sla_material") + if (! tab || + tab->GetName() == "sla_material")// ys_FIXME don't set event till doesn't exist material_preset combobox on plater continue; tab->set_event_value_change(wxEventType(event_value_change)); tab->set_event_presets_changed(wxEventType(event_presets_changed)); @@ -511,6 +517,9 @@ void create_preset_tabs(bool no_controller, int event_value_change, int event_pr TabIface* get_preset_tab_iface(char *name) { + if (std::strcmp(name, "print") == 0) return new TabIface(dynamic_cast(g_PrintTab)); + if (std::strcmp(name, "filament") == 0) return new TabIface(dynamic_cast(g_FilamentTab)); + if (std::strcmp(name, "material") == 0) return new TabIface(dynamic_cast(g_MaterialTab)); for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); if (! tab) @@ -634,7 +643,24 @@ void add_created_tab(Tab* panel) // Load the currently selected preset into the GUI, update the preset selection box. panel->load_current_preset(); - g_wxTabPanel->AddPage(panel, panel->title()); + + const wxString& tab_name = panel->GetName(); + bool add_panel = true; + if (tab_name == "print") { + g_PrintTab = panel; + add_panel = g_PresetBundle->printers.get_edited_preset().printer_technology() == ptFFF; + } + else if (tab_name == "filament") { + g_FilamentTab = panel; + add_panel = g_PresetBundle->printers.get_edited_preset().printer_technology() == ptFFF; + } + else if (tab_name == "sla_material") { + g_MaterialTab = panel; + add_panel = g_PresetBundle->printers.get_edited_preset().printer_technology() == ptSLA; + } + + if (add_panel) + g_wxTabPanel->AddPage(panel, panel->title()); } void load_current_presets() @@ -675,6 +701,22 @@ PresetBundle* get_preset_bundle() return g_PresetBundle; } +wxNotebook* get_tab_panel() { + return g_wxTabPanel; +} + +wxWindow* get_print_tab() { + return g_PrintTab; +} + +wxWindow* get_filament_tab(){ + return g_FilamentTab; +} + +wxWindow* get_material_tab(){ + return g_MaterialTab; +} + const wxColour& get_label_clr_modified() { return g_color_label_modified; } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index efb11b7dfa..0e24811a91 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -96,6 +96,7 @@ void set_3DScene(_3DScene *scene); AppConfig* get_app_config(); wxApp* get_app(); PresetBundle* get_preset_bundle(); +wxNotebook* get_tab_panel(); const wxColour& get_label_clr_modified(); const wxColour& get_label_clr_sys(); @@ -107,6 +108,10 @@ void set_label_clr_sys(const wxColour& clr); const wxFont& small_font(); const wxFont& bold_font(); +wxWindow* get_print_tab(); +wxWindow* get_filament_tab(); +wxWindow* get_material_tab(); + extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); // This is called when closing the application, when loading a config file or when starting the config wizard diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index dc40ced3b2..2844418fa1 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -313,19 +313,21 @@ void PresetBundle::load_selections(const AppConfig &config) sla_materials.select_preset_by_name_strict(initial_sla_material_profile_name); printers.select_preset_by_name(initial_printer_profile_name, true); - // Load the names of the other filament profiles selected for a multi-material printer. - auto *nozzle_diameter = dynamic_cast(printers.get_selected_preset().config.option("nozzle_diameter")); - size_t num_extruders = nozzle_diameter->values.size(); - this->filament_presets = { initial_filament_profile_name }; - for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) { - char name[64]; - sprintf(name, "filament_%d", i); - if (! config.has("presets", name)) - break; - this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name))); + if (printers.get_selected_preset().printer_technology() == ptFFF){ + // Load the names of the other filament profiles selected for a multi-material printer. + auto *nozzle_diameter = dynamic_cast(printers.get_selected_preset().config.option("nozzle_diameter")); + size_t num_extruders = nozzle_diameter->values.size(); + this->filament_presets = { initial_filament_profile_name }; + for (unsigned int i = 1; i < (unsigned int)num_extruders; ++i) { + char name[64]; + sprintf(name, "filament_%d", i); + if (!config.has("presets", name)) + break; + this->filament_presets.emplace_back(remove_ini_suffix(config.get("presets", name))); + } + // Do not define the missing filaments, so that the update_compatible_with_printer() will use the preferred filaments. + this->filament_presets.resize(num_extruders, ""); } - // Do not define the missing filaments, so that the update_compatible_with_printer() will use the preferred filaments. - this->filament_presets.resize(num_extruders, ""); // Update visibility of presets based on their compatibility with the active printer. // Always try to select a compatible print and filament preset to the current printer preset, @@ -523,8 +525,8 @@ DynamicPrintConfig PresetBundle::full_sla_config() const // Collect the "compatible_printers_condition" and "inherits" values over all presets (sla_materials, printers) into a single vector. std::vector compatible_printers_condition; std::vector inherits; - compatible_printers_condition.emplace_back(this->prints.get_edited_preset().compatible_printers_condition()); - inherits .emplace_back(this->prints.get_edited_preset().inherits()); + compatible_printers_condition.emplace_back(this->/*prints*/sla_materials.get_edited_preset().compatible_printers_condition()); + inherits .emplace_back(this->/*prints*/sla_materials.get_edited_preset().inherits()); inherits .emplace_back(this->printers.get_edited_preset().inherits()); // These two value types clash between the print and filament profiles. They should be renamed. diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index a6d24fd759..2f1e87687e 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -681,6 +681,8 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) // Show/hide the 'purging volumes' button void Tab::update_wiping_button_visibility() { + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; bool single_extruder_mm = dynamic_cast( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; @@ -700,6 +702,8 @@ void Tab::update_wiping_button_visibility() { // to uddate number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME if (m_event_presets_changed > 0) { wxCommandEvent event(m_event_presets_changed); event.SetString(m_name); @@ -1007,6 +1011,9 @@ void TabPrint::reload_config(){ void TabPrint::update() { + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME + Freeze(); double fill_density = m_config->option("fill_density")->value; @@ -1363,6 +1370,9 @@ void TabFilament::reload_config(){ void TabFilament::update() { + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + return; // ys_FIXME + Freeze(); wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); @@ -1408,6 +1418,13 @@ void TabPrinter::build() m_presets = &m_preset_bundle->printers; load_initial_data(); + m_printer_technology_old = m_presets->get_selected_preset().printer_technology(); + + if (m_presets->get_selected_preset().printer_technology() == ptSLA){ + build_sla(); + return; + } + m_current_pages = &m_pages; // to avoid redundant memory allocation / deallocation during extruders count changing @@ -1688,8 +1705,6 @@ void TabPrinter::build() if (!m_no_controller) update_serial_ports(); - - build_sla(); } void TabPrinter::build_sla() @@ -1947,6 +1962,7 @@ void TabPrinter::build_extruder_pages() // this gets executed after preset is loaded and before GUI fields are updated void TabPrinter::on_preset_loaded() { + return; // ys_FIXME // update the extruders count field auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); int extruders_count = nozzle_diameter->values.size(); @@ -1956,6 +1972,7 @@ void TabPrinter::on_preset_loaded() } void TabPrinter::update(){ + return; // ys_FIXME Freeze(); bool en; @@ -2060,11 +2077,16 @@ void Tab::load_current_preset() auto preset = m_presets->get_edited_preset(); (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); - update(); - // For the printer profile, generate the extruder pages. - on_preset_loaded(); - // Reload preset pages with the new configuration values. - reload_config(); + + if (m_name == "printer" && m_presets->get_edited_preset().printer_technology() == ptSLA) {} // ys_FIXME + else { + update(); + // For the printer profile, generate the extruder pages. + on_preset_loaded(); + // Reload preset pages with the new configuration values. + reload_config(); + } + m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; @@ -2079,7 +2101,36 @@ void Tab::load_current_preset() // checking out if this Tab exists till this moment if (!checked_tab(this)) return; - update_tab_ui(); + update_tab_ui(); + + // update show/hide tabs + if (m_name == "printer"){ + bool printer_technology = m_presets->get_edited_preset().printer_technology(); + if (printer_technology != static_cast(this)->m_printer_technology_old) + { + wxWindow* del_page = printer_technology == ptFFF ? get_material_tab() : get_print_tab(); + int del_page_id = get_tab_panel()->FindPage(del_page); + if (del_page_id != wxNOT_FOUND) { + if (printer_technology == ptFFF) + { + get_tab_panel()->GetPage(del_page_id)->Show(false); + get_tab_panel()->RemovePage(del_page_id); + get_tab_panel()->InsertPage(del_page_id, get_filament_tab(), static_cast(get_filament_tab())->title()); + get_tab_panel()->InsertPage(del_page_id, get_print_tab(), static_cast(get_print_tab())->title()); + } + else + { + for (int i = 0; i < 2; ++i) { + get_tab_panel()->GetPage(del_page_id)->Show(false); + get_tab_panel()->RemovePage(del_page_id); + } + get_tab_panel()->InsertPage(del_page_id, get_material_tab(), static_cast(get_material_tab())->title()); + } + static_cast(this)->m_printer_technology_old = printer_technology; + } + } + } + on_presets_changed(); if (name() == "print") @@ -2172,7 +2223,8 @@ void Tab::select_preset(std::string preset_name /*= ""*/) if (! canceled) { for (PresetUpdate &pu : updates) { // The preset will be switched to a different, compatible preset, or the '-- default --'. - m_reload_dependent_tabs.emplace_back(pu.name); + if (pu.technology == new_printer_technology) + m_reload_dependent_tabs.emplace_back(pu.name); if (pu.old_preset_dirty) pu.presets->discard_current_changes(); } diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 4a8f95b986..5605daf48c 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -330,6 +330,8 @@ public: size_t m_initial_extruders_count; size_t m_sys_extruders_count; + bool m_printer_technology_old = ptFFF; + std::vector *m_current_pages; TabPrinter() {} From 709b898eba441489c8d49774335c5a12e88b00fa Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 7 Aug 2018 11:58:27 +0200 Subject: [PATCH 104/185] Correct printer pages updating according to the printer_technology --- xs/src/slic3r/GUI/Tab.cpp | 93 ++++++++++++++++++++++++--------------- xs/src/slic3r/GUI/Tab.hpp | 15 ++++--- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 2f1e87687e..1999a66741 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -239,9 +239,8 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); - std::vector & pages = name() != "printer" ? m_pages : *static_cast(this)->m_current_pages; - if (!is_extruder_pages) - pages.push_back(page); + if (!is_extruder_pages) + m_pages.push_back(page); page->set_config(m_config); return page; @@ -1418,15 +1417,15 @@ void TabPrinter::build() m_presets = &m_preset_bundle->printers; load_initial_data(); - m_printer_technology_old = m_presets->get_selected_preset().printer_technology(); + m_printer_technology = m_presets->get_selected_preset().printer_technology(); - if (m_presets->get_selected_preset().printer_technology() == ptSLA){ - build_sla(); - return; - } - - m_current_pages = &m_pages; + m_presets->get_selected_preset().printer_technology() == ptSLA ? build_sla() : build_fff(); +} +void TabPrinter::build_fff() +{ + if (!m_pages.empty()) + m_pages.resize(0); // to avoid redundant memory allocation / deallocation during extruders count changing m_pages.reserve(30); @@ -1709,11 +1708,8 @@ void TabPrinter::build() void TabPrinter::build_sla() { -// m_presets = &m_preset_bundle->printers; -// load_initial_data(); - - m_current_pages = &m_sla_pages; - + if (!m_pages.empty()) + m_pages.resize(0); auto page = add_options_page(_(L("General")), "printer_empty.png"); auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); @@ -1962,7 +1958,6 @@ void TabPrinter::build_extruder_pages() // this gets executed after preset is loaded and before GUI fields are updated void TabPrinter::on_preset_loaded() { - return; // ys_FIXME // update the extruders count field auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); int extruders_count = nozzle_diameter->values.size(); @@ -1971,8 +1966,36 @@ void TabPrinter::on_preset_loaded() extruders_count_changed(extruders_count); } -void TabPrinter::update(){ - return; // ys_FIXME +void TabPrinter::update_pages() +{ + // update m_pages ONLY if printer technology is changed + if (m_presets->get_edited_preset().printer_technology() == m_printer_technology) + return; + + // hide all old pages + for (auto& el : m_pages) + el.get()->Hide(); + + // set m_pages to m_pages_(technology before changing) + m_printer_technology == ptFFF ? m_pages.swap(m_pages_fff) : m_pages.swap(m_pages_sla); + + // build Tab according to the technology, if it's not exist jet OR + // set m_pages_(technology after changing) to m_pages + if (m_presets->get_edited_preset().printer_technology() == ptFFF) + m_pages_fff.empty() ? build_fff() : m_pages.swap(m_pages_fff); + else + m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla); + + rebuild_page_tree(true); +} + +void TabPrinter::update() +{ + m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla(); +} + +void TabPrinter::update_fff() +{ Freeze(); bool en; @@ -2071,6 +2094,8 @@ void TabPrinter::update(){ Thaw(); } +void TabPrinter::update_sla(){ ; } + // Initialize the UI from the current preset void Tab::load_current_preset() { @@ -2078,14 +2103,12 @@ void Tab::load_current_preset() (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); - if (m_name == "printer" && m_presets->get_edited_preset().printer_technology() == ptSLA) {} // ys_FIXME - else { - update(); - // For the printer profile, generate the extruder pages. + update(); + // For the printer profile, generate the extruder pages. + if (preset.printer_technology() == ptFFF) on_preset_loaded(); - // Reload preset pages with the new configuration values. - reload_config(); - } + // Reload preset pages with the new configuration values. + reload_config(); m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; @@ -2105,8 +2128,8 @@ void Tab::load_current_preset() // update show/hide tabs if (m_name == "printer"){ - bool printer_technology = m_presets->get_edited_preset().printer_technology(); - if (printer_technology != static_cast(this)->m_printer_technology_old) + PrinterTechnology& printer_technology = m_presets->get_edited_preset().printer_technology(); + if (printer_technology != static_cast(this)->m_printer_technology) { wxWindow* del_page = printer_technology == ptFFF ? get_material_tab() : get_print_tab(); int del_page_id = get_tab_panel()->FindPage(del_page); @@ -2126,7 +2149,7 @@ void Tab::load_current_preset() } get_tab_panel()->InsertPage(del_page_id, get_material_tab(), static_cast(get_material_tab())->title()); } - static_cast(this)->m_printer_technology_old = printer_technology; + static_cast(this)->m_printer_technology = printer_technology; } } } @@ -2148,7 +2171,7 @@ void Tab::load_current_preset() } //Regerenerate content of the page tree. -void Tab::rebuild_page_tree() +void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) { Freeze(); // get label of the currently selected item @@ -2163,9 +2186,9 @@ void Tab::rebuild_page_tree() m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); if (p->title() == selected) { if (!(p->title() == _(L("Machine limits")) || p->title() == _(L("Single extruder MM setup")))) // These Pages have to be updated inside OnTreeSelChange - m_disable_tree_sel_changed_event = 1; + m_disable_tree_sel_changed_event = !tree_sel_change_event; m_treectrl->SelectItem(itemId); - m_disable_tree_sel_changed_event = 0; + m_disable_tree_sel_changed_event = false; have_selection = 1; } } @@ -2245,6 +2268,8 @@ void Tab::select_preset(std::string preset_name /*= ""*/) if (current_dirty || printer_tab) m_preset_bundle->update_compatible_with_printer(true); // Initialize the UI from the current preset. + if (printer_tab) + static_cast(this)->update_pages(); load_current_preset(); } } @@ -2299,11 +2324,9 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) wxWindowUpdateLocker noUpdates(this); #endif - std::vector & pages = name() != "printer" ? m_pages : *static_cast(this)->m_current_pages; - Page* page = nullptr; auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); - for (auto p : pages) + for (auto p : m_pages) if (p->title() == selection) { page = p.get(); @@ -2313,7 +2336,7 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) } if (page == nullptr) return; - for (auto& el : pages) + for (auto& el : m_pages) el.get()->Hide(); #ifdef __linux__ diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 5605daf48c..c5b6b09363 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -218,7 +218,7 @@ public: void create_preset_tab(PresetBundle *preset_bundle); void load_current_preset(); - void rebuild_page_tree(); + void rebuild_page_tree(bool tree_sel_change_event = false); void select_preset(std::string preset_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn); @@ -320,7 +320,8 @@ class TabPrinter : public Tab void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key); bool m_rebuild_kinematics_page = false; - std::vector m_sla_pages; + std::vector m_pages_fff; + std::vector m_pages_sla; public: wxButton* m_serial_test_btn; wxButton* m_octoprint_host_test_btn; @@ -330,17 +331,19 @@ public: size_t m_initial_extruders_count; size_t m_sys_extruders_count; - bool m_printer_technology_old = ptFFF; - - std::vector *m_current_pages; + PrinterTechnology m_printer_technology = ptFFF; TabPrinter() {} TabPrinter(wxNotebook* parent, bool no_controller) : Tab(parent, _(L("Printer Settings")), "printer", no_controller) {} ~TabPrinter(){} void build() override; - void build_sla() ; + void build_fff(); + void build_sla(); void update() override; + void update_fff(); + void update_sla(); + void update_pages(); // update m_pages according to printer technology void update_serial_ports(); void extruders_count_changed(size_t extruders_count); PageShp build_kinematics_page(); From da16b28c1426b420a1ce023ff8a301141c9c2224 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 7 Aug 2018 17:23:48 +0200 Subject: [PATCH 105/185] Correct show_preset_comboboxes --- lib/Slic3r/GUI/MainFrame.pm | 20 ++++++++++++++------ lib/Slic3r/GUI/Plater.pm | 35 +++++++++++++++++++++++++++++------ xs/src/slic3r/GUI/Field.hpp | 7 +++++++ xs/src/slic3r/GUI/GUI.cpp | 5 ++--- xs/src/slic3r/GUI/Tab.cpp | 33 +++++++++++++++++++++++++++------ xs/src/slic3r/GUI/Tab.hpp | 2 +- 6 files changed, 80 insertions(+), 22 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 91f0c57f24..fb56284a68 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -153,6 +153,10 @@ sub _init_tabpanel { my $value = $event->GetInt(); $self->{plater}->on_extruders_change($value); } + if ($opt_key eq 'printer_technology'){ + my $value = $event->GetInt();# 0 ~ "ptFFF"; 1 ~ "ptSLA" + $self->{plater}->show_preset_comboboxes($value); + } } # don't save while loading for the first time $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave && $self->{loaded}; @@ -165,7 +169,7 @@ sub _init_tabpanel { my $tab = Slic3r::GUI::get_preset_tab($tab_name); if ($self->{plater}) { - # Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. + # Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs. my $presets = $tab->get_presets; if (defined $presets){ my $reload_dependent_tabs = $tab->get_dependent_tabs; @@ -173,7 +177,7 @@ sub _init_tabpanel { $self->{plater}->{"selected_item_$tab_name"} = $tab->get_selected_preset_item; if ($tab_name eq 'printer') { # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. - for my $tab_name_other (qw(print filament)) { + for my $tab_name_other (qw(print filament material)) { # If the printer tells us that the print or filament preset has been switched or invalidated, # refresh the print or filament tab page. Otherwise just refresh the combo box. my $update_action = ($reload_dependent_tabs && (first { $_ eq $tab_name_other } (@{$reload_dependent_tabs}))) @@ -189,7 +193,7 @@ sub _init_tabpanel { }); Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT); $self->{options_tabs} = {}; - for my $tab_name (qw(print filament printer)) { + for my $tab_name (qw(print filament material printer)) { $self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name"); } @@ -201,10 +205,14 @@ sub _init_tabpanel { # load initial config my $full_config = wxTheApp->{preset_bundle}->full_config; $self->{plater}->on_config_change($full_config); - #return if $num_extruders is undefined because of SLA printer is selected - return if (!defined $full_config->nozzle_diameter); # ys_FIXME + # Show a correct number of filament fields. - $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); + if (defined $full_config->nozzle_diameter){ # nozzle_diameter is undefined when SLA printer is selected + $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); + } + + # Show correct preset comboboxes according to the printer_technology + $self->{plater}->show_preset_comboboxes(($full_config->printer_technology eq "FFF") ? 0 : 1); } } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a5d5eaf532..4964c39923 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -450,20 +450,21 @@ sub new { { my $presets; { - $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2); + $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(4, 2, 1, 2); $presets->AddGrowableCol(1, 1); $presets->SetFlexibleDirection(wxHORIZONTAL); my %group_labels = ( print => L('Print settings'), filament => L('Filament'), + material => L('SLA material'), printer => L('Printer'), ); - # UI Combo boxes for a print, multiple filaments, and a printer. + # UI Combo boxes for a print, multiple filaments, SLA material and a printer. # Initially a single filament combo box is created, but the number of combo boxes for the filament selection may increase, # once a printer preset with multiple extruders is activated. # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; - for my $group (qw(print filament printer)) { + for my $group (qw(print filament material printer)) { my $text = Wx::StaticText->new($self->{right_panel}, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); @@ -484,7 +485,7 @@ sub new { $presets->Layout; } - my $frequently_changed_parameters_sizer = Wx::BoxSizer->new(wxHORIZONTAL); + my $frequently_changed_parameters_sizer = $self->{frequently_changed_parameters_sizer} = Wx::BoxSizer->new(wxHORIZONTAL); Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); my $object_info_sizer; @@ -655,18 +656,20 @@ sub update_ui_from_settings } } -# Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. +# Update preset combo boxes (Print settings, Filament, Material, Printer) from their respective tabs. # Called by # Slic3r::GUI::Tab::Print::_on_presets_changed # Slic3r::GUI::Tab::Filament::_on_presets_changed +# Slic3r::GUI::Tab::Material::_on_presets_changed # Slic3r::GUI::Tab::Printer::_on_presets_changed # when the presets are loaded or the user selects another preset. # For Print settings and Printer, synchronize the selection index with their tabs. # For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. sub update_presets { - # $group: one of qw(print filament printer) + # $group: one of qw(print filament material printer) # $presets: PresetCollection my ($self, $group, $presets) = @_; + print "$group \n"; my @choosers = @{$self->{preset_choosers}{$group}}; if ($group eq 'filament') { my $choice_idx = 0; @@ -680,6 +683,8 @@ sub update_presets { } } elsif ($group eq 'print') { wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); + } elsif ($group eq 'material') { + wxTheApp->{preset_bundle}->sla_material->update_platter_ui($choosers[0]); } elsif ($group eq 'printer') { # Update the print choosers to only contain the compatible presets, update the dirty flags. wxTheApp->{preset_bundle}->print->update_platter_ui($self->{preset_choosers}{print}->[0]); @@ -1844,6 +1849,24 @@ sub update { $self->{preview3D}->reload_print if $self->{preview3D}; } +# When a printer technology is changed, the UI needs to be updated to show/hide needed preset combo boxes. +sub show_preset_comboboxes{ + my ($self, $showSLA) = @_; #if showSLA is oposite value to "ptFFF" + + my $choices = $self->{preset_choosers}{filament}; + my $print_filament_ctrls_cnt = 2 + 2 * ($#$choices+1); + + foreach (0..$print_filament_ctrls_cnt-1){ + $self->{presets_sizer}->Show($_, !$showSLA); + } + $self->{presets_sizer}->Show($print_filament_ctrls_cnt , $showSLA); + $self->{presets_sizer}->Show($print_filament_ctrls_cnt+1, $showSLA); + + $self->{frequently_changed_parameters_sizer}->Show(0,!$showSLA); + + $self->Layout; +} + # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. # Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly # and some reasonable default has to be selected for the additional extruders. diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index db8d2a4085..290cb31367 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -38,6 +38,7 @@ wxString double_to_string(double const value); class MyButton : public wxButton { + bool hidden = false; // never show button if it's hidden ones public: MyButton() {} MyButton(wxWindow* parent, wxWindowID id, const wxString& label = wxEmptyString, @@ -52,6 +53,12 @@ public: // overridden from wxWindow base class virtual bool AcceptsFocusFromKeyboard() const { return false; } + + virtual bool Show(bool show = true) override { + if (!show) + hidden = true; + return wxButton::Show(!hidden); + } }; class Field { diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index c4851b26c8..4815201b87 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -507,8 +507,7 @@ void create_preset_tabs(bool no_controller, int event_value_change, int event_pr add_created_tab(new TabPrinter (g_wxTabPanel, no_controller)); for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (! tab || - tab->GetName() == "sla_material")// ys_FIXME don't set event till doesn't exist material_preset combobox on plater + if (! tab ) continue; tab->set_event_value_change(wxEventType(event_value_change)); tab->set_event_presets_changed(wxEventType(event_presets_changed)); @@ -654,7 +653,7 @@ void add_created_tab(Tab* panel) g_FilamentTab = panel; add_panel = g_PresetBundle->printers.get_edited_preset().printer_technology() == ptFFF; } - else if (tab_name == "sla_material") { + else if (tab_name == "material") { g_MaterialTab = panel; add_panel = g_PresetBundle->printers.get_edited_preset().printer_technology() == ptSLA; } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 1999a66741..75315750cb 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -567,6 +567,8 @@ void Tab::update_dirty(){ void Tab::update_tab_ui() { +// if (this == nullptr) +// return; // ys_FIXME m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets); // update_tab_presets(m_cc_presets_choice, m_show_incompatible_presets); // update_presetsctrl(m_presetctrl, m_show_incompatible_presets); @@ -576,6 +578,8 @@ void Tab::update_tab_ui() // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. void Tab::load_config(const DynamicPrintConfig& config) { +// if (this == nullptr) +// return; // ys_FIXME bool modified = 0; for(auto opt_key : m_config->diff(config)) { m_config->set_key_value(opt_key, config.option(opt_key)->clone()); @@ -650,6 +654,15 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) int val = boost::any_cast(value); event.SetInt(val); } + + if (opt_key == "printer_technology") + { + int val = boost::any_cast(value); + event.SetInt(val); + g_wxMainFrame->ProcessWindowEvent(event); + return; + } + g_wxMainFrame->ProcessWindowEvent(event); } if (opt_key == "fill_density") @@ -701,9 +714,11 @@ void Tab::update_wiping_button_visibility() { // to uddate number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { - if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) - return; // ys_FIXME - if (m_event_presets_changed > 0) { +// if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) +// return; + if (m_event_presets_changed > 0 + && get_preset_bundle()->printers.get_selected_preset().printer_technology() != ptSLA // ys_FIXME + ) { wxCommandEvent event(m_event_presets_changed); event.SetString(m_name); g_wxMainFrame->ProcessWindowEvent(event); @@ -1420,6 +1435,8 @@ void TabPrinter::build() m_printer_technology = m_presets->get_selected_preset().printer_technology(); m_presets->get_selected_preset().printer_technology() == ptSLA ? build_sla() : build_fff(); + +// on_value_change("printer_technology", m_printer_technology); // to update show/hide preset ComboBoxes } void TabPrinter::build_fff() @@ -1987,6 +2004,8 @@ void TabPrinter::update_pages() m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla); rebuild_page_tree(true); + + on_value_change("printer_technology", m_presets->get_edited_preset().printer_technology()); // to update show/hide preset ComboBoxes } void TabPrinter::update() @@ -2112,7 +2131,7 @@ void Tab::load_current_preset() m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; m_undo_to_sys_btn->Enable(!preset.is_default); @@ -2235,7 +2254,8 @@ void Tab::select_preset(std::string preset_name /*= ""*/) std::vector updates = { { "print", &m_preset_bundle->prints, ptFFF }, { "filament", &m_preset_bundle->filaments, ptFFF }, - { "sla_materials", &m_preset_bundle->sla_materials, ptSLA } + { "sla_materials", &m_preset_bundle->sla_materials, ptSLA } +// { "material", &m_preset_bundle->sla_materials, ptSLA } }; for (PresetUpdate &pu : updates) { pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty(); @@ -2974,7 +2994,8 @@ void TabSLAMaterial::build() void TabSLAMaterial::update() { - + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptFFF) + return; // ys_FIXME } } // GUI diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index c5b6b09363..79c1281a45 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -357,7 +357,7 @@ class TabSLAMaterial : public Tab public: TabSLAMaterial() {} TabSLAMaterial(wxNotebook* parent, bool no_controller) : - Tab(parent, _(L("SLA Material Settings")), "sla_material", no_controller) {} + Tab(parent, _(L("SLA Material Settings")), "material", no_controller) {} ~TabSLAMaterial(){} void build() override; From adf003f0ed0747d846226f6fcca7bc0bdcd02e2a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 8 Aug 2018 16:22:56 +0200 Subject: [PATCH 106/185] Correct preset/tabs updating according to the technology + some code refactoring --- lib/Slic3r/GUI/MainFrame.pm | 4 +- lib/Slic3r/GUI/Plater.pm | 9 ++-- xs/src/slic3r/GUI/GUI.cpp | 76 +++++++++++++----------------- xs/src/slic3r/GUI/GUI.hpp | 16 +++++-- xs/src/slic3r/GUI/PresetBundle.cpp | 2 +- xs/src/slic3r/GUI/Tab.cpp | 37 ++++----------- xs/src/slic3r/GUI/Tab.hpp | 2 +- xs/xsp/GUI_Preset.xsp | 1 + 8 files changed, 64 insertions(+), 83 deletions(-) diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index fb56284a68..f733a5f78d 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -177,7 +177,7 @@ sub _init_tabpanel { $self->{plater}->{"selected_item_$tab_name"} = $tab->get_selected_preset_item; if ($tab_name eq 'printer') { # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. - for my $tab_name_other (qw(print filament material)) { + for my $tab_name_other (qw(print filament sla_material)) { # If the printer tells us that the print or filament preset has been switched or invalidated, # refresh the print or filament tab page. Otherwise just refresh the combo box. my $update_action = ($reload_dependent_tabs && (first { $_ eq $tab_name_other } (@{$reload_dependent_tabs}))) @@ -193,7 +193,7 @@ sub _init_tabpanel { }); Slic3r::GUI::create_preset_tabs($self->{no_controller}, $VALUE_CHANGE_EVENT, $PRESETS_CHANGED_EVENT); $self->{options_tabs} = {}; - for my $tab_name (qw(print filament material printer)) { + for my $tab_name (qw(print filament sla_material printer)) { $self->{options_tabs}{$tab_name} = Slic3r::GUI::get_preset_tab("$tab_name"); } diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4964c39923..323f54f121 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -456,7 +456,7 @@ sub new { my %group_labels = ( print => L('Print settings'), filament => L('Filament'), - material => L('SLA material'), + sla_material=> L('SLA material'), printer => L('Printer'), ); # UI Combo boxes for a print, multiple filaments, SLA material and a printer. @@ -464,7 +464,7 @@ sub new { # once a printer preset with multiple extruders is activated. # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; - for my $group (qw(print filament material printer)) { + for my $group (qw(print filament sla_material printer)) { my $text = Wx::StaticText->new($self->{right_panel}, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); @@ -666,10 +666,9 @@ sub update_ui_from_settings # For Print settings and Printer, synchronize the selection index with their tabs. # For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. sub update_presets { - # $group: one of qw(print filament material printer) + # $group: one of qw(print filament sla_material printer) # $presets: PresetCollection my ($self, $group, $presets) = @_; - print "$group \n"; my @choosers = @{$self->{preset_choosers}{$group}}; if ($group eq 'filament') { my $choice_idx = 0; @@ -683,7 +682,7 @@ sub update_presets { } } elsif ($group eq 'print') { wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); - } elsif ($group eq 'material') { + } elsif ($group eq 'sla_material') { wxTheApp->{preset_bundle}->sla_material->update_platter_ui($choosers[0]); } elsif ($group eq 'printer') { # Update the print choosers to only contain the compatible presets, update the dirty flags. diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 4815201b87..c57cb66216 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -127,11 +127,6 @@ std::shared_ptr m_optgroup; double m_brim_width = 0.0; wxButton* g_wiping_dialog_button = nullptr; -// Windows, associated with Print, Filament & Material Tabs accordingly -wxWindow *g_PrintTab = nullptr; -wxWindow *g_FilamentTab = nullptr; -wxWindow *g_MaterialTab = nullptr; - static void init_label_colours() { auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -501,24 +496,33 @@ void open_preferences_dialog(int event_preferences) void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed) { update_label_colours_from_appconfig(); - add_created_tab(new TabPrint (g_wxTabPanel, no_controller)); - add_created_tab(new TabFilament (g_wxTabPanel, no_controller)); - add_created_tab(new TabSLAMaterial (g_wxTabPanel, no_controller)); - add_created_tab(new TabPrinter (g_wxTabPanel, no_controller)); - for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { - Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); - if (! tab ) - continue; - tab->set_event_value_change(wxEventType(event_value_change)); - tab->set_event_presets_changed(wxEventType(event_presets_changed)); - } + add_created_tab(new TabPrint (g_wxTabPanel, no_controller), event_value_change, event_presets_changed); + add_created_tab(new TabFilament (g_wxTabPanel, no_controller), event_value_change, event_presets_changed); + add_created_tab(new TabSLAMaterial (g_wxTabPanel, no_controller), event_value_change, event_presets_changed); + add_created_tab(new TabPrinter (g_wxTabPanel, no_controller), event_value_change, event_presets_changed); +} + +std::vector preset_tabs = { + { "print", nullptr, ptFFF }, + { "filament", nullptr, ptFFF }, + { "sla_material", nullptr, ptSLA } +}; +const std::vector& get_preset_tabs() { + return preset_tabs; +} + +Tab* get_tab(const std::string& name) +{ + std::vector::iterator it = std::find_if(preset_tabs.begin(), preset_tabs.end(), + [name](PresetTab& tab){ return name == tab.name; }); + return it != preset_tabs.end() ? it->panel : nullptr; } TabIface* get_preset_tab_iface(char *name) { - if (std::strcmp(name, "print") == 0) return new TabIface(dynamic_cast(g_PrintTab)); - if (std::strcmp(name, "filament") == 0) return new TabIface(dynamic_cast(g_FilamentTab)); - if (std::strcmp(name, "material") == 0) return new TabIface(dynamic_cast(g_MaterialTab)); + Tab* tab = get_tab(name); + if (tab) return new TabIface(tab); + for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++ i) { Tab *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); if (! tab) @@ -636,26 +640,24 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt } } -void add_created_tab(Tab* panel) +void add_created_tab(Tab* panel, int event_value_change, int event_presets_changed) { panel->create_preset_tab(g_PresetBundle); // Load the currently selected preset into the GUI, update the preset selection box. panel->load_current_preset(); + panel->set_event_value_change(wxEventType(event_value_change)); + panel->set_event_presets_changed(wxEventType(event_presets_changed)); + const wxString& tab_name = panel->GetName(); bool add_panel = true; - if (tab_name == "print") { - g_PrintTab = panel; - add_panel = g_PresetBundle->printers.get_edited_preset().printer_technology() == ptFFF; - } - else if (tab_name == "filament") { - g_FilamentTab = panel; - add_panel = g_PresetBundle->printers.get_edited_preset().printer_technology() == ptFFF; - } - else if (tab_name == "material") { - g_MaterialTab = panel; - add_panel = g_PresetBundle->printers.get_edited_preset().printer_technology() == ptSLA; + + auto it = std::find_if( preset_tabs.begin(), preset_tabs.end(), + [tab_name](PresetTab& tab){return tab.name == tab_name; }); + if (it != preset_tabs.end()) { + it->panel = panel; + add_panel = it->technology == g_PresetBundle->printers.get_edited_preset().printer_technology(); } if (add_panel) @@ -704,18 +706,6 @@ wxNotebook* get_tab_panel() { return g_wxTabPanel; } -wxWindow* get_print_tab() { - return g_PrintTab; -} - -wxWindow* get_filament_tab(){ - return g_FilamentTab; -} - -wxWindow* get_material_tab(){ - return g_MaterialTab; -} - const wxColour& get_label_clr_modified() { return g_color_label_modified; } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 0e24811a91..04d19cb01d 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -34,6 +34,8 @@ class DynamicPrintConfig; class TabIface; class _3DScene; +enum PrinterTechnology; + #define _(s) Slic3r::GUI::I18N::translate((s)) namespace GUI { namespace I18N { @@ -79,6 +81,13 @@ inline t_file_wild_card& get_file_wild_card() { return FILE_WILDCARDS; } +struct PresetTab { + std::string name; + Tab* panel; + PrinterTechnology technology; +}; + + void disable_screensaver(); void enable_screensaver(); bool debugged(); @@ -108,9 +117,8 @@ void set_label_clr_sys(const wxColour& clr); const wxFont& small_font(); const wxFont& bold_font(); -wxWindow* get_print_tab(); -wxWindow* get_filament_tab(); -wxWindow* get_material_tab(); +Tab* get_tab(const std::string& name); +const std::vector& get_preset_tabs(); extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); @@ -134,7 +142,7 @@ void create_preset_tabs(bool no_controller, int event_value_change, int event_pr TabIface* get_preset_tab_iface(char *name); // add it at the end of the tab panel. -void add_created_tab(Tab* panel); +void add_created_tab(Tab* panel, int event_value_change, int event_presets_changed); // Change option value in config void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 2844418fa1..996d793bb5 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -1299,7 +1299,7 @@ bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui) { - if (ui == nullptr) + if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA) return; unsigned char rgb[3]; diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 75315750cb..13941acc37 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -567,8 +567,6 @@ void Tab::update_dirty(){ void Tab::update_tab_ui() { -// if (this == nullptr) -// return; // ys_FIXME m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets); // update_tab_presets(m_cc_presets_choice, m_show_incompatible_presets); // update_presetsctrl(m_presetctrl, m_show_incompatible_presets); @@ -578,8 +576,6 @@ void Tab::update_tab_ui() // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. void Tab::load_config(const DynamicPrintConfig& config) { -// if (this == nullptr) -// return; // ys_FIXME bool modified = 0; for(auto opt_key : m_config->diff(config)) { m_config->set_key_value(opt_key, config.option(opt_key)->clone()); @@ -714,11 +710,7 @@ void Tab::update_wiping_button_visibility() { // to uddate number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { -// if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) -// return; - if (m_event_presets_changed > 0 - && get_preset_bundle()->printers.get_selected_preset().printer_technology() != ptSLA // ys_FIXME - ) { + if (m_event_presets_changed > 0) { wxCommandEvent event(m_event_presets_changed); event.SetString(m_name); g_wxMainFrame->ProcessWindowEvent(event); @@ -2150,26 +2142,18 @@ void Tab::load_current_preset() PrinterTechnology& printer_technology = m_presets->get_edited_preset().printer_technology(); if (printer_technology != static_cast(this)->m_printer_technology) { - wxWindow* del_page = printer_technology == ptFFF ? get_material_tab() : get_print_tab(); - int del_page_id = get_tab_panel()->FindPage(del_page); - if (del_page_id != wxNOT_FOUND) { - if (printer_technology == ptFFF) + for (auto& tab : get_preset_tabs()){ + if (tab.technology != printer_technology) { - get_tab_panel()->GetPage(del_page_id)->Show(false); - get_tab_panel()->RemovePage(del_page_id); - get_tab_panel()->InsertPage(del_page_id, get_filament_tab(), static_cast(get_filament_tab())->title()); - get_tab_panel()->InsertPage(del_page_id, get_print_tab(), static_cast(get_print_tab())->title()); + int page_id = get_tab_panel()->FindPage(tab.panel); + get_tab_panel()->GetPage(page_id)->Show(false); + get_tab_panel()->RemovePage(page_id); } else - { - for (int i = 0; i < 2; ++i) { - get_tab_panel()->GetPage(del_page_id)->Show(false); - get_tab_panel()->RemovePage(del_page_id); - } - get_tab_panel()->InsertPage(del_page_id, get_material_tab(), static_cast(get_material_tab())->title()); - } - static_cast(this)->m_printer_technology = printer_technology; + get_tab_panel()->InsertPage(get_tab_panel()->FindPage(this), tab.panel, tab.panel->title()); } + + static_cast(this)->m_printer_technology = printer_technology; } } @@ -2254,8 +2238,7 @@ void Tab::select_preset(std::string preset_name /*= ""*/) std::vector updates = { { "print", &m_preset_bundle->prints, ptFFF }, { "filament", &m_preset_bundle->filaments, ptFFF }, - { "sla_materials", &m_preset_bundle->sla_materials, ptSLA } -// { "material", &m_preset_bundle->sla_materials, ptSLA } + { "sla_material", &m_preset_bundle->sla_materials, ptSLA } }; for (PresetUpdate &pu : updates) { pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty(); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 79c1281a45..c5b6b09363 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -357,7 +357,7 @@ class TabSLAMaterial : public Tab public: TabSLAMaterial() {} TabSLAMaterial(wxNotebook* parent, bool no_controller) : - Tab(parent, _(L("SLA Material Settings")), "material", no_controller) {} + Tab(parent, _(L("SLA Material Settings")), "sla_material", no_controller) {} ~TabSLAMaterial(){} void build() override; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 9486012468..99d23a1421 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -132,6 +132,7 @@ PresetCollection::arrayref() Ref print() %code%{ RETVAL = &THIS->prints; %}; Ref filament() %code%{ RETVAL = &THIS->filaments; %}; + Ref sla_material() %code%{ RETVAL = &THIS->sla_materials; %}; Ref printer() %code%{ RETVAL = &THIS->printers; %}; Ref project_config() %code%{ RETVAL = &THIS->project_config; %}; From 5dbc8fb427c77baefa1e9d82f5292cb823415223 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 8 Aug 2018 17:47:59 +0200 Subject: [PATCH 107/185] Correct UI updating for SLA tabs --- xs/src/libslic3r/PrintConfig.cpp | 5 ++-- xs/src/slic3r/GUI/Preset.cpp | 5 ++-- xs/src/slic3r/GUI/Preset.hpp | 8 +++--- xs/src/slic3r/GUI/Tab.cpp | 46 +++++++++++++++++++++++++------- xs/src/slic3r/GUI/Tab.hpp | 1 + 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 933feb229f..f8471e302a 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -2079,14 +2079,15 @@ void PrintConfigDef::init_sla_params() def->default_value = new ConfigOptionFloat(100.); def = this->add("display_pixels_x", coInt); - def->label = L("Number of pixels in X"); + def->full_label = L("Number of pixels in"); + def->label = ("X"); def->tooltip = L("Number of pixels in X"); def->cli = "display-pixels-x=i"; def->min = 100; def->default_value = new ConfigOptionInt(2000); def = this->add("display_pixels_y", coInt); - def->label = L("Number of pixels in Y"); + def->label = ("Y"); def->tooltip = L("Number of pixels in Y"); def->cli = "display-pixels-y=i"; def->min = 100; diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 412895eeb0..7aefd38274 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -387,6 +387,7 @@ const std::vector& Preset::sla_material_options() "exposure_time", "initial_exposure_time", "material_correction_printing", "material_correction_curing", "material_notes", + "compatible_printers", "compatible_printers_condition", "inherits" }; } @@ -895,11 +896,11 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) return was_dirty != is_dirty; } -std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type /*= false*/) +std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/) { std::vector changed; if (edited != nullptr && reference != nullptr) { - changed = is_printer_type ? + changed = deep_compare ? reference->config.deep_diff(edited->config) : reference->config.diff(edited->config); // The "compatible_printers" option key is handled differently from the others: diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index d1a4063214..821d7dc543 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -342,11 +342,11 @@ public: // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ. bool current_is_dirty() const { return ! this->current_dirty_options().empty(); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. - std::vector current_dirty_options(const bool is_printer_type = false) const - { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), is_printer_type); } + std::vector current_dirty_options(const bool deep_compare = false) const + { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), deep_compare); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. - std::vector current_different_from_parent_options(const bool is_printer_type = false) const - { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), is_printer_type); } + std::vector current_different_from_parent_options(const bool deep_compare = false) const + { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); } // Update the choice UI from the list of presets. // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 13941acc37..ac398e3471 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -314,10 +314,10 @@ void Tab::update_changed_ui() if (m_postpone_update_ui) return; - const bool is_printer_type = (name() == "printer"); - auto dirty_options = m_presets->current_dirty_options(is_printer_type); - auto nonsys_options = m_presets->current_different_from_parent_options(is_printer_type); - if (is_printer_type){ + const bool deep_compare = (m_name == "printer" || m_name == "sla_material"); + auto dirty_options = m_presets->current_dirty_options(deep_compare); + auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); + if (name() == "printer"){ TabPrinter* tab = static_cast(this); if (tab->m_initial_extruders_count != tab->m_extruders_count) dirty_options.emplace_back("extruders_count"); @@ -398,7 +398,7 @@ void Tab::init_options_list() } template -void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, TabPrinter *tab, const int& value) +void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, Tab *tab, const int& value) { T *opt_cur = static_cast(tab->m_config->option(opt_key)); for (int i = 0; i < opt_cur->values.size(); i++) @@ -430,6 +430,30 @@ void TabPrinter::init_options_list() m_options_list.emplace("extruders_count", m_opt_status_value); } +void TabSLAMaterial::init_options_list() +{ + if (!m_options_list.empty()) + m_options_list.clear(); + + for (const auto opt_key : m_config->keys()) + { + if (opt_key == "compatible_printers"){ + m_options_list.emplace(opt_key, m_opt_status_value); + continue; + } + switch (m_config->option(opt_key)->type()) + { + case coInts: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coBools: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coFloats: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coStrings: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPoints: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + default: m_options_list.emplace(opt_key, m_opt_status_value); break; + } + } +} + void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) { auto opt = m_options_list.find(opt_key); @@ -1750,8 +1774,9 @@ void TabPrinter::build_sla() optgroup->append_single_option_line("display_width"); optgroup->append_single_option_line("display_height"); - line = { _(L("Number of pixels in axes")), "" }; - line.append_option(optgroup->get_option("display_pixels_x")); + auto option = optgroup->get_option("display_pixels_x"); + line = { _(option.opt.full_label), "" }; + line.append_option(option); line.append_option(optgroup->get_option("display_pixels_y")); optgroup->append_line(line); @@ -1769,7 +1794,7 @@ void TabPrinter::build_sla() page = add_options_page(_(L("Notes")), "note.png"); optgroup = page->new_optgroup(_(L("Notes")), 0); - auto option = optgroup->get_option("printer_notes"); + option = optgroup->get_option("printer_notes"); option.opt.full_width = true; option.opt.height = 250; optgroup->append_single_option_line(option); @@ -2215,7 +2240,7 @@ void Tab::select_preset(std::string preset_name /*= ""*/) auto printer_tab = m_presets->name() == "printer"; auto canceled = false; m_reload_dependent_tabs = {}; - if (!current_dirty && !may_discard_current_dirty_preset()) { + if (current_dirty && !may_discard_current_dirty_preset()) { canceled = true; } else if (printer_tab) { // Before switching the printer to a new one, verify, whether the currently active print and filament @@ -2921,7 +2946,7 @@ void TabSLAMaterial::build() m_presets = &m_preset_bundle->sla_materials; load_initial_data(); - auto page = add_options_page(_(L("General")), "spool.png"); + auto page = add_options_page(_(L("Material")), "spool.png"); auto optgroup = page->new_optgroup(_(L("Layers"))); optgroup->append_single_option_line("layer_height"); @@ -2932,6 +2957,7 @@ void TabSLAMaterial::build() optgroup->append_single_option_line("initial_exposure_time"); optgroup = page->new_optgroup(_(L("Corrections"))); + optgroup->label_width = 190; std::vector corrections = { "material_correction_printing", "material_correction_curing" }; std::vector axes{ "X", "Y", "Z" }; for (auto& opt_key : corrections){ diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index c5b6b09363..a271b268d1 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -362,6 +362,7 @@ public: void build() override; void update() override; + void init_options_list() override; }; class SavePresetWindow :public wxDialog From 0210dcf3b681de957dccf3645a6f318a3a1e3a44 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 8 Aug 2018 18:17:19 +0200 Subject: [PATCH 108/185] Fixed build crashing --- xs/src/slic3r/GUI/GUI.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 04d19cb01d..ef2f54473e 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -3,7 +3,7 @@ #include #include -#include "Config.hpp" +#include "PrintConfig.hpp" #include #include @@ -34,8 +34,6 @@ class DynamicPrintConfig; class TabIface; class _3DScene; -enum PrinterTechnology; - #define _(s) Slic3r::GUI::I18N::translate((s)) namespace GUI { namespace I18N { From c1bef2f8de69e6cbff5dc967c594a066e3437f81 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 9 Aug 2018 09:25:23 +0200 Subject: [PATCH 109/185] Added set_default_suppressed for sla_materials. --- xs/src/slic3r/GUI/PresetBundle.cpp | 3 ++- xs/src/slic3r/GUI/Tab.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index 996d793bb5..cd3924dd0a 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -90,7 +90,7 @@ PresetBundle::PresetBundle() : // Load the default preset bitmaps. this->prints .load_bitmap_default("cog.png"); this->filaments .load_bitmap_default("spool.png"); - this->sla_materials.load_bitmap_default("spool.png"); + this->sla_materials.load_bitmap_default("package_green.png"); this->printers .load_bitmap_default("printer_empty.png"); this->load_compatible_bitmaps(); @@ -1394,6 +1394,7 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) { prints.set_default_suppressed(default_suppressed); filaments.set_default_suppressed(default_suppressed); + sla_materials.set_default_suppressed(default_suppressed); printers.set_default_suppressed(default_suppressed); } diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index ac398e3471..ab8bae1381 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -2946,7 +2946,7 @@ void TabSLAMaterial::build() m_presets = &m_preset_bundle->sla_materials; load_initial_data(); - auto page = add_options_page(_(L("Material")), "spool.png"); + auto page = add_options_page(_(L("Material")), "package_green.png"); auto optgroup = page->new_optgroup(_(L("Layers"))); optgroup->append_single_option_line("layer_height"); From dc8cdcc2ba27fc31f2d3d08a4b46e1ae255ccc00 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 9 Aug 2018 12:02:09 +0200 Subject: [PATCH 110/185] Added tooltips with manifold_warning information --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 43 +++++++++++++++++++++++---- xs/src/slic3r/GUI/wxExtensions.cpp | 2 +- xs/src/slic3r/GUI/wxExtensions.hpp | 5 ++-- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index a0dee41172..fcccede419 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -228,6 +228,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) else if (title == _("Name") && pt.x >15 && m_objects_model->GetParent(item) == wxDataViewItem(0)) { + // ys_FIXME // auto menu = create_add_settings_popupmenu(true);// create_correction_stl_menu !!! // get_tab_panel()->GetPage(0)->PopupMenu(menu); } @@ -275,12 +276,44 @@ wxBoxSizer* content_objects_list(wxWindow *win) wxDataViewItem item; wxDataViewColumn* col; m_objects_ctrl->HitTest(pt, item, col); - if (col->GetTitle() == " " && item) - m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("For object settings changing click on icon"))); -// else if (col->GetTitle() == _("Name") && item && m_objects_model->GetIcon(item) == m_icon_manifold_warning ) -// m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("Information about auto-repaired errors\n To fix errors, click on the icon"))); + if (!item) return; + + if ( col->GetTitle() == " " ) + m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("Click the icon to change the object settings"))); + else if ( col->GetTitle() == _("Name") && + m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) { + int obj_idx = m_objects_model->GetIdByItem(item); + auto& stats = (*m_objects)[obj_idx]->volumes[0]->mesh.stl.stats; + int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + + wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); + + std::map error_msg; + error_msg[L("degenerate facets")] = stats.degenerate_facets; + error_msg[L("edges fixed")] = stats.edges_fixed; + error_msg[L("facets removed")] = stats.facets_removed; + error_msg[L("facets added")] = stats.facets_added; + error_msg[L("facets reversed")] = stats.facets_reversed; + error_msg[L("backwards edges")] = stats.backwards_edges; + + for (auto error: error_msg) + { + if (error.second > 0) + tooltip += wxString::Format(_("\t%d %s\n"), error.second, error.first); + } +// OR +// tooltip += wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " +// "%d facets added, %d facets reversed, %d backwards edges")), +// stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, +// stats.facets_added, stats.facets_reversed, stats.backwards_edges); + + // ysFIXME uncomment this when fix_error function will be exist +// tooltip += _(L("Click the icon to fix errors")); + m_objects_ctrl->GetMainWindow()->SetToolTip(tooltip); + } else - m_objects_ctrl->GetMainWindow()->SetToolTip(""); + m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip }); return objects_sz; diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index e7bb27b928..71f49a9703 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -566,7 +566,7 @@ wxString PrusaObjectDataViewModel::GetScale(const wxDataViewItem &item) const return node->m_scale; } -wxIcon PrusaObjectDataViewModel::GetIcon(const wxDataViewItem &item) const +wxIcon& PrusaObjectDataViewModel::GetIcon(const wxDataViewItem &item) const { PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); return node->m_icon; diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index fbe8129fe1..daff37a60d 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -155,6 +155,7 @@ class PrusaObjectDataViewModelNode { PrusaObjectDataViewModelNode* m_parent; MyObjectTreeModelNodePtrArray m_children; + wxIcon m_empty_icon; public: PrusaObjectDataViewModelNode(const wxString &name, int instances_count=1, int scale=100) { m_parent = NULL; @@ -198,7 +199,7 @@ public: } wxString m_name; - wxIcon m_icon; + wxIcon& m_icon = m_empty_icon; wxString m_copy; wxString m_scale; std::string m_type; @@ -365,7 +366,7 @@ public: wxString GetName(const wxDataViewItem &item) const; wxString GetCopy(const wxDataViewItem &item) const; wxString GetScale(const wxDataViewItem &item) const; - wxIcon GetIcon(const wxDataViewItem &item) const; + wxIcon& GetIcon(const wxDataViewItem &item) const; // helper methods to change the model From fede9e95ff38f01f194d34f0e72e4b12cf5a1828 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 9 Aug 2018 15:55:08 +0200 Subject: [PATCH 111/185] Experiments with wxEVT_LEFT_DOWN/wxEVT_MOTION on OSX --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index fcccede419..3b5ae37b97 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -12,6 +12,7 @@ #include #include #include "Geometry.hpp" +#include "slic3r/Utils/FixModelByWin10.hpp" namespace Slic3r { @@ -203,6 +204,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { + auto msg_box = wxMessageBox("wxEVT_DATAVIEW_SELECTION_CHANGED"); object_ctrl_selection_changed(); // #ifdef __WXOSX__ // update_extruder_in_config(g_selected_extruder); @@ -212,6 +214,8 @@ wxBoxSizer* content_objects_list(wxWindow *win) // m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) m_objects_ctrl->GetMainWindow()->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event) { wxPoint pt = event.GetPosition(); + wxString msg = wxString::Format("wxEVT_LEFT_DOWN\n Position: x - %d, y - %d", pt.x, pt.y); + auto msg_box = wxMessageBox(msg); wxDataViewItem item; wxDataViewColumn* col; m_objects_ctrl->HitTest(pt, item, col); @@ -226,11 +230,11 @@ wxBoxSizer* content_objects_list(wxWindow *win) if (title == " ") object_ctrl_context_menu(); else if (title == _("Name") && pt.x >15 && - m_objects_model->GetParent(item) == wxDataViewItem(0)) + m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) { // ys_FIXME -// auto menu = create_add_settings_popupmenu(true);// create_correction_stl_menu !!! -// get_tab_panel()->GetPage(0)->PopupMenu(menu); +// if (is_windows10()) +// fix_through_netfabb(); } } event.Skip(); @@ -273,6 +277,8 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { wxPoint pt = event.GetPosition(); + wxString msg = wxString::Format("wxEVT_MOTION\n Position: x - %d, y - %d", pt.x, pt.y); + auto msg_box = wxMessageBox(msg); wxDataViewItem item; wxDataViewColumn* col; m_objects_ctrl->HitTest(pt, item, col); @@ -308,8 +314,9 @@ wxBoxSizer* content_objects_list(wxWindow *win) // stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, // stats.facets_added, stats.facets_reversed, stats.backwards_edges); - // ysFIXME uncomment this when fix_error function will be exist -// tooltip += _(L("Click the icon to fix errors")); + if (is_windows10()) + tooltip += _(L("Click the icon to fix STL through Netfabb")); + m_objects_ctrl->GetMainWindow()->SetToolTip(tooltip); } else From 72c77a3592b60c85f79aa814bb81a1d82e42da94 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 9 Aug 2018 17:33:44 +0200 Subject: [PATCH 112/185] Next experiment --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 40 ++++++++++++++------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 3b5ae37b97..ea0f0a3009 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -215,28 +215,30 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->GetMainWindow()->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event) { wxPoint pt = event.GetPosition(); wxString msg = wxString::Format("wxEVT_LEFT_DOWN\n Position: x - %d, y - %d", pt.x, pt.y); - auto msg_box = wxMessageBox(msg); + wxMessageBox(msg); wxDataViewItem item; wxDataViewColumn* col; m_objects_ctrl->HitTest(pt, item, col); wxString title = col->GetTitle(); - if (item && (title==" " || title == _("Name"))) { - if (item != m_objects_ctrl->GetSelection()) { - m_objects_ctrl->Select(item); - object_ctrl_selection_changed(); - g_prevent_list_events = false; - } - - if (title == " ") - object_ctrl_context_menu(); - else if (title == _("Name") && pt.x >15 && - m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) - { - // ys_FIXME -// if (is_windows10()) -// fix_through_netfabb(); - } + if (!item) { + event.Skip(); + return; } + if (item != m_objects_ctrl->GetSelection()) { + m_objects_ctrl->Select(item); + object_ctrl_selection_changed(); + g_prevent_list_events = false; + } + + if (title == " ") + object_ctrl_context_menu(); + // ys_FIXME +// else if (title == _("Name") && pt.x >15 && +// m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) +// { +// if (is_windows10()) +// fix_through_netfabb(); +// } event.Skip(); }); @@ -277,8 +279,8 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { wxPoint pt = event.GetPosition(); - wxString msg = wxString::Format("wxEVT_MOTION\n Position: x - %d, y - %d", pt.x, pt.y); - auto msg_box = wxMessageBox(msg); + wxString msg = wxString::Format("wxEVT_MOTION\n Position: x = %d, y = %d", pt.x, pt.y); + wxMessageBox(msg, wxEmptyString, 4, nullptr, pt.x, pt.y); wxDataViewItem item; wxDataViewColumn* col; m_objects_ctrl->HitTest(pt, item, col); From 1029a4c0e031e7f4ca2ccf2a3971f3aefc024472 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 9 Aug 2018 17:53:34 +0200 Subject: [PATCH 113/185] Experiments with tooltips --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index ea0f0a3009..bdd0e46408 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -204,8 +204,9 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { - auto msg_box = wxMessageBox("wxEVT_DATAVIEW_SELECTION_CHANGED"); + m_objects_ctrl->SetToolTip("wxEVT_DATAVIEW_SELECTION_CHANGED"); object_ctrl_selection_changed(); + m_objects_ctrl->GetMainWindow()->SetToolTip("wxEVT_DATAVIEW_SELECTION_CHANGED from MainWindow"); // #ifdef __WXOSX__ // update_extruder_in_config(g_selected_extruder); // #endif //__WXOSX__ From a7c29b98bd69275abf61d747df49aed51d835cc8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 10 Aug 2018 08:26:15 +0200 Subject: [PATCH 114/185] Try to understand wxEVT_MOTION on OSX --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index bdd0e46408..977f6fffbd 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -204,9 +204,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { - m_objects_ctrl->SetToolTip("wxEVT_DATAVIEW_SELECTION_CHANGED"); object_ctrl_selection_changed(); - m_objects_ctrl->GetMainWindow()->SetToolTip("wxEVT_DATAVIEW_SELECTION_CHANGED from MainWindow"); // #ifdef __WXOSX__ // update_extruder_in_config(g_selected_extruder); // #endif //__WXOSX__ @@ -279,7 +277,8 @@ wxBoxSizer* content_objects_list(wxWindow *win) #endif //__WXMSW__ m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { - wxPoint pt = event.GetPosition(); + m_objects_ctrl->GetMainWindow()->SetToolTip("wxEVT_MOTION"); +/* wxPoint pt = event.GetPosition(); wxString msg = wxString::Format("wxEVT_MOTION\n Position: x = %d, y = %d", pt.x, pt.y); wxMessageBox(msg, wxEmptyString, 4, nullptr, pt.x, pt.y); wxDataViewItem item; @@ -324,7 +323,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) } else m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip - }); +*/ }); return objects_sz; } From 0477d4d8028bf8eccdbf16b66f13c2483c15027a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 10 Aug 2018 12:19:35 +0200 Subject: [PATCH 115/185] Fixed tooltip showing on Linux and OSX(maybe) --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 133 +++++++++++++------------- 1 file changed, 64 insertions(+), 69 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 977f6fffbd..d071874853 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -157,6 +157,59 @@ bool is_part_settings_changed(){ return m_part_settings_changed; } static wxString dots("…", wxConvUTF8); +void set_tooltip_for_item(const wxPoint& pt) +{ + wxDataViewItem item; + wxDataViewColumn* col; + m_objects_ctrl->HitTest(pt, item, col); + if (!item) return; + + if (col->GetTitle() == " ") + m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); + else if (col->GetTitle() == _("Name") && + m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) { + int obj_idx = m_objects_model->GetIdByItem(item); + auto& stats = (*m_objects)[obj_idx]->volumes[0]->mesh.stl.stats; + int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + + wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); + + std::map error_msg; + error_msg[L("degenerate facets")] = stats.degenerate_facets; + error_msg[L("edges fixed")] = stats.edges_fixed; + error_msg[L("facets removed")] = stats.facets_removed; + error_msg[L("facets added")] = stats.facets_added; + error_msg[L("facets reversed")] = stats.facets_reversed; + error_msg[L("backwards edges")] = stats.backwards_edges; + + for (auto error : error_msg) + { + if (error.second > 0) + tooltip += wxString::Format(_("\t%d %s\n"), error.second, error.first); + } +// OR +// tooltip += wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " +// "%d facets added, %d facets reversed, %d backwards edges")), +// stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, +// stats.facets_added, stats.facets_reversed, stats.backwards_edges); + + if (is_windows10()) + tooltip += _(L("Right button click the icon to fix STL through Netfabb")); + + m_objects_ctrl->GetMainWindow()->SetToolTip(tooltip); + } + else + m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip +} + +wxPoint get_mouse_position_in_control() { + const wxPoint& pt = wxGetMousePosition(); + wxWindow* win = m_objects_ctrl->GetMainWindow(); + return wxPoint(pt.x - win->GetScreenPosition().x, + pt.y - win->GetScreenPosition().y); +} + // ****** from GUI.cpp wxBoxSizer* content_objects_list(wxWindow *win) { @@ -205,29 +258,17 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { object_ctrl_selection_changed(); -// #ifdef __WXOSX__ -// update_extruder_in_config(g_selected_extruder); -// #endif //__WXOSX__ +#ifndef __WXMSW__ + set_tooltip_for_item(get_mouse_position_in_control()); +#endif //__WXMSW__ }); -// m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) - m_objects_ctrl->GetMainWindow()->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event) { - wxPoint pt = event.GetPosition(); - wxString msg = wxString::Format("wxEVT_LEFT_DOWN\n Position: x - %d, y - %d", pt.x, pt.y); - wxMessageBox(msg); + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) { wxDataViewItem item; wxDataViewColumn* col; - m_objects_ctrl->HitTest(pt, item, col); + m_objects_ctrl->HitTest(get_mouse_position_in_control(), item, col); wxString title = col->GetTitle(); - if (!item) { - event.Skip(); - return; - } - if (item != m_objects_ctrl->GetSelection()) { - m_objects_ctrl->Select(item); - object_ctrl_selection_changed(); - g_prevent_list_events = false; - } + if (!item) return; if (title == " ") object_ctrl_context_menu(); @@ -256,10 +297,13 @@ wxBoxSizer* content_objects_list(wxWindow *win) }); #ifdef __WXMSW__ - m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) - { + m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); }); + + m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { + set_tooltip_for_item(event.GetPosition()); + }); #else m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { @@ -276,55 +320,6 @@ wxBoxSizer* content_objects_list(wxWindow *win) }); #endif //__WXMSW__ - m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { - m_objects_ctrl->GetMainWindow()->SetToolTip("wxEVT_MOTION"); -/* wxPoint pt = event.GetPosition(); - wxString msg = wxString::Format("wxEVT_MOTION\n Position: x = %d, y = %d", pt.x, pt.y); - wxMessageBox(msg, wxEmptyString, 4, nullptr, pt.x, pt.y); - wxDataViewItem item; - wxDataViewColumn* col; - m_objects_ctrl->HitTest(pt, item, col); - if (!item) return; - - if ( col->GetTitle() == " " ) - m_objects_ctrl->GetMainWindow()->SetToolTip(_(L("Click the icon to change the object settings"))); - else if ( col->GetTitle() == _("Name") && - m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) { - int obj_idx = m_objects_model->GetIdByItem(item); - auto& stats = (*m_objects)[obj_idx]->volumes[0]->mesh.stl.stats; - int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_added + stats.facets_reversed + stats.backwards_edges; - - wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors):\n")), errors); - - std::map error_msg; - error_msg[L("degenerate facets")] = stats.degenerate_facets; - error_msg[L("edges fixed")] = stats.edges_fixed; - error_msg[L("facets removed")] = stats.facets_removed; - error_msg[L("facets added")] = stats.facets_added; - error_msg[L("facets reversed")] = stats.facets_reversed; - error_msg[L("backwards edges")] = stats.backwards_edges; - - for (auto error: error_msg) - { - if (error.second > 0) - tooltip += wxString::Format(_("\t%d %s\n"), error.second, error.first); - } -// OR -// tooltip += wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " -// "%d facets added, %d facets reversed, %d backwards edges")), -// stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, -// stats.facets_added, stats.facets_reversed, stats.backwards_edges); - - if (is_windows10()) - tooltip += _(L("Click the icon to fix STL through Netfabb")); - - m_objects_ctrl->GetMainWindow()->SetToolTip(tooltip); - } - else - m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip -*/ }); - return objects_sz; } From 4b8d7bd7fa9ea1d49512c056b8e74bfa651ed3db Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 10 Aug 2018 14:02:47 +0200 Subject: [PATCH 116/185] Fry to fix OSX-crashing on UnselectAll --- lib/Slic3r/GUI/Plater.pm | 2 ++ xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 6d6e0591f2..e3f473fcad 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -2222,6 +2222,8 @@ sub selection_changed { } $self->Layout; } + + print "selection_changed -> have_sel = $have_sel\n"; # prepagate the event to the frame (a custom Wx event would be cleaner) $self->GetFrame->on_plater_selection_changed($have_sel); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index d071874853..f10d223795 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -279,6 +279,9 @@ wxBoxSizer* content_objects_list(wxWindow *win) // if (is_windows10()) // fix_through_netfabb(); // } +#ifndef __WXMSW__ + m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip +#endif //__WXMSW__ event.Skip(); }); @@ -721,8 +724,10 @@ void set_object_scale(int idx, int scale) void unselect_objects() { printf("UNSELECT OBJECTS\n"); + g_prevent_list_events = true; m_objects_ctrl->UnselectAll(); part_selection_changed(); + g_prevent_list_events = false; get_optgroup(ogFrequentlyObjectSettings)->disable(); } @@ -1310,9 +1315,7 @@ void parts_changed(int obj_idx) void update_settings_value() { - printf("update_settings_value\n"); auto og = get_optgroup(ogFrequentlyObjectSettings); - printf("selected_object_id = %d\n", m_selected_object_id); if (m_selected_object_id < 0 || m_objects->size() <= m_selected_object_id) { og->set_value("scale_x", 0); og->set_value("scale_y", 0); @@ -1327,7 +1330,6 @@ void update_settings_value() void part_selection_changed() { - printf("part_selection_changed\n"); auto item = m_objects_ctrl->GetSelection(); int obj_idx = -1; auto og = get_optgroup(ogFrequentlyObjectSettings); From 9df680483571d93356f7dfd6fecb900cf1af1e9e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 10 Aug 2018 14:55:34 +0200 Subject: [PATCH 117/185] next try --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index f10d223795..bcd1846fb9 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -726,7 +726,7 @@ void unselect_objects() printf("UNSELECT OBJECTS\n"); g_prevent_list_events = true; m_objects_ctrl->UnselectAll(); - part_selection_changed(); +// part_selection_changed(); g_prevent_list_events = false; get_optgroup(ogFrequentlyObjectSettings)->disable(); From bb07100a4fc2726d4db32b0eebd968395ee48f8c Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 13 Aug 2018 09:23:10 +0200 Subject: [PATCH 118/185] Code cleanup --- lib/Slic3r/GUI/Plater.pm | 45 ++----------------------- xs/src/slic3r/GUI/3DScene.cpp | 6 ---- xs/src/slic3r/GUI/3DScene.hpp | 6 ---- xs/src/slic3r/GUI/GLCanvas3D.cpp | 44 ------------------------ xs/src/slic3r/GUI/GLCanvas3D.hpp | 23 ------------- xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 6 ---- xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 6 ---- xs/src/slic3r/GUI/GLTexture.cpp | 26 -------------- xs/src/slic3r/GUI/GLTexture.hpp | 4 --- 9 files changed, 2 insertions(+), 164 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 04b5432c2d..da7b8a251e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -160,7 +160,6 @@ sub new { } }; -#====================================================================================================================================================== # callbacks for toolbar my $on_action_add = sub { $self->add; @@ -214,7 +213,6 @@ sub new { my $state = Slic3r::GUI::_3DScene::is_toolbar_item_pressed($self->{canvas3D}, "layersediting"); $self->on_layer_editing_toggled($state); }; -#====================================================================================================================================================== # Initialize 3D plater if ($Slic3r::GUI::have_OpenGL) { @@ -235,7 +233,6 @@ sub new { Slic3r::GUI::_3DScene::register_on_gizmo_scale_uniformly_callback($self->{canvas3D}, $on_gizmo_scale_uniformly); Slic3r::GUI::_3DScene::register_on_gizmo_rotate_callback($self->{canvas3D}, $on_gizmo_rotate); Slic3r::GUI::_3DScene::register_on_update_geometry_info_callback($self->{canvas3D}, $on_update_geometry_info); -#====================================================================================================================================================== Slic3r::GUI::_3DScene::register_action_add_callback($self->{canvas3D}, $on_action_add); Slic3r::GUI::_3DScene::register_action_delete_callback($self->{canvas3D}, $on_action_delete); Slic3r::GUI::_3DScene::register_action_deleteall_callback($self->{canvas3D}, $on_action_deleteall); @@ -249,11 +246,8 @@ sub new { Slic3r::GUI::_3DScene::register_action_cut_callback($self->{canvas3D}, $on_action_cut); Slic3r::GUI::_3DScene::register_action_settings_callback($self->{canvas3D}, $on_action_settings); Slic3r::GUI::_3DScene::register_action_layersediting_callback($self->{canvas3D}, $on_action_layersediting); -#====================================================================================================================================================== Slic3r::GUI::_3DScene::enable_gizmos($self->{canvas3D}, 1); -#====================================================================================================================================================== Slic3r::GUI::_3DScene::enable_toolbar($self->{canvas3D}, 1); -#====================================================================================================================================================== Slic3r::GUI::_3DScene::enable_shader($self->{canvas3D}, 1); Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($self->{canvas3D}, 1); @@ -710,9 +704,7 @@ sub on_layer_editing_toggled { $self->{"btn_layer_editing"}->Disable; $self->{"btn_layer_editing"}->SetValue(0); } -#=================================================================================================================================================== Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0); -#=================================================================================================================================================== } $self->{canvas3D}->Refresh; $self->{canvas3D}->Update; @@ -1028,9 +1020,7 @@ sub increase { $self->update; } -#======================================================================================================================== $self->selection_changed; # refresh info (size, volume etc.) -#======================================================================================================================== $self->schedule_background_process; } @@ -2007,9 +1997,7 @@ sub on_config_change { $self->{"btn_layer_editing"}->Disable; $self->{"btn_layer_editing"}->SetValue(0); } -#=================================================================================================================================================== Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0); -#=================================================================================================================================================== Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, 0); $self->{canvas3D}->Refresh; $self->{canvas3D}->Update; @@ -2020,9 +2008,7 @@ sub on_config_change { } else { $self->{"btn_layer_editing"}->Enable; } -#=================================================================================================================================================== Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 1); -#=================================================================================================================================================== } } elsif ($opt_key eq 'extruder_colour') { $update_scheduled = 1; @@ -2190,33 +2176,20 @@ sub object_list_changed { # Enable/disable buttons depending on whether there are any objects on the platter. my $have_objects = @{$self->{objects}} ? 1 : 0; -#=================================================================================================================================================== -# my $variable_layer_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D}); -#=================================================================================================================================================== if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_objects) -#=================================================================================================================================================== for (TB_RESET, TB_ARRANGE); -# for (TB_RESET, TB_ARRANGE, TB_LAYER_EDITING); -# $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0) if (! $variable_layer_height_allowed); -#=================================================================================================================================================== } else { # On MSW my $method = $have_objects ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method -#=================================================================================================================================================== for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode); -# for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode layer_editing); -# $self->{"btn_layer_editing"}->Disable if (! $variable_layer_height_allowed); -#=================================================================================================================================================== } -#=================================================================================================================================================== for my $toolbar_item (qw(deleteall arrange)) { Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_objects); } -#=================================================================================================================================================== my $export_in_progress = $self->{export_gcode_output_file} || $self->{send_gcode_file}; my $model_fits = $self->{canvas3D} ? Slic3r::GUI::_3DScene::check_volumes_outside_state($self->{canvas3D}, $self->{config}) : 1; @@ -2231,20 +2204,14 @@ sub selection_changed { my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; -#=================================================================================================================================================== my $layers_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D}) && $have_sel; -#=================================================================================================================================================== $self->{right_panel}->Freeze; if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_sel) -#=================================================================================================================================================== for (TB_REMOVE, TB_MORE, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); -# for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); -#=================================================================================================================================================== - -#=================================================================================================================================================== + $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, $layers_height_allowed); if ($have_sel) { @@ -2253,18 +2220,13 @@ sub selection_changed { } else { $self->{htoolbar}->EnableTool(TB_FEWER, 0); } -#=================================================================================================================================================== } else { # On MSW my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method -#=================================================================================================================================================== for grep $self->{"btn_$_"}, qw(remove increase rotate45cw rotate45ccw changescale split cut settings); -# for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); -#=================================================================================================================================================== - -#=================================================================================================================================================== + if ($layers_height_allowed) { $self->{"btn_layer_editing"}->Enable; } else { @@ -2281,10 +2243,8 @@ sub selection_changed { } else { $self->{"btn_decrease"}->Disable; } -#=================================================================================================================================================== } -#=================================================================================================================================================== for my $toolbar_item (qw(delete more fewer ccw45 cw45 scale split cut settings)) { Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_sel); } @@ -2295,7 +2255,6 @@ sub selection_changed { my $model_object = $self->{model}->objects->[$obj_idx]; Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "fewer", $model_object->instances_count > 1); } -#=================================================================================================================================================== if ($self->{object_info_size}) { # have we already loaded the info pane? if ($have_sel) { diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 2e8181c7a8..fadd2d1019 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -1820,12 +1820,10 @@ void _3DScene::enable_gizmos(wxGLCanvas* canvas, bool enable) s_canvas_mgr.enable_gizmos(canvas, enable); } -//################################################################################################################################### void _3DScene::enable_toolbar(wxGLCanvas* canvas, bool enable) { s_canvas_mgr.enable_toolbar(canvas, enable); } -//################################################################################################################################### void _3DScene::enable_shader(wxGLCanvas* canvas, bool enable) { @@ -1847,7 +1845,6 @@ void _3DScene::allow_multisample(wxGLCanvas* canvas, bool allow) s_canvas_mgr.allow_multisample(canvas, allow); } -//################################################################################################################################### void _3DScene::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable) { s_canvas_mgr.enable_toolbar_item(canvas, name, enable); @@ -1857,7 +1854,6 @@ bool _3DScene::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& na { return s_canvas_mgr.is_toolbar_item_pressed(canvas, name); } -//################################################################################################################################### void _3DScene::zoom_to_bed(wxGLCanvas* canvas) { @@ -1994,7 +1990,6 @@ void _3DScene::register_on_update_geometry_info_callback(wxGLCanvas* canvas, voi s_canvas_mgr.register_on_update_geometry_info_callback(canvas, callback); } -//################################################################################################################################### void _3DScene::register_action_add_callback(wxGLCanvas* canvas, void* callback) { s_canvas_mgr.register_action_add_callback(canvas, callback); @@ -2059,7 +2054,6 @@ void _3DScene::register_action_layersediting_callback(wxGLCanvas* canvas, void* { s_canvas_mgr.register_action_layersediting_callback(canvas, callback); } -//################################################################################################################################### static inline int hex_digit_to_int(const char c) { diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index f29ed89361..7f01c4627c 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -497,18 +497,14 @@ public: static void enable_picking(wxGLCanvas* canvas, bool enable); static void enable_moving(wxGLCanvas* canvas, bool enable); static void enable_gizmos(wxGLCanvas* canvas, bool enable); -//################################################################################################################################### static void enable_toolbar(wxGLCanvas* canvas, bool enable); -//################################################################################################################################### static void enable_shader(wxGLCanvas* canvas, bool enable); static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); static void enable_dynamic_background(wxGLCanvas* canvas, bool enable); static void allow_multisample(wxGLCanvas* canvas, bool allow); -//################################################################################################################################### static void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable); static bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name); -//################################################################################################################################### static void zoom_to_bed(wxGLCanvas* canvas); static void zoom_to_volumes(wxGLCanvas* canvas); @@ -542,7 +538,6 @@ public: static void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback); static void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback); -//################################################################################################################################### static void register_action_add_callback(wxGLCanvas* canvas, void* callback); static void register_action_delete_callback(wxGLCanvas* canvas, void* callback); static void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback); @@ -556,7 +551,6 @@ public: static void register_action_cut_callback(wxGLCanvas* canvas, void* callback); static void register_action_settings_callback(wxGLCanvas* canvas, void* callback); static void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback); -//################################################################################################################################### static std::vector load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector instance_idxs); static std::vector load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index b1b4e84a04..ae8bde374a 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1793,9 +1793,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) : m_canvas(canvas) , m_context(nullptr) , m_timer(nullptr) -//################################################################################################################################### , m_toolbar(*this) -//################################################################################################################################### , m_config(nullptr) , m_print(nullptr) , m_model(nullptr) @@ -1805,9 +1803,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_force_zoom_to_bed_enabled(false) , m_apply_zoom_to_volumes_filter(false) , m_hover_volume_id(-1) -//################################################################################################################################### , m_toolbar_action_running(false) -//################################################################################################################################### , m_warning_texture_enabled(false) , m_legend_texture_enabled(false) , m_picking_enabled(false) @@ -1914,10 +1910,8 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) if (m_gizmos.is_enabled() && !m_gizmos.init()) return false; -//################################################################################################################################### if (!_init_toolbar()) return false; -//################################################################################################################################### m_initialized = true; @@ -2182,12 +2176,10 @@ void GLCanvas3D::enable_gizmos(bool enable) m_gizmos.set_enabled(enable); } -//################################################################################################################################### void GLCanvas3D::enable_toolbar(bool enable) { m_toolbar.set_enabled(enable); } -//################################################################################################################################### void GLCanvas3D::enable_shader(bool enable) { @@ -2209,7 +2201,6 @@ void GLCanvas3D::allow_multisample(bool allow) m_multisample_allowed = allow; } -//################################################################################################################################### void GLCanvas3D::enable_toolbar_item(const std::string& name, bool enable) { if (enable) @@ -2222,7 +2213,6 @@ bool GLCanvas3D::is_toolbar_item_pressed(const std::string& name) const { return m_toolbar.is_item_pressed(name); } -//################################################################################################################################### void GLCanvas3D::zoom_to_bed() { @@ -2353,9 +2343,7 @@ void GLCanvas3D::render() _render_warning_texture(); _render_legend_texture(); _render_gizmo(); -//################################################################################################################################### _render_toolbar(); -//################################################################################################################################### _render_layer_editing_overlay(); m_canvas->SwapBuffers(); @@ -2658,7 +2646,6 @@ void GLCanvas3D::register_on_update_geometry_info_callback(void* callback) m_on_update_geometry_info_callback.register_callback(callback); } -//################################################################################################################################### void GLCanvas3D::register_action_add_callback(void* callback) { if (callback != nullptr) @@ -2736,7 +2723,6 @@ void GLCanvas3D::register_action_layersediting_callback(void* callback) if (callback != nullptr) m_action_layersediting_callback.register_callback(callback); } -//################################################################################################################################### void GLCanvas3D::bind_event_handlers() { @@ -2915,9 +2901,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; m_layers_editing.last_object_id = layer_editing_object_idx; bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); -//################################################################################################################################### int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position); -//################################################################################################################################### if (evt.Entering()) { @@ -2937,13 +2921,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.LeftDClick() && (m_hover_volume_id != -1)) m_on_double_click_callback.call(); -//################################################################################################################################### else if (evt.LeftDClick() && (toolbar_contains_mouse != -1)) { m_toolbar_action_running = true; m_toolbar.do_action((unsigned int)toolbar_contains_mouse); } -//################################################################################################################################### else if (evt.LeftDown() || evt.RightDown()) { // If user pressed left or right button we first check whether this happened @@ -2982,13 +2964,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.drag.gizmo_volume_idx = _get_first_selected_volume_id(selected_object_idx); m_dirty = true; } -//################################################################################################################################### else if (toolbar_contains_mouse != -1) { m_toolbar_action_running = true; m_toolbar.do_action((unsigned int)toolbar_contains_mouse); } -//################################################################################################################################### else { // Select volume in this 3D canvas. @@ -3045,20 +3025,16 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.RightDown()) { -//################################################################################################################################### // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while // the context menu is already shown, ensuring it to disappear if the mouse is outside any volume m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); render(); if (m_hover_volume_id != -1) { -//################################################################################################################################### // if right clicking on volume, propagate event through callback (shows context menu) if (m_volumes.volumes[volume_idx]->hover) m_on_right_click_callback.call(pos.x, pos.y); -//################################################################################################################################### } -//################################################################################################################################### } } } @@ -3257,16 +3233,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) _on_move(volume_idxs); } -//############################################################################################################################################################################# else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) -// else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) -//############################################################################################################################################################################# { // deselect and propagate event through callback -//################################################################################################################################### if (m_picking_enabled && !m_toolbar_action_running) -// if (m_picking_enabled) -//################################################################################################################################### { deselect_volumes(); _on_select(-1); @@ -3298,9 +3268,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_3D_as_invalid(); m_mouse.set_start_position_2D_as_invalid(); m_mouse.dragging = false; -//################################################################################################################################### m_toolbar_action_running = false; -//################################################################################################################################### m_dirty = true; } else if (evt.Moving()) @@ -3367,13 +3335,11 @@ void GLCanvas3D::reset_legend_texture() m_legend_texture.reset(); } -//################################################################################################################################### void GLCanvas3D::set_tooltip(const std::string& tooltip) { if (m_canvas != nullptr) m_canvas->SetToolTip(tooltip); } -//################################################################################################################################### bool GLCanvas3D::_is_shown_on_screen() const { @@ -3386,7 +3352,6 @@ void GLCanvas3D::_force_zoom_to_bed() m_force_zoom_to_bed_enabled = false; } -//################################################################################################################################### bool GLCanvas3D::_init_toolbar() { if (!m_toolbar.is_enabled()) @@ -3523,7 +3488,6 @@ bool GLCanvas3D::_init_toolbar() return true; } -//################################################################################################################################### void GLCanvas3D::_resize(unsigned int w, unsigned int h) { @@ -3749,7 +3713,6 @@ void GLCanvas3D::_deregister_callbacks() m_on_gizmo_rotate_callback.deregister_callback(); m_on_update_geometry_info_callback.deregister_callback(); -//################################################################################################################################### m_action_add_callback.deregister_callback(); m_action_delete_callback.deregister_callback(); m_action_deleteall_callback.deregister_callback(); @@ -3763,7 +3726,6 @@ void GLCanvas3D::_deregister_callbacks() m_action_cut_callback.deregister_callback(); m_action_settings_callback.deregister_callback(); m_action_layersediting_callback.deregister_callback(); -//################################################################################################################################### } void GLCanvas3D::_mark_volumes_for_layer_height() const @@ -3876,9 +3838,7 @@ void GLCanvas3D::_picking_pass() const else m_gizmos.reset_all_states(); -//################################################################################################################################### m_toolbar.update_hover_state(pos); -//################################################################################################################################### } } @@ -4078,13 +4038,11 @@ void GLCanvas3D::_render_gizmo() const m_gizmos.render(*this, _selected_volumes_bounding_box()); } -//################################################################################################################################### void GLCanvas3D::_render_toolbar() const { _resize_toolbar(); m_toolbar.render(); } -//################################################################################################################################### float GLCanvas3D::_get_layers_editing_cursor_z_relative() const { @@ -5337,7 +5295,6 @@ bool GLCanvas3D::_is_any_volume_outside() const return false; } -//################################################################################################################################### void GLCanvas3D::_resize_toolbar() const { Size cnv_size = get_canvas_size(); @@ -5368,7 +5325,6 @@ void GLCanvas3D::_resize_toolbar() const } } } -//################################################################################################################################### } // namespace GUI } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index 6162cc6cbc..e1b323a748 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -2,10 +2,7 @@ #define slic3r_GLCanvas3D_hpp_ #include "../../slic3r/GUI/3DScene.hpp" -//################################################################################################################################### #include "../../slic3r/GUI/GLToolbar.hpp" -//#include "../../slic3r/GUI/GLTexture.hpp" -//################################################################################################################################### class wxTimer; class wxSizeEvent; @@ -450,9 +447,7 @@ private: Shader m_shader; Mouse m_mouse; mutable Gizmos m_gizmos; -//################################################################################################################################### mutable GLToolbar m_toolbar; -//################################################################################################################################### mutable GLVolumeCollection m_volumes; DynamicPrintConfig* m_config; @@ -465,9 +460,7 @@ private: bool m_force_zoom_to_bed_enabled; bool m_apply_zoom_to_volumes_filter; mutable int m_hover_volume_id; -//################################################################################################################################### bool m_toolbar_action_running; -//################################################################################################################################### bool m_warning_texture_enabled; bool m_legend_texture_enabled; bool m_picking_enabled; @@ -505,7 +498,6 @@ private: PerlCallback m_on_gizmo_rotate_callback; PerlCallback m_on_update_geometry_info_callback; -//################################################################################################################################### PerlCallback m_action_add_callback; PerlCallback m_action_delete_callback; PerlCallback m_action_deleteall_callback; @@ -519,7 +511,6 @@ private: PerlCallback m_action_cut_callback; PerlCallback m_action_settings_callback; PerlCallback m_action_layersediting_callback; -//################################################################################################################################### public: GLCanvas3D(wxGLCanvas* canvas); @@ -578,18 +569,14 @@ public: void enable_picking(bool enable); void enable_moving(bool enable); void enable_gizmos(bool enable); -//################################################################################################################################### void enable_toolbar(bool enable); -//################################################################################################################################### void enable_shader(bool enable); void enable_force_zoom_to_bed(bool enable); void enable_dynamic_background(bool enable); void allow_multisample(bool allow); -//################################################################################################################################### void enable_toolbar_item(const std::string& name, bool enable); bool is_toolbar_item_pressed(const std::string& name) const; -//################################################################################################################################### void zoom_to_bed(); void zoom_to_volumes(); @@ -631,7 +618,6 @@ public: void register_on_gizmo_rotate_callback(void* callback); void register_on_update_geometry_info_callback(void* callback); -//################################################################################################################################### void register_action_add_callback(void* callback); void register_action_delete_callback(void* callback); void register_action_deleteall_callback(void* callback); @@ -645,7 +631,6 @@ public: void register_action_cut_callback(void* callback); void register_action_settings_callback(void* callback); void register_action_layersediting_callback(void* callback); -//################################################################################################################################### void bind_event_handlers(); void unbind_event_handlers(); @@ -664,17 +649,13 @@ public: void reset_legend_texture(); -//################################################################################################################################### void set_tooltip(const std::string& tooltip); -//################################################################################################################################### private: bool _is_shown_on_screen() const; void _force_zoom_to_bed(); -//################################################################################################################################### bool _init_toolbar(); -//################################################################################################################################### void _resize(unsigned int w, unsigned int h); @@ -701,9 +682,7 @@ private: void _render_layer_editing_overlay() const; void _render_volumes(bool fake_colors) const; void _render_gizmo() const; -//################################################################################################################################### void _render_toolbar() const; -//################################################################################################################################### float _get_layers_editing_cursor_z_relative() const; void _perform_layer_editing_action(wxMouseEvent* evt = nullptr); @@ -761,9 +740,7 @@ private: bool _is_any_volume_outside() const; -//################################################################################################################################### void _resize_toolbar() const; -//################################################################################################################################### static std::vector _parse_colors(const std::vector& colors); }; diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 406649546d..0fe24ed43f 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -404,14 +404,12 @@ void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable) it->second->enable_gizmos(enable); } -//################################################################################################################################### void GLCanvas3DManager::enable_toolbar(wxGLCanvas* canvas, bool enable) { CanvasesMap::iterator it = _get_canvas(canvas); if (it != m_canvases.end()) it->second->enable_toolbar(enable); } -//################################################################################################################################### void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable) { @@ -441,7 +439,6 @@ void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow) it->second->allow_multisample(allow); } -//################################################################################################################################### void GLCanvas3DManager::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable) { CanvasesMap::iterator it = _get_canvas(canvas); @@ -454,7 +451,6 @@ bool GLCanvas3DManager::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::s CanvasesMap::const_iterator it = _get_canvas(canvas); return (it != m_canvases.end()) ? it->second->is_toolbar_item_pressed(name) : false; } -//################################################################################################################################### void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas) { @@ -699,7 +695,6 @@ void GLCanvas3DManager::register_on_update_geometry_info_callback(wxGLCanvas* ca it->second->register_on_update_geometry_info_callback(callback); } -//################################################################################################################################### void GLCanvas3DManager::register_action_add_callback(wxGLCanvas* canvas, void* callback) { CanvasesMap::iterator it = _get_canvas(canvas); @@ -790,7 +785,6 @@ void GLCanvas3DManager::register_action_layersediting_callback(wxGLCanvas* canva if (it != m_canvases.end()) it->second->register_action_layersediting_callback(callback); } -//################################################################################################################################### GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) { diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index 7e0ce96853..58ee1c9d98 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -110,18 +110,14 @@ public: void enable_picking(wxGLCanvas* canvas, bool enable); void enable_moving(wxGLCanvas* canvas, bool enable); void enable_gizmos(wxGLCanvas* canvas, bool enable); -//################################################################################################################################### void enable_toolbar(wxGLCanvas* canvas, bool enable); -//################################################################################################################################### void enable_shader(wxGLCanvas* canvas, bool enable); void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable); void enable_dynamic_background(wxGLCanvas* canvas, bool enable); void allow_multisample(wxGLCanvas* canvas, bool allow); -//################################################################################################################################### void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable); bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const; -//################################################################################################################################### void zoom_to_bed(wxGLCanvas* canvas); void zoom_to_volumes(wxGLCanvas* canvas); @@ -165,7 +161,6 @@ public: void register_on_gizmo_rotate_callback(wxGLCanvas* canvas, void* callback); void register_on_update_geometry_info_callback(wxGLCanvas* canvas, void* callback); -//################################################################################################################################### void register_action_add_callback(wxGLCanvas* canvas, void* callback); void register_action_delete_callback(wxGLCanvas* canvas, void* callback); void register_action_deleteall_callback(wxGLCanvas* canvas, void* callback); @@ -179,7 +174,6 @@ public: void register_action_cut_callback(wxGLCanvas* canvas, void* callback); void register_action_settings_callback(wxGLCanvas* canvas, void* callback); void register_action_layersediting_callback(wxGLCanvas* canvas, void* callback); -//################################################################################################################################### private: CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas); diff --git a/xs/src/slic3r/GUI/GLTexture.cpp b/xs/src/slic3r/GUI/GLTexture.cpp index 03890d7807..235e3d93b6 100644 --- a/xs/src/slic3r/GUI/GLTexture.cpp +++ b/xs/src/slic3r/GUI/GLTexture.cpp @@ -12,9 +12,7 @@ namespace Slic3r { namespace GUI { -//################################################################################################################################### GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; -//################################################################################################################################### GLTexture::GLTexture() : m_id(0) @@ -133,32 +131,9 @@ const std::string& GLTexture::get_source() const void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top) { -//################################################################################################################################### render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs); - -// ::glEnable(GL_BLEND); -// ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -// -// ::glEnable(GL_TEXTURE_2D); -// ::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); -// -// ::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id); -// -// ::glBegin(GL_QUADS); -// ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(left, bottom); -// ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(right, bottom); -// ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(right, top); -// ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(left, top); -// ::glEnd(); -// -// ::glBindTexture(GL_TEXTURE_2D, 0); -// -// ::glDisable(GL_TEXTURE_2D); -// ::glDisable(GL_BLEND); -//################################################################################################################################### } -//################################################################################################################################### void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs) { ::glEnable(GL_BLEND); @@ -181,7 +156,6 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, ::glDisable(GL_TEXTURE_2D); ::glDisable(GL_BLEND); } -//################################################################################################################################### unsigned int GLTexture::_generate_mipmaps(wxImage& image) { diff --git a/xs/src/slic3r/GUI/GLTexture.hpp b/xs/src/slic3r/GUI/GLTexture.hpp index a7d610200c..e027bd152f 100644 --- a/xs/src/slic3r/GUI/GLTexture.hpp +++ b/xs/src/slic3r/GUI/GLTexture.hpp @@ -10,7 +10,6 @@ namespace GUI { class GLTexture { -//################################################################################################################################### public: struct UV { @@ -27,7 +26,6 @@ namespace GUI { }; static Quad_UVs FullTextureUVs; -//################################################################################################################################### protected: unsigned int m_id; @@ -49,9 +47,7 @@ namespace GUI { const std::string& get_source() const; static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); -//################################################################################################################################### static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); -//################################################################################################################################### protected: unsigned int _generate_mipmaps(wxImage& image); From 73ba96381e3c7fbaf45dec9ed624733ca1bf4c6e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 13 Aug 2018 10:30:36 +0200 Subject: [PATCH 119/185] Drag&Drop test on Linux and OSX --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 59 +++++++++++++++++++++++++++ xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 5 +++ 2 files changed, 64 insertions(+) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index bcd1846fb9..90fc615658 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -323,6 +323,9 @@ wxBoxSizer* content_objects_list(wxWindow *win) }); #endif //__WXMSW__ + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, [](wxDataViewEvent& e) {on_begin_drag(e);}); + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, [](wxDataViewEvent& e) {on_drop_possible(e); }); + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_DROP, [](wxDataViewEvent& e) {on_drop(e);}); return objects_sz; } @@ -1528,5 +1531,61 @@ void update_rotation_value(const double angle, const std::string& axis) og->set_value("rotation_"+axis, deg); } +void on_begin_drag(wxDataViewEvent &event) +{ + wxDataViewItem item(event.GetItem()); + + // only allow drags for item, not containers + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + { + event.Veto(); + return; + } + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + wxTextDataObject *obj = new wxTextDataObject; + obj->SetText(node->m_name); + event.SetDataObject(obj); + event.SetDragFlags(wxDrag_AllowMove); // allows both copy and move; +} + +void on_drop_possible(wxDataViewEvent &event) +{ + wxDataViewItem item(event.GetItem()); + + // only allow drags for item or background, not containers + if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0)) + event.Veto(); + + if (event.GetDataFormat() != wxDF_UNICODETEXT) + event.Veto(); +} + +void on_drop(wxDataViewEvent &event) +{ + wxDataViewItem item(event.GetItem()); + + // only allow drops for item, not containers + if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0)) + { + event.Veto(); + return; + } + + if (event.GetDataFormat() != wxDF_UNICODETEXT) + { + event.Veto(); + return; + } + + wxTextDataObject obj; + obj.SetData(wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer()); + + if (item.IsOk()) + wxMessageBox(wxString::Format("Text dropped on item %s: %s", m_objects_model->GetName(item), obj.GetText())); + else + wxMessageBox(wxString::Format("Text dropped on background: %s", obj.GetText())); +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 17c99f65c5..8c35bea35f 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -7,6 +7,7 @@ class wxBoxSizer; class wxString; class wxArrayString; class wxMenu; +class wxDataViewEvent; namespace Slic3r { class ModelObject; @@ -109,6 +110,10 @@ void update_rotation_values(); // update rotation value after "gizmos" void update_rotation_value(const double angle, const std::string& axis); +void on_begin_drag(wxDataViewEvent &event); +void on_drop_possible(wxDataViewEvent &event); +void on_drop(wxDataViewEvent &event); + } //namespace GUI } //namespace Slic3r #endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file From 13388f1caa1ccdf2a7d3b87121920a979dfa1227 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 13 Aug 2018 14:15:43 +0200 Subject: [PATCH 120/185] Drag & Drop for sub-objects (parts of the object) --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 42 +++++++++++++-------------- xs/src/slic3r/GUI/wxExtensions.cpp | 26 +++++++++++++++++ xs/src/slic3r/GUI/wxExtensions.hpp | 5 ++++ 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 90fc615658..a5174fff38 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -306,6 +306,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { set_tooltip_for_item(event.GetPosition()); + event.Skip(); }); #else m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) @@ -1536,17 +1537,15 @@ void on_begin_drag(wxDataViewEvent &event) wxDataViewItem item(event.GetItem()); // only allow drags for item, not containers - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) - { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { event.Veto(); return; } - PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); wxTextDataObject *obj = new wxTextDataObject; - obj->SetText(node->m_name); + obj->SetText(wxString::Format("%d", m_objects_model->GetVolumeIdByItem(item))); event.SetDataObject(obj); - event.SetDragFlags(wxDrag_AllowMove); // allows both copy and move; + event.SetDragFlags(/*wxDrag_AllowMove*/wxDrag_DefaultMove); // allows both copy and move; } void on_drop_possible(wxDataViewEvent &event) @@ -1554,10 +1553,8 @@ void on_drop_possible(wxDataViewEvent &event) wxDataViewItem item(event.GetItem()); // only allow drags for item or background, not containers - if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0)) - event.Veto(); - - if (event.GetDataFormat() != wxDF_UNICODETEXT) + if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || + event.GetDataFormat() != wxDF_UNICODETEXT) event.Veto(); } @@ -1566,25 +1563,26 @@ void on_drop(wxDataViewEvent &event) wxDataViewItem item(event.GetItem()); // only allow drops for item, not containers - if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0)) - { + if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || + event.GetDataFormat() != wxDF_UNICODETEXT) { event.Veto(); return; - } - - if (event.GetDataFormat() != wxDF_UNICODETEXT) - { - event.Veto(); - return; - } + } wxTextDataObject obj; obj.SetData(wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer()); - if (item.IsOk()) - wxMessageBox(wxString::Format("Text dropped on item %s: %s", m_objects_model->GetName(item), obj.GetText())); - else - wxMessageBox(wxString::Format("Text dropped on background: %s", obj.GetText())); + int from_volume_id = std::stoi(obj.GetText().ToStdString()); + int to_volume_id = m_objects_model->GetVolumeIdByItem(item); + + m_objects_ctrl->Select(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, + m_objects_model->GetParent(item))); + + auto& volumes = (*m_objects)[m_selected_object_id]->volumes; + auto delta = to_volume_id < from_volume_id ? -1 : 1; + int cnt = 0; + for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id+=delta, cnt++) + std::swap(volumes[id], volumes[id +delta]); } } //namespace GUI diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 71f49a9703..6c330a3d62 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -664,6 +664,32 @@ wxDataViewItem PrusaObjectDataViewModel::MoveChildDown(const wxDataViewItem &ite return ret_item; } +wxDataViewItem PrusaObjectDataViewModel::ReorganizeChildren(int current_volume_id, int new_volume_id, const wxDataViewItem &parent) +{ + auto ret_item = wxDataViewItem(0); + if (current_volume_id == new_volume_id) + return ret_item; + wxASSERT(parent.IsOk()); + PrusaObjectDataViewModelNode *node_parent = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!node_parent) // happens if item.IsOk()==false + return ret_item; + + PrusaObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id); + node_parent->GetChildren().Remove(deleted_node); + ItemDeleted(parent, wxDataViewItem(deleted_node)); + node_parent->Insert(deleted_node, new_volume_id); + ItemAdded(parent, wxDataViewItem(deleted_node)); + + //update volume_id value for child-nodes + auto children = node_parent->GetChildren(); + int id_frst = current_volume_id < new_volume_id ? current_volume_id : new_volume_id; + int id_last = current_volume_id > new_volume_id ? current_volume_id : new_volume_id; + for (int id = id_frst; id <= id_last; ++id) + children[id]->SetVolumeId(id); + + return wxDataViewItem(node_parent->GetNthChild(new_volume_id)); +} + // bool MyObjectTreeModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const // { // diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index daff37a60d..b536232b81 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -381,6 +381,11 @@ public: wxDataViewItem MoveChildUp(const wxDataViewItem &item); wxDataViewItem MoveChildDown(const wxDataViewItem &item); + // For parent move child from cur_volume_id place to new_volume_id + // Remaining items will moved up/down accordingly + wxDataViewItem ReorganizeChildren(int cur_volume_id, + int new_volume_id, + const wxDataViewItem &parent); // virtual bool IsEnabled(const wxDataViewItem &item, // unsigned int col) const override; From 6742735596eb342d8166ce00aa370cf0fba15c1f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 13 Aug 2018 16:16:37 +0200 Subject: [PATCH 121/185] Better fix for minimum z of object to lay on the bed after rotations --- xs/src/libslic3r/Model.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index bceeea2582..f9936537fa 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -771,21 +771,12 @@ void ModelObject::scale(const Pointf3 &versor) void ModelObject::rotate(float angle, const Axis &axis) { - float min_z = FLT_MAX; for (ModelVolume *v : this->volumes) { v->mesh.rotate(angle, axis); - min_z = std::min(min_z, v->mesh.stl.stats.min.z); } - if (min_z != 0.0f) - { - // translate the object so that its minimum z lays on the bed - for (ModelVolume *v : this->volumes) - { - v->mesh.translate(0.0f, 0.0f, -min_z); - } - } + center_around_origin(); this->origin_translation = Pointf3(0, 0, 0); this->invalidate_bounding_box(); From 168d38df2b823b9c3c52fc288b8a688e00ac1d99 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 14 Aug 2018 09:33:58 +0200 Subject: [PATCH 122/185] Fixed object sinking into print bed after parts import --- lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index a20a241f79..783c1a9f5b 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -157,7 +157,7 @@ sub new { my ($volume_idx) = @_; $self->reload_tree($volume_idx); }); - Slic3r::GUI::_3DScene::load_model_object($canvas, $self->{model_object}, 0, [0]); + Slic3r::GUI::_3DScene::load_model_object($canvas, $self->{model_object}, 0, [0]); Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas); Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size })); $canvas->SetSize([500,700]); @@ -377,7 +377,8 @@ sub on_btn_load { } } } - + + $self->{model_object}->center_around_origin if $self->{parts_changed}; $self->_parts_changed; } @@ -479,7 +480,8 @@ sub on_btn_delete { $self->{model_object}->delete_volume($itemData->{volume_id}); $self->{parts_changed} = 1; } - + + $self->{model_object}->center_around_origin if $self->{parts_changed}; $self->_parts_changed; } From acac6b0b44da4ed330670a66d19d28de10407c54 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Aug 2018 13:04:11 +0200 Subject: [PATCH 123/185] Fixed DnD down-moving on GTK --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index a5174fff38..aba007a8db 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -690,7 +690,7 @@ void add_object_to_list(const std::string &name, ModelObject* model_object) } // part_selection_changed(); -#ifdef __WXMSW__ +#ifndef __WXOSX__ //#ifdef __WXMSW__ object_ctrl_selection_changed(); #endif //__WXMSW__ } @@ -1230,7 +1230,7 @@ void on_btn_del() void on_btn_split(const bool split_part) { auto item = m_objects_ctrl->GetSelection(); - if (!item) + if (!item || m_selected_object_id<0) return; auto volume_id = m_objects_model->GetVolumeIdByItem(item); ModelVolume* volume; @@ -1542,6 +1542,13 @@ void on_begin_drag(wxDataViewEvent &event) return; } + /* Under MSW or OSX, DnD moves an item to the place of another selected item + * But under GTK, DnD moves an item between another two items. + * And as a result - call EVT_CHANGE_SELECTION to unselect all items. + * To prevent such behavior use g_prevent_list_events + **/ + g_prevent_list_events = true;//it's needed for GTK + wxTextDataObject *obj = new wxTextDataObject; obj->SetText(wxString::Format("%d", m_objects_model->GetVolumeIdByItem(item))); event.SetDataObject(obj); @@ -1575,6 +1582,14 @@ void on_drop(wxDataViewEvent &event) int from_volume_id = std::stoi(obj.GetText().ToStdString()); int to_volume_id = m_objects_model->GetVolumeIdByItem(item); +#ifdef __WXGTK__ + /* Under GTK, DnD moves an item between another two items. + * And event.GetItem() return item, which is under "insertion line" + * So, if we move item down we should to decrease the to_volume_id value + **/ + if (to_volume_id > from_volume_id) to_volume_id--; +#endif // __WXGTK__ + m_objects_ctrl->Select(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, m_objects_model->GetParent(item))); @@ -1583,6 +1598,8 @@ void on_drop(wxDataViewEvent &event) int cnt = 0; for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id+=delta, cnt++) std::swap(volumes[id], volumes[id +delta]); + + g_prevent_list_events = false; } } //namespace GUI From a2eff85fa8d73b461a55f443c764f62610599187 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Aug 2018 14:13:07 +0200 Subject: [PATCH 124/185] Try to fix OSX crashing on UnselectAll --- lib/Slic3r/GUI/Plater.pm | 2 +- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e3f473fcad..062746e791 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -2223,7 +2223,7 @@ sub selection_changed { $self->Layout; } - print "selection_changed -> have_sel = $have_sel\n"; + #print "selection_changed -> have_sel = $have_sel\n"; # prepagate the event to the frame (a custom Wx event would be cleaner) $self->GetFrame->on_plater_selection_changed($have_sel); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index aba007a8db..c11aa46367 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -287,14 +287,16 @@ wxBoxSizer* content_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { + printf("wxEVT_CHAR : "); if (event.GetKeyCode() == WXK_TAB) m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); else if (event.GetKeyCode() == WXK_DELETE #ifdef __WXOSX__ || event.GetKeyCode() == WXK_BACK #endif //__WXOSX__ - ) - remove(); + ){ + printf("WXK_BACK\n"); + remove();} else event.Skip(); }); @@ -729,8 +731,10 @@ void unselect_objects() { printf("UNSELECT OBJECTS\n"); g_prevent_list_events = true; - m_objects_ctrl->UnselectAll(); -// part_selection_changed(); + if (m_objects_ctrl->GetSelection()) + m_objects_ctrl->UnselectAll(); + else + printf("all items are UNSELECTED\n"); g_prevent_list_events = false; get_optgroup(ogFrequentlyObjectSettings)->disable(); @@ -774,11 +778,9 @@ void object_ctrl_selection_changed() part_selection_changed(); -// if (m_selected_object_id < 0) return; - if (m_event_object_selection_changed > 0) { wxCommandEvent event(m_event_object_selection_changed); - event.SetInt(int(m_objects_model->GetParent(/*item*/ m_objects_ctrl->GetSelection()) != wxDataViewItem(0))); + event.SetInt(int(m_objects_model->GetParent(m_objects_ctrl->GetSelection()) != wxDataViewItem(0))); event.SetId(m_selected_object_id); get_main_frame()->ProcessWindowEvent(event); } From 79f2801d2a34f8dc698bd46d45f5d16234b4a8ec Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Aug 2018 14:34:04 +0200 Subject: [PATCH 125/185] One more try to understand OSX crashing on UnselectAll --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index c11aa46367..3260b4d27c 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -731,13 +731,13 @@ void unselect_objects() { printf("UNSELECT OBJECTS\n"); g_prevent_list_events = true; - if (m_objects_ctrl->GetSelection()) + if (m_objects_ctrl->GetSelection()) { m_objects_ctrl->UnselectAll(); + get_optgroup(ogFrequentlyObjectSettings)->disable(); + } else printf("all items are UNSELECTED\n"); g_prevent_list_events = false; - - get_optgroup(ogFrequentlyObjectSettings)->disable(); } void select_current_object(int idx) From 817fb5adb39dc6b1d74b0c767abaf7786bf5e287 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Aug 2018 15:35:54 +0200 Subject: [PATCH 126/185] Test of item_changed_selection(obj_idx) --- lib/Slic3r/GUI/Plater.pm | 2 ++ xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 062746e791..3632bb07c9 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1986,6 +1986,7 @@ sub on_config_change { sub item_changed_selection{ my ($self, $obj_idx) = @_; + printf "BEGIN item_changed_selection : obj_idx = $obj_idx\n"; $self->{canvas}->Refresh; if ($self->{canvas3D}) { @@ -1996,6 +1997,7 @@ sub item_changed_selection{ } Slic3r::GUI::_3DScene::render($self->{canvas3D}); } + printf "END item_changed_selection"; } sub collect_selections { diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 3260b4d27c..47be0b8321 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -733,7 +733,7 @@ void unselect_objects() g_prevent_list_events = true; if (m_objects_ctrl->GetSelection()) { m_objects_ctrl->UnselectAll(); - get_optgroup(ogFrequentlyObjectSettings)->disable(); + part_selection_changed(); } else printf("all items are UNSELECTED\n"); @@ -751,8 +751,6 @@ void select_current_object(int idx) m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); part_selection_changed(); g_prevent_list_events = false; - - get_optgroup(ogFrequentlyObjectSettings)->enable(); } void remove() @@ -1359,11 +1357,13 @@ void part_selection_changed() auto config = m_config; og->set_value("object_name", m_objects_model->GetName(item)); + og->enable(); m_default_config = std::make_shared(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part))); } else { wxString empty_str = wxEmptyString; og->set_value("object_name", empty_str); + og->disable(); m_config = nullptr; } From 92b578779ed0669a242a40349059cbe81a4fef40 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Aug 2018 16:10:58 +0200 Subject: [PATCH 127/185] Test of update_extruder_in_config() --- lib/Slic3r/GUI/Plater.pm | 2 -- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 3632bb07c9..062746e791 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1986,7 +1986,6 @@ sub on_config_change { sub item_changed_selection{ my ($self, $obj_idx) = @_; - printf "BEGIN item_changed_selection : obj_idx = $obj_idx\n"; $self->{canvas}->Refresh; if ($self->{canvas3D}) { @@ -1997,7 +1996,6 @@ sub item_changed_selection{ } Slic3r::GUI::_3DScene::render($self->{canvas3D}); } - printf "END item_changed_selection"; } sub collect_selections { diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 47be0b8321..93d649abf8 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -1325,11 +1325,13 @@ void update_settings_value() og->set_value("scale_y", 0); og->set_value("scale_z", 0); printf("return because of unselect\n"); + og->disable(); return; } g_is_percent_scale = boost::any_cast(og->get_value("scale_unit")) == _("%"); update_scale_values(); update_rotation_values(); + og->enable(); } void part_selection_changed() @@ -1357,13 +1359,11 @@ void part_selection_changed() auto config = m_config; og->set_value("object_name", m_objects_model->GetName(item)); - og->enable(); m_default_config = std::make_shared(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part))); } else { wxString empty_str = wxEmptyString; og->set_value("object_name", empty_str); - og->disable(); m_config = nullptr; } @@ -1475,6 +1475,7 @@ void set_extruder_column_hidden(bool hide) void update_extruder_in_config(const wxString& selection) { + printf("BEGIN OF update_extruder_in_config\n"); if (!*m_config || selection.empty()) return; @@ -1485,6 +1486,7 @@ void update_extruder_in_config(const wxString& selection) wxCommandEvent e(m_event_update_scene); get_main_frame()->ProcessWindowEvent(e); } + printf("END OF update_extruder_in_config\n"); } void update_scale_values() From 3c3b8ed76f23e048e16ca4897ab2651b0d9ac465 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Aug 2018 16:16:26 +0200 Subject: [PATCH 128/185] fixed typo in update_extruder_in_config --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 93d649abf8..aae583ea90 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -1476,7 +1476,7 @@ void set_extruder_column_hidden(bool hide) void update_extruder_in_config(const wxString& selection) { printf("BEGIN OF update_extruder_in_config\n"); - if (!*m_config || selection.empty()) + if (!m_config || selection.empty()) return; int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); From f0095d19be57b5fd77f766608a1d790bb531f7cc Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 15 Aug 2018 10:09:05 +0200 Subject: [PATCH 129/185] Some code refactoring --- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 166 +++++++++++++------------- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 4 + 2 files changed, 88 insertions(+), 82 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index aae583ea90..8f74158e90 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -210,13 +210,12 @@ wxPoint get_mouse_position_in_control() { pt.y - win->GetScreenPosition().y); } -// ****** from GUI.cpp -wxBoxSizer* content_objects_list(wxWindow *win) +void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz) { m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_objects_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects - auto objects_sz = new wxBoxSizer(wxVERTICAL); + objects_sz = new wxBoxSizer(wxVERTICAL); objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT, 20); m_objects_model = new PrusaObjectDataViewModel; @@ -228,7 +227,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) // column 0(Icon+Text) of the view control: m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 120, - wxALIGN_LEFT, /*wxDATAVIEW_COL_SORTABLE | */wxDATAVIEW_COL_RESIZABLE); + wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE); // column 1 of the view control: m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 45, @@ -254,9 +253,15 @@ wxBoxSizer* content_objects_list(wxWindow *win) // column 4 of the view control: m_objects_ctrl->AppendBitmapColumn(" ", 4, wxDATAVIEW_CELL_INERT, 25, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); +} - m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) - { +// ****** from GUI.cpp +wxBoxSizer* create_objects_list(wxWindow *win) +{ + wxBoxSizer* objects_sz; + create_objects_ctrl(win, objects_sz); + + m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) { object_ctrl_selection_changed(); #ifndef __WXMSW__ set_tooltip_for_item(get_mouse_position_in_control()); @@ -264,66 +269,21 @@ wxBoxSizer* content_objects_list(wxWindow *win) }); m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) { - wxDataViewItem item; - wxDataViewColumn* col; - m_objects_ctrl->HitTest(get_mouse_position_in_control(), item, col); - wxString title = col->GetTitle(); - if (!item) return; - - if (title == " ") - object_ctrl_context_menu(); - // ys_FIXME -// else if (title == _("Name") && pt.x >15 && -// m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) -// { -// if (is_windows10()) -// fix_through_netfabb(); -// } -#ifndef __WXMSW__ - m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip -#endif //__WXMSW__ + object_ctrl_context_menu(); event.Skip(); }); - m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) - { - printf("wxEVT_CHAR : "); - if (event.GetKeyCode() == WXK_TAB) - m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); - else if (event.GetKeyCode() == WXK_DELETE -#ifdef __WXOSX__ - || event.GetKeyCode() == WXK_BACK -#endif //__WXOSX__ - ){ - printf("WXK_BACK\n"); - remove();} - else - event.Skip(); - }); + m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { object_ctrl_key_event(event); }); #ifdef __WXMSW__ - m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { - update_extruder_in_config(event.GetString()); - }); + m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); }); m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) { set_tooltip_for_item(event.GetPosition()); event.Skip(); }); #else - m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) - { - if (event.GetColumn() == 3) - { - wxVariant variant; - m_objects_model->GetValue(variant, event.GetItem(), 3); -#ifdef __WXOSX__ - g_selected_extruder = variant.GetString(); -#else // --> for Linux - update_extruder_in_config(variant.GetString()); -#endif //__WXOSX__ - } - }); + m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { object_ctrl_item_value_change(event); }); #endif //__WXMSW__ m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, [](wxDataViewEvent& e) {on_begin_drag(e);}); @@ -332,7 +292,7 @@ wxBoxSizer* content_objects_list(wxWindow *win) return objects_sz; } -wxBoxSizer* content_edit_object_buttons(wxWindow* win) +wxBoxSizer* create_edit_object_buttons(wxWindow* win) { auto sizer = new wxBoxSizer(wxVERTICAL); @@ -493,7 +453,7 @@ wxBoxSizer* content_settings(wxWindow *win) get_optgroups().push_back(optgroup); // ogObjectSettings auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(content_edit_object_buttons(win), 0, wxEXPAND, 0); // *** Edit Object Buttons*** + sizer->Add(create_edit_object_buttons(win), 0, wxEXPAND, 0); // *** Edit Object Buttons*** sizer->Add(optgroup->sizer, 1, wxEXPAND | wxLEFT, 20); @@ -509,7 +469,7 @@ wxBoxSizer* content_settings(wxWindow *win) void add_objects_list(wxWindow* parent, wxBoxSizer* sizer) { - const auto ol_sizer = content_objects_list(parent); + const auto ol_sizer = create_objects_list(parent); sizer->Add(ol_sizer, 1, wxEXPAND | wxTOP, 20); set_objects_list_sizer(ol_sizer); } @@ -646,7 +606,7 @@ wxCollapsiblePane* add_collapsible_pane(wxWindow* parent, wxBoxSizer* sizer_pare void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer) { // *** Objects List *** - auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", content_objects_list); + auto collpane = add_collapsible_pane(parent, sizer, "Objects List:", create_objects_list); collpane->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, ([collpane](wxCommandEvent& e){ // wxWindowUpdateLocker noUpdates(g_right_panel); if (collpane->IsCollapsed()) { @@ -788,6 +748,58 @@ void object_ctrl_selection_changed() #endif //__WXOSX__ } +void object_ctrl_context_menu() +{ + wxDataViewItem item; + wxDataViewColumn* col; + m_objects_ctrl->HitTest(get_mouse_position_in_control(), item, col); + wxString title = col->GetTitle(); + if (!item) return; + + if (title == " ") + show_context_menu(); +// ys_FIXME +// else if (title == _("Name") && pt.x >15 && +// m_objects_model->GetIcon(item).GetRefData() == m_icon_manifold_warning.GetRefData()) +// { +// if (is_windows10()) +// fix_through_netfabb(); +// } +#ifndef __WXMSW__ + m_objects_ctrl->GetMainWindow()->SetToolTip(""); // hide tooltip +#endif //__WXMSW__ +} + +void object_ctrl_key_event(wxKeyEvent& event) +{ + if (event.GetKeyCode() == WXK_TAB) + m_objects_ctrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); + else if (event.GetKeyCode() == WXK_DELETE +#ifdef __WXOSX__ + || event.GetKeyCode() == WXK_BACK +#endif //__WXOSX__ + ){ + printf("WXK_BACK\n"); + remove(); + } + else + event.Skip(); +} + +void object_ctrl_item_value_change(wxDataViewEvent& event) +{ + if (event.GetColumn() == 3) + { + wxVariant variant; + m_objects_model->GetValue(variant, event.GetItem(), 3); +#ifdef __WXOSX__ + g_selected_extruder = variant.GetString(); +#else // --> for Linux + update_extruder_in_config(variant.GetString()); +#endif //__WXOSX__ + } +} + //update_optgroup void update_settings_list() { @@ -1049,30 +1061,20 @@ wxMenu *create_add_settings_popupmenu(bool is_part) return menu; } -void object_ctrl_context_menu() +void show_context_menu() { -// auto cur_column = m_objects_ctrl->GetCurrentColumn(); -// auto action_column = m_objects_ctrl->GetColumn(4); -// if (cur_column == action_column) - { - auto item = m_objects_ctrl->GetSelection(); - if (item) - { - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { - auto menu = create_add_part_popupmenu(); - get_tab_panel()->GetPage(0)->PopupMenu(menu); - } - else { -// auto parent = m_objects_model->GetParent(item); -// // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene -// obj_idx = m_objects_model->GetIdByItem(parent); -// auto volume_id = m_objects_model->GetVolumeIdByItem(item); -// if (volume_id < 0) return; - auto menu = create_part_settings_popupmenu(); - get_tab_panel()->GetPage(0)->PopupMenu(menu); - } - } - } + auto item = m_objects_ctrl->GetSelection(); + if (item) + { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + auto menu = create_add_part_popupmenu(); + get_tab_panel()->GetPage(0)->PopupMenu(menu); + } + else { + auto menu = create_part_settings_popupmenu(); + get_tab_panel()->GetPage(0)->PopupMenu(menu); + } + } } // ****** diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 8c35bea35f..f9fffb58b7 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -8,6 +8,7 @@ class wxString; class wxArrayString; class wxMenu; class wxDataViewEvent; +class wxKeyEvent; namespace Slic3r { class ModelObject; @@ -71,6 +72,9 @@ void remove(); void object_ctrl_selection_changed(); void object_ctrl_context_menu(); +void object_ctrl_key_event(wxKeyEvent& event); +void object_ctrl_item_value_change(wxDataViewEvent& event); +void show_context_menu(); void init_mesh_icons(); void set_event_object_selection_changed(const int& event); From 211790f8c35a7f950fddb95839a7b1009470a6ac Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 7 Aug 2018 11:15:47 +0200 Subject: [PATCH 130/185] Added qhull library to xs/src and cmake --- xs/CMakeLists.txt | 6 +- xs/src/qhull/Announce.txt | 47 + xs/src/qhull/CMakeLists.txt | 128 + xs/src/qhull/COPYING.txt | 38 + xs/src/qhull/README.txt | 623 +++ xs/src/qhull/REGISTER.txt | 32 + xs/src/qhull/html/index.htm | 935 ++++ .../html/normal_voronoi_knauss_oesterle.jpg | Bin 0 -> 23924 bytes xs/src/qhull/html/qconvex.htm | 630 +++ xs/src/qhull/html/qdelau_f.htm | 416 ++ xs/src/qhull/html/qdelaun.htm | 628 +++ xs/src/qhull/html/qh--4d.gif | Bin 0 -> 4372 bytes xs/src/qhull/html/qh--cone.gif | Bin 0 -> 2946 bytes xs/src/qhull/html/qh--dt.gif | Bin 0 -> 3772 bytes xs/src/qhull/html/qh--geom.gif | Bin 0 -> 318 bytes xs/src/qhull/html/qh--half.gif | Bin 0 -> 2537 bytes xs/src/qhull/html/qh--rand.gif | Bin 0 -> 3875 bytes xs/src/qhull/html/qh-code.htm | 1062 +++++ xs/src/qhull/html/qh-eg.htm | 693 +++ xs/src/qhull/html/qh-faq.htm | 1547 +++++++ xs/src/qhull/html/qh-get.htm | 106 + xs/src/qhull/html/qh-impre.htm | 826 ++++ xs/src/qhull/html/qh-optc.htm | 292 ++ xs/src/qhull/html/qh-optf.htm | 736 +++ xs/src/qhull/html/qh-optg.htm | 274 ++ xs/src/qhull/html/qh-opto.htm | 353 ++ xs/src/qhull/html/qh-optp.htm | 253 + xs/src/qhull/html/qh-optq.htm | 731 +++ xs/src/qhull/html/qh-optt.htm | 278 ++ xs/src/qhull/html/qh-quick.htm | 495 ++ xs/src/qhull/html/qhalf.htm | 626 +++ xs/src/qhull/html/qhull-cpp.xml | 214 + xs/src/qhull/html/qhull.htm | 473 ++ xs/src/qhull/html/qhull.man | 1008 ++++ xs/src/qhull/html/qhull.txt | 1263 +++++ xs/src/qhull/html/qvoron_f.htm | 396 ++ xs/src/qhull/html/qvoronoi.htm | 667 +++ xs/src/qhull/html/rbox.htm | 277 ++ xs/src/qhull/html/rbox.man | 176 + xs/src/qhull/html/rbox.txt | 195 + xs/src/qhull/index.htm | 284 ++ xs/src/qhull/origCMakeLists.txt | 426 ++ xs/src/qhull/src/Changes.txt | 2129 +++++++++ xs/src/qhull/src/libqhull/DEPRECATED.txt | 29 + xs/src/qhull/src/libqhull/Makefile | 240 + xs/src/qhull/src/libqhull/Mborland | 206 + xs/src/qhull/src/libqhull/geom.c | 1234 +++++ xs/src/qhull/src/libqhull/geom.h | 176 + xs/src/qhull/src/libqhull/geom2.c | 2094 +++++++++ xs/src/qhull/src/libqhull/global.c | 2217 +++++++++ xs/src/qhull/src/libqhull/index.htm | 264 ++ xs/src/qhull/src/libqhull/io.c | 4062 +++++++++++++++++ xs/src/qhull/src/libqhull/io.h | 159 + xs/src/qhull/src/libqhull/libqhull.c | 1403 ++++++ xs/src/qhull/src/libqhull/libqhull.h | 1140 +++++ xs/src/qhull/src/libqhull/libqhull.pro | 67 + xs/src/qhull/src/libqhull/mem.c | 576 +++ xs/src/qhull/src/libqhull/mem.h | 222 + xs/src/qhull/src/libqhull/merge.c | 3628 +++++++++++++++ xs/src/qhull/src/libqhull/merge.h | 178 + xs/src/qhull/src/libqhull/poly.c | 1205 +++++ xs/src/qhull/src/libqhull/poly.h | 296 ++ xs/src/qhull/src/libqhull/poly2.c | 3222 +++++++++++++ xs/src/qhull/src/libqhull/qh-geom.htm | 295 ++ xs/src/qhull/src/libqhull/qh-globa.htm | 165 + xs/src/qhull/src/libqhull/qh-io.htm | 305 ++ xs/src/qhull/src/libqhull/qh-mem.htm | 145 + xs/src/qhull/src/libqhull/qh-merge.htm | 366 ++ xs/src/qhull/src/libqhull/qh-poly.htm | 485 ++ xs/src/qhull/src/libqhull/qh-qhull.htm | 279 ++ xs/src/qhull/src/libqhull/qh-set.htm | 308 ++ xs/src/qhull/src/libqhull/qh-stat.htm | 163 + xs/src/qhull/src/libqhull/qh-user.htm | 271 ++ xs/src/qhull/src/libqhull/qhull-exports.def | 417 ++ xs/src/qhull/src/libqhull/qhull_a.h | 150 + xs/src/qhull/src/libqhull/qhull_p-exports.def | 418 ++ xs/src/qhull/src/libqhull/qset.c | 1340 ++++++ xs/src/qhull/src/libqhull/qset.h | 490 ++ xs/src/qhull/src/libqhull/random.c | 245 + xs/src/qhull/src/libqhull/random.h | 34 + xs/src/qhull/src/libqhull/rboxlib.c | 870 ++++ xs/src/qhull/src/libqhull/stat.c | 717 +++ xs/src/qhull/src/libqhull/stat.h | 543 +++ xs/src/qhull/src/libqhull/user.c | 538 +++ xs/src/qhull/src/libqhull/user.h | 909 ++++ xs/src/qhull/src/libqhull/usermem.c | 94 + xs/src/qhull/src/libqhull/userprintf.c | 66 + xs/src/qhull/src/libqhull/userprintf_rbox.c | 53 + xs/src/qhull/src/libqhull_r/Makefile | 240 + xs/src/qhull/src/libqhull_r/geom2_r.c | 2096 +++++++++ xs/src/qhull/src/libqhull_r/geom_r.c | 1234 +++++ xs/src/qhull/src/libqhull_r/geom_r.h | 184 + xs/src/qhull/src/libqhull_r/global_r.c | 2100 +++++++++ xs/src/qhull/src/libqhull_r/index.htm | 266 ++ xs/src/qhull/src/libqhull_r/io_r.c | 4062 +++++++++++++++++ xs/src/qhull/src/libqhull_r/io_r.h | 167 + xs/src/qhull/src/libqhull_r/libqhull_r.c | 1403 ++++++ xs/src/qhull/src/libqhull_r/libqhull_r.h | 1134 +++++ xs/src/qhull/src/libqhull_r/libqhull_r.pro | 67 + xs/src/qhull/src/libqhull_r/mem_r.c | 562 +++ xs/src/qhull/src/libqhull_r/mem_r.h | 234 + xs/src/qhull/src/libqhull_r/merge_r.c | 3627 +++++++++++++++ xs/src/qhull/src/libqhull_r/merge_r.h | 186 + xs/src/qhull/src/libqhull_r/poly2_r.c | 3222 +++++++++++++ xs/src/qhull/src/libqhull_r/poly_r.c | 1205 +++++ xs/src/qhull/src/libqhull_r/poly_r.h | 303 ++ xs/src/qhull/src/libqhull_r/qh-geom_r.htm | 295 ++ xs/src/qhull/src/libqhull_r/qh-globa_r.htm | 163 + xs/src/qhull/src/libqhull_r/qh-io_r.htm | 305 ++ xs/src/qhull/src/libqhull_r/qh-mem_r.htm | 145 + xs/src/qhull/src/libqhull_r/qh-merge_r.htm | 366 ++ xs/src/qhull/src/libqhull_r/qh-poly_r.htm | 485 ++ xs/src/qhull/src/libqhull_r/qh-qhull_r.htm | 279 ++ xs/src/qhull/src/libqhull_r/qh-set_r.htm | 308 ++ xs/src/qhull/src/libqhull_r/qh-stat_r.htm | 161 + xs/src/qhull/src/libqhull_r/qh-user_r.htm | 271 ++ .../qhull/src/libqhull_r/qhull_r-exports.def | 404 ++ xs/src/qhull/src/libqhull_r/qhull_ra.h | 158 + xs/src/qhull/src/libqhull_r/qset_r.c | 1340 ++++++ xs/src/qhull/src/libqhull_r/qset_r.h | 502 ++ xs/src/qhull/src/libqhull_r/random_r.c | 247 + xs/src/qhull/src/libqhull_r/random_r.h | 41 + xs/src/qhull/src/libqhull_r/rboxlib_r.c | 842 ++++ xs/src/qhull/src/libqhull_r/stat_r.c | 682 +++ xs/src/qhull/src/libqhull_r/stat_r.h | 533 +++ xs/src/qhull/src/libqhull_r/user_r.c | 527 +++ xs/src/qhull/src/libqhull_r/user_r.h | 882 ++++ xs/src/qhull/src/libqhull_r/usermem_r.c | 94 + xs/src/qhull/src/libqhull_r/userprintf_r.c | 65 + .../qhull/src/libqhull_r/userprintf_rbox_r.c | 53 + xs/src/qhull/src/libqhullcpp/Coordinates.cpp | 198 + xs/src/qhull/src/libqhullcpp/Coordinates.h | 303 ++ .../src/libqhullcpp/PointCoordinates.cpp | 348 ++ .../qhull/src/libqhullcpp/PointCoordinates.h | 161 + xs/src/qhull/src/libqhullcpp/Qhull.cpp | 352 ++ xs/src/qhull/src/libqhullcpp/Qhull.h | 132 + xs/src/qhull/src/libqhullcpp/QhullError.h | 62 + xs/src/qhull/src/libqhullcpp/QhullFacet.cpp | 519 +++ xs/src/qhull/src/libqhullcpp/QhullFacet.h | 151 + .../qhull/src/libqhullcpp/QhullFacetList.cpp | 174 + xs/src/qhull/src/libqhullcpp/QhullFacetList.h | 106 + .../qhull/src/libqhullcpp/QhullFacetSet.cpp | 147 + xs/src/qhull/src/libqhullcpp/QhullFacetSet.h | 97 + .../qhull/src/libqhullcpp/QhullHyperplane.cpp | 187 + .../qhull/src/libqhullcpp/QhullHyperplane.h | 123 + xs/src/qhull/src/libqhullcpp/QhullIterator.h | 173 + .../qhull/src/libqhullcpp/QhullLinkedList.h | 388 ++ xs/src/qhull/src/libqhullcpp/QhullPoint.cpp | 203 + xs/src/qhull/src/libqhullcpp/QhullPoint.h | 136 + .../qhull/src/libqhullcpp/QhullPointSet.cpp | 62 + xs/src/qhull/src/libqhullcpp/QhullPointSet.h | 77 + xs/src/qhull/src/libqhullcpp/QhullPoints.cpp | 320 ++ xs/src/qhull/src/libqhullcpp/QhullPoints.h | 266 ++ xs/src/qhull/src/libqhullcpp/QhullQh.cpp | 237 + xs/src/qhull/src/libqhullcpp/QhullQh.h | 110 + xs/src/qhull/src/libqhullcpp/QhullRidge.cpp | 124 + xs/src/qhull/src/libqhullcpp/QhullRidge.h | 112 + xs/src/qhull/src/libqhullcpp/QhullSet.cpp | 62 + xs/src/qhull/src/libqhullcpp/QhullSet.h | 462 ++ xs/src/qhull/src/libqhullcpp/QhullSets.h | 27 + xs/src/qhull/src/libqhullcpp/QhullStat.cpp | 42 + xs/src/qhull/src/libqhullcpp/QhullStat.h | 49 + xs/src/qhull/src/libqhullcpp/QhullVertex.cpp | 112 + xs/src/qhull/src/libqhullcpp/QhullVertex.h | 104 + .../qhull/src/libqhullcpp/QhullVertexSet.cpp | 161 + xs/src/qhull/src/libqhullcpp/QhullVertexSet.h | 86 + xs/src/qhull/src/libqhullcpp/RboxPoints.cpp | 224 + xs/src/qhull/src/libqhullcpp/RboxPoints.h | 69 + xs/src/qhull/src/libqhullcpp/RoadError.cpp | 158 + xs/src/qhull/src/libqhullcpp/RoadError.h | 88 + xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp | 122 + xs/src/qhull/src/libqhullcpp/RoadLogEvent.h | 77 + .../qhull/src/libqhullcpp/functionObjects.h | 67 + xs/src/qhull/src/libqhullcpp/libqhullcpp.pro | 71 + xs/src/qhull/src/libqhullcpp/qt-qhull.cpp | 130 + .../qhull/src/libqhullcpp/usermem_r-cpp.cpp | 93 + .../src/libqhullstatic/libqhullstatic.pro | 19 + .../src/libqhullstatic_r/libqhullstatic_r.pro | 21 + xs/src/qhull/src/qconvex/qconvex.c | 326 ++ xs/src/qhull/src/qconvex/qconvex.pro | 9 + xs/src/qhull/src/qconvex/qconvex_r.c | 328 ++ xs/src/qhull/src/qdelaunay/qdelaun.c | 315 ++ xs/src/qhull/src/qdelaunay/qdelaun_r.c | 317 ++ xs/src/qhull/src/qdelaunay/qdelaunay.pro | 9 + xs/src/qhull/src/qhalf/qhalf.c | 316 ++ xs/src/qhull/src/qhalf/qhalf.pro | 9 + xs/src/qhull/src/qhalf/qhalf_r.c | 318 ++ xs/src/qhull/src/qhull-all.pro | 94 + xs/src/qhull/src/qhull-app-c.pri | 24 + xs/src/qhull/src/qhull-app-c_r.pri | 26 + xs/src/qhull/src/qhull-app-cpp.pri | 23 + xs/src/qhull/src/qhull-app-shared.pri | 27 + xs/src/qhull/src/qhull-app-shared_r.pri | 29 + xs/src/qhull/src/qhull-libqhull-src.pri | 39 + xs/src/qhull/src/qhull-libqhull-src_r.pri | 39 + xs/src/qhull/src/qhull-warn.pri | 57 + xs/src/qhull/src/qhull/qhull.pro | 9 + xs/src/qhull/src/qhull/unix.c | 372 ++ xs/src/qhull/src/qhull/unix_r.c | 374 ++ .../qhull/src/qhulltest/Coordinates_test.cpp | 539 +++ .../src/qhulltest/PointCoordinates_test.cpp | 478 ++ .../src/qhulltest/QhullFacetList_test.cpp | 196 + .../src/qhulltest/QhullFacetSet_test.cpp | 153 + .../qhull/src/qhulltest/QhullFacet_test.cpp | 283 ++ .../src/qhulltest/QhullHyperplane_test.cpp | 429 ++ .../src/qhulltest/QhullLinkedList_test.cpp | 330 ++ .../src/qhulltest/QhullPointSet_test.cpp | 378 ++ .../qhull/src/qhulltest/QhullPoint_test.cpp | 437 ++ .../qhull/src/qhulltest/QhullPoints_test.cpp | 561 +++ .../qhull/src/qhulltest/QhullRidge_test.cpp | 159 + xs/src/qhull/src/qhulltest/QhullSet_test.cpp | 434 ++ .../src/qhulltest/QhullVertexSet_test.cpp | 152 + .../qhull/src/qhulltest/QhullVertex_test.cpp | 184 + xs/src/qhull/src/qhulltest/Qhull_test.cpp | 360 ++ .../qhull/src/qhulltest/RboxPoints_test.cpp | 215 + xs/src/qhull/src/qhulltest/RoadTest.cpp | 94 + xs/src/qhull/src/qhulltest/RoadTest.h | 102 + xs/src/qhull/src/qhulltest/qhulltest.cpp | 94 + xs/src/qhull/src/qhulltest/qhulltest.pro | 36 + xs/src/qhull/src/qvoronoi/qvoronoi.c | 303 ++ xs/src/qhull/src/qvoronoi/qvoronoi.pro | 9 + xs/src/qhull/src/qvoronoi/qvoronoi_r.c | 305 ++ xs/src/qhull/src/rbox/rbox.c | 88 + xs/src/qhull/src/rbox/rbox.pro | 9 + xs/src/qhull/src/rbox/rbox_r.c | 78 + xs/src/qhull/src/testqset/testqset.c | 891 ++++ xs/src/qhull/src/testqset/testqset.pro | 30 + xs/src/qhull/src/testqset_r/testqset_r.c | 890 ++++ xs/src/qhull/src/testqset_r/testqset_r.pro | 30 + xs/src/qhull/src/user_eg/user_eg.c | 330 ++ xs/src/qhull/src/user_eg/user_eg.pro | 11 + xs/src/qhull/src/user_eg/user_eg_r.c | 326 ++ xs/src/qhull/src/user_eg2/user_eg2.c | 746 +++ xs/src/qhull/src/user_eg2/user_eg2.pro | 11 + xs/src/qhull/src/user_eg2/user_eg2_r.c | 742 +++ xs/src/qhull/src/user_eg3/user_eg3.pro | 12 + xs/src/qhull/src/user_eg3/user_eg3_r.cpp | 162 + 237 files changed, 104145 insertions(+), 1 deletion(-) create mode 100644 xs/src/qhull/Announce.txt create mode 100644 xs/src/qhull/CMakeLists.txt create mode 100644 xs/src/qhull/COPYING.txt create mode 100644 xs/src/qhull/README.txt create mode 100644 xs/src/qhull/REGISTER.txt create mode 100644 xs/src/qhull/html/index.htm create mode 100644 xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg create mode 100644 xs/src/qhull/html/qconvex.htm create mode 100644 xs/src/qhull/html/qdelau_f.htm create mode 100644 xs/src/qhull/html/qdelaun.htm create mode 100644 xs/src/qhull/html/qh--4d.gif create mode 100644 xs/src/qhull/html/qh--cone.gif create mode 100644 xs/src/qhull/html/qh--dt.gif create mode 100644 xs/src/qhull/html/qh--geom.gif create mode 100644 xs/src/qhull/html/qh--half.gif create mode 100644 xs/src/qhull/html/qh--rand.gif create mode 100644 xs/src/qhull/html/qh-code.htm create mode 100644 xs/src/qhull/html/qh-eg.htm create mode 100644 xs/src/qhull/html/qh-faq.htm create mode 100644 xs/src/qhull/html/qh-get.htm create mode 100644 xs/src/qhull/html/qh-impre.htm create mode 100644 xs/src/qhull/html/qh-optc.htm create mode 100644 xs/src/qhull/html/qh-optf.htm create mode 100644 xs/src/qhull/html/qh-optg.htm create mode 100644 xs/src/qhull/html/qh-opto.htm create mode 100644 xs/src/qhull/html/qh-optp.htm create mode 100644 xs/src/qhull/html/qh-optq.htm create mode 100644 xs/src/qhull/html/qh-optt.htm create mode 100644 xs/src/qhull/html/qh-quick.htm create mode 100644 xs/src/qhull/html/qhalf.htm create mode 100644 xs/src/qhull/html/qhull-cpp.xml create mode 100644 xs/src/qhull/html/qhull.htm create mode 100644 xs/src/qhull/html/qhull.man create mode 100644 xs/src/qhull/html/qhull.txt create mode 100644 xs/src/qhull/html/qvoron_f.htm create mode 100644 xs/src/qhull/html/qvoronoi.htm create mode 100644 xs/src/qhull/html/rbox.htm create mode 100644 xs/src/qhull/html/rbox.man create mode 100644 xs/src/qhull/html/rbox.txt create mode 100644 xs/src/qhull/index.htm create mode 100644 xs/src/qhull/origCMakeLists.txt create mode 100644 xs/src/qhull/src/Changes.txt create mode 100644 xs/src/qhull/src/libqhull/DEPRECATED.txt create mode 100644 xs/src/qhull/src/libqhull/Makefile create mode 100644 xs/src/qhull/src/libqhull/Mborland create mode 100644 xs/src/qhull/src/libqhull/geom.c create mode 100644 xs/src/qhull/src/libqhull/geom.h create mode 100644 xs/src/qhull/src/libqhull/geom2.c create mode 100644 xs/src/qhull/src/libqhull/global.c create mode 100644 xs/src/qhull/src/libqhull/index.htm create mode 100644 xs/src/qhull/src/libqhull/io.c create mode 100644 xs/src/qhull/src/libqhull/io.h create mode 100644 xs/src/qhull/src/libqhull/libqhull.c create mode 100644 xs/src/qhull/src/libqhull/libqhull.h create mode 100644 xs/src/qhull/src/libqhull/libqhull.pro create mode 100644 xs/src/qhull/src/libqhull/mem.c create mode 100644 xs/src/qhull/src/libqhull/mem.h create mode 100644 xs/src/qhull/src/libqhull/merge.c create mode 100644 xs/src/qhull/src/libqhull/merge.h create mode 100644 xs/src/qhull/src/libqhull/poly.c create mode 100644 xs/src/qhull/src/libqhull/poly.h create mode 100644 xs/src/qhull/src/libqhull/poly2.c create mode 100644 xs/src/qhull/src/libqhull/qh-geom.htm create mode 100644 xs/src/qhull/src/libqhull/qh-globa.htm create mode 100644 xs/src/qhull/src/libqhull/qh-io.htm create mode 100644 xs/src/qhull/src/libqhull/qh-mem.htm create mode 100644 xs/src/qhull/src/libqhull/qh-merge.htm create mode 100644 xs/src/qhull/src/libqhull/qh-poly.htm create mode 100644 xs/src/qhull/src/libqhull/qh-qhull.htm create mode 100644 xs/src/qhull/src/libqhull/qh-set.htm create mode 100644 xs/src/qhull/src/libqhull/qh-stat.htm create mode 100644 xs/src/qhull/src/libqhull/qh-user.htm create mode 100644 xs/src/qhull/src/libqhull/qhull-exports.def create mode 100644 xs/src/qhull/src/libqhull/qhull_a.h create mode 100644 xs/src/qhull/src/libqhull/qhull_p-exports.def create mode 100644 xs/src/qhull/src/libqhull/qset.c create mode 100644 xs/src/qhull/src/libqhull/qset.h create mode 100644 xs/src/qhull/src/libqhull/random.c create mode 100644 xs/src/qhull/src/libqhull/random.h create mode 100644 xs/src/qhull/src/libqhull/rboxlib.c create mode 100644 xs/src/qhull/src/libqhull/stat.c create mode 100644 xs/src/qhull/src/libqhull/stat.h create mode 100644 xs/src/qhull/src/libqhull/user.c create mode 100644 xs/src/qhull/src/libqhull/user.h create mode 100644 xs/src/qhull/src/libqhull/usermem.c create mode 100644 xs/src/qhull/src/libqhull/userprintf.c create mode 100644 xs/src/qhull/src/libqhull/userprintf_rbox.c create mode 100644 xs/src/qhull/src/libqhull_r/Makefile create mode 100644 xs/src/qhull/src/libqhull_r/geom2_r.c create mode 100644 xs/src/qhull/src/libqhull_r/geom_r.c create mode 100644 xs/src/qhull/src/libqhull_r/geom_r.h create mode 100644 xs/src/qhull/src/libqhull_r/global_r.c create mode 100644 xs/src/qhull/src/libqhull_r/index.htm create mode 100644 xs/src/qhull/src/libqhull_r/io_r.c create mode 100644 xs/src/qhull/src/libqhull_r/io_r.h create mode 100644 xs/src/qhull/src/libqhull_r/libqhull_r.c create mode 100644 xs/src/qhull/src/libqhull_r/libqhull_r.h create mode 100644 xs/src/qhull/src/libqhull_r/libqhull_r.pro create mode 100644 xs/src/qhull/src/libqhull_r/mem_r.c create mode 100644 xs/src/qhull/src/libqhull_r/mem_r.h create mode 100644 xs/src/qhull/src/libqhull_r/merge_r.c create mode 100644 xs/src/qhull/src/libqhull_r/merge_r.h create mode 100644 xs/src/qhull/src/libqhull_r/poly2_r.c create mode 100644 xs/src/qhull/src/libqhull_r/poly_r.c create mode 100644 xs/src/qhull/src/libqhull_r/poly_r.h create mode 100644 xs/src/qhull/src/libqhull_r/qh-geom_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-globa_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-io_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-mem_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-merge_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-poly_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-qhull_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-set_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-stat_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qh-user_r.htm create mode 100644 xs/src/qhull/src/libqhull_r/qhull_r-exports.def create mode 100644 xs/src/qhull/src/libqhull_r/qhull_ra.h create mode 100644 xs/src/qhull/src/libqhull_r/qset_r.c create mode 100644 xs/src/qhull/src/libqhull_r/qset_r.h create mode 100644 xs/src/qhull/src/libqhull_r/random_r.c create mode 100644 xs/src/qhull/src/libqhull_r/random_r.h create mode 100644 xs/src/qhull/src/libqhull_r/rboxlib_r.c create mode 100644 xs/src/qhull/src/libqhull_r/stat_r.c create mode 100644 xs/src/qhull/src/libqhull_r/stat_r.h create mode 100644 xs/src/qhull/src/libqhull_r/user_r.c create mode 100644 xs/src/qhull/src/libqhull_r/user_r.h create mode 100644 xs/src/qhull/src/libqhull_r/usermem_r.c create mode 100644 xs/src/qhull/src/libqhull_r/userprintf_r.c create mode 100644 xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c create mode 100644 xs/src/qhull/src/libqhullcpp/Coordinates.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/Coordinates.h create mode 100644 xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/PointCoordinates.h create mode 100644 xs/src/qhull/src/libqhullcpp/Qhull.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/Qhull.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullError.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacet.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetList.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullFacetSet.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullHyperplane.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullIterator.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullLinkedList.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoint.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoint.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPointSet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPointSet.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoints.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullPoints.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullQh.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullQh.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullRidge.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullRidge.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullSet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullSet.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullSets.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullStat.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullStat.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertex.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertex.h create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/QhullVertexSet.h create mode 100644 xs/src/qhull/src/libqhullcpp/RboxPoints.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/RboxPoints.h create mode 100644 xs/src/qhull/src/libqhullcpp/RoadError.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/RoadError.h create mode 100644 xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/RoadLogEvent.h create mode 100644 xs/src/qhull/src/libqhullcpp/functionObjects.h create mode 100644 xs/src/qhull/src/libqhullcpp/libqhullcpp.pro create mode 100644 xs/src/qhull/src/libqhullcpp/qt-qhull.cpp create mode 100644 xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp create mode 100644 xs/src/qhull/src/libqhullstatic/libqhullstatic.pro create mode 100644 xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro create mode 100644 xs/src/qhull/src/qconvex/qconvex.c create mode 100644 xs/src/qhull/src/qconvex/qconvex.pro create mode 100644 xs/src/qhull/src/qconvex/qconvex_r.c create mode 100644 xs/src/qhull/src/qdelaunay/qdelaun.c create mode 100644 xs/src/qhull/src/qdelaunay/qdelaun_r.c create mode 100644 xs/src/qhull/src/qdelaunay/qdelaunay.pro create mode 100644 xs/src/qhull/src/qhalf/qhalf.c create mode 100644 xs/src/qhull/src/qhalf/qhalf.pro create mode 100644 xs/src/qhull/src/qhalf/qhalf_r.c create mode 100644 xs/src/qhull/src/qhull-all.pro create mode 100644 xs/src/qhull/src/qhull-app-c.pri create mode 100644 xs/src/qhull/src/qhull-app-c_r.pri create mode 100644 xs/src/qhull/src/qhull-app-cpp.pri create mode 100644 xs/src/qhull/src/qhull-app-shared.pri create mode 100644 xs/src/qhull/src/qhull-app-shared_r.pri create mode 100644 xs/src/qhull/src/qhull-libqhull-src.pri create mode 100644 xs/src/qhull/src/qhull-libqhull-src_r.pri create mode 100644 xs/src/qhull/src/qhull-warn.pri create mode 100644 xs/src/qhull/src/qhull/qhull.pro create mode 100644 xs/src/qhull/src/qhull/unix.c create mode 100644 xs/src/qhull/src/qhull/unix_r.c create mode 100644 xs/src/qhull/src/qhulltest/Coordinates_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullFacet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullPointSet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullPoint_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullPoints_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullRidge_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullSet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/QhullVertex_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/Qhull_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/RboxPoints_test.cpp create mode 100644 xs/src/qhull/src/qhulltest/RoadTest.cpp create mode 100644 xs/src/qhull/src/qhulltest/RoadTest.h create mode 100644 xs/src/qhull/src/qhulltest/qhulltest.cpp create mode 100644 xs/src/qhull/src/qhulltest/qhulltest.pro create mode 100644 xs/src/qhull/src/qvoronoi/qvoronoi.c create mode 100644 xs/src/qhull/src/qvoronoi/qvoronoi.pro create mode 100644 xs/src/qhull/src/qvoronoi/qvoronoi_r.c create mode 100644 xs/src/qhull/src/rbox/rbox.c create mode 100644 xs/src/qhull/src/rbox/rbox.pro create mode 100644 xs/src/qhull/src/rbox/rbox_r.c create mode 100644 xs/src/qhull/src/testqset/testqset.c create mode 100644 xs/src/qhull/src/testqset/testqset.pro create mode 100644 xs/src/qhull/src/testqset_r/testqset_r.c create mode 100644 xs/src/qhull/src/testqset_r/testqset_r.pro create mode 100644 xs/src/qhull/src/user_eg/user_eg.c create mode 100644 xs/src/qhull/src/user_eg/user_eg.pro create mode 100644 xs/src/qhull/src/user_eg/user_eg_r.c create mode 100644 xs/src/qhull/src/user_eg2/user_eg2.c create mode 100644 xs/src/qhull/src/user_eg2/user_eg2.pro create mode 100644 xs/src/qhull/src/user_eg2/user_eg2_r.c create mode 100644 xs/src/qhull/src/user_eg3/user_eg3.pro create mode 100644 xs/src/qhull/src/user_eg3/user_eg3_r.cpp diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index d41b4c13a9..f1609bc14a 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -459,7 +459,7 @@ if(APPLE) # Ignore undefined symbols of the perl interpreter, they will be found in the caller image. target_link_libraries(XS "-undefined dynamic_lookup") endif() -target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri semver avrdude) +target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri semver avrdude qhull) if(SLIC3R_PROFILE) target_link_libraries(XS Shiny) endif() @@ -540,6 +540,10 @@ endif() add_subdirectory(src/avrdude) +add_subdirectory(src/qhull) +include_directories(${LIBDIR}/qhull/src) +message(STATUS ${LIBDIR}/qhull/src) + ## REQUIRED packages # Find and configure boost diff --git a/xs/src/qhull/Announce.txt b/xs/src/qhull/Announce.txt new file mode 100644 index 0000000000..635cff1afb --- /dev/null +++ b/xs/src/qhull/Announce.txt @@ -0,0 +1,47 @@ + + Qhull 2015.2 2016/01/18 + + http://www.qhull.org + git@github.com:qhull/qhull.git + http://www.geomview.org + +Qhull computes convex hulls, Delaunay triangulations, Voronoi diagrams, +furthest-site Voronoi diagrams, and halfspace intersections about a point. +It runs in 2-d, 3-d, 4-d, or higher. It implements the Quickhull algorithm +for computing convex hulls. Qhull handles round-off errors from floating +point arithmetic. It can approximate a convex hull. + +The program includes options for hull volume, facet area, partial hulls, +input transformations, randomization, tracing, multiple output formats, and +execution statistics. The program can be called from within your application. +You can view the results in 2-d, 3-d and 4-d with Geomview. + +To download Qhull: + http://www.qhull.org/download + git@github.com:qhull/qhull.git + +Download qhull-96.ps for: + + Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, "The + Quickhull Algorithm for Convex Hulls," ACM Trans. on + Mathematical Software, 22(4):469-483, Dec. 1996. + http://www.acm.org/pubs/citations/journals/toms/1996-22-4/p469-barber/ + http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.117.405 + +Abstract: + +The convex hull of a set of points is the smallest convex set that contains +the points. This article presents a practical convex hull algorithm that +combines the two-dimensional Quickhull Algorithm with the general dimension +Beneath-Beyond Algorithm. It is similar to the randomized, incremental +algorithms for convex hull and Delaunay triangulation. We provide empirical +evidence that the algorithm runs faster when the input contains non-extreme +points, and that it uses less memory. + +Computational geometry algorithms have traditionally assumed that input sets +are well behaved. When an algorithm is implemented with floating point +arithmetic, this assumption can lead to serious errors. We briefly describe +a solution to this problem when computing the convex hull in two, three, or +four dimensions. The output is a set of "thick" facets that contain all +possible exact convex hulls of the input. A variation is effective in five +or more dimensions. diff --git a/xs/src/qhull/CMakeLists.txt b/xs/src/qhull/CMakeLists.txt new file mode 100644 index 0000000000..d798b018fa --- /dev/null +++ b/xs/src/qhull/CMakeLists.txt @@ -0,0 +1,128 @@ + +# This CMake file is written specifically to integrate qhull library with Slic3rPE +# (see https://github.com/prusa3d/Slic3r for more information about the project) +# +# Only original libraries qhullstatic_r and qhullcpp are included. +# They are built as a single statically linked library. +# +# Created by modification of the original qhull CMakeLists. +# Lukas Matena (25.7.2018), lukasmatena@seznam.cz + + +project(qhull) +cmake_minimum_required(VERSION 2.6) + +# Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, qhull-warn.pri +set(qhull_VERSION2 "2015.2 2016/01/18") # not used, See global.c, global_r.c, rbox.c, rbox_r.c +set(qhull_VERSION "7.2.0") # Advance every release + +#include(CMakeModules/CheckLFS.cmake) +#option(WITH_LFS "Enable Large File Support" ON) +#check_lfs(WITH_LFS) + + +message(STATUS "qhull Version: ${qhull_VERSION} (static linking)") + + +set(libqhull_HEADERS + # reentrant qhull HEADERS: + src/libqhull_r/libqhull_r.h + src/libqhull_r/geom_r.h + src/libqhull_r/io_r.h + src/libqhull_r/mem_r.h + src/libqhull_r/merge_r.h + src/libqhull_r/poly_r.h + src/libqhull_r/qhull_ra.h + src/libqhull_r/qset_r.h + src/libqhull_r/random_r.h + src/libqhull_r/stat_r.h + src/libqhull_r/user_r.h + + # C++ interface to reentrant Qhull HEADERS: + src/libqhullcpp/Coordinates.h + src/libqhullcpp/functionObjects.h + src/libqhullcpp/PointCoordinates.h + src/libqhullcpp/Qhull.h + src/libqhullcpp/QhullError.h + src/libqhullcpp/QhullFacet.h + src/libqhullcpp/QhullFacetList.h + src/libqhullcpp/QhullFacetSet.h + src/libqhullcpp/QhullHyperplane.h + src/libqhullcpp/QhullIterator.h + src/libqhullcpp/QhullLinkedList.h + src/libqhullcpp/QhullPoint.h + src/libqhullcpp/QhullPoints.h + src/libqhullcpp/QhullPointSet.h + src/libqhullcpp/QhullQh.h + src/libqhullcpp/QhullRidge.h + src/libqhullcpp/QhullSet.h + src/libqhullcpp/QhullSets.h + src/libqhullcpp/QhullStat.h + src/libqhullcpp/QhullVertex.h + src/libqhullcpp/QhullVertexSet.h + src/libqhullcpp/RboxPoints.h + src/libqhullcpp/RoadError.h + src/libqhullcpp/RoadLogEvent.h + src/qhulltest/RoadTest.h +) + +set(libqhull_SOURCES + # reentrant qhull SOURCES: + src/libqhull_r/global_r.c + src/libqhull_r/stat_r.c + src/libqhull_r/geom2_r.c + src/libqhull_r/poly2_r.c + src/libqhull_r/merge_r.c + src/libqhull_r/libqhull_r.c + src/libqhull_r/geom_r.c + src/libqhull_r/poly_r.c + src/libqhull_r/qset_r.c + src/libqhull_r/mem_r.c + src/libqhull_r/random_r.c + src/libqhull_r/usermem_r.c + src/libqhull_r/userprintf_r.c + src/libqhull_r/io_r.c + src/libqhull_r/user_r.c + src/libqhull_r/rboxlib_r.c + src/libqhull_r/userprintf_rbox_r.c + + # C++ interface to reentrant Qhull SOURCES: + src/libqhullcpp/Coordinates.cpp + src/libqhullcpp/PointCoordinates.cpp + src/libqhullcpp/Qhull.cpp + src/libqhullcpp/QhullFacet.cpp + src/libqhullcpp/QhullFacetList.cpp + src/libqhullcpp/QhullFacetSet.cpp + src/libqhullcpp/QhullHyperplane.cpp + src/libqhullcpp/QhullPoint.cpp + src/libqhullcpp/QhullPointSet.cpp + src/libqhullcpp/QhullPoints.cpp + src/libqhullcpp/QhullQh.cpp + src/libqhullcpp/QhullRidge.cpp + src/libqhullcpp/QhullSet.cpp + src/libqhullcpp/QhullStat.cpp + src/libqhullcpp/QhullVertex.cpp + src/libqhullcpp/QhullVertexSet.cpp + src/libqhullcpp/RboxPoints.cpp + src/libqhullcpp/RoadError.cpp + src/libqhullcpp/RoadLogEvent.cpp + + # headers for both (libqhullr and libqhullcpp: + ${libqhull_HEADERS} +) + + +################################################## +# combined library (reentrant qhull and qhullcpp) for Slic3r: +set(qhull_STATIC qhull) +add_library(${qhull_STATIC} STATIC ${libqhull_SOURCES}) +set_target_properties(${qhull_STATIC} PROPERTIES + VERSION ${qhull_VERSION}) + +if(UNIX) + target_link_libraries(${qhull_STATIC} m) +endif(UNIX) +################################################## + +# LIBDIR is defined in the main xs CMake file: +target_include_directories(${qhull_STATIC} PRIVATE ${LIBDIR}/qhull/src) diff --git a/xs/src/qhull/COPYING.txt b/xs/src/qhull/COPYING.txt new file mode 100644 index 0000000000..2895ec6a32 --- /dev/null +++ b/xs/src/qhull/COPYING.txt @@ -0,0 +1,38 @@ + Qhull, Copyright (c) 1993-2015 + + C.B. Barber + Arlington, MA + + and + + The National Science and Technology Research Center for + Computation and Visualization of Geometric Structures + (The Geometry Center) + University of Minnesota + + email: qhull@qhull.org + +This software includes Qhull from C.B. Barber and The Geometry Center. +Qhull is copyrighted as noted above. Qhull is free software and may +be obtained via http from www.qhull.org. It may be freely copied, modified, +and redistributed under the following conditions: + +1. All copyright notices must remain intact in all files. + +2. A copy of this text file must be distributed along with any copies + of Qhull that you redistribute; this includes copies that you have + modified, or copies of programs or other software products that + include Qhull. + +3. If you modify Qhull, you must include a notice giving the + name of the person performing the modification, the date of + modification, and the reason for such modification. + +4. When distributing modified versions of Qhull, or other software + products that include Qhull, you must provide notice that the original + source code may be obtained as noted above. + +5. There is no warranty or other guarantee of fitness for Qhull, it is + provided solely "as is". Bug reports or fixes may be sent to + qhull_bug@qhull.org; the authors may or may not act on them as + they desire. diff --git a/xs/src/qhull/README.txt b/xs/src/qhull/README.txt new file mode 100644 index 0000000000..f4c7a3b220 --- /dev/null +++ b/xs/src/qhull/README.txt @@ -0,0 +1,623 @@ +This distribution of qhull library is only meant for interfacing qhull with Slic3rPE +(https://github.com/prusa3d/Slic3r). + +The qhull source file was acquired from https://github.com/qhull/qhull at revision +f0bd8ceeb84b554d7cdde9bbfae7d3351270478c. + +No changes to the qhull library were made, except for +- setting REALfloat=1 in user_r.h to enforce calculations in floats +- modifying CMakeLists.txt (the original was renamed to origCMakeLists.txt) + +Many thanks to C. Bradford Barber and all contributors. + +Lukas Matena (lukasmatena@seznam.cz) +25.7.2018 + + +See original contents of the README file below. + +====================================================================================== +====================================================================================== +====================================================================================== + + +Name + + qhull, rbox 2015.2 2016/01/18 + +Convex hull, Delaunay triangulation, Voronoi diagrams, Halfspace intersection + + Documentation: + html/index.htm + + + Available from: + + + (git@github.com:qhull/qhull.git) + + News and a paper: + + + + Version 1 (simplicial only): + + +Purpose + + Qhull is a general dimension convex hull program that reads a set + of points from stdin, and outputs the smallest convex set that contains + the points to stdout. It also generates Delaunay triangulations, Voronoi + diagrams, furthest-site Voronoi diagrams, and halfspace intersections + about a point. + + Rbox is a useful tool in generating input for Qhull; it generates + hypercubes, diamonds, cones, circles, simplices, spirals, + lattices, and random points. + + Qhull produces graphical output for Geomview. This helps with + understanding the output. + +Environment requirements + + Qhull and rbox should run on all 32-bit and 64-bit computers. Use + an ANSI C or C++ compiler to compile the program. The software is + self-contained. It comes with examples and test scripts. + + Qhull's C++ interface uses the STL. The C++ test program uses QTestLib + from the Qt Framework. Qhull's C++ interface may change without + notice. Eventually, it will move into the qhull shared library. + + Qhull is copyrighted software. Please read COPYING.txt and REGISTER.txt + before using or distributing Qhull. + +To cite Qhull, please use + + Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The Quickhull + algorithm for convex hulls," ACM Trans. on Mathematical Software, + 22(4):469-483, Dec 1996, http://www.qhull.org. + +To modify Qhull, particularly the C++ interface + + Qhull is on GitHub + (http://github.com/qhull/qhull, git@github.com:qhull/qhull.git) + + For internal documentation, see html/qh-code.htm + +To install Qhull + + Qhull is precompiled for Windows 32-bit, otherwise it needs compilation. + + Qhull includes Makefiles for gcc and other targets, CMakeLists.txt for CMake, + .sln/.vcproj/.vcxproj files for Microsoft Visual Studio, and .pro files + for Qt Creator. It compiles under Windows with mingw. + + Install and build instructions follow. + + See the end of this document for a list of distributed files. + +----------------- +Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT + + The zip file contains rbox.exe, qhull.exe, qconvex.exe, qdelaunay.exe, + qhalf.exe, qvoronoi.exe, testqset.exe, user_eg*.exe, documentation files, + and source files. Qhull.exe and user-eg3.exe are compiled with the reentrant + library while the other executables use the non-reentrant library. + + To install Qhull: + - Unzip the files into a directory (e.g., named 'qhull') + - Click on QHULL-GO or open a command window into Qhull's bin directory. + - Test with 'rbox D4 | qhull' + + To uninstall Qhull + - Delete the qhull directory + + To learn about Qhull: + - Execute 'qconvex' for a synopsis and examples. + - Execute 'rbox 10 | qconvex' to compute the convex hull of 10 random points. + - Execute 'rbox 10 | qconvex i TO file' to write results to 'file'. + - Browse the documentation: qhull\html\index.htm + - If an error occurs, Windows sends the error to stdout instead of stderr. + Use 'TO xxx' to send normal output to xxx + + To improve the command window + - Double-click the window bar to increase the size of the window + - Right-click the window bar + - Select Properties + - Check QuickEdit Mode + Select text with right-click or Enter + Paste text with right-click + - Change Font to Lucinda Console + - Change Layout to Screen Buffer Height 999, Window Size Height 55 + - Change Colors to Screen Background White, Screen Text Black + - Click OK + - Select 'Modify shortcut that started this window', then OK + + If you use qhull a lot, install a bash shell such as + MSYS (www.mingw.org/wiki/msys), Road Bash (www.qhull.org/bash), + or Cygwin (www.cygwin.com). + +----------------- +Installing Qhull on Unix with gcc + + To build Qhull, static libraries, shared library, and C++ interface + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - make + - export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH + + The Makefiles may be edited for other compilers. + If 'testqset' exits with an error, qhull is broken + + A simple Makefile for Qhull is in src/libqhull and src/libqhull_r. + To build the Qhull executables and libqhullstatic + - Extract Qhull from qhull...tgz or qhull...zip + - cd src/libqhull_r # cd src/libqhull + - make + + +----------------- +Installing Qhull with CMake 2.6 or later + + See CMakeLists.txt for examples and further build instructions + + To build Qhull, static libraries, shared library, and C++ interface + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - cd build + - cmake --help # List build generators + - make -G "" .. && cmake .. + - cmake .. + - make + - make install + + The ".." is important. It refers to the parent directory (i.e., qhull/) + + On Windows, CMake installs to C:/Program Files/qhull. 64-bit generators + have a "Win64" tag. + + If creating a qhull package, please include a pkg-config file based on build/qhull*.pc.in + + If cmake fails with "No CMAKE_C_COMPILER could be found" + - cmake was not able to find the build environment specified by -G "..." + +----------------- +Installing Qhull with Qt + + To build Qhull, including its C++ test (qhulltest) + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load src/qhull-all.pro into QtCreator + - Build + +------------------- +Working with Qhull's C++ interface + + See html/qh-code.htm#cpp for calling Qhull from C++ programs + + See html/qh-code.htm#reentrant for converting from Qhull-2012 + + Examples of using the C++ interface + user_eg3_r.cpp + qhulltest/*_test.cpp + + Qhull's C++ interface is likely to change. Stay current with GitHub. + + To clone Qhull's next branch from http://github.com/qhull/qhull + git init + git clone git@github.com:qhull/qhull.git + cd qhull + git checkout next + ... + git pull origin next + + Compile qhullcpp and libqhullstatic_r with the same compiler. Both libraries + use the C routines setjmp() and longjmp() for error handling. They must + be compiled with the same compiler. + +------------------- +Calling Qhull from C programs + + See html/qh-code.htm#library for calling Qhull from C programs + + See html/qh-code.htm#reentrant for converting from Qhull-2012 + + Warning: You will need to understand Qhull's data structures and read the + code. Most users will find it easier to call Qhull as an external command. + + The new, reentrant 'C' code (src/libqhull_r), passes a pointer to qhT + to most Qhull routines. This allows multiple instances of Qhull to run + at the same time. It simplifies the C++ interface. + + The non-reentrant 'C' code (src/libqhull) looks unusual. It refers to + Qhull's global data structure, qhT, through a 'qh' macro (e.g., 'qh ferr'). + This allows the same code to use static memory or heap memory. + If qh_QHpointer is defined, qh_qh is a pointer to an allocated qhT; + otherwise qh_qh is a global static data structure of type qhT. + +------------------ +Compiling Qhull with Microsoft Visual C++ + + To compile 32-bit Qhull with Microsoft Visual C++ 2010 and later + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load solution build/qhull-32.sln + - Build target 'Win32' + - Project qhulltest requires Qt for DevStudio (http://www.qt.io) + Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012) + If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' + + To compile 64-bit Qhull with Microsoft Visual C++ 2010 and later + - 64-bit Qhull has larger data structures due to 64-bit pointers + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load solution build/qhull-64.sln + - Build target 'Win32' + - Project qhulltest requires Qt for DevStudio (http://www.qt.io) + Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012_64) + If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' + + To compile Qhull with Microsoft Visual C++ 2005 (vcproj files) + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Load solution build/qhull.sln + - Build target 'win32' (not 'x64') + - Project qhulltest requires Qt for DevStudio (http://www.qt.io) + Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/4.7.4) + If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' + +----------------- +Compiling Qhull with Qt Creator + + Qt (http://www.qt.io) is a C++ framework for Windows, Linux, and Macintosh + + Qhull uses QTestLib to test qhull's C++ interface (see src/qhulltest/) + + To compile Qhull with Qt Creator + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Download the Qt SDK + - Start Qt Creator + - Load src/qhull-all.pro + - Build + +----------------- +Compiling Qhull with mingw on Windows + + To compile Qhull with MINGW + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Install Road Bash (http://www.qhull.org/bash) + or install MSYS (http://www.mingw.org/wiki/msys) + - Install MINGW-w64 (http://sourceforge.net/projects/mingw-w64). + Mingw is included with Qt SDK. + - make + +----------------- +Compiling Qhull with cygwin on Windows + + To compile Qhull with cygwin + - Download and extract Qhull (either GitHub, .tgz file, or .zip file) + - Install cygwin (http://www.cygwin.com) + - Include packages for gcc, make, ar, and ln + - make + +----------------- +Compiling from Makfile without gcc + + The file, qhull-src.tgz, contains documentation and source files for + qhull and rbox. + + To unpack the tgz file + - tar zxf qhull-src.tgz + - cd qhull + - Use qhull/Makefile + Simpler Makefiles are qhull/src/libqhull/Makefile and qhull/src/libqhull_r/Makefile + + Compiling qhull and rbox with Makefile + - in Makefile, check the CC, CCOPTS1, PRINTMAN, and PRINTC defines + - the defaults are gcc and enscript + - CCOPTS1 should include the ANSI flag. It defines __STDC__ + - in user.h, check the definitions of qh_SECticks and qh_CPUclock. + - use '#define qh_CLOCKtype 2' for timing runs longer than 1 hour + - type: make + - this builds: qhull qconvex qdelaunay qhalf qvoronoi rbox libqhull.a libqhull_r.a + - type: make doc + - this prints the man page + - See also qhull/html/index.htm + - if your compiler reports many errors, it is probably not a ANSI C compiler + - you will need to set the -ansi switch or find another compiler + - if your compiler warns about missing prototypes for fprintf() etc. + - this is ok, your compiler should have these in stdio.h + - if your compiler warns about missing prototypes for memset() etc. + - include memory.h in qhull_a.h + - if your compiler reports "global.c: storage size of 'qh_qh' isn't known" + - delete the initializer "={0}" in global.c, stat.c and mem.c + - if your compiler warns about "stat.c: improper initializer" + - this is ok, the initializer is not used + - if you have trouble building libqhull.a with 'ar' + - try 'make -f Makefile.txt qhullx' + - if the code compiles, the qhull test case will automatically execute + - if an error occurs, there's an incompatibility between machines + - If you can, try a different compiler + - You can turn off the Qhull memory manager with qh_NOmem in mem.h + - You can turn off compiler optimization (-O2 in Makefile) + - If you find the source of the problem, please let us know + - to install the programs and their man pages: + - define MANDIR and BINDIR + - type 'make install' + + - if you have Geomview (www.geomview.org) + - try 'rbox 100 | qconvex G >a' and load 'a' into Geomview + - run 'q_eg' for Geomview examples of Qhull output (see qh-eg.htm) + +------------------ +Compiling on other machines and compilers + + Qhull may compile with Borland C++ 5.0 bcc32. A Makefile is included. + Execute 'cd src/libqhull; make -f Mborland'. If you use the Borland IDE, set + the ANSI option in Options:Project:Compiler:Source:Language-compliance. + + Qhull may compile with Borland C++ 4.02 for Win32 and DOS Power Pack. + Use 'cd src/libqhull; make -f Mborland -D_DPMI'. Qhull 1.0 compiles with + Borland C++ 4.02. For rbox 1.0, use "bcc32 -WX -w- -O2-e -erbox -lc rbox.c". + Use the same options for Qhull 1.0. [D. Zwick] + + If you have troubles with the memory manager, you can turn it off by + defining qh_NOmem in mem.h. + +----------------- +Distributed files + + README.txt // Instructions for installing Qhull + REGISTER.txt // Qhull registration + COPYING.txt // Copyright notice + QHULL-GO.lnk // Windows icon for eg/qhull-go.bat + Announce.txt // Announcement + CMakeLists.txt // CMake build file (2.6 or later) + CMakeModules/CheckLFS.cmake // enables Large File Support in cmake + File_id.diz // Package descriptor + index.htm // Home page + Makefile // Makefile for gcc and other compilers + qhull*.md5sum // md5sum for all files + + bin/* // Qhull executables and dll (.zip only) + build/qhull*.pc.in // pkg-config templates for qhull_r, qhull, and qhull_p + build/qhull-32.sln // 32-bit DevStudio solution and project files (2010 and later) + build/*-32.vcxproj + build/qhull-64.sln // 64-bit DevStudio solution and project files (2010 and later) + build/*-64.vcxproj + build/qhull.sln // DevStudio solution and project files (2005 and 2009) + build/*.vcproj + eg/* // Test scripts and geomview files from q_eg + html/index.htm // Manual + html/qh-faq.htm // Frequently asked questions + html/qh-get.htm // Download page + html/qhull-cpp.xml // C++ style notes as a Road FAQ (www.qhull.org/road) + src/Changes.txt // Change history for Qhull and rbox + src/qhull-all.pro // Qt project + +eg/ + q_eg // shell script for Geomview examples (eg.01.cube) + q_egtest // shell script for Geomview test examples + q_test // shell script to test qhull + q_test-ok.txt // output from q_test + qhulltest-ok.txt // output from qhulltest (Qt only) + make-vcproj.sh // bash shell script to create vcproj and vcxprog files + qhull-zip.sh // bash shell script for distribution files + +rbox consists of (bin, html): + rbox.exe // Win32 executable (.zip only) + rbox.htm // html manual + rbox.man // Unix man page + rbox.txt + +qhull consists of (bin, html): + qconvex.exe // Win32 executables and dlls (.zip download only) + qhull.exe // Built with the reentrant library (about 2% slower) + qdelaunay.exe + qhalf.exe + qvoronoi.exe + qhull_r.dll + qhull-go.bat // command window + qconvex.htm // html manual + qdelaun.htm + qdelau_f.htm + qhalf.htm + qvoronoi.htm + qvoron_f.htm + qh-eg.htm + qh-code.htm + qh-impre.htm + index.htm + qh-opt*.htm + qh-quick.htm + qh--*.gif // images for manual + normal_voronoi_knauss_oesterle.jpg + qhull.man // Unix man page + qhull.txt + +bin/ + msvcr80.dll // Visual C++ redistributable file (.zip download only) + +src/ + qhull/unix.c // Qhull and rbox applications using non-reentrant libqhullstatic.a + rbox/rbox.c + qconvex/qconvex.c + qhalf/qhalf.c + qdelaunay/qdelaunay.c + qvoronoi/qvoronoi.c + + qhull/unix_r.c // Qhull and rbox applications using reentrant libqhullstatic_r.a + rbox/rbox_r.c + qconvex/qconvex_r.c // Qhull applications built with reentrant libqhull_r/Makefile + qhalf/qhalf_r.c + qdelaunay/qdelaun_r.c + qvoronoi/qvoronoi_r.c + + user_eg/user_eg_r.c // example of using qhull_r.dll from a user program + user_eg2/user_eg2_r.c // example of using libqhullstatic_r.a from a user program + user_eg3/user_eg3_r.cpp // example of Qhull's C++ interface libqhullcpp with libqhullstatic_r.a + qhulltest/qhulltest.cpp // Test of Qhull's C++ interface using Qt's QTestLib + qhull-*.pri // Include files for Qt projects + testqset_r/testqset_r.c // Test of reentrant qset_r.c and mem_r.c + testqset/testqset.c // Test of non-rentrant qset.c and mem.c + + +src/libqhull + libqhull.pro // Qt project for non-rentrant, shared library (qhull.dll) + index.htm // design documentation for libqhull + qh-*.htm + qhull-exports.def // Export Definition file for Visual C++ + Makefile // Simple gcc Makefile for qhull and libqhullstatic.a + Mborland // Makefile for Borland C++ 5.0 + + libqhull.h // header file for qhull + user.h // header file of user definable constants + libqhull.c // Quickhull algorithm with partitioning + user.c // user re-definable functions + usermem.c + userprintf.c + userprintf_rbox.c + + qhull_a.h // include files for libqhull/*.c + geom.c // geometric routines + geom2.c + geom.h + global.c // global variables + io.c // input-output routines + io.h + mem.c // memory routines, this is stand-alone code + mem.h + merge.c // merging of non-convex facets + merge.h + poly.c // polyhedron routines + poly2.c + poly.h + qset.c // set routines, this only depends on mem.c + qset.h + random.c // utilities w/ Park & Miller's random number generator + random.h + rboxlib.c // point set generator for rbox + stat.c // statistics + stat.h + +src/libqhull_r + libqhull_r.pro // Qt project for rentrant, shared library (qhull_r.dll) + index.htm // design documentation for libqhull_r + qh-*_r.htm + qhull-exports_r.def // Export Definition file for Visual C++ + Makefile // Simple gcc Makefile for qhull and libqhullstatic.a + + libqhull_r.h // header file for qhull + user_r.h // header file of user definable constants + libqhull_r.c // Quickhull algorithm wi_r.hpartitioning + user_r.c // user re-definable functions + usermem.c + userprintf.c + userprintf_rbox.c + qhull_ra.h // include files for libqhull/*_r.c + geom_r.c // geometric routines + geom2.c + geom_r.h + global_r.c // global variables + io_r.c // input-output routines + io_r.h + mem_r.c // memory routines, this is stand-alone code + mem.h + merge_r.c // merging of non-convex facets + merge.h + poly_r.c // polyhedron routines + poly2.c + poly_r.h + qset_r.c // set routines, this only depends on mem_r.c + qset.h + random_r.c // utilities w/ Park & Miller's random number generator + random.h + rboxlib_r.c // point set generator for rbox + stat_r.c // statistics + stat.h + +src/libqhullcpp/ + libqhullcpp.pro // Qt project for renentrant, static C++ library + Qhull.cpp // Calls libqhull_r.c from C++ + Qhull.h + qt-qhull.cpp // Supporting methods for Qt + + Coordinates.cpp // input classes + Coordinates.h + + PointCoordinates.cpp + PointCoordinates.h + RboxPoints.cpp // call rboxlib.c from C++ + RboxPoints.h + + QhullFacet.cpp // data structure classes + QhullFacet.h + QhullHyperplane.cpp + QhullHyperplane.h + QhullPoint.cpp + QhullPoint.h + QhullQh.cpp + QhullRidge.cpp + QhullRidge.h + QhullVertex.cpp + QhullVertex.h + + QhullFacetList.cpp // collection classes + QhullFacetList.h + QhullFacetSet.cpp + QhullFacetSet.h + QhullIterator.h + QhullLinkedList.h + QhullPoints.cpp + QhullPoints.h + QhullPointSet.cpp + QhullPointSet.h + QhullSet.cpp + QhullSet.h + QhullSets.h + QhullVertexSet.cpp + QhullVertexSet.h + + functionObjects.h // supporting classes + QhullError.cpp + QhullError.h + QhullQh.cpp + QhullQh.h + QhullStat.cpp + QhullStat.h + RoadError.cpp // Supporting base classes + RoadError.h + RoadLogEvent.cpp + RoadLogEvent.h + usermem_r-cpp.cpp // Optional override for qh_exit() to throw an error + +src/libqhullstatic/ + libqhullstatic.pro // Qt project for non-reentrant, static library + +src/libqhullstatic_r/ + libqhullstatic_r.pro // Qt project for reentrant, static library + +src/qhulltest/ + qhulltest.pro // Qt project for test of C++ interface + Coordinates_test.cpp // Test of each class + PointCoordinates_test.cpp + Qhull_test.cpp + QhullFacet_test.cpp + QhullFacetList_test.cpp + QhullFacetSet_test.cpp + QhullHyperplane_test.cpp + QhullLinkedList_test.cpp + QhullPoint_test.cpp + QhullPoints_test.cpp + QhullPointSet_test.cpp + QhullRidge_test.cpp + QhullSet_test.cpp + QhullVertex_test.cpp + QhullVertexSet_test.cpp + RboxPoints_test.cpp + RoadTest.cpp // Run multiple test files with QTestLib + RoadTest.h + +----------------- +Authors: + + C. Bradford Barber Hannu Huhdanpaa (Version 1.0) + bradb@shore.net hannu@qhull.org + + Qhull 1.0 and 2.0 were developed under NSF grants NSF/DMS-8920161 + and NSF-CCR-91-15793 750-7504 at the Geometry Center and Harvard + University. If you find Qhull useful, please let us know. diff --git a/xs/src/qhull/REGISTER.txt b/xs/src/qhull/REGISTER.txt new file mode 100644 index 0000000000..16ccb1a58d --- /dev/null +++ b/xs/src/qhull/REGISTER.txt @@ -0,0 +1,32 @@ +Dear Qhull User + +We would like to find out how you are using our software. Think of +Qhull as a new kind of shareware: you share your science and successes +with us, and we share our software and support with you. + +If you use Qhull, please send us a note telling +us what you are doing with it. + +We need to know: + + (1) What you are working on - an abstract of your work would be + fine. + + (2) How Qhull has helped you, for example, by increasing your + productivity or allowing you to do things you could not do + before. If Qhull had a direct bearing on your work, please + tell us about this. + +We encourage you to cite Qhull in your publications. + +To cite Qhull, please use + + Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The Quickhull + algorithm for convex hulls," ACM Trans. on Mathematical Software, + 22(4):469-483, Dec 1996, http://www.qhull.org. + +Please send e-mail to + + bradb@shore.net + +Thank you! diff --git a/xs/src/qhull/html/index.htm b/xs/src/qhull/html/index.htm new file mode 100644 index 0000000000..ca4789b47f --- /dev/null +++ b/xs/src/qhull/html/index.htm @@ -0,0 +1,935 @@ + + + + + + +Qhull manual + + + + + +
+ +

[random-fixed] Qhull manual

+ +

Qhull is a general dimension code for computing convex hulls, +Delaunay triangulations, halfspace intersections about a point, Voronoi +diagrams, furthest-site Delaunay triangulations, and +furthest-site Voronoi diagrams. These structures have +applications in science, engineering, statistics, and +mathematics. See Fukuda's +introduction to convex hulls, Delaunay triangulations, +Voronoi diagrams, and linear programming. For a detailed +introduction, see O'Rourke ['94], Computational +Geometry in C. +

+ +

There are six programs. Except for rbox, they use +the same code. Each program includes instructions and examples. +

+
    +
  • qconvex -- convex hulls +
  • qdelaunay -- Delaunay triangulations and + furthest-site Delaunay triangulations +
  • qhalf -- halfspace intersections about a point +
  • qhull -- all structures with additional options +
  • qvoronoi -- Voronoi diagrams and + furthest-site Voronoi diagrams +
  • rbox -- generate point distributions for qhull +
+
+ +

Qhull implements the Quickhull algorithm for computing the +convex hull. Qhull includes options +for hull volume, facet area, multiple output formats, and +graphical output. It can approximate a convex hull.

+ +

Qhull handles roundoff errors from floating point +arithmetic. It generates a convex hull with "thick" facets. +A facet's outer plane is clearly above all of the points; +its inner plane is clearly below the facet's vertices. Any +exact convex hull must lie between the inner and outer plane. + +

Qhull uses merged facets, triangulated output, or joggled +input. Triangulated output triangulates non-simplicial, merged +facets. Joggled input also +guarantees simplicial output, but it +is less accurate than merged facets. For merged facets, Qhull +reports the maximum outer and inner plane. + +

Brad Barber, Arlington, MA

+ +

Copyright © 1995-2015 C.B. Barber

+ +
+ +

»Qhull manual: Table of +Contents

+ + +

»When to use Qhull

+
+ +

Qhull constructs convex hulls, Delaunay triangulations, +halfspace intersections about a point, Voronoi diagrams, furthest-site Delaunay +triangulations, and furthest-site Voronoi diagrams.

+ +

For convex hulls and halfspace intersections, Qhull may be used +for 2-d upto 8-d. For Voronoi diagrams and Delaunay triangulations, Qhull may be +used for 2-d upto 7-d. In higher dimensions, the size of the output +grows rapidly and Qhull does not work well with virtual memory. +If n is the size of +the input and d is the dimension (d>=3), the size of the output +and execution time +grows by n^(floor(d/2) +[see Performance]. For example, do +not try to build a 16-d convex hull of 1000 points. It will +have on the order of 1,000,000,000,000,000,000,000,000 facets. + +

On a 600 MHz Pentium 3, Qhull computes the 2-d convex hull of +300,000 cocircular points in 11 seconds. It computes the +2-d Delaunay triangulation and 3-d convex hull of 120,000 points +in 12 seconds. It computes the +3-d Delaunay triangulation and 4-d convex hull of 40,000 points +in 18 seconds. It computes the +4-d Delaunay triangulation and 5-d convex hull of 6,000 points +in 12 seconds. It computes the +5-d Delaunay triangulation and 6-d convex hull of 1,000 points +in 12 seconds. It computes the +6-d Delaunay triangulation and 7-d convex hull of 300 points +in 15 seconds. It computes the +7-d Delaunay triangulation and 8-d convex hull of 120 points +in 15 seconds. It computes the +8-d Delaunay triangulation and 9-d convex hull of 70 points +in 15 seconds. It computes the +9-d Delaunay triangulation and 10-d convex hull of 50 points +in 17 seconds. The 10-d convex hull of 50 points has about 90,000 facets. + + +

Qhull does not support constrained Delaunay +triangulations, triangulation of non-convex surfaces, mesh +generation of non-convex objects, or medium-sized inputs in 9-D +and higher.

+ +

This is a big package with many options. It is one of the +fastest available. It is the only 3-d code that handles precision +problems due to floating point arithmetic. For example, it +implements the identity function for extreme points (see Imprecision in Qhull).

+ +

[2016] A newly discovered, bad case for Qhull is multiple, nearly incident points within a 10^-13 ball of 3-d and higher +Delaunay triangulations (input sites in the unit cube). Nearly incident points within substantially +smaller or larger balls are OK. Error QH6271 is reported if a problem occurs. A future release of Qhull +will handle this case. For more information, see "Nearly coincident points on an edge" in Limitations of merged facets + +

If you need a short code for convex hull, Delaunay +triangulation, or Voronoi volumes consider Clarkson's hull +program. If you need 2-d Delaunay triangulations consider +Shewchuk's triangle +program. It is much faster than Qhull and it allows +constraints. Both programs use exact arithmetic. They are in http://www.netlib.org/voronoi/. + +

If your input is in general position (i.e., no coplanar or colinear points), +

  • Tomilov's quickhull.hpp () +or Qhull version +1.0 may meet your needs. Both programs detect precision problems, +but do not handle them.

    + +

    CGAL is a library of efficient and reliable +geometric algorithms. It uses C++ templates and the Boost library to produce dimension-specific +code. This allows more efficient use of memory than Qhull's general-dimension +code. CGAL simulates arbitrary precision while Qhull handles round-off error +with thick facets. Compare the two approaches with Robustness Issues in CGAL, +and Imprecision in Qhull. + + +

    Leda is a +library for writing computational +geometry programs and other combinatorial algorithms. It +includes routines for computing 3-d convex +hulls, 2-d Delaunay triangulations, and 3-d Delaunay triangulations. +It provides rational arithmetic and graphical output. It runs on most +platforms. + +

    If your problem is in high dimensions with a few, +non-simplicial facets, try Fukuda's cdd. +It is much faster than Qhull for these distributions.

    + +

    Custom software for 2-d and 3-d convex hulls may be faster +than Qhull. Custom software should use less memory. Qhull uses +general-dimension data structures and code. The data structures +support non-simplicial facets.

    + +

    Qhull is not suitable for mesh generation or triangulation of +arbitrary surfaces. You may use Qhull if the surface is convex or +completely visible from an interior point (e.g., a star-shaped +polyhedron). First, project each site to a sphere that is +centered at the interior point. Then, compute the convex hull of +the projected sites. The facets of the convex hull correspond to +a triangulation of the surface. For mesh generation of arbitrary +surfaces, see Schneiders' +Finite Element Mesh Generation.

    + +

    Qhull is not suitable for constrained Delaunay triangulations. +With a lot of work, you can write a program that uses Qhull to +add constraints by adding additional points to the triangulation.

    + +

    Qhull is not suitable for the subdivision of arbitrary +objects. Use qdelaunay to subdivide a convex object.

    + +
  • +

    »Description of +Qhull

    +
    + +

    »definition

    +
    + +

    The convex hull of a point set P is the smallest +convex set that contains P. If P is finite, the +convex hull defines a matrix A and a vector b such +that for all x in P, Ax+b <= [0,...].

    + +

    Qhull computes the convex hull in 2-d, 3-d, 4-d, and higher +dimensions. Qhull represents a convex hull as a list of facets. +Each facet has a set of vertices, a set of neighboring facets, +and a halfspace. A halfspace is defined by a unit normal and an +offset (i.e., a row of A and an element of b).

    + +

    Qhull accounts for round-off error. It returns +"thick" facets defined by two parallel hyperplanes. The +outer planes contain all input points. The inner planes exclude +all output vertices. See Imprecise +convex hulls.

    + +

    Qhull may be used for the Delaunay triangulation or the +Voronoi diagram of a set of points. It may be used for the +intersection of halfspaces.

    + +
    +

    »input format

    +
    + +

    The input data on stdin consists of:

    + +
      +
    • first line contains the dimension
    • +
    • second line contains the number of input points
    • +
    • remaining lines contain point coordinates
    • +
    + +

    For example:

    + +
    +    3  #sample 3-d input
    +    5
    +    0.4 -0.5 1.0
    +    1000 -1e-5 -100
    +    0.3 0.2 0.1
    +    1.0 1.0 1.0
    +    0 0 0
    +
    + +

    Input may be entered by hand. End the input with a control-D +(^D) character.

    + +

    To input data from a file, use I/O redirection or 'TI file'. The filename may not +include spaces or quotes.

    + +

    A comment starts with a non-numeric character and continues to +the end of line. The first comment is reported in summaries and +statistics. With multiple qhull commands, use option 'FQ' to place a comment in the output.

    + +

    The dimension and number of points can be reversed. Comments +and line breaks are ignored. Error reporting is better if there +is one point per line.

    + +
    +

    »option format

    +
    + +

    Use options to specify the output formats and control +Qhull. The qhull program takes all options. The +other programs use a subset of the options. They disallow +experimental and inappropriate options. + +

    +
      +
    • +qconvex == qhull +
    • +qdelaunay == qhull d Qbb +
    • +qhalf == qhull H +
    • +qvoronoi == qhull v Qbb +
    +
    + +

    Single letters are used for output formats and precision +constants. The other options are grouped into menus for formats +('F'), Geomview ('G '), printing ('P'), Qhull control ('Q '), and tracing ('T'). The menu options may be listed +together (e.g., 'GrD3' for 'Gr' and 'GD3'). Options may be in any +order. Capitalized options take a numeric argument (except for 'PG' and 'F' +options). Use option 'FO' to print +the selected options.

    + +

    Qhull uses zero-relative indexing. If there are n +points, the index of the first point is 0 and the index of +the last point is n-1.

    + +

    The default options are:

    + +
      +
    • summary output ('s')
    • +
    • merged facets ('C-0' in 2-d, + 3-d, 4-d; 'Qx' in 5-d and + up)
    • +
    + +

    Except for bounding box +('Qbk:n', etc.), drop facets +('Pdk:n', etc.), and +Qhull command ('FQ'), only the last +occurence of an option counts. +Bounding box and drop facets may be repeated for each dimension. +Option 'FQ' may be repeated any number of times. + +

    The Unix tcsh and ksh shells make it easy to +try out different options. In Windows 95, use a command window with doskey +and a window scroller (e.g., peruse).

    + +
    +

    »output format

    +
    + +

    To write the results to a file, use I/O redirection or 'TO file'. Windows 95 users should use +'TO file' or the console. If a filename is surrounded by single quotes, +it may include spaces. +

    + +

    The default output option is a short summary ('s') to stdout. There are many +others (see output and formats). You can list vertex incidences, +vertices and facets, vertex coordinates, or facet normals. You +can view Qhull objects with Geomview, Mathematica, or Maple. You can +print the internal data structures. You can call Qhull from your +application (see Qhull library).

    + +

    For example, 'qhull o' lists the +vertices and facets of the convex hull.

    + +

    Error messages and additional summaries ('s') go to stderr. Unless +redirected, stderr is the console.

    + +
    +

    »algorithm

    +
    + +

    Qhull implements the Quickhull algorithm for convex hull +[Barber et al. '96]. This algorithm +combines the 2-d Quickhull algorithm with the n-d +beneath-beyond algorithm [c.f., Preparata & Shamos '85]. It is similar to the randomized +algorithms of Clarkson and others [Clarkson & Shor '89; Clarkson et al. '93; +Mulmuley '94]. For a demonstration, see How Qhull adds a point. The main +advantages of Quickhull are output sensitive performance (in +terms of the number of extreme points), reduced space +requirements, and floating-point error handling.

    + +
    +

    »data structures

    +
    + +

    Qhull produces the following data structures for dimension d: +

    + +
      +
    • A coordinate is a real number in floating point + format.
    • +
    • A point is an array of d coordinates. + With option 'QJ', the + coordinates are joggled by a small amount.
    • +
    • A vertex is an input point.
    • +
    • A hyperplane is d normal coefficients and + an offset. The length of the normal is one. The + hyperplane defines a halfspace. If V is a normal, b + is an offset, and x is a point inside the convex + hull, then Vx+b <0.
    • +
    • An outer plane is a positive + offset from a hyperplane. When Qhull is done, all points + will be below all outer planes.
    • +
    • An inner plane is a negative + offset from a hyperplane. When Qhull is done, all + vertices will be above the corresponding inner planes.
    • +
    • An orientation is either 'top' or 'bottom'. It is the + topological equivalent of a hyperplane's geometric + orientation.
    • +
    • A simplicial facet is a set of + d neighboring facets, a set of d vertices, a + hyperplane equation, an inner plane, an outer plane, and + an orientation. For example in 3-d, a simplicial facet is + a triangle.
    • +
    • A centrum is a point on a facet's hyperplane. A + centrum is the average of a facet's vertices. Neighboring + facets are convex if each centrum is below the + neighbor facet's hyperplane.
    • +
    • A ridge is a set of d-1 vertices, two + neighboring facets, and an orientation. For example in + 3-d, a ridge is a line segment.
    • +
    • A non-simplicial facet is a set of ridges, a + hyperplane equation, a centrum, an outer plane, and an + inner plane. The ridges determine a set of neighboring + facets, a set of vertices, and an orientation. Qhull + produces a non-simplicial facet when it merges two facets + together. For example, a cube has six non-simplicial + facets.
    • +
    + +

    For examples, use option 'f'. See polyhedron operations for further +design documentation.

    + +
    +

    »Imprecision in Qhull

    +
    + +

    See Imprecision in Qhull and Merged facets or joggled input

    + +
    +

    »Examples of Qhull

    +
    + +

    See Examples of Qhull. Most of these examples require Geomview. +Some of the examples have pictures +.

    + +
    +
    +

    »Options for using Qhull

    +
    + +

    See Options.

    + +
    +

    »Qhull internals

    +
    + +

    See Internals.

    + +
    +

    »Geomview, Qhull's +graphical viewer

    +
    + +

    Geomview +is an interactive geometry viewing program. +Geomview provides a good visualization of Qhull's 2-d and 3-d results. + +

    Qhull includes Examples of Qhull that may be viewed with Geomview. + +

    Geomview can help visulalize a 3-d Delaunay triangulation or the surface of a 4-d convex hull, +Use option 'QVn' to select the 3-D facets adjacent to a vertex. + +

    You may use Geomview to create movies that animate your objects (c.f., How can I create a video animation?). +Geomview helped create the mathematical videos "Not Knot", "Outside In", and "The Shape of Space" from the Geometry Center. + + +

    »Installing Geomview

    +
    + +

    Geomview is an open source project +under SourceForge. + +

    +For build instructions see +Downloading Geomview. +Geomview builds under Linux, Unix, Macintosh OS X, and Windows. + +

    Geomview has installable packages for Debian and Ubuntu. +The OS X build needs Xcode, an X11 SDK, and Lesstif or Motif. +The Windows build uses Cygwin (see Building Geomview below for instructions). + +

    If using Xforms (e.g., for Geomview's External Modules), install the 'libXpm-devel' package from cygwin and move the xforms directory into your geomview directory, e.g.,
    mv xforms-1.2.4 geomview-1.9.5/xforms + +

    Geomview's ndview provides multiple views into 4-d and higher objects. +This module is out-of-date (geomview-users: 4dview). +Download NDview-sgi.tar.Z at newpieces and 4dview at Geomview/modules. + +

    +

    »Using Geomview

    +
    + +

    Use Geomview to view Examples of Qhull. You can spin the convex hull, fly a camera through its facets, +and see how Qhull produces thick facets in response to round-off error. + +

    Follow these instructions to view 'eg,01.cube' from Examples of Qhull +

      +
    1. Launch an XTerm command shell +
        +
      • If needed, start the X terminal server, Use 'xinit' or 'startx' in /usr/X11R6/bin
        xinit -- -multiwindow -clipboard
        startx +
      • Start an XTerm command shell. In Windows, click the Cygwin/bash icon on your desktop. +
      • Set the DISPLAY variable, e.g.,
        export DISPLAY=:0
        export DISPLAY=:0 >>~/.bashenv +
      +
    2. Use Qhull's Geomview options to create a geomview object +
        +
      • rbox c D3 | qconvex G >eg.01.cube +
      • On windows, convert the output to Unix text format with 'd2u'
        rbox c D3 | qconvex G | d2u >eg.01.cube
        d2u eg.* +
      +
    3. Run Geomview +
        +
      • Start Geomview with your example
        ./geomview eg.01.cube +
      • Follow the instructions in Gemoview Tutorial +
      • Geomview creates the Geomview control panel with Targets and External Module, the Geomview toolbar with buttons for controlling Geomview, and the Geomview camera window showing a cube. +
      • Clear the camera window by selecting your object in the Targets list and 'Edit > Delete' or 'dd' +
      • Load the Geomview files panel. Select 'Open' in the 'File' menu. +
      • Set 'Filter' in the files panel to your example directory followed by '/*' (e.g., '/usr/local/qhull-2015.2/eg/*') +
      • Click 'Filter' in the files panel to view your examples in the 'Files' list. +
      • Load another example into the camera window by selecting it and clicking 'OK'. +
      • Review the instructions for Interacting with Geomview +
      • When viewing multiple objects at once, you may want to turn off normalization. In the 'Inspect > Apperance' control panel, set 'Normalize' to 'None'. +
      +
    + +

    Geomview defines GCL (a textual API for controlling Geomview) and OOGL (a textual file format for defining objects). +

      +
    • To control Geomview, you may use any program that reads and writes from stdin and stdout. For example, it could report Qhull's information about a vertex identified by a double-click 'pick' event. +
    • GCL command language for controlling Geomview +
    • OOGL file format for defining objects (tutorial). +
    • External Modules for interacting with Geomview via GCL +
    • Interact with your objects via pick commands in response to right-mouse double clicks. Enable pick events with the interest command. +
    + +
    +

    »Building Geomview for Windows

    +
    + +

    Compile Geomview under Cygwin. For detailed instructions, see +Building Savi and Geomview under Windows. These instructions are somewhat out-of-date. Updated +instructions follow. + +

    How to compile Geomview under 32-bit Cygwin (October 2015)

    +
      +
    1. Note: L. Wood has run into multiple issues with Geomview on Cygwin. He recommends Virtualbox/Ubuntu +and a one-click install of geomview via the Ubuntu package. See his Savi/Geomview link above. +
    2. Install 32-bit Cygwin as follows. +For additional guidance, see Cygwin's Installing and Updating Cygwin Packages +and Setup cygwin. +
        +
      • Launch the cygwin installer. +
      • Select a mirror from Cygwin mirrors (e.g., http://mirrors.kernel.org/sourceware/cygwin/ in California). +
      • Select the packages to install. Besides the cygwin packages listed in the Savi/Windows instructions consider adding +
          +
        • Default -- libXm-devel (required for /usr/include/Xm/Xm.h) +
        • Devel -- bashdb, gcc-core (in place of gcc), gdb +
        • Lib -- libGL-devel, libGLU1 (required, obsolete), libGLU-devel (required, obsolete), libjpeg-devel(XForms), libXext-devel (required), libXpm-devel (Xforms) +libGL and lib +
        • Math -- bc +
        • Net -- autossh, inetutils, openssh +
        • System -- chere +
        • Utils -- dos2unix (required for qhull), keychain +
        • If installing perl, ActiveState Perl may be a better choice than cygwin's perl. Perl is not used by Geomview or Qhull. +
        • Cygwin Package Search -- Search for cygwin programs and packages +
        +
      • Click 'Next' to download and install the packages. +
      • If the download is incomplete, try again. +
      • If you try again after a successful install, cygwin will uninstall and reinstall all modules.. +
      • Click on the 'Cywin Terminal' icon on the Desktop. It sets up a user directory in /home from /etc/skel/... +
      • Mount your disk drives
        mount c: /c # Ignore the warning /c does not exist +
      +
    3. Consider installing the Road Bash scripts (/etc/road-*) from Road. +They define aliases and functions for Unix command shells (Unix, Linux, Mac OS X, Windows), +
        +
      • Download Road Bash and unzip the downloaded file +
      • Copy .../bash/etc/road-* to the Cywin /etc directory (by default, C:\cygwin\etc). +
      • Using the cygwin terminal, convert the road scripts to Unix format
        d2u /etc/road-* +
      • Try it
        source /etc/road-home.bashrc +
      • Install it
        cp /etc/road-home.bashrc ~/.bashrc +
      +
    4. Launch the X terminal server from 'Start > All programs > Cygwin-X > Xwin Server'. Alternatively, run 'startx' +
    5. Launch an XTerm shell +
        +
      • Right click the Cywin icon on the system tray in the Windows taskbar. +
      • Select 'System Tools > XTerm' +
      +
    6. Download and extract Geomview -- Downloading Geomview +
    7. Compile Geomview +
        +
      • ./configure +
      • make +
      +
    8. If './configure' fails, check 'config.log' at the failing step. Look carefully for missing libraries, etc. The Geomview FAQ contains suggestions (e.g., "configure claims it can't find OpenGl"). +
    9. If 'make' fails, read the output carefully for error messages. Usually it is a missing include file or package. Locate and install the missing cygwin packages +(Cygwin Package Search). +
    + +
    +
    +

    »What to do if something +goes wrong

    +
    + +

    Please report bugs to qhull_bug@qhull.org +. Please report if Qhull crashes. Please report if Qhull +generates an "internal error". Please report if Qhull +produces a poor approximate hull in 2-d, 3-d or 4-d. Please +report documentation errors. Please report missing or incorrect +links.

    + +

    If you do not understand something, try a small example. The rbox program is an easy way to generate +test cases. The Geomview program helps to +visualize the output from Qhull.

    + +

    If Qhull does not compile, it is due to an incompatibility +between your system and ours. The first thing to check is that +your compiler is ANSI standard. Qhull produces a compiler error +if __STDC__ is not defined. You may need to set a flag (e.g., +'-A' or '-ansi').

    + +

    If Qhull compiles but crashes on the test case (rbox D4), +there's still incompatibility between your system and ours. +Sometimes it is due to memory management. This can be turned off +with qh_NOmem in mem.h. Please let us know if you figure out how +to fix these problems.

    + +

    If you doubt the output from Qhull, add option 'Tv'. It checks that every point is +inside the outer planes of the convex hull. It checks that every +facet is convex with its neighbors. It checks the topology of the +convex hull.

    + +

    Qhull should work on all inputs. It may report precision +errors if you turn off merged facets with option 'Q0'. This can get as bad as facets with +flipped orientation or two facets with the same vertices. You'll +get a long help message if you run into such a case. They are +easy to generate with rbox.

    + +

    If you do find a problem, try to simplify it before reporting +the error. Try different size inputs to locate the smallest one +that causes an error. You're welcome to hunt through the code +using the execution trace ('T4') as +a guide. This is especially true if you're incorporating Qhull +into your own program.

    + +

    When you report an error, please attach a data set to the end +of your message. Include the options that you used with Qhull, +the results of option 'FO', and any +messages generated by Qhull. This allows me to see the error for +myself. Qhull is maintained part-time.

    + +
    +

    »Email

    +
    + +

    Please send correspondence to Brad Barber at qhull@qhull.org +and report bugs to qhull_bug@qhull.org +. Let me know how you use Qhull. If you mention it in a +paper, please send a reference and abstract.

    + +

    If you would like to get Qhull announcements (e.g., a new +version) and news (any bugs that get fixed, etc.), let us know +and we will add you to our mailing list. For Internet news about geometric algorithms +and convex hulls, look at comp.graphics.algorithms and +sci.math.num-analysis. For Qhull news look at qhull-news.html.

    + +
    +

    »Authors

    +
    + +
    +   C. Bradford Barber                    Hannu Huhdanpaa
    +   bradb@shore.net                       hannu@qhull.org
    +
    + +
    +

    »Acknowledgments

    +
    + +

    A special thanks to David Dobkin for his guidance. A special +thanks to Albert Marden, Victor Milenkovic, the Geometry Center, +and Harvard University for supporting this work.

    + +

    A special thanks to Mark Phillips, Robert Miner, and Stuart Levy for running the Geometry + Center web site long after the Geometry Center closed. + Stuart moved the web site to the University of Illinois at Champaign-Urbana. +Mark and Robert are founders of Geometry Technologies. +Mark, Stuart, and Tamara Munzner are the original authors of Geomview. + +

    A special thanks to Endocardial +Solutions, Inc. of St. Paul, Minnesota for their support of the +internal documentation (src/libqhull/index.htm). They use Qhull to build 3-d models of +heart chambers.

    + +

    Qhull 1.0 and 2.0 were developed under National Science Foundation +grants NSF/DMS-8920161 and NSF-CCR-91-15793 750-7504. If you find +it useful, please let us know.

    + +

    The Geometry Center was supported by grant DMS-8920161 from the +National Science Foundation, by grant DOE/DE-FG02-92ER25137 from +the Department of Energy, by the University of Minnesota, and by +Minnesota Technology, Inc.

    + +
    +

    »References

    +
    + +

    Aurenhammer, F., "Voronoi diagrams +-- A survey of a fundamental geometric data structure," ACM +Computing Surveys, 1991, 23:345-405.

    + +

    Barber, C. B., D.P. Dobkin, and H.T. +Huhdanpaa, "The Quickhull Algorithm for Convex Hulls," ACM +Transactions on Mathematical Software, 22(4):469-483, Dec 1996, www.qhull.org +[http://portal.acm.org; +http://citeseerx.ist.psu.edu]. +

    + +

    Clarkson, K.L. and P.W. Shor, +"Applications of random sampling in computational geometry, +II", Discrete Computational Geometry, 4:387-421, 1989

    + +

    Clarkson, K.L., K. Mehlhorn, and R. +Seidel, "Four results on randomized incremental +construction," Computational Geometry: Theory and +Applications, vol. 3, p. 185-211, 1993.

    + +

    Devillers, et. al., +"Walking in a triangulation," ACM Symposium on +Computational Geometry, June 3-5,2001, Medford MA. + +

    Dobkin, D.P. and D.G. Kirkpatrick, +"Determining the separation of preprocessed polyhedra--a +unified approach," in Proc. 17th Inter. Colloq. Automata +Lang. Program., in Lecture Notes in Computer Science, +Springer-Verlag, 443:400-413, 1990.

    + +

    Edelsbrunner, H, Geometry and Topology for Mesh Generation, +Cambridge University Press, 2001. + +

    Gartner, B., "Fast and robust smallest enclosing balls", Algorithms - ESA '99, LNCS 1643. + +

    Golub, G.H. and van Loan, C.F., Matric Computations, Baltimore, Maryland, USA: John Hopkins Press, 1983 + +

    Fortune, S., "Computational +geometry," in R. Martin, editor, Directions in Geometric +Computation, Information Geometers, 47 Stockers Avenue, +Winchester, SO22 5LB, UK, ISBN 1-874728-02-X, 1993.

    + +

    Milenkovic, V., "Robust polygon +modeling," Computer-Aided Design, vol. 25, p. 546-566, +September 1993.

    + +

    Mucke, E.P., I. Saias, B. Zhu, Fast +randomized point location without preprocessing in Two- and +Three-dimensional Delaunay Triangulations, ACM Symposium on +Computational Geometry, p. 274-283, 1996 [GeomDir]. +

    + +

    Mulmuley, K., Computational Geometry, +An Introduction Through Randomized Algorithms, Prentice-Hall, +NJ, 1994.

    + +

    O'Rourke, J., Computational Geometry +in C, Cambridge University Press, 1994.

    + +

    Preparata, F. and M. Shamos, Computational +Geometry, Springer-Verlag, New York, 1985.

    + +
    + +
    + +

    Up: Home page for Qhull
    +Up:News about Qhull
    +Up: FAQ about Qhull
    +To: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Dn: Imprecision in Qhull
    +Dn: Description of Qhull examples
    +Dn: Qhull internals
    +Dn: Qhull functions, macros, and data +structures + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg b/xs/src/qhull/html/normal_voronoi_knauss_oesterle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f46d421274edd97de13d25e306985c43e872165b GIT binary patch literal 23924 zcmbTe1ymf((=WU@K|*ksu($EtF09Z^I zI82z=UH~~%NCcRFHo$*27+5$czevb$P*9;2>fZriVc_6k;o%Sv;NhXQeW2w4cuWMW z_iUnw*ouZolnyxT0kJvAz>n2kxJpxJR2)W*-`=3$;S&%NQPa@U(KB#zar5x<@r!*D zmync_{;aH`s-~`?sby?pYG!U>Y31bX;_Bw^;TiZnC^#haM_62ZLSj<#&y>{My!?W~ zqT-U$n%cVhhQ_AmmhPV3zW#y1q2cKn@a)|D!Xjj2b8CBNckj>s!TH7I)%DHo-TlK~ zxnKZr|B40u{#UU7LoQ6HT(I!)aPUZf<${5AgYIyc@CfhO5V1rRkqjNMDcJ*%aX!Z8 zRCm1rawwhQ8aYm(;8AgIP@n%5?H|eh?*#ky|4Xv}670X_f&kFqV4x2V4if+ZjPVU^ zPn$fXdXS{bs%@hWU2{TzxYG3jcxb^>6D37Pk#&IOEM*CWe@|C^%Lb%Owja+yCJ;;tQPP|Hdoy#yW~&0C^ud8mKI`dA0yr;lg-m1AS0dw*SKw zl)>hIdBrjECm!cd)$@^K`P&&;%96r`8Dam5oySL#s#}-sNC(sFXiZ%+EZWC+N*hp_L-O;1i02pv@DCO9x2*OG}3J zZziMblMdLIK!9<0SdPXBAU_I(wVoo~x`p67%Un_M2_Gj(11N1(Oe(N~n-(s?-nz^QGi9m$-nh23f-6uSYQs$LN zN{ee`@^+~Qgm*Fgpfp}lsA}3K|7yjg8pj?M4;znz(66Y_$ASQm3}aLH+cAn^zVu&( z=wSKj?+QAvvXRgKWj%~R7UMH39jxGgm9r@sL*j7&!}ND~&_;~HeChgN91{K!J|WwY zexU#^Rs;)od#a&;aVXCgmU2k~l59%@0YuvpTj|!NS-=X_*_LomA7>*&A9MbaOoRX5 zD)CU2`KQEn+BO^c=5Gm9o&H`USNH!wKwD9*eDA}qO|g3*5Ny$a)^PqS0z+rkk3?G% zuY9O6u$z4LLjO03>Z!g+ z(;MR0?T8xJOLvmW0o4CrdZ5(;mice5K?fV~f35o81QrM&{J%PD5{WW@=`%j`IV3_= z_Mhh<07FL4rnDJZr-5886BdtS6#g$ELeT)&N5i072PNRqJ17}J36y;VB@WUJ&>D1o zG7bN!0Uc6I@EQewm0sLG9H*&-O@i(h)*lUhoIj(UR5cwtgrMoRj_zb7jRR(IEyraT>%lu6j2;+QRg9AzTdx57+bx%;sWN)Js3NtxZ&E<;)lOf+ z9y-C^V0?=Y|JFHaq|AobPD6{ecJPw5OOTlm(BW49y6c9frj4(Z`THsQ^nDUOoXJ!? z*z*yyYr+Plxop3`d!5dndsXx|p+isA6)=aa?&-UWlEl>D)L?7$IC zx>H)20Wf(sIk)2qBzmaErdE$hMu#HZNl07I?-mAm&kPR7G0zc9x={qrgNm)=hKU5; z&7G5>?%hV-y#kPL$TMh;@VM+xZEe_V$(=_xW6$a&AL`~)A ztudc8+WQ2n;s-o7UO&VrR;K=^VjX%!_(tJ!jpP^ zw!eqzO=G|DB>HQVuBX>x-v&A13j=2>yMhI>YCkR$R3v=N_d4E=F5v`M;_(O1uwXk5 zQ%0#Ot;!7MegsfGrA&w@0PB_eOx@-8ZMnXFmHqf8RiYEriY!Cs6$ikrQZfJH09wy^-JPE>IBuEFzzX+IK0ZNdP2B#(t+5_40;~Jg+LtJ zG9~RAiJEUUBCh~Pjqz>!9Mb0&u(o9Y9^I1CG1OQA>xf-wou2S`B3}V<)AR8b$Bk1%1Tz zh;u)y=>Ftfd*9^Ima#=Urkwbj!f~X85ngs2NmO-OcS34tZ-u-mUF{(Aug_U*BdpyV#{xAwORV*{`Ih>kXgmje9}5SZSBB_3R(ZBQJzDo-nkxJdfb(JcgexblE$&c%xsn?22c1!xLL znuRQ697ku1lb?%H$b?-trkmKSmF(l_5A(gU8*u{e*i;;I-F|x z;UyaZA*!*R_K$m~rXS|+g2C0h2O><<0Niu~Eo$`m@dA#pWsa1v4m_8L9-~+(? zu4xX%vP0Gc0uuXF#`RzPJbs=bFa|{h+iJ^)2c>EUAH5IVy0r222T!;sq0oTWG4Tka zCVb8OJ`VT?9=y3JuWHb;*s@CuPg~n;ce(%a3Yg&nJz(xlqU04VtQ_*^yPlmSZJLb; zq;EBw3Hv+B(JYJ)yUK*ipbq9rCGDc`U<;@Qvd3_|ocIw{HB>H3BD8VeaGSK(mnNv& zRrcF|D=s^0O7XDuumJ+kipB(qRFA;6>Lq@3DaLqh`|L=g7^4E1<*M}uLdM`* zz@^b+&!1s;uBnB?B>X1r0zB-jO+pLp`^0C->9%4SinMWcLvApsjai21t8}HLxQS;pe0qVgxOvaj%&IG%`es>^= z!=cW6NWDxX_Y3YNffi@k-pA!Kw9BqP4r|4^8+&KBKkR(RyE0eJBSHwVSfm(wnt&8D zcI)D9o5v>cgiVcX!x51LwLz)@$`|PrgTjd;Vt%=6tUT|e3zlC26H**ZNC)3f^owa1 z)I~Q(=xkR9lxKQK**6vIu;Wpq+ljrRR`as7E+0eN(o*t3oqc{lrT(Z1rD;1j1vlH8 zlgVl)uI5!7^3%6vuK-xsv<+K67X?hqIY$Sy+7GEdK$7Ms9HRc<120RX%xO!*3`|Wu zZib#6!fkcs4PC!u&-kU)9!t{WWh_XKFZPIu$Gc)Cd=#g2?1;nD z60Y7;I{h2zbzhCbgX5cJJ^JO3<&Gb2zp`SGw)lb+RqPDnARY!8M0|NhQIWG!>wyZ5 z85Ji~20ZW0)WoKO6>w|>691I(aBmQ5VQIZVRE)k5-YR+pRMrCHc2718YQLutHbh)F z&72i|DkT#=; zCUL^3NMMZa0i22bAh=~@WknMO1QoVHnHdPVIuaMA+k`3cXcC^==`i5TcI%OP)TIiE z{S*7WxJ|c%3zDp&UpJKlipr)Na>5)vYgG2H0GlaC5O-|HX{r%*nqGBuXp&d(E5P(4 z{gVRl<-QdAE^PhW-X0!UZ8O+&+5HOGs*et1Q=#k|FYs|JFmFwXxfZt#^;>5Qcu?i~ zl7|8y>&}OU4#sQ}46}*px3!_RMwV29>#k(^H)Pj^R5eo_U=KG)=qn&8-;5yo5qLMz zymcsrY+INA4Zd}b%G%47Fp8SIJ?(jF!LQB_4a^@^LJg*bAkWS9$*Zk$^!sRBo85v6 zRGU+^OIEFakZJ1dWQvLsc+sbz!OVCkxlGe_zozr0i_4merRhaJ1>Mmv|k;#B4q@^sBmmaXV^eJSwrJ=ZwjTwoZ zN(><+-(>tiIaq&)IDOsvh!4+IcZx4ODz2nQ{7n=z(RhDbIkQPNQia7C$NmZ^6h#NK z-;DI5!X3A|36({~aYv?*;JWZqTqFLBBqo)LtX?)v&K4TDzp8BPJ)s59tvj+*yb&8o zLQb$}lbK;jq}5brRtaL;LxRCU%gXk0=nyUOFnJA^r2N=K;BJTfnp#$tuQpbU`@-gCy(ZR=1MDa6*Kwgp@d zqQm(`C6eu3BfGdTAS6Q0BUBa#ndU~+T-YL&=32gcFqz_f#*hw*A9c^%{32pdsZ*@y zzGV$FK(JYG!jT*`%4V65?gnI|3SJ`fI*GX!?zt>;)ss6O8)aWc4R@#TP@7)HQ9~Vq z2|er*!XVtOKCz>|(9+CzOHx=VF^)s>JA0A?b=TDY?yzJpHkG*V`t#-a=+Sme0C``K zx^%+F!v$3gN6Qf+i#5&GAk^jNv=TisM3q{`QP)VEz7wU9+L)u!UKQCPYD}anyMUWhYyBW847!B|9yd&HUG7rpM!_*y^ZSWRrLa>Pa^W(xpTv@7*_#~SI z#Fs(F%fpUFBReubF$%VLh)YB?Y`SLpR)pnr=aD(~&$j8#jri&HA0ey3s0ry^L(=0j zsG%{~rHg))yo>&|b*MR?%(1#mSeXyCNhUXB{;p(T|5~q-lSb}TZ++7mkca5V$jR~A z{-mdx83YPquo5;jdJ7cx{&`5s4JtPo{l5HU+lhWotD93-rLto&Vpz(S>F#f-RBs01`5Ka>mj*(!HRf0lE zP*Vhjn4qycG*pM8Fr!LFe@!r=QOUt^tz5JHgIUidyr0&LMO&*{bCdh=v3mIo?g?X7z(tlxk5P`C0zxuSE!y1PeOH7_gD+>)B>|eX}hfXb&}XgDvlQ zEXM$e%`|wb++^)%ps&h}!H?atJS=|>fXH+K!T+kJDMa#R>cD4~g0v9Q(=;4C% zmf$aRf~ddSAwk}4W%A3+ka(}o=B$s{UL4$DVXWxw&-DK^aBIO}6CrW>`CuFd==^4V zxKL5h;Gri@rre+Rb?Z03LG+IL%K-JLC5asJ?}+4geZ0&|+1FdyeC5YvehvGn?d?r) zHqs0X*BhOD*Bc$5M>1guBMV{^_9!IhFOQ|na}Uh+`pC%Qm4n$+kNua5KLzeJM;N>b z@s5JS$hod0%+`3ZUWCP*roK-*C*RVo>N$M2yH%=-SE$asuvql-{ka_G-W##TNKf)^ zMe-w~qduBDeVe8OX=M)SPH%Z3I+sjEi&U=}cLr_^M>I7qcy2aK@y6zk| zalCfpXA&b=_cn=v6^Bf=`t5;N^u}JJSTRX|VeQg^(&DNZ6?@kO!yxPDsF3r+%n+{W zJA1LrouD>4@B3gAsx70auhM^zloaFz&Q;n#o`>*k?wPZN)d3-kx?R}4PE(+^z!K%I z<8My7$zgGOGAnuelcRGNl+w(QO1X8zW+@)F|?MGWg^qJG;~BH6L}gu|dN|6hUdQg@Oc7B~d+j5A~3sNP8iiE4aZm zUQbut-j*=VQUf|9sAkx7KjGC{++>*ta8&4hl8F;}(GWil~i@-lU@88{LPb)7KmGUWcSF@pk| zMiHvWP*bVZSev;;LpP*28;v5If5wn8SCXrNMcc+xi*avi!h_K-cJZQ_za2Ae15JF0 z7FjD=gDf#MB`ug!V1pcwhC45MulbZG-_~2>(0;Y*ff@oRx(%0VM71=OhjYQl`LoD? z5bEds1^Dgg`UL)F4#L>}Nh=s7;~YV$W)L0aYA6VM690HZCOvwfGdF8`Em$v>Q&xdxA1gmfFAVDx5Lz_5CTYjhP- zUl*7$1!DUwd=v;K-v<-@sZ)tMu4)r@4fjzM?+dVtU9NPkYB){hxK`FVi7%$IrpfZ0 zz+vh&43=eg9wZ$gU_3rRkQxu~?AYgi0nR9ZtY)ZO$f_{0qNmUue`}{p6W9z~+Ud>C zdJD;}rPm3C}j!@QGnj48L7RH{{{N^^F zBWd!AxZ_T?-uCSdiXC7ZQMF3_~%9694!)u0Wxfug}dBR<6>WT>0!ggy&4c?g&PQWSbBO_gtXk&mThv z+t}N|f`NrDKhA~M(o0|bNm8*5w&&$>O(WHYt*HrY7w^*EaxAiTF|9o5ORDe+&}$?b zKh(c7FMtnvS<2TFO0$E#x6Ytc?-5Z{CEee)~c()iDkI* zUt6Uv)P5mV@@c~4ZSj42Bt!!DN(V^}DObBO=Hn4g%)cf1NLm68#2sK?EyB_72s4(l^-CSlok|aqnLN0erH>u?9_jCqqXC6$-hBL`F zADlO+co{@QS8)pHDin+F##Rq1h2N?RZj-Ht9eBIsEUtY&m&o!kOLghFR}V)vqG;*Y zp^SMu_t?XGBA^b!-6?28CCztiDH%%o!Y<)~IuCfJ5AfXx4|06cjByRzqwJ&u znmSqcW|>Wdg05V8CmWRbnA%ciNgIv2D~)SSHhTv+WP#@o8uMJybt6=27Vsv>BXkU{ z_$U3%+Z4|VG&R6)mg$ryW|?vr?-j@2vQI&q1rmmM1`^fjk`ngdc z{(>+U9BNzt#WSy?Sv$&O=4MdXua}6~x#LP;&xME9bD*oaMf4N;=V)-)Nwq2_TT>oM zm@B==Zh7p&_EZ>#vN(Vs4@Xf1k**Kt3k)$4 zr*kDaCbdbyTP7u(14%WZ1h3Q!X1_lEsDfh7;cDuZr)sh2YwP{YJurA!ZPJo-n~XN) zWJ8McYJ|U-`Kvs;!;Gn;-#g02unUEE-64wAzcZ$ENwPyDyrT!u_yEonCaND$t>!uI zg1h;Wt?mORMq-2Rqk;RHG44r+J?BfJ)NMQv-M%KChBz{+)xMS+ULg9yoZNCM%R z7axRUfRxdGQ!klk!JB<42M;J4g}k$hlJYKND{4J9R%blLlKB;oXh?NN9OK%%@#EgB zWUEMjM+u>cbFZb*VXuF#xOCUqCiFZY{i{cjzRv!|ikxGd7GCTKo&)1{mI2_!g*G&Y zU#OrV#psYzf}o1V{enn=4X&iC3j$|Mt~1ZaX(Sg1cM;o33R8%p<@1w zw?KpRe1)z%bu#n$QMlcjdUmWYp{%afhC#z;_b|rOrBV&cSQ8@~>a&vJR_7^@@Yo{^ z!FKP!W34}ruA3heS!QD>O)po4RvpzP%J?w$5_x3FKk|QHV!iLi68Q11Qi#BFK1~`; z>&Z!Xe~=Qa&g^8-_J(bC>!oa|k(6KIjm`>n(0H6E|L+jcO@=?IwjrX$KrP*}kQ8pv zRyfzRx0@GpIH#H;`vKkr;lL2Z+iY^%B|j7?n{LLP?qa{tH&nOe-5t^T!)bdQKw%C_ zv^}ti`X@Y$cRkEq?&a46$zv_c$Wf~6_ypqw=CNyns-Jv>N76KWXWx?_R*Gu80@m5C z?FXW*a?s|O3!3Ix$dEHd9!-dGvf8q59_HqQgH}_lov5&%ldcrdLdE7GTTFDXI>z$2Ek)dFe{%58NWBkIbjmT70bl zZBj<{52h{#6x1e(Ql_a)K67ZD>k*C;U)^`N!JOn69%uW|`5XdCz`}{!y=KFaeJc!Q zFdY7?c8687sey~MAi0SauNtquQj=~z1HIAFE8uq2INCwy(!FIuzNtCBhv-YNCJi(7 z&i-Ygue5;rNs+LBmZykChJf~*iLG+4o1aO&0jz@e5)7#Wkqa<0j7|MLxQ*_tr(mtJ z#LJ$TO=fTwGp2zZ7lYbs{nu`@SJg3UYoJf*nv`)edU%WpG{ai^&0Y9OIj2P=4i)(m z8paxPbLhj(6Mg9Q2aG>5mWCx_8*)}lUz%Dle`dFZTW2FtYq7r>g1T_%v|MsKt z@#A8hK-|)&aw2O82NV(SZZeN~&e0$9*+SZ;BDp@E@I0>5C$ny_GH6K>xOx+4 z@0R4Mxb*ejf8cDd@v|8Ir?aFy;T)(1{ooA{Sp} zdOyAg@^m{nz8+8zL9(hMYc;mt@R3xSxtg6xHIJG*p%#+))fcj>{7#!vfCn)|C3K zf;>7%PzF7SWbJLXvX6wnrKkXN&Rk<@e+Y`%nlj$IhNsw(e69Y}R_pl|l;NEDeok2z ztaSo^Q4aRtp1P4r4kC}4*iubbLh3l*O5} zt`Np16UN{V&FpK@!Bo&U(tm9?bV=~*l(OH~OQQ^m{dNVvKA+MO{IMv9ph{%A@KHYA zm`$(^^W+uaPKR`jnC^zXP#?xNm>U3AjUyeyapU{~BMzOv{^u?Y96b!BvMg>zH^e7Uy#k~mvg zR&?mf`1TiCgC{Mms_xSXGwI&V$5e5Q7qCJsAW6uUCR zGKuhvj!S<>@tW1gmzxt!yRJO|+Z-(xc`_V=7%O^}<Xe4WoP4NIUF)+$hM#*xIU26(oBmV^tnb0QAvJ1sy-cL_=ut%5Cr+5V-=h#2{cn4Hloa9_77;}!6xC^%ve{x@)%Vu{^_ zry;QU+WG1H1OcFYms6CckJa5U>OE*`?)s~nY9;T)LE&G0~3vO@PUcE11<;()2-w`UP$7Vr<~E%B?BUB5JL=! zKT9~C=b0CFPge>BdEAtE+CtV8YMo)RQ>a1#WMcCKFPM|QK8N5Zh6~A!+q3@i8+@&u zb?ZlT;fDn|BADSkSid>eR`Sp7d+#KVrDlFzBdZ$gfkLt|+KR6ITItJuDVO((K=L`cIkLY^) zTP@mUU3l?UVISGMP;wGyD9*-yng5piItD+j6~4kUO}FdTTh^{+uy#~61Ak0<*nFP) zyNW0Dm`7j;am;KmPTHL&(tDO{xDR6r-9ys=BHII#N(i?#$=dY_m{jv8Pb-#6)jzD-K&gQnJuOT zQu6S13BFwgch<5nQ^)akVFfdsct#uU%ZuYt3N#}g7HzI_K$kmY8IB8C_4T#4cz9-v z#G!m=dy7^>H?ZT+amAHFEOfls)a*UzYrqx|we0ETLx;I0hZ(X_7>{yd=|U>6nek)D zF{~tOY>cCqhc5Fy*!D!)`n`YzfukYgMKIZdVyu=jWu*GXZjz>mjA~1w^fA@wK=iGu zbxIq%3}-SHxXHj}wY31w_nR1)qPA>{rtdgb zq!%gmpss>K|F5IHkKcy(+ge>gO^G3_)yFUU&x|wW<$fD)&m5ORl*=&3T5TOYR4x2wm97FymJr_gI2ec4Ady==nSD6E^&ocUr=K>_D_AH^;d zqI`$4=kfv5O8R~9+{`1-bCJ$S)SlB@5P)%VcPbaL3c-TL8J*D*){U*NWF9z1$)v0L1h)BI1QqPV^Jr&18u{3YCk}fD?iUU_v?K zB`ea@Ng-8MP8eqA;Q=@bvOjTRQM=Qu2&%LD&W#($$FX!cghG zvk)v{8RI+KGCnmTtw?Yet%HbS-s@xNbLFmB&$C*j8asAa5Ni%RVq&C3H$Uy)@V6%h zR-XlD?8@P<&R#S5o~|gLR5dtSqg-R!rS9pqBrAip( z82UbI6ulk80~q(Wop`jqrfeec{z;=iM@Tb>GIn9G7h%jizpv|>Z0xf6#y+P-fl=E3 znCIf@8{C@lD*$ui%#T4ECad($X5VvIQz3VG%t-(SRj_Bc3F&V0;yW zzJGGS-VpSI!0E=lD{5fLW5^Ojf>Lj~=`KiXXq>0nev^}@Q)>ra)nQLkBIxA17@VNE z@EH55_455eXfumb7ovl~y?X4S8FPChoL8M!Or+-746dLGQB2%3l8qXNuMr;-=rp{n zD$SHsTq7h(jI$i=u&QfxryRQG} zegI2H`fhb^jJ{~x&`2;I36+ic4mgpI zxS2DzzQs);@b#$DICJ{39WUP2nEEj7Bdlrq5oLfX6fMl{t>`i<@&Ilud6)|^8Pk;? zh9v_c2iqky>prm9kqB@gl56ihTN{99SD;W42`fkw==#I=LUd>&2;3y7>vDRV%&)sl zFYZx|)tT{zdJraB!SK2abe438C0pjSsVU;rCTWk6NG9}l5<|GD(oHk$xt|-yibU)i% z^9mViq;{{m9_4Ihk&L#c?~IdU(OZ2GlZ9WKROc0(Q0aHP>5i_gJV{uw`D{K7_P=@EDHw;ve2kRk=1f7la=t&XMFjsiSa7ap?RYHc7=6PS;b1 z&$D&RR4_*Nr^sM0i9e|OCvn`}=B|VW^`y7ZFF%YM&&c0xW{HB@v_H#R$O4yr~%-Dn9n=ZhEX>+=3!$$17JzRa+$`?Jf7c9Jrj6cu=bCa=9BStRGVtjjzi zsFns}dyn4oZ*d9%+H>50>FT#1U#FwHnPiy;^Mdj?C#&H-D`a8nv%Ve4EE3LO)H@vbP22Fh-qvczzI-Hb7wzY+{Fa?-rU0h;wUAR! z(nJ1G$)9LzTgtME@eGP3%1p7J8aA{=D~l5q_^2ekuYhybaF;1o?YLIpo2G2SKkU4> z6I|&1$@GmL6;dss4!>a?+oN_KhmlGMZY$H>%?=I~ye7~NxF7OuR!u4uVwo(b+<;qa z0@AoNu?4G=e()ncLPrfpm9|%ZB05<4-fwSwaXoM^1S;#Pgm2nz`Ydqz0Ef^cj_J*# zpV7<0X13BPWMuH{P$hbQ8&60QOWpr&bFSRnMGYTQ*8pFKSAd}5^I)O@$SNlIt81cu z0@_=S2{^7mmvW1DXAKX*l|3iVmHG^rSCOFpa2<4VWJ_1A-yJro3rUK0-(-ILUL^?y z?=;dY0gi7&v}Whp&q|~tgP4(ux)7M-j_P#@mmxw;x)f(Y8t01 zbKPlzA-EAiHfSdYQWM^@W7e`sJ7&K!zPD6n5LgtDUp!hylwsT?_@t>K1;Aux+0hyQYPVf9sUSJE{ zED7o&G^3T|<+$+r9P0l10IG2U&G`jFa?}7E#V@M^Ouq=$lZVaLhZ-Femjhzs@4|@T z($9q}byK^?&LNigjgP?9<~t}R4ij;-G#MD-%hk4u-SVSRYgKDGN`8m~o4mvb1|+5` z|D!bg`;WRJcF@Wa@%l3{mGM=1rS7-2UjB-fAeqA-@BGk;pvdLc;xmG@wVSQi`)@w5 z!)^(eH&PHn;+MNlP@-nC>`Yzph;{9+CQ@dmZ7k5#&&aa-Iqtm8TfAfY0#1JM=8dEU zk9gi!fR%hl5`!~gW0?gR!LKkL@CTNAZMJxh|Ag8~Wy!{2av*^UEcf!*OR}`SjxG363CER*C|21`0hr z1*;HM#3{hE{~9TcUZp*!y8=z#W}CSdc{o_Vm&(!5zWZ)$`Sg%AUbeWja-goLt1^H?y!0 z^xkJ^?TX^OOYgFVJqmmOG8_dWtGAX3AFc&g>ZYvZz1zNJ@py)LI^^vkWBa9O9Z->N zRc5EfIQTuOW^3X{2-!5<^@RSYmz`D@LK~K4?a)mH;(7?2JNshuV@*+MmR(+8pbi(L z?%h*_O7+PCbd6bA=^1+6{6kbMVli$7Ws=Nd&xbegGVLJ^uFt}TQium+c(qd}g@HQf z=#gdp3v8mq6Fdzn0GzFAu8+j0=nHZrsb(2vR>>$!Nir$k2}tuM9zw14#)m{Jt9hlm ztAhtM%fr(d#T-d|hY>TjyFW#ySBEbsc*Uu{4b1F|kV-yL<4<*m7xftD5fa>2Yj;-1 zPr$R?cW_+gX{MfgQ`-vO)^d$Le9xDi_TIq6F?J%^Vt#<4fi`vpaW^NjS~>SwIc7)U znb5Z#d~qAku{8SQH+~wsvbC2dYn(m~A-_n`!PVZ0z6V#X;$>{7u9#BQrHuf)51g(9 ztjC!t8ZF6(Runc_JkWNhxCeVC;+C7pNvHjT=OBI==+&=?cetuMW1J#$6mYMkK> z`7y+Mo{glWD;DXK!#vz%MC=u?-hAH;Tg&Q=gUu2dJsjc_5)RDN9m<$}o3bHEEw)9- zF}5wQG7;?!j{)wk638+Dk+IXM4ZKb&GFB2LmgP-uDM=AN~R zY4KUO&|e6tTi^Ul-~Yt>%g)B^b&!`36MI>m;wylp2Vmlg^knT{9}4NCKFz|JX6=>5 zwnXAJ;{%*OTPtsphvN79eo&d-4r8Vn6R$|XAjj-Zw?kSZ+#@YbsMYEcs&f+AWDK}5 zF%ZS}9kGVj@`+pG_w?c;WN=>}gEL z7E6;fUM`jRBEKXG>r-~rs`$hlo=0bhjbPpx$C&$X8 zXpv4oRt7(GIJaVm28{`O7IR7yjKJPL*i+dcQoxm4i}blyLzBh+{e9@TYyYU9x>oQJ zgkO@cfszkNLmxrAiQ*fGNo_oXJH)N1B4{Y)*l|!#fnpEFu_{>aW2I{6eh*rzcJ$j& zB`Ky;BPZTb*ZHP~nDpmuBU$JwY#2v{0)V5NqS$Wl%|$Cfit-aglB_pscEv)S<1iHk zRT||kM2yU*4DM16c_)53D%(f$T%D99O{m%a_zh*fsu8RFE)`j)>M>Bomh6Ifg@s?1_Q&jYFV{3K1)KdKEf`^tcaXc4C%NI9;db%SBSYWK(wXo zWUXHTpqolBs#?jL^&8v812SPQ& zs!;t#Bplw6e|eO8D)xgT79=&+pR#cupAZ~|rcH*U59M0gn_6t=IYu}<2X~-9wfE!L z@KajezXF)HVP;zgOV5a@|+3( zSfU+icO@U^A=iyma45Sa42stup!u^3-(cv>gZwu1u{FQhIeQHPUoJlC_^pWnuSL)k5QQ&d!$f$U_8n5l^{*C3(f1aoM+RyErUq~xF_^)!(QVM_8-uUD4idEe zu^J}HfTPJ1Lub0n8k%*ukn&C=KpOA#E7KbmlD>M#(mM23*g#hr)Ew4Y*hg!fSFE$0 zEAUK0s)nwrxtvr7mK}tD#e&Q`E{fg4rPpSjtnOXy%OYCgk9;@*d$?9V{pt-7@EJN$ zaMH!^l>$FUjY&|?a6W$Vajmn`zAz(CsbuLVuRp*$qg~51jWBMeC7)}Vz+Xvup+Bh3 z<&0?`(M%AH8x)oYF}>gbvJg+;^|9Lo-%t=oWnT@)KJ1pc@5#uJV_ALq6r@G?^XJmH zcN+m@dGLOjmSR2YV`G-Q$=c9K_&ewT=@IF1wXg;HmUI|V)0DHo*ykmgIfG0dNnQC_ zQ%V&Z65gJOZ|(a!OkauMBhhD?;Eu+KnbtE=jI(-OZ~KIK>O?-FV`Gm~o00zZ_pOJ# z0&Lw;RqB+>-Tf3N&02|NTT~|#0Z4*W=?GM=LZxlOh-Y&-&_5~wMI7z;^IF>4uX|Z` zo5!@_8b|#UoJ5$wevU(j`*gM(R{cK$@)-^0Pjzqh+>^r1C)@<*rqu)SuRqh`o;dGg zmM4h$dsGlcPT}A4uAAabcfWcf6dFp>k^jFcZJQ488wA)pLc#p(5d%|W! zXe|fbKuPC!Q}y;07lAZ5?ry9l)@LcBXcnVT)V(B;{$-Co_+C8$uQ~D8igj&!#Fv`A z{F24?rzRv;$|?>ORE`b?I#31Zbk@+c$*ylA%#wmIq%bN!gb|U+>yE;(W4xB;-tmNo z&)w$&rx_!t{J)i4@b;nop%dvEmBEc?ZT6G7<_YnEll|7{e+tjoY;Ve3MdVFyAxYxL z2eV_-uTGwn0e@JTCAwK;g&sN4W>UW^432pndCyR4W!2=F7CW~~*jJA)P>jj6dMvAl zU%-J=CXuM!X;4LLZ6xn*?;aIHJ0^0hqp2Nv9@Uv6G;*{THli614Uov+k>m8FGU00r zl`E%1aRlH3V3CA~-s;>Q{RljFt?wO)Y_-cxGI5Wy*+Qy4$Si+Wtlt3JL36Jq(iwGb9WML{n{Q!-Xqqw zJSh@Fx~8W8056klVozx$AqGDJO>$mznIcDZanxrO*+B@j@I|PSR*mnjT5<<0=!0sH zrb~WRLNUopLHpnyL-%7nz1#dh`t>cIw|#0PMU+c#A?q9f_7=OT9kD(Q_ zr|9rop!2_fCvyCcz*Kq)t%jc)IV>#X-ry2(44*83dgF%r*1YOrnd5A3Mj(;U(@0I4 zdKJu3JeD^^Esfq0h#%_T_*{KImle?XW@%!3jSBaRi*3(u&T_q30q#GDdlQP{3uIvO z4RjFCHTIho^|O(yX+>DKcZhw~_s2u*eFav`Tb*`jrblxX^`sZ}qii<=B|qnzkG!9O z{RpFVYjOwCOa_!10Cg=lG-iMuKzrnB(-Uo2?l}OfouqINU!m z{uQ>*?@>IS^&G39?s9Y4T|^6cWzcnK&f)r12A2mF*-FYzNco3;(XDHH|jix=#Y~1OV&z~)x6aN5xuk=68vTn4?l_bJaasL2J&p(momBPhY zWDWwevJi5TFW!v$k8e>`Z%(Oqr`&&OUCFi?)C-9Y;*kU;h-1mg+ku|GoYSMWo?DR# zMpo+EhBkrJns%$CYI>9r!KUf=v)Y$-+^Rqfe~F2}?s)C_)U>xL8CQNUhH&vurbBZN zMH0El1Au)2`eL$x((fey0FO(HWo~mE!Igh8TY7(hE@qiwp7&2%q6-^;JI#;**W`~S zkF`ZUo8jnK3#~^`ykz6cwtzx_k9RpAl`Gr}Q&^ok8;wB7V}{x_wYGAM!Xicswg*$r zJ;|;_+t?P#)Y-BQc0kmosU$aqIRc?YbSzOKo*&DrCan^Ug)+RkG5e7871@D|`O_XGe9h`e2U;ai(~EjeMAOt^wJmJ5~h zBD)P5^6Gy9$4nk+t)OT=H9l{H=l;po&o820OZ&M`er%$Vo!m*94lSr~g3c2KimBAfPx2*tfct1(Hu=tIo*=pAFTD`U6$vkr* zQi_Lh$UU$r`qrawq~Bl5X2NY&?pJwk2O>ShlB@ahYoU|I8r%2@!PMU3drO43GrFme z#L7rQ#BM8*(3-;5Y%T7r&c79=+l_)tiy@3F9+KmzL)RYulmUl%YbL5R`G|9OBzqb) zT(jWeSbaws_N(_Ag{6wy=rGw@-^CM`m8UE~kmGj3q!0idd*Y#%Su~4gxSfgCZxf95 zw}}4$CL{6{ogKq1n@w=5ER1}#EHFs#k~yFY_ZqH;bV|>sUR$E!6x-Ye7#%RKync0h z(%VdU4c(>P%+cCLAMDe_6L~F#%LY7T{*}x$@C8x!DE)r>e~oUdMHhxI-|XrHhTc2| zJpJa!Uep29>DrZ-h`c%WUl7FjfwoC3f9q|D;5z!Rs-8ccVb9>}`)?HKcIme3OuTi9 z-s&HkIg`K9v64aNs(5QqSiaLcM6Kka`G`q7;w`m-^~Oixn)ELM>PNylwaxvE(o3R5 z5^-rFnFLJCHn>0C198t$wCB{9?ggKXx`JrBGWa`8mlr|S{{U8G7%b=RN1sGczmPTJ z@LtN90m>HYa2Z?uZH1ZUcS7{bx15`Y4DzoqZ(EP-WuOViKw^u#g-Naf+YC zwy{ZSn!c8it%c_*%Ahn&{Bbro;Nu6W2fbm(eSZ3UHX`O(Vln{~EO#oO_Ce{KicJk3 zlc8K%OW_$LI(&A`tK1t%MX6;5H2K}wF+3L9qzrzQh4BW_H5Tym#V}imG{p+LPSyfR z2T{}U827CY7~LHj^GDMnd<`}@<8T1MZ0?o67C8R^fKvQGh>asmw}|X?wUDOY;zgPA zzmo&_PzM~!`e?OFh??qIHkiwa+j-2VXh^BT_h zezMErD@YzfER~G+3Drs@zd-qGqH5yoRB7G&58#)D$DOYP^P=%}~f`Fd>zy3EC(i^Jd>O9+1O~pQ$_# z)YOhMP)MiJq-IR|?ynrI_E9~z8$eN=quV(-_pHrw>iYIA5Na0}60l;~er%WmR=@+qVOP4s)8@y43FPZ9miFf_wBM zW#g~!NbVfx4tij)C*Ff^FL>YbrWj=Y<9<|FE*=`x?(|(kS!D@%XLP13%UMY&aT)3l zzi@hkgIBKf*}N&K#i?j4xBeN_!H;C)D#_L0m=27+bJu{w9kX6HAHd(0bRHGGFn`3R z=GIH=9aX%!?NzWiy4fO-|ms6iu%vT< zcqhG9v+&KKk)&;NN|rd+dnjoD_S|-GGv0tD(WSPJ!#b>kPDl>e?gT9%lWbcwZD#jdLGNheXC!?x?||tcDLd=u41~98$Iwz z79QNcnu?3hp*h9}0=(Mx?(0yymgeqxC%Bc1JGsdwkR65H=Z5vGo%-*Gue95#o148V z#?Ea20Koo2N%U&x^z(6TaU83(DzIXN z3^4RQ)RWtT`KC@GtyR!nA_Yq;Qj`maE&L0;f)lR zmgoQg$vYzh`qqz!^`8-GlcX9Q_3UjU1rS2ytcTF2M*Kx}8s4XGG%?$1w+jxifVA>y zcG0RXe}sj=FX~tI#V|On4@r{F?O#xuD|DF}6BD1EgN?mAb?R%X(1+UXXV7%(o0~mi z(oZ(X$m0__ZVeIRsmZ|TdVP8I()5d&1HN3xxNua z+AU6VzB9>Bs+{B6m93y0#PcG`?Q95<&RSE+{4>ygwPwp$o*Q+vzKt~t5z-0rc6#h@ zyocy<>J3&)V0yRdQmv|YB++2G)_7cLcTwEw&|J6nb?^dPOOgc8IQz0je(U4DFvNci zGs4;nY5q0~sVFx3LN)B9a;~U1DF?VGZY!ID$jvfE1WhEa!BrGwvFre^SxqWW_(o^& z?6+{q;&!zdg$7&7Rt_!8_kN@gU_I#Q2bSxXlIpsx&FqcLu2=)q4A)QN8?rR7311K# z1eslbygzr5`0h2zv;<@fkF9NZhkN)JKxP>++}x+>86V1kCDE*om8JNO>^|(owopI! zgr_n;0E8n$fV_L=XSt9&1tEUqdCy)I zu~=fo{{V`6b^ic?=lKd?R8}LXBDb`ue%EI9+02XcC)9TQM;}nNpY~Xn@7>4$00Bln z(x8(&)5tRx?XC|-L&JWA(-FCRVri1%-9p>kpq@1y6gO4>0D!jT z{{Vp0O4Q|npo0GZO=irRWX0nrt3e?g{R0(0_R!P{)X`^NYiv~1*We8_XmL|@;UVbxvx4_t-lNE%cftEbiQSU%9w6rJsA&wsOy>l?r$MB z*9{nO=s;v~(bV!7eMLs5i#T2=rqQK;Hftm^*-QTbJ?&389_#7bkx;8urb%QqJzA@) zRDjbFU>Z<)6#!~*JcMoBdWxpfwoZ!Vjsvc24cZQa5jx;w_F)71C+S3wT=BAh^WOCszN{{RvL z*KekMYn-^yopNJs!}X-lBb7G5TZoj9H*P1_(x%d+lT5x#m`HqOUN;$86pS*k?g+@O zIqYPJoT1{hd@W}Pn&VZ!Ky0+|BxOJH(nI$X@jRbkYHT}~;l8r)b-Y?!wjXG<)CrqX zwUPHSLf>*Q*x}S+w`1>Ou{=qkE{}B+TFNeT%cS$H?o?n)AHSmxj5{B~s@_RHrF%8h z`=pv@F3pal*G1u$4PicutfYfVoS7n&47H@#;zb?7=*!%DfklRL1XYbYPq@^s(%Q}= zB(2+^1Gy)l1Ju>{^hw%fMv^o_*fJd84_=x5D)zZA*sPmO(G}vfaf$9&VoQ_n{nhke z&nB3PciM%vg#?ybEP`v-1uiegVcjD19<9{-3grV8aOy~4bDFwsFn(f9D78`iIixbH z#}urygwG>-j>p!l$>hs4l9c7xw@*_~@`>^8G-f!ntO{QMs_0B(<7V+HYH2W38<*^1M9|6?usk;GduJS+^Imcz^vznZxt_-0O9SAJqdi3cYu@-C zTlnR^@U83`<*>`TTdvE9hkfv$n0-b^y>hntb>+qCS=-ymCDXRWiAT&&rYq9CK)xUF zzPi8hE4#SVLZ!!_9os;GokL|d@;ds}n7$<2-s&v+rkE|nu$yyxYQe6f2l#`q`H!x7 zKB9q`FD0*mU~79vj_btEyJomwnJmHTsp|fxwQ_d%v%z|okxb&{AIuVG=G~mX&V&a`;;13j>=VS=bZUZU5$X`A z{{T~Pe~1t6zh7gK+*K=x9`nF6c05UBlVq zKAwbi2eB30X&O1wA!G;bc2Qjm>uHp6JYqIvAbRdlq;??sPzNL8sfYH|_OUi??E+e$ z9Y1!vN&HUZ_|{sH=qdvtk=fkk*pg4FG~o2aSOG};qw=Q&U+#*8Jvd1~69iB0ih3YI zc4c$a1I0kHQ-qWO)lG839XD1*5;H{h7fO0J%0iRxf(Z9PRMF|)9e=jk-)X0ol|1Nw z>!Ndk?nXsbj!cSTE~`|^)k$8ZN2Uj$cwYW}M^A>{-M`{^Uc4B54b<3E6lX(*=%nixwdkXM8TB%*QZ#|Z*~?)il&{cHs#L( z*7OcR^jGKg^b{=w$Q7zU)mg(uBS#{YbtPF(P)BNdwra#!4NnJ=Py@6(R*KU~Qn1RUO*F@vx3Zd2iv`MtI%$rs6{hT>z*3UkBp7uBdx}Y|;(b0VFDX_8 zkjg;*H$Bg;2Li8br1I#nT+T~K;}}F$9>WwEN2O>O#UYNBfX*;+(xH2NZ9j2>wW={_XJWxvtx2mH6v;BtNbVf`zQHu*f_ zyPp~lH$?EP{EeBr$sfJNs}$3{L2~t0AaI|r_k~}O;2m5EC3QWH?3L``%%l(E@8)`!Vk_1 z{cBn&W{So;IOJrvjO7)0+IsMMikPN5Qf;STF=WzVKX3*70jknzY`=CiVeBiSC_O0( zQD8Y{vs6Dk0e-7Mw~HLczm;^&9(sy}?4o|C2h)ly2S4^$PyDkK@3BC7#w%I$@9sGs z#-V#T)A&OVY5=cc8rfaNb8YfV6K+%JX_DLTk=+PV#GSa{eJWfs95Rr7#XB?sOsuKZ zay>^h zHI}6#lSoA|709Nr;-_V%6`%+Rrj@G%Q%ceTxmsyjvR0T@fFLPND^?2AO3(#zwHqy3 zDNJPcpbDjFrE1YiLX_+ULbRJI*2+z^fUIMr^6JT2LbMF8vlMNq_ELnP3fpMeR)SLb zbi(CLw1zy@zb=n1kX)tZpGrKUdsW{qjg$q-MjAs1tG-<($^zzmma6N9P)EIFU+60; zlMl+>y(_2LNwSrK=5C{=HP?jvISa`jood$!Du&4bvX$bQZ)T826$UF%r?oJrwH6DM zjC9hNtA#0yl)~jkOw@i~HF1)oD@BGuvYWE3ZKT^pfP-Zl4Nta>w5$bvv=q%)+e@~c zfT+-h7$Y@aT|OrM9jDY*p4wfstQRn((^!A2f6P^BEoYCa5$F$3C*1 z95BK6rQp{~C7MVu$`904NpzUo?qyH4D+S709rU_Js}1$9-;Dgr+1I!==^B*qNv3K4 z0A@ngq!zWCKXx<8g#Q4zPaec)n)8WtyNLOW?)ukB;EVQVmqXOiqJrS=f>s|aQjUi` zf$#M_@k?RuY9XnkJc@|NBr?2HAz340tf#RZYDDTP$Tdpxc&6?8(M2#Dk7`0ajTBG= zN}ozXr#&d5fDozlG~I{NiYNgJA4+LM*il6QG^e#Rr?nJN14C8YrM+T0S~dO&QNhD4+!L{i!w|Lq!w}LWk0m z%RZD*KnBC;X?CAVD4+l*wL53E6i@=}K8BPb^rDJF6em3?;XSCLm=Y|0)hfg@_kT(# RpbH2+s%0FGD58Kr|Jfm)sf+*s literal 0 HcmV?d00001 diff --git a/xs/src/qhull/html/qconvex.htm b/xs/src/qhull/html/qconvex.htm new file mode 100644 index 0000000000..38a363b082 --- /dev/null +++ b/xs/src/qhull/html/qconvex.htm @@ -0,0 +1,630 @@ + + + + +qconvex -- convex hull + + + + +Up: +Home page for Qhull
    +Up: Qhull manual -- Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options +
    + +

    [cone]qconvex -- convex hull

    + +

    The convex hull of a set of points is the smallest convex set +containing the points. See the detailed introduction by O'Rourke +['94]. See Description of Qhull and How Qhull adds a point.

    + +
    +
    +
    Example: rbox 10 D3 | qconvex s o TO result
    +
    Compute the 3-d convex hull of 10 random points. Write a + summary to the console and the points and facets to + 'result'.
    + +
     
    +
    Example: rbox c | qconvex n
    +
    Print the normals for each facet of a cube.
    +
     
    +
    Example: rbox c | qconvex i Qt
    +
    Print the triangulated facets of a cube.
    +
     
    +
    Example: rbox y 500 W0 | qconvex
    +
    Compute the convex hull of a simplex with 500 + points on its surface.
    +
     
    +
    Example: rbox x W1e-12 1000 | qconvex + QR0
    +
    Compute the convex hull of 1000 points near the + surface of a randomly rotated simplex. Report + the maximum thickness of a facet.
    +
     
    +
    Example: rbox 1000 s | qconvex s FA
    +
    Compute the convex hull of 1000 cospherical + points. Verify the results and print a summary + with the total area and volume.
    +
     
    +
    Example: rbox d D12 | qconvex QR0 FA
    +
    Compute the convex hull of a 12-d diamond. + Randomly rotate the input. Note the large number + of facets and the small volume.
    +
     
    +
    Example: rbox c D7 | qconvex FA TF1000
    +
    Compute the convex hull of the 7-d hypercube. + Report on progress every 1000 facets. Computing + the convex hull of the 9-d hypercube takes too + much time and space.
    +
     
    +
    Example: rbox c d D2 | qconvex Qc s f Fx | more
    +
    Dump all fields of all facets for a square and a + diamond. Also print a summary and a list of + vertices. Note the coplanar points.
    +
     
    +
    +
    + +

    Except for rbox, all of the qhull programs compute a convex hull. + +

    By default, Qhull merges coplanar facets. For example, the convex +hull of a cube's vertices has six facets. + +

    If you use 'Qt' (triangulated output), +all facets will be simplicial (e.g., triangles in 2-d). For the cube +example, it will have 12 facets. Some facets may be +degenerate and have zero area. + +

    If you use 'QJ' (joggled input), +all facets will be simplicial. The corresponding vertices will be +slightly perturbed and identical points will be joggled apart. +Joggled input is less accurate that triangulated +output.See Merged facets or joggled input.

    + +

    The output for 4-d convex hulls may be confusing if the convex +hull contains non-simplicial facets (e.g., a hypercube). See +Why +are there extra points in a 4-d or higher convex hull?
    +

    +

    + +

    The 'qconvex' program is equivalent to +'qhull' in 2-d to 4-d, and +'qhull Qx' +in 5-d and higher. It disables the following Qhull +options: d v H Qbb Qf Qg Qm +Qr Qu Qv Qx Qz TR E V Fp Gt Q0,etc. + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »qconvex synopsis

    +
    +qconvex- compute the convex hull.
    +    input (stdin): dimension, number of points, point coordinates
    +    comments start with a non-numeric character
    +
    +options (qconvex.htm):
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    i    - vertices incident to each facet
    +    n    - normals with offsets
    +    p    - vertex coordinates (includes coplanar points if 'Qc')
    +    Fx   - extreme points (convex hull vertices)
    +    FA   - compute total area and volume
    +    o    - OFF format (dim, n, points, facets)
    +    G    - Geomview output (2-d, 3-d, and 4-d)
    +    m    - Mathematica output (2-d and 3-d)
    +    QVn  - print facets that include point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox c D2 | qconvex s n                    rbox c D2 | qconvex i
    +    rbox c D2 | qconvex o                      rbox 1000 s | qconvex s Tv FA
    +    rbox c d D2 | qconvex s Qc Fx              rbox y 1000 W0 | qconvex s n
    +    rbox y 1000 W0 | qconvex s QJ              rbox d G1 D12 | qconvex QR0 FA Pp
    +    rbox c D7 | qconvex FA TF1000
    +
    + +

    »qconvex +input

    +
    + +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qconvex < data.txt), a pipe (e.g., rbox 10 | qconvex), +or the 'TI' option (e.g., qconvex TI data.txt). + +

    Comments start with a non-numeric character. Error reporting is +simpler if there is one point per line. Dimension +and number of points may be reversed. + +

    Here is the input for computing the convex +hull of the unit cube. The output is the normals, one +per facet.

    + +
    +

    rbox c > data

    +
    +3 RBOX c
    +8
    +  -0.5   -0.5   -0.5
    +  -0.5   -0.5    0.5
    +  -0.5    0.5   -0.5
    +  -0.5    0.5    0.5
    +   0.5   -0.5   -0.5
    +   0.5   -0.5    0.5
    +   0.5    0.5   -0.5
    +   0.5    0.5    0.5
    +
    +

    qconvex s n < data

    +
    +
    +Convex hull of 8 points in 3-d:
    +
    +  Number of vertices: 8
    +  Number of facets: 6
    +  Number of non-simplicial facets: 6
    +
    +Statistics for: RBOX c | QCONVEX s n
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 11
    +  Number of distance tests for qhull: 35
    +  Number of merged facets: 6
    +  Number of distance tests for merging: 84
    +  CPU seconds to compute hull (after input): 0.081
    +
    +4
    +6
    +     0      0     -1   -0.5
    +     0     -1      0   -0.5
    +     1      0      0   -0.5
    +    -1      0      0   -0.5
    +     0      1      0   -0.5
    +     0      0      1   -0.5
    +
    +
    + +
    +

    »qconvex outputs

    +
    + +

    These options control the output of qconvex. They may be used +individually or together.

    +
    +
    +
     
    +
    Vertices
    +
    Fx
    +
    list extreme points (i.e., vertices). The first line is the number of + extreme points. Each point is listed, one per line. The cube example + has eight vertices.
    +
    Fv
    +
    list vertices for each facet. The first line is the number of facets. + Each remaining line starts with the number of vertices. For the cube example, + each facet has four vertices.
    +
    i
    +
    list vertices for each facet. The first line is the number of facets. The + remaining lines list the vertices for each facet. In 4-d and + higher, triangulate non-simplicial facets by adding an extra point.
    +
     
    +
     
    +
    Coordinates
    +
    o
    +
    print vertices and facets of the convex hull in OFF format. The + first line is the dimension. The second line is the number of + vertices, facets, and ridges. The vertex + coordinates are next, followed by the facets. Each facet starts with + the number of vertices. The cube example has four vertices per facet.
    +
    Ft
    +
    print a triangulation of the convex hull in OFF format. The first line + is the dimension. The second line is the number of vertices and added points, + followed by the number of facets and the number of ridges. + The vertex coordinates are next, followed by the centrum coordinates. There is + one centrum for each non-simplicial facet. + The cube example has six centrums, one per square. + Each facet starts with the number of vertices or centrums. + In the cube example, each facet uses two vertices and one centrum.
    +
    p
    +
    print vertex coordinates. The first line is the dimension and the second + line is the number of vertices. The following lines are the coordinates of each + vertex. The cube example has eight vertices.
    +
    Qc p
    +
    print coordinates of vertices and coplanar points. The first line is the dimension. + The second line is the number of vertices and coplanar points. The coordinates + are next, one line per point. Use 'Qc Qi p' + to print the coordinates of all points.
    +
     
    +
     
    +
    Facets
    +
    Fn
    +
    list neighboring facets for each facet. The first line is the + number of facets. Each remaining line starts with the number of + neighboring facets. The cube example has four neighbors per facet.
    +
    FN
    +
    list neighboring facets for each point. The first line is the + total number of points. Each remaining line starts with the number of + neighboring facets. Each vertex of the cube example has three neighboring + facets. Use 'Qc Qi FN' + to include coplanar and interior points.
    +
    Fa
    +
    print area for each facet. The first line is the number of facets. + Facet area follows, one line per facet. For the cube example, each facet has area one.
    +
    FI
    +
    list facet IDs. The first line is the number of + facets. The IDs follow, one per line.
    + +
     
    +
     
    +
    Coplanar and interior points
    +
    Fc
    +
    list coplanar points for each facet. The first line is the number + of facets. The remaining lines start with the number of coplanar points. + A coplanar point is assigned to one facet.
    +
    Qi Fc
    +
    list interior points for each facet. The first line is the number + of facets. The remaining lines start with the number of interior points. + A coplanar point is assigned to one facet.
    +
    FP
    +
    print distance to nearest vertex for coplanar points. The first line is the + number of coplanar points. Each remaining line starts with the point ID of + a vertex, followed by the point ID of a coplanar point, its facet, and distance. + Use 'Qc Qi + FP' for coplanar and interior points.
    + +
     
    +
     
    +
    Hyperplanes
    +
    n
    +
    print hyperplane for each facet. The first line is the dimension. The + second line is the number of facets. Each remaining line is the hyperplane's + coefficients followed by its offset.
    +
    Fo
    +
    print outer plane for each facet. The output plane is above all points. + The first line is the dimension. The + second line is the number of facets. Each remaining line is the outer plane's + coefficients followed by its offset.
    +
    Fi
    +
    print inner plane for each facet. The inner plane of a facet is + below its vertices. + The first line is the dimension. The + second line is the number of facets. Each remaining line is the inner plane's + coefficients followed by its offset.
    + +
     
    +
     
    +
    General
    +
    s
    +
    print summary for the convex hull. Use 'Fs' and 'FS' if you need numeric data.
    +
    FA
    +
    compute total area and volume for 's' and 'FS'
    +
    m
    +
    Mathematica output for the convex hull in 2-d or 3-d.
    +
    FM
    +
    Maple output for the convex hull in 2-d or 3-d.
    +
    G
    +
    Geomview output for the convex hull in 2-d, 3-d, or 4-d.
    + +
     
    +
     
    +
    Scaling and rotation
    +
    Qbk:n
    +
    scale k'th coordinate to lower bound.
    +
    QBk:n
    +
    scale k'th coordinate to upper bound.
    +
    QbB
    +
    scale input to unit cube centered at the origin.
    +
    QRn
    +
    randomly rotate the input with a random seed of n. If n=0, the + seed is the time. If n=-1, use time for the random seed, but do + not rotate the input.
    +
    Qbk:0Bk:0
    +
    remove k'th coordinate from input. This computes the + convex hull in one lower dimension.
    +
    +
    + +
    +

    »qconvex controls

    +
    + +

    These options provide additional control:

    + +
    +
    +
    Qt
    +
    triangulated output. Qhull triangulates non-simplicial facets. It may produce + degenerate facets of zero area.
    +
    QJ
    +
    joggle the input instead of merging facets. This guarantees simplicial facets + (e.g., triangles in 3-d). It is less accurate than triangulated output ('Qt').
    +
    Qc
    +
    keep coplanar points
    +
    Qi
    +
    keep interior points
    +
    f
    +
    facet dump. Print the data structure for each facet.
    +
    QVn
    +
    select facets containing point n as a vertex,
    +
    QGn
    +
    select facets that are visible from point n + (marked 'good'). Use -n for the remainder.
    +
    PDk:0
    +
    select facets with a negative coordinate for dimension k
    +
    TFn
    +
    report progress after constructing n facets
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    Qs
    +
    search all points for the initial simplex. If Qhull can + not construct an initial simplex, it reports a +descriptive message. Usually, the point set is degenerate and one +or more dimensions should be removed ('Qbk:0Bk:0'). +If not, use option 'Qs'. It performs an exhaustive search for the +best initial simplex. This is expensive is high dimensions.
    +
    +
    + +
    +

    »qconvex graphics

    +
    + +

    Display 2-d, 3-d, and 4-d convex hulls with Geomview ('G').

    + +

    Display 2-d and 3-d convex hulls with Mathematica ('m').

    + +

    To view 4-d convex hulls in 3-d, use 'Pd0d1d2d3' to select the positive +octant and 'GrD2' to drop dimension +2.

    + +
    +

    »qconvex notes

    +
    + +

    Qhull always computes a convex hull. The +convex hull may be used for other geometric structures. The +general technique is to transform the structure into an +equivalent convex hull problem. For example, the Delaunay +triangulation is equivalent to the convex hull of the input sites +after lifting the points to a paraboloid.

    + +
    +

    »qconvex +conventions

    +
    + +

    The following terminology is used for convex hulls in Qhull. +See Qhull's data structures.

    + +
      +
    • point - d coordinates
    • +
    • vertex - extreme point of the input set
    • +
    • ridge - d-1 vertices between two + neighboring facets
    • +
    • hyperplane - halfspace defined by a unit normal + and offset
    • +
    • coplanar point - a nearly incident point to a + hyperplane
    • +
    • centrum - a point on the hyperplane for testing + convexity
    • +
    • facet - a facet with vertices, ridges, coplanar + points, neighboring facets, and hyperplane
    • +
    • simplicial facet - a facet with d + vertices, d ridges, and d neighbors
    • +
    • non-simplicial facet - a facet with more than d + vertices
    • +
    • good facet - a facet selected by 'QVn', etc.
    • +
    +
    +

    »qconvex options

    + +
    +qconvex- compute the convex hull
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qc   - keep coplanar points with nearest facet
    +    Qi   - keep interior points with nearest facet
    +
    +Qhull control options:
    +    Qbk:n   - scale coord k so that low bound is n
    +      QBk:n - scale coord k so that upper bound is n (QBk is 0.5)
    +    QbB  - scale input to unit cube centered at the origin
    +    Qbk:0Bk:0 - remove k-th coordinate from input
    +    QJn  - randomly joggle input in range [-n,n]
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)
    +    Qs   - search all points for the initial simplex
    +    QGn  - good facet if visible from point n, -n for not visible
    +    QVn  - good facet if it includes point n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Un   - max distance below plane for a new, coplanar point
    +    Wn   - min facet width for outside point (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (see below)
    +    i    - vertices incident to each facet
    +    m    - Mathematica output (2-d and 3-d)
    +    n    - normals with offsets
    +    o    - OFF file format (dim, points and facets; Voronoi regions)
    +    p    - point coordinates
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fa   - area for each facet
    +    FA   - compute total area and volume for option 's'
    +    Fc   - count plus coplanar points for each facet
    +           use 'Qc' (default) for coplanar and 'Qi' for interior
    +    FC   - centrum for each facet
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for numeric output (offset first)
    +    FF   - facet dump without ridges
    +    Fi   - inner plane for each facet
    +    FI   - ID for each facet
    +    Fm   - merge count for each facet (511 max)
    +    FM   - Maple output (2-d and 3-d)
    +    Fn   - count plus neighboring facets for each facet
    +    FN   - count plus neighboring facets for each point
    +    Fo   - outer plane (or max_outside) for each facet
    +    FO   - options and precision constants
    +    FP   - nearest vertex for each coplanar point
    +    FQ   - command used for qconvex
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                      for output: #vertices, #facets,
    +                                  #coplanar points, #non-simplicial facets
    +                    #real (2), max outer plane, min vertex
    +    FS   - sizes:   #int (0)
    +                    #real(2) tot area, tot volume
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)
    +    Fv   - count plus vertices for each facet
    +    FV   - average of vertices (a feasible point for 'H')
    +    Fx   - extreme points (in order for 2-d)
    +
    +Geomview output (2-d, 3-d, and 4-d)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +
    +Print options:
    +    PAn  - keep n largest facets by area
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good facets (needs 'QGn' or 'QVn')
    +    PFn  - keep facets whose area is at least n
    +    PG   - print neighbors of good facets
    +    PMn  - keep n facets with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +•Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qdelau_f.htm b/xs/src/qhull/html/qdelau_f.htm new file mode 100644 index 0000000000..d8981e16bc --- /dev/null +++ b/xs/src/qhull/html/qdelau_f.htm @@ -0,0 +1,416 @@ + + + + +qdelaunay Qu -- furthest-site Delaunay triangulation + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [delaunay]qdelaunay Qu -- furthest-site Delaunay triangulation

    + +

    The furthest-site Delaunay triangulation corresponds to the upper facets of the Delaunay construction. +Its vertices are the +extreme points of the input sites. +It is the dual of the furthest-site Voronoi diagram. + +

    +
    +
    Example: rbox 10 D2 | qdelaunay Qu Qt s + i TO + result
    +
    Compute the 2-d, furthest-site Delaunay triangulation of 10 random + points. Triangulate the output. + Write a summary to the console and the regions to + 'result'.
    +
     
    +
    Example: rbox 10 D2 | qdelaunay Qu QJ s + i TO + result
    +
    Compute the 2-d, furthest-site Delaunay triangulation of 10 random + points. Joggle the input to guarantee triangular output. + Write a summary to the console and the regions to + 'result'.
    +
     
    +
    Example: rbox r y c G1 D2 | qdelaunay Qu s + Fv TO + result
    +
    Compute the 2-d, furthest-site Delaunay triangulation of a triangle inside + a square. + Write a summary to the console and unoriented regions to 'result'. + Merge regions for cocircular input sites (e.g., the square). + The square is the only furthest-site + Delaunay region.
    +
    +
    + +

    As with the Delaunay triangulation, Qhull computes the +furthest-site Delaunay triangulation by lifting the input sites to a +paraboloid. The lower facets correspond to the Delaunay +triangulation while the upper facets correspond to the +furthest-site triangulation. Neither triangulation includes +"vertical" facets (i.e., facets whose last hyperplane +coefficient is nearly zero). Vertical facets correspond to input +sites that are coplanar to the convex hull of the input. An +example is points on the boundary of a lattice.

    + +

    By default, qdelaunay merges cocircular and cospherical regions. +For example, the furthest-site Delaunay triangulation of a square inside a diamond +('rbox D2 c d G4 | qdelaunay Qu') consists of one region (the diamond). + +

    If you use 'Qt' (triangulated output), +all furthest-site Delaunay regions will be simplicial (e.g., triangles in 2-d). +Some regions may be +degenerate and have zero area. + +

    If you use 'QJ' (joggled input), all furthest-site +Delaunay regions +will be simplicial (e.g., triangles in 2-d). Joggled input +is less accurate than triangulated output ('Qt'). See Merged facets or joggled input.

    + +

    The output for 3-d, furthest-site Delaunay triangulations may be confusing if the +input contains cospherical data. See the FAQ item +Why +are there extra points in a 4-d or higher convex hull? +Avoid these problems with triangulated output ('Qt') or +joggled input ('QJ'). +

    + +

    The 'qdelaunay' program is equivalent to +'qhull d Qbb' in 2-d to 3-d, and +'qhull d Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n v H U Qb QB Qc Qf Qg Qi +Qm Qr QR Qv Qx TR E V FC Fi Fo Fp FV Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »furthest-site qdelaunay synopsis

    +
    + +See qdelaunay synopsis. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Delaunay triangulations. + +
    +

    »furthest-site qdelaunay +input

    + +
    +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qdelaunay Qu < data.txt), a pipe (e.g., rbox 10 | qdelaunay Qu), +or the 'TI' option (e.g., qdelaunay Qu TI data.txt). + +

    For example, this is a square containing four random points. +Its furthest-site Delaunay +triangulation contains one square. +

    +

    +rbox c 4 D2 > data +
    +2 RBOX c 4 D2
    +8
    +-0.4999921736307369 -0.3684622117955817
    +0.2556053225468894 -0.0413498678629751
    +0.0327672376602583 -0.2810408135699488
    +-0.452955383763607 0.17886471718444
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +

    qdelaunay Qu i < data +

    +
    +Furthest-site Delaunay triangulation by the convex hull of 8 points in 3-d:
    +
    +  Number of input sites: 8
    +  Number of Delaunay regions: 1
    +  Number of non-simplicial Delaunay regions: 1
    +
    +Statistics for: RBOX c 4 D2 | QDELAUNAY s Qu i
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 20
    +  Number of facets in hull: 11
    +  Number of distance tests for qhull: 34
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 107
    +  CPU seconds to compute hull (after input): 0.02
    +
    +1
    +7 6 4 5
    +
    +
    + +
    +

    »furthest-site qdelaunay +outputs

    +
    + +

    These options control the output of furthest-site Delaunay triangulations:

    +
    + +
    +
    furthest-site Delaunay regions
    +
    i
    +
    list input sites for each furthest-site Delaunay region. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In 3-d and + higher, report cospherical sites by adding extra points. For the points-in-square example, + the square is the only furthest-site Delaunay region.
    +
    Fv
    +
    list input sites for each furthest-site Delaunay region. The first line is the number of regions. + Each remaining line starts with the number of input sites. The regions + are unoriented. For the points-in-square example, + the square is the only furthest-site Delaunay region.
    +
    Ft
    +
    print a triangulation of the furthest-site Delaunay regions in OFF format. The first line + is the dimension. The second line is the number of input sites and added points, + followed by the number of simplices and the number of ridges. + The input coordinates are next, followed by the centrum coordinates. There is + one centrum for each non-simplicial furthest-site Delaunay region. Each remaining line starts + with dimension+1. The + simplices are oriented. + For the points-in-square example, the square has a centrum at the + origin. It splits the square into four triangular regions.
    +
    Fn
    +
    list neighboring regions for each furthest-site Delaunay region. The first line is the + number of regions. Each remaining line starts with the number of + neighboring regions. Negative indices (e.g., -1) indicate regions + outside of the furthest-site Delaunay triangulation. + For the points-in-square example, the four neighboring regions + are outside of the triangulation. They belong to the regular + Delaunay triangulation.
    +
    FN
    +
    list the furthest-site Delaunay regions for each input site. The first line is the + total number of input sites. Each remaining line starts with the number of + furthest-site Delaunay regions. Negative indices (e.g., -1) indicate regions + outside of the furthest-site Delaunay triangulation. + For the points-in-square example, the four random points belong to no region + while the square's vertices belong to region 0 and three + regions outside of the furthest-site Delaunay triangulation.
    +
    Fa
    +
    print area for each furthest-site Delaunay region. The first line is the number of regions. + The areas follow, one line per region. For the points-in-square example, the + square has unit area.
    + +
     
    +
     
    +
    Input sites
    +
    Fx
    +
    list extreme points of the input sites. These points are vertices of the furthest-point + Delaunay triangulation. They are on the + boundary of the convex hull. The first line is the number of + extreme points. Each point is listed, one per line. The points-in-square example + has four extreme points.
    +
     
    +
     
    +
    General
    +
    FA
    +
    compute total area for 's' + and 'FS'. This is the + same as the area of the convex hull.
    +
    o
    +
    print upper facets of the corresponding convex hull (a + paraboloid)
    +
    m
    +
    Mathematica output for the upper facets of the paraboloid (2-d triangulations).
    +
    FM
    +
    Maple output for the upper facets of the paraboloid (2-d triangulations).
    +
    G
    +
    Geomview output for the paraboloid (2-d or 3-d triangulations).
    +
    s
    +
    print summary for the furthest-site Delaunay triangulation. Use 'Fs' and 'FS' for numeric data.
    +
    +
    + +
    +

    »furthest-site qdelaunay +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qu
    +
    must be used for furthest-site Delaunay triangulation.
    +
    Qt
    +
    triangulated output. Qhull triangulates non-simplicial facets. It may produce + degenerate facets of zero area.
    +
    QJ
    +
    joggle the input to avoid cospherical and coincident + sites. It is less accurate than triangulated output ('Qt').
    +
    QVn
    +
    select facets adjacent to input site n (marked + 'good').
    +
    Tv
    +
    verify result.
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., furthest-site Delaunay region).
    +
    +
    + +
    +

    »furthest-site qdelaunay +graphics

    +
    + +See Delaunay graphics. +They are the same except for Mathematica and Maple output. + +
    +

    »furthest-site +qdelaunay notes

    +
    + +

    The furthest-site Delaunay triangulation does not +record coincident input sites. Use qdelaunay instead. + +

    qdelaunay Qu does not work for purely cocircular +or cospherical points (e.g., rbox c | qdelaunay Qu). Instead, +use qdelaunay Qz -- when all points are vertices of the convex +hull of the input sites, the Delaunay triangulation is the same +as the furthest-site Delaunay triangulation. + +

    A non-simplicial, furthest-site Delaunay region indicates nearly cocircular or +cospherical input sites. To avoid non-simplicial regions triangulate +the output ('Qt') or joggle +the input ('QJ'). Joggled input +is less accurate than triangulated output. +You may also triangulate +non-simplicial regions with option 'Ft'. It adds +the centrum to non-simplicial regions. Alternatively, use an exact arithmetic code.

    + +

    Furthest-site Delaunay triangulations do not include facets that are +coplanar with the convex hull of the input sites. A facet is +coplanar if the last coefficient of its normal is +nearly zero (see qh_ZEROdelaunay). + +

    +

    »furthest-site qdelaunay conventions

    +
    + +

    The following terminology is used for furthest-site Delaunay +triangulations in Qhull. The underlying structure is the upper +facets of a convex hull in one higher dimension. See convex hull conventions, Delaunay conventions, +and Qhull's data structures

    +
    +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - d+1 coordinates. The last + coordinate is the sum of the squares of the input site's + coordinates
    • +
    • vertex - a point on the paraboloid. It + corresponds to a unique input site.
    • +
    • furthest-site Delaunay facet - an upper facet of the + paraboloid. The last coefficient of its normal is + clearly positive.
    • +
    • furthest-site Delaunay region - a furthest-site Delaunay + facet projected to the input sites
    • +
    • non-simplicial facet - more than d + points are cocircular or cospherical
    • +
    • good facet - a furthest-site Delaunay facet with optional + restrictions by 'QVn', etc.
    • +
    +
    +
    +

    »furthest-site qdelaunay options

    +
    + +See qdelaunay options. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Delaunay triangulations. + +
    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qdelaun.htm b/xs/src/qhull/html/qdelaun.htm new file mode 100644 index 0000000000..a42223c663 --- /dev/null +++ b/xs/src/qhull/html/qdelaun.htm @@ -0,0 +1,628 @@ + + + + +qdelaunay -- Delaunay triangulation + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [delaunay]qdelaunay -- Delaunay triangulation

    + +

    The Delaunay triangulation is the triangulation with empty +circumspheres. It has many useful properties and applications. +See the survey article by Aurenhammer ['91] and the detailed introduction +by O'Rourke ['94].

    + +
    +
    +
    Example: rbox r y c G0.1 D2 | qdelaunay s + Fv TO + result
    +
    Compute the 2-d Delaunay triangulation of a triangle and + a small square. + Write a summary to the console and unoriented regions to 'result'. + Merge regions for cocircular input sites (i.e., the + square).
    +
     
    +
    Example: rbox r y c G0.1 D2 | qdelaunay s + Fv Qt
    +
    Compute the 2-d Delaunay triangulation of a triangle and + a small square. Write a summary and unoriented + regions to the console. Produce triangulated output.
    +
     
    +
    Example: rbox 10 D2 | qdelaunay QJ s + i TO + result
    +
    Compute the 2-d Delaunay triangulation of 10 random + points. Joggle the input to guarantee triangular output. + Write a summary to the console and the regions to + 'result'.
    +
    +
    + +

    Qhull computes the Delaunay triangulation by computing a +convex hull. It lifts the input sites to a paraboloid by adding +the sum of the squares of the coordinates. It scales the height +of the paraboloid to improve numeric precision ('Qbb'). +It computes the convex +hull of the lifted sites, and projects the lower convex hull to +the input. + +

    Each region of the Delaunay triangulation +corresponds to a facet of the lower half of the convex hull. +Facets of the upper half of the convex hull correspond to the furthest-site Delaunay triangulation. +See the examples, Delaunay and +Voronoi diagrams.

    + +

    See Qhull FAQ - Delaunay and +Voronoi diagram questions.

    + +

    By default, qdelaunay merges cocircular and cospherical regions. +For example, the Delaunay triangulation of a square inside a diamond +('rbox D2 c d G4 | qdelaunay') contains one region for the square. + +

    Use option 'Qz' if the input is circular, cospherical, or +nearly so. It improves precision by adding a point "at infinity," above the corresponding paraboloid. + +

    If you use 'Qt' (triangulated output), +all Delaunay regions will be simplicial (e.g., triangles in 2-d). +Some regions may be +degenerate and have zero area. Triangulated output identifies coincident +points. + +

    If you use 'QJ' (joggled input), all Delaunay regions +will be simplicial (e.g., triangles in 2-d). Coincident points will +create small regions since the points are joggled apart. Joggled input +is less accurate than triangulated output ('Qt'). See Merged facets or joggled input.

    + +

    The output for 3-d Delaunay triangulations may be confusing if the +input contains cospherical data. See the FAQ item +Why +are there extra points in a 4-d or higher convex hull? +Avoid these problems with triangulated output ('Qt') or +joggled input ('QJ'). +

    + +

    The 'qdelaunay' program is equivalent to +'qhull d Qbb' in 2-d to 3-d, and +'qhull d Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n v H U Qb QB Qc Qf Qg Qi +Qm Qr QR Qv Qx TR E V FC Fi Fo Fp Ft FV Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »qdelaunay synopsis

    + +
    +qdelaunay- compute the Delaunay triangulation.
    +    input (stdin): dimension, number of points, point coordinates
    +    comments start with a non-numeric character
    +
    +options (qdelaun.htm):
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qu   - furthest-site Delaunay triangulation
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    i    - vertices incident to each Delaunay region
    +    Fx   - extreme points (vertices of the convex hull)
    +    o    - OFF format (shows the points lifted to a paraboloid)
    +    G    - Geomview output (2-d and 3-d points lifted to a paraboloid)
    +    m    - Mathematica output (2-d inputs lifted to a paraboloid)
    +    QVn  - print Delaunay regions that include point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox c P0 D2 | qdelaunay s o          rbox c P0 D2 | qdelaunay i
    +    rbox c P0 D3 | qdelaunay Fv Qt        rbox c P0 D2 | qdelaunay s Qu Fv
    +    rbox c G1 d D2 | qdelaunay s i        rbox c G1 d D2 | qdelaunay s i Qt
    +    rbox M3,4 z 100 D2 | qdelaunay s      rbox M3,4 z 100 D2 | qdelaunay s Qt
    +
    + + +

    »qdelaunay +input

    + +
    +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qdelaunay < data.txt), a pipe (e.g., rbox 10 | qdelaunay), +or the 'TI' option (e.g., qdelaunay TI data.txt). + +

    For example, this is four cocircular points inside a square. Its Delaunay +triangulation contains 8 triangles and one four-sided +figure. +

    +

    +rbox s 4 W0 c G1 D2 > data +
    +2 RBOX s 4 W0 c D2
    +8
    +-0.4941988586954018 -0.07594397977563715
    +-0.06448037284989526 0.4958248496365813
    +0.4911154367094632 0.09383830681375946
    +-0.348353580869097 -0.3586778257652367
    +    -1     -1
    +    -1      1
    +     1     -1
    +     1      1
    +
    + +

    qdelaunay s i < data +

    +
    +Delaunay triangulation by the convex hull of 8 points in 3-d
    +
    +  Number of input sites: 8
    +  Number of Delaunay regions: 9
    +  Number of non-simplicial Delaunay regions: 1
    +
    +Statistics for: RBOX s 4 W0 c D2 | QDELAUNAY s i
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 18
    +  Number of facets in hull: 10
    +  Number of distance tests for qhull: 33
    +  Number of merged facets: 2
    +  Number of distance tests for merging: 102
    +  CPU seconds to compute hull (after input): 0.028
    +
    +9
    +1 7 5
    +6 3 4
    +2 3 6
    +7 2 6
    +2 7 1
    +0 5 4
    +3 0 4
    +0 1 5
    +1 0 3 2
    +
    +
    + +
    +

    »qdelaunay +outputs

    +
    + +

    These options control the output of Delaunay triangulations:

    +
    + +
    +
    Delaunay regions
    +
    i
    +
    list input sites for each Delaunay region. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In 3-d and + higher, report cospherical sites by adding extra points. Use triangulated + output ('Qt') to avoid non-simpicial regions. For the circle-in-square example, + eight Delaunay regions are triangular and the ninth has four input sites.
    +
    Fv
    +
    list input sites for each Delaunay region. The first line is the number of regions. + Each remaining line starts with the number of input sites. The regions + are unoriented. For the circle-in-square example, + eight Delaunay regions are triangular and the ninth has four input sites.
    +
    Fn
    +
    list neighboring regions for each Delaunay region. The first line is the + number of regions. Each remaining line starts with the number of + neighboring regions. Negative indices (e.g., -1) indicate regions + outside of the Delaunay triangulation. + For the circle-in-square example, the four regions on the square are neighbors to + the region-at-infinity.
    +
    FN
    +
    list the Delaunay regions for each input site. The first line is the + total number of input sites. Each remaining line starts with the number of + Delaunay regions. Negative indices (e.g., -1) indicate regions + outside of the Delaunay triangulation. + For the circle-in-square example, each point on the circle belongs to four + Delaunay regions. Use 'Qc FN' + to include coincident input sites and deleted vertices.
    +
    Fa
    +
    print area for each Delaunay region. The first line is the number of regions. + The areas follow, one line per region. For the circle-in-square example, the + cocircular region has area 0.4.
    +
     
    +
     
    +
    Input sites
    +
    Fc
    +
    list coincident input sites for each Delaunay region. + The first line is the number of regions. The remaining lines start with + the number of coincident sites and deleted vertices. Deleted vertices + indicate highly degenerate input (see'Fs'). + A coincident site is assigned to one Delaunay + region. Do not use 'QJ' with 'Fc'; the joggle will separate + coincident sites.
    +
    FP
    +
    print coincident input sites with distance to + nearest site (i.e., vertex). The first line is the + number of coincident sites. Each remaining line starts with the point ID of + an input site, followed by the point ID of a coincident point, its region, and distance. + Includes deleted vertices which + indicate highly degenerate input (see'Fs'). + Do not use 'QJ' with 'FP'; the joggle will separate + coincident sites.
    +
    Fx
    +
    list extreme points of the input sites. These points are on the + boundary of the convex hull. The first line is the number of + extreme points. Each point is listed, one per line. The circle-in-square example + has four extreme points.
    +
     
    +
     
    +
    General
    +
    FA
    +
    compute total area for 's' + and 'FS'
    +
    o
    +
    print lower facets of the corresponding convex hull (a + paraboloid)
    +
    m
    +
    Mathematica output for the lower facets of the paraboloid (2-d triangulations).
    +
    FM
    +
    Maple output for the lower facets of the paraboloid (2-d triangulations).
    +
    G
    +
    Geomview output for the paraboloid (2-d or 3-d triangulations).
    +
    s
    +
    print summary for the Delaunay triangulation. Use 'Fs' and 'FS' for numeric data.
    +
    +
    + +
    +

    »qdelaunay +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qt
    +
    triangulated output. Qhull triangulates non-simplicial facets. It may produce +degenerate facets of zero area.
    +
    QJ
    +
    joggle the input to avoid cospherical and coincident + sites. It is less accurate than triangulated output ('Qt').
    +
    Qu
    +
    compute the furthest-site Delaunay triangulation.
    +
    Qz
    +
    add a point above the paraboloid to reduce precision + errors. Use it for nearly cocircular/cospherical input + (e.g., 'rbox c | qdelaunay Qz'). The point is printed for + options 'Ft' and 'o'.
    +
    QVn
    +
    select facets adjacent to input site n (marked + 'good').
    +
    Tv
    +
    verify result.
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., Delaunay region).
    +
    +
    + +
    +

    »qdelaunay +graphics

    +
    + +

    For 2-d and 3-d Delaunay triangulations, Geomview ('qdelaunay G') displays the corresponding convex +hull (a paraboloid).

    + +

    To view a 2-d Delaunay triangulation, use 'qdelaunay GrD2' to drop the last dimension. This +is the same as viewing the hull without perspective (see +Geomview's 'cameras' menu).

    + +

    To view a 3-d Delaunay triangulation, use 'qdelaunay GrD3' to drop the last dimension. You +may see extra edges. These are interior edges that Geomview moves +towards the viewer (see 'lines closer' in Geomview's camera +options). Use option 'Gt' to make +the outer ridges transparent in 3-d. See Delaunay and Voronoi examples.

    + +

    For 2-d Delaunay triangulations, Mathematica ('m') and Maple ('FM') output displays the lower facets of the corresponding convex +hull (a paraboloid).

    + +

    For 2-d, furthest-site Delaunay triangulations, Maple and Mathematica output ('Qu m') displays the upper facets of the corresponding convex +hull (a paraboloid).

    + +
    +

    »qdelaunay +notes

    +
    + +

    You can simplify the Delaunay triangulation by enclosing the input +sites in a large square or cube. This is particularly recommended +for cocircular or cospherical input data. + +

    A non-simplicial Delaunay region indicates nearly cocircular or +cospherical input sites. To avoid non-simplicial regions either triangulate +the output ('Qt') or joggle +the input ('QJ'). Triangulated output +is more accurate than joggled input. Alternatively, use an exact arithmetic code.

    + +

    Delaunay triangulations do not include facets that are +coplanar with the convex hull of the input sites. A facet is +coplanar if the last coefficient of its normal is +nearly zero (see qh_ZEROdelaunay). + +

    See Imprecision issues :: Delaunay triangulations +for a discussion of precision issues. Deleted vertices indicate +highly degenerate input. They are listed in the summary output and +option 'Fs'.

    + +

    To compute the Delaunay triangulation of points on a sphere, +compute their convex hull. If the sphere is the unit sphere at +the origin, the facet normals are the Voronoi vertices of the +input. The points may be restricted to a hemisphere. [S. Fortune] +

    + +

    The 3-d Delaunay triangulation of regular points on a half +spiral (e.g., 'rbox 100 l | qdelaunay') has quadratic size, while the Delaunay triangulation +of random 3-d points is +approximately linear for reasonably sized point sets. + +

    With the Qhull library, you +can use qh_findbestfacet in poly2.c to locate the facet +that contains a point. You should first lift the point to the +paraboloid (i.e., the last coordinate is the sum of the squares +of the point's coordinates -- qh_setdelaunay). Do not use options +'Qbb', 'QbB', +'Qbk:n', or 'QBk:n' since these scale the last +coordinate.

    + +

    If a point is interior to the convex hull of the input set, it +is interior to the adjacent vertices of the Delaunay +triangulation. This is demonstrated by the following pipe for +point 0: + +

    +    qdelaunay <data s FQ QV0 p | qconvex s Qb3:0B3:0 p
    +
    + +

    The first call to qdelaunay returns the neighboring points of +point 0 in the Delaunay triangulation. The second call to qconvex +returns the vertices of the convex hull of these points (after +dropping the lifted coordinate). If point 0 is interior to the +original point set, it is interior to the reduced point set.

    + +
    +

    »qdelaunay conventions

    +
    + +

    The following terminology is used for Delaunay triangulations +in Qhull for dimension d. The underlying structure is the +lower facets of a convex hull in dimension d+1. For +further information, see data +structures and convex hull +conventions.

    +
    +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - a point has d+1 coordinates. The + last coordinate is the sum of the squares of the input + site's coordinates
    • +
    • coplanar point - a coincident + input site or a deleted vertex. Deleted vertices + indicate highly degenerate input.
    • +
    • vertex - a point on the paraboloid. It + corresponds to a unique input site.
    • +
    • point-at-infinity - a point added above the + paraboloid by option 'Qz'
    • +
    • lower facet - a facet corresponding to a + Delaunay region. The last coefficient of its normal is + clearly negative.
    • +
    • upper facet - a facet corresponding to a + furthest-site Delaunay region. The last coefficient of + its normal is clearly positive.
    • +
    • Delaunay region - a + lower facet projected to the input sites
    • +
    • upper Delaunay region - an upper facet projected + to the input sites
    • +
    • non-simplicial facet - more than d + input sites are cocircular or cospherical
    • +
    • good facet - a Delaunay region with optional + restrictions by 'QVn', etc.
    • +
    +
    +
    +

    »qdelaunay options

    + +
    +qdelaunay- compute the Delaunay triangulation
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qu   - compute furthest-site Delaunay triangulation
    +
    +Qhull control options:
    +    QJn  - randomly joggle input in range [-n,n]
    +    Qs   - search all points for the initial simplex
    +    Qz   - add point-at-infinity to Delaunay triangulation
    +    QGn  - print Delaunay region if visible from point n, -n if not
    +    QVn  - print Delaunay regions that include point n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Wn   - min facet width for outside point (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (see below)
    +    i    - vertices incident to each Delaunay region
    +    m    - Mathematica output (2-d only, lifted to a paraboloid)
    +    o    - OFF format (dim, points, and facets as a paraboloid)
    +    p    - point coordinates (lifted to a paraboloid)
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fa   - area for each Delaunay region
    +    FA   - compute total area for option 's'
    +    Fc   - count plus coincident points for each Delaunay region
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for numeric output (offset first)
    +    FF   - facet dump without ridges
    +    FI   - ID of each Delaunay region
    +    Fm   - merge count for each Delaunay region (511 max)
    +    FM   - Maple output (2-d only, lifted to a paraboloid)
    +    Fn   - count plus neighboring region for each Delaunay region
    +    FN   - count plus neighboring region for each point
    +    FO   - options and precision constants
    +    FP   - nearest point and distance for each coincident point
    +    FQ   - command used for qdelaunay
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                    for output: #vertices, #Delaunay regions,
    +                                #coincident points, #non-simplicial regions
    +                    #real (2), max outer plane, min vertex
    +    FS   - sizes:   #int (0)
    +                    #real (2), tot area, 0
    +    Fv   - count plus vertices for each Delaunay region
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)
    +
    +Geomview options (2-d and 3-d)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc     - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +    Gt   - transparent outer ridges to view 3-d Delaunay
    +
    +Print options:
    +    PAn  - keep n largest Delaunay regions by area
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good Delaunay regions (needs 'QGn' or 'QVn')
    +    PFn  - keep Delaunay regions whose area is at least n
    +    PG   - print neighbors of good regions (needs 'QGn' or 'QVn')
    +    PMn  - keep n Delaunay regions with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh--4d.gif b/xs/src/qhull/html/qh--4d.gif new file mode 100644 index 0000000000000000000000000000000000000000..08be18c8a5a1c89f07da6abb451283cbf176d5b4 GIT binary patch literal 4372 zcmaJ=X*d)L)E+YoGZYir|o5^xf*3;;M12za_bF~wb( zLco*QTn>lBX0w@0cQ-l)Ypm|-Dv!gFaYW)?l{c3XM5Ugjc{TYnQ#mx27n8~2H2AUW zPk7e)vTA+kZf-2TkEbce)QI4taDtFd=fb^_$KVHWI1e~e3ypO@$)Un&`;o54&vJ-x z5&=fU!w9Z0oFS1&rV|JRI>y=x?ux}LlX09NZnHnbgW>M(&Y&|q$R4a3Z+5khXT=Fk z5|MeG%dFtK;oVpcEM_s6WyP|mQuVBG?iTJWBNpc%#~M$-;Vp?-bYceGBht&>0Z!4O z5RIM42gp=6Ptl4yXowt*d|QAs2m9E>0k0trtf5J)(p37$YPCgU~Shy?OJ zSF8?~Xh|S2lDKHhG2J~bq@0Z{42>~BUu04gXk0H21y197a^M&=8BQfCkmX!lSRNkK zg9I{>NZNCp6i>k8@JbY%E0)_F;MMHUY4k&oNEAORJ(5pi;s;+~pV)MJiQ}CXYr_Dz*A{d*F=UqfdJo|i$roi#oSvM z5l1AUN%q<#cV8wEMMO|3giIPGiQyK*wm$|(9+Pu+(O1BrNz@2MCk)I9MW>Uk_Z)CB zl;2b2<%P#!*esR{8t;fw^YCEMsC%FgPM)HW_dp^ONz6horvr329!DY)@uUO-9#3*1 zBFSW87LBXuMU3ItJIL*ccGah$X*3lajuGssZQ}~~e?f%HVH- zOG2E)+x)*r^JXg_Tv0E)G2LkBH)0u6EXak-w{OZw|1cVTgg#JZTLD>n-F3#V#l*0y zW&Q!ss*|ItTt~fA`ZKyd%6iqmsaifU-Mpi7^_7Cs+WGzm_ddSq2)NsDH;i>?x@_!R z?7fODqYvcRuU{Pg=>9r?i(+JxU_oF_jnqBePy1y(AbOUl(bw+Xz4htO(%qacwZMwa zf5seU?w|2uy_$W;3;&v{<#=vmF3_$&>+$i^b>ma_g%>x@1XuI=zW*5H0-culddNTwq5xi&ZEb@LNLneqiX_g=vYb>jLbLS*)D z>UqnDj2E*xk8KwKdY@6!CSN)(sPgSzpw%zOBXx6RSle$-n0}hV4plNDN<|kXk3I;A zN%+YJ-5;K4qY&upbP9R3EesEb2!<%cL4A2Zem(R5ZlsHbf~mNec#Yh1u?z*xk%P^xe9NY!oLDRKbm|OSX;Ea z8KA(Zn$)n!^ui@TmJC1AXDFxW5TLQgdFe|WHY&rE8EGrZg2`8-FbI^DgjqzN)`_NP z2ZGbUx|TY53CRE*3D?1ok;5tvrJO+OKl?>G{<2^$c81nn@lWWtYmOqcB88XjldNzY z3GXv#ZS0|+gN3?EX>E;64B}uqXH&IPLI1^oC`mx*qoD?~#GXoy_E=uSYp&M$Ua3H2 zLD|r0XT;ngC}8L%c;Z=u^>P;UFd;GlvD@h>etH8d`xeYGb1AC3|vv_!H!1kt0qX~rbSAZt+U;-fw1862;5CIz&>U+ohqD#C_M_n zn4B8r7trcLhJt(IHb!N#Yb$}{wRo`=S-?$y)rgQ}xJN#)Vr;XS^4x$D+Kr7`L}@sT zSDOJiw3=uS&1%s>j7d4r1AU=^{wS1UbL6;vjAiS#mjnQ)&PblPkV^CD#ROzeluvYu z&sdVhQ|Y94#bbOw5K3cZkZ}IN(K8|Qi^n`re-ARPJ8q1kM`50WPdH|>Z++1k`yUSV zl`EnOH}{MBonV+uP}ftbDm$-Gifui!yH)m+UJ#E*6agfc9--s@z64o9syrC? z5VuQm%H9{B+`0TLT=B8=`$5XS>_v4muyLdha6U;`f{Ng&REib}bE-lb59%*Ue^74O z)R@dF8)b_*nFyTv zSiz5{DnR*6uOy&^(nM$IqMax8#C^*e(|fVxwiW*z#6|JGfYJ-+ycPb4D&rHN`p=6;=KxPsyD9G_AB`c7I*+@};FaWFq|h(NZ=j+F<9< z@=5UF;|YsKx4^&`2(Y;EJ2}QJWZN}_lGE1B`Ll=gOB=HQk!6${=v{c34)Am(l4XLf|@S6(O`2Z=z8?{(jYU1eW4>lN?JAUjB4Ee9|09giCih!SXx$0 zh{Rog@WMz&<@Wj8>scOMG7}w{N*>vMk{POjSTBK0F<&xHlzp)& zNKRhnV_b~CN=B3d*nTC&Q8i(I6Rb`IosmlYFv)Xly3&~!vCskLM()~iw|U|N;t+(L zxt$5cAt3+74ML+JFa1G|9T4+@ZX}_Akh|4Qr7dvI;y%RyI ze6dj-Mc=xMHdTv@0`u20uAEX!7(qb8)DlKV#553Q{1M1V_a)5;`mxf?M%(D{=91Wv z?C@3b;~mCMaOk0k(y~&h2=0CvW{k)C3b?-iz6f*D-gRSo)N7hg#Ztu$7Z8Gsbi1}f(atE|e%BU9H< z0~Kq{FwdWbR6)VrWXM}P@tL0mq@M}b;h@Ey%HLC!bkF=%e8G5Vo+l#jcc#pWFX-hr z(D&HtnV!@@h+rW!f7dg=1tDNH7k&=SpcF*uOE>WvI8<}JJcSSK6_)0&mYQ?Q3-~f|C`ek5aaCTO{a{^?!PUIjtEyJ@ z=4((7ZJ;nIg#(pn@P(4s>K_IJoljn^@dPr|>&!;Pz82Nh2*AF`%vgLwLUaS-l+>R+ z#+zkzXLlNcw(IKO1gk^yGzo&;(MG}Kl;D8i`dLnGz(5mX>iRFW;^EjD;M*FB@`~<( zDw04(OS}56f_T=BSPt~^RYJ=(X#Tpk)V>F`s_IQ2W1Gv@#I=M18}*jDwd;rDTFjek zl?Pjj2~FZhuBRLU>$cR_Kw{<8NC{kX2@c#&Em6lc{^@3^|>BGkzzF#;K?` zYztE>#UqWZg$*@=wrOPMp>@gk$Qz=T65kQAvaH4>b;;H29Lim{=fa6=lALNJbw=_cUzmt7(fhQ6#xi5jt}}RiN!%N zL-aKxo!KSvB)lfGf~A8)yU^i8UoG7PYn=qLsS()44vtR1`UL3(9K>kZm{~;XAz|R) za6?NR1a4x?DWeu#FZbaZ)ZMz`%|Y!hb+WgH@|%l>9zO8Tw%IGNbQe%Ddvp;P?A{ZW zZYAOMcdud5AQK;iR+O`q1K9DHpN%n8%iJ_1!-BB~m3}ydorXilV-T@cE;)ft0)In0 zxCIx<$gy|fdxcfeXP-XVTSenmxH8j;zM%$=6j)evq*N+RPD=do<45PMj_@qP_V#vA znBmseRv_2l^XJc-A2)3&P-ZfYh)1uluY*`Lx{nT;4AnL@?db0gstBR_f%&H3fBpL9$wL18`7=D5 z0O#P`1ukE|eswDhgLvUTefs2|YHp+t3G(+NJ3-#Pd*^^gdz8n3{AsRP9&}F~hYTN* z6V}9E7iR*o4JBG4U>?ORoTqkq`Gugm;J`XIJc^)Yk43^k{+9z87g@VvjHafiOi5(+ z@8-_7aD5%E+Qvr4K4UH?2ySY^4m0fS?L{Hs_VzHl6bFMC+=D+K?m8dobljI<218(R zfd6yy|Kra90OtSVzh3|-Ndfzmbv?zR{%j=(jyoc*8O#G2c~^Pfs2wVRk>VFdZqz+E zjiTr4dfluaJ&Or!;f~&H7%S5~C7hQ(YiyAcF86)Mut~WJ$l7no&`2p%ISqQ>BJ>Y( z;2DwO_^vbc3^2Q;6)1mb(QrbeS-*^BpZ=xAK26wpSWB+c=;W3CW5G@ZUWq75z2#5v;)M2=m`s!MfK zsj{lZT=VVV)d5PY+VskEZoc^N+?y{4o*laKn`YT?3i?5n>@rGD}rGcz2AKZE-13fT)47$WV%UjYv(QGUB;`@Ul z^$lMZy+*Q)MNvyd3bvx6>?hS8?kOm#Nfo!1cj#}+roP&`+=TKnE(S7;sV^*n9W}(w zzwTZ_e7O-T7pEDuHICgWy?F}G6TOk}dyZ+u#wk@r8JuY1Wt&tW0zR;LD|b%qQ9hM< zW&|5kduF9Zq2$40q>u0NYSdYkkE5t;a!S5TUhS#eY>R-El{&$(>B9PR$7`uTwer#y zS+!S3tHY!$1{|H5!rmOc)`bJe$kAdhHprf$9GyFS)A{L(k|+#6;d=7@$^5pf8zT*2 zC|&k?Vc(+o>J<>#q2Nq!{B$2;$yVk`8C_4Ows63>=NfRF;!s>XsnD~Z^-X|)shwfO zuK|*SCd{?+^!|{SFBm`%3Ad#={9dUEO5B&s|0HbWQK+t(EdQyb==m*&lR%*F!U6fA z?kt7cgsYQuzdphbq8hp|{VggeDB@6`?-B9MM((-Y(@k@Y31qg?s|as3K}EdR9o7Q@ z?(Wi*A_T7jy}NP17!{q5g_KVf0)yOCZvpgJ)x6u5$#omI+*`Bha}n7u3IVb7q84e} zeA12KyJqmx(dQiqGcTjGr`58Whh8Bq+cHmAioHED&6R;@U(xrKCo{l4xV>WL&DYl! z;&Sy9QXhiLw_EB4Rw`4n+$?TAV%>aQyYok41#kQ3M{lQZvmX}3%n~}KWBR44ZTEXe zwfDS{)q3yCYn7>+cBz*#Ej;BW9XI4E1&#W_ZJ#-d!avzrJ0kLs6w~LX>U5Q2vrQr8 zo|U1RA2y8^ylt4}_tbO3S)2?6Z2E-Bl}oy#ButNwGF4s+3O3Q#PAib9xhoeGa^w!S zB_C}UDrSDkvmT)u(<91uv<3jywoXz@;ZgsCwY8t~>A9D49HOd3lwTGY`LC(I9s( zX~hBZ0}c?6zpR_PeC(0ok-ChCt@nUio`NIHETToxf+1&EXY58#S^;tQ_@Bln+}- zE%;3?a%QE$$h~R;O}9n-evhcAKoFh3(1Et&*^O~pmelTGUa5||%ZE2C!tgjR0{c6V za84kzk7}aul{FSS6>tHG8Cvh;IkY>udRz&iYx{x$36m`vlTyr}%^0Z2Yl>gB9Mg!4 z$9mEZ-q7(r$staNR6__^g=l5I)_roGl{xh?bT8jRkIK;d(5I*`kM(px1TgLqiy`g$ z6;l$`^p-^(zJH!pJu$B^YNobxZcyDF7f_ns(d5{@tgRk*Q%;1{S_+|g0Y$mq&HX!C zOZa#tDxWgbS8KDcPv_vI;-%bk_2}wlkDA-NSQYDF=k~v`E(1Z1l1SKj(b|sY7Nx5k zGUC;dVNy%~tay8-4eht8y(}gXDr(evbw?Fu2szlk^ZyM-jk&ALP`Hx?pWqLZ$YW2P zM*>7LX|b*eJRqH#2r%mCBYFc>hV47SCFiB)GMLb88&-6C#kd98ekUh(D_G0qi$vu$ zv~7R?fa<40UbMc>vyo20<@`fYU0RgIdcJ1?gCcxCOhzhiwpC~;6nH(53nG`KV5h=MlwQG~)< z>wf<0){winV3ls7Aib|<1M!Dy!v(Z*3@nGASfUuIRCoU7Oj(TQz#slwN6NNBS> zCi1MFNCdh)BAf45m50u#MKBJErDq!!6#KT$^^B5Sb>Ar#%-%yQugn3;hYq~5>gt26 z?Q4r|Fk8lFQaV2Sg*h}d7KNdqv8WNM3?u0kuSIQ-$nz*vU!Av{Mc`)HWcR=x4=JV{q^ zrg3nvr@#HuVCGUJRA<-{PY!Y2vA?}@0O*3Bz#BTfiHB6e35+C|yGR!I4{B1F!&d}{ zq6De5k=Dn9%8#p8(%y=iv>U6H)*XsHXZ}ICjSQ3x-g%GYEslmobcJ{%^y~>iX(}>3 z!Pje6A><#6%EjGkzY%VaG7LW}H$DF5T0!05+72vMXnm*VvT#GK1ogEV&*fAq=~HFX}BO*SovC`M*hK0sv2{Frg%dFD60w6;7WvA4_WRe-Ip}5 aB{fPSgP!@PkGDE|e!fd8+eHNc9R366^3h-b literal 0 HcmV?d00001 diff --git a/xs/src/qhull/html/qh--dt.gif b/xs/src/qhull/html/qh--dt.gif new file mode 100644 index 0000000000000000000000000000000000000000..b6d4e26727ce648be6ed6d4c8e5a49a367064f20 GIT binary patch literal 3772 zcma)*Ygm#8!^dwx~2`C@Y_>Haw=wijFhu-GjD6?b({Gt=gW==iT$|{rLXA{jcl)>33bfER_be6n`lCsG?l^n>{xg{!DVx?bDx2JHgyPOmtBl)Y+-C{Dl6hZ7H zvB-ZHMUh8VUXo;vOdL!*;T${=l9Eak?2DE(N;7zLU5Zy)WqQV2&e;@hR&KUh z4VA?)^VQDPN}70AY(_be+(^llP(8M}h;|Yg3W`?Zk&{DAk6@SZJ!1_}M0Z52DOR{g z7-x)2&?ksn#Bqi=QHw~tOPtu8C~c53nY83>$?{rxN_C1tqsVpVDT|e=&8p18%q(?Q zc3yT)PL7nxQ5DEa;^|Q(ZtjL{|Ctb^SK8ALI0iqZ2ND=k`&;svj?m4_rG$m+bXOIe%*LFgyE`$EK-kUw(+q^-H~T&2Q-_yz|GQEV0z5 z`dy>Fr3!iLAV%KBLU0`vM=&k)&yUCGmr$_Qt*Mxze}JariK5*5rr8NB{ct(SdWUfbPQn3uu>9bpa0PZGLwK zY66o(b`?E7e-*oe3^``=EMw&inIwh(sCaxSVdFpN+>dh`6)>>zU!@i}>L>N`+o+HJ z{OnC|53*!X@qKDY`K&Z7qx?rrKithD2zA0R5q#-h)z)+K-kvqmbrkKmZBXH{a`@4S<|cAL-*Wrmm=;pSfj%L&u|>j5nH?P-CF$* zu9l)0MhJV_Z4t-A2{KkIymV{pu!_B4uT}QWjr&he>_iSB`pWO-yqJr8Bw8~9g+HMl zdu;|5=e$@A1E$*2JFnHPD3-bvky|mvI$AQUI+;zp`TJhFMkhYt^*_+Fg66Ww%d04F zD{r>{QN@&#paB2vOsDleW1ilk6H5>q&!H!ZaLtXTK`n`S0U7dW67~^o#&zZ(r|^XU zMlr}|(EcM2UZh8X?33ypKJiCzOw0LbDb1U57dWOUif*MZ%P-8+M@IUShoAq-rC*#p zeI)(T!A8U3B)$BJpv5znF!bgWlzbuS8 zlyj$LS*Oo}yf3cYIHRno`T6RXPn)5@_G4m2itzL4Yjp1~_~GQHSDHj;@N6xtXF2gF z{!-@lmvdKt9JZuqk1s%&jd{v@_46S0a7EI!Vf=dPvdxqp88O)~knF!p?K66}?u(+| zBMBFt$Y?uqreCwq*?d%imS5}V{aNjjH-WH@xJwI#6xe=(Ob;mBbf5h-as4RG)XAII zblrsQBTiu0VOA9wFxXph{fBedbCU~87D+qvDP6pXDVgwi`Q1wUKljiiOBt74P(2(Q z&N7^Jovk%zN$`HhF>y6?<$qF1UKZbW#}zGtlw}Q0HyvtGwDtY|)bXGQCv~)FlY$$p zhT>s3>PBEE`KDMMB=2> z?vRDZ?9Fr>6AZjuri}TlG3tDuBu0J*!ejZZ4zA$0gg!goIb4ir^{5znei81^)WCMA z^f#(`h^*e*q%dd!O0NIJ$8qt1%jBuG2U+;Ir>mV7TqQe&zJDv-f@Z3uZdTYq2c{Lu zT%Yw;K;YS{bkczBv`AOgjd!bqY5A19dkmxyNX{u^C=TOBOvynf!{;wa1%T#YQ~Dr3B6B~#8BWO%F&GsHu&rl}ZL;Wk7EA*Ihq>YY65@JVr!Zw*4X%ph8?T7B1IieKz~4Xc!k zUGH=~E@~m*&r{?zh=04T6h>XDw<997V|Vu@GH6(RP4o3M&5L*+Ou!paecbwiGzSSRoE@N*mbFQe2%U)9|!{0PCguR+=8$Dns|sj*#*uJH`cL5Y6);wU=%ge+YC!< zNdISky3hBNK6oR?MVe+D0nG!PbamFBHQGHBxZkNoMD`k@QYoAigO&dIx5i+d{@xS2 z4R+b>uTj52n*;M`^Wu|?)(wA<*B;$?K~jo?ovU1--e>b8qxq;9Q0HP!m~&pYc=a_t0{V(6LFK z?Y^Fys%#9}F7?!jRr3Mg9e*rY-$H?Xsy$rxj7RVglJiH>)}A*ar}5wDCr*!}XtrI5 zIvQF)jvpc_rC09$g~KpWYd9m%jje_yh?~V1Iv9QiD(wz z@SBZYoK(PA8~Xk|e(WcIx8<8SAKWebtN!5<8zJ~WXK?vunw(9MOe5dLDfM*t5+q`% z%@CSKduSE=zjcezAr!oeBB~QZ>gx>1mU5V9@AOUOpSQqo)>gNw!0VDVBu`q2!YsG0 zrpSrZd+z8PXxRi|11SlF?uF2olk7&;b#0yL~=(<3-Vs z6veOW?E9((hPVD(|Nb#U0QbpvM)?eXx7DD89?@TZw#fX04#2qf-^@P)49*l#>0$jP z?P()gRBQ;Su+w&eyF(_%a5X(^hQKd6^U(%asKt&N^=}JpsXH+LRk6E&?~Lo08|$rC znl@f~gI63r7~k(?_67&l4dbQ!AB`G1@ZFfqk5h{0U-|k~+AVV3gFA!DhcsN_1 z8HEyy)F0Kq;PaF`DNG8y#L;EDFOBT!SC8C_*Vxt}r!c-toQj)D)M z^IXgV7p{dIl5D6d5x7O)`sx}`F^1bMa!A%fBldW1E22UUO{l<#K?s@+)vlxat*UXo z<4y=*+mIEy*4<{A>w^xC7$fv8THt1*2-skYWMeoN5dvU_EQqsu$E$QiydC*21aOri9(owFbe|cBTIb?8wyn&DlecVnDrM`Ain*8zZiRVEULv9@!CWf(RK3d+8YvVc!7I_3e$qXgo-Lj z3~av{fvY-vV@KufI!G{PYAMHn5C2w<8QcjD+k}F*-yhRq(h4vUkO^Cgi5N2uj^*Y@ z6V3zTmb>s_?jdK_@_sI2F!Dok|6y_)uit){Aj2%^yY-8N33E@{BHUNhurGiZqeq;! b)E_=_r0*X`jy*hb;`bvK_r5-N2#Eb(@t%X< literal 0 HcmV?d00001 diff --git a/xs/src/qhull/html/qh--geom.gif b/xs/src/qhull/html/qh--geom.gif new file mode 100644 index 0000000000000000000000000000000000000000..9c70b54991a4574015671ec0e0b29102c5346737 GIT binary patch literal 318 zcmV-E0m1%9Nk%v~VJHA70P+9;0002<^YcthOuxUs=H})A0000000000EC2ui04M+` z000C27`oj4Fv>~UxH#+0`w)sJjvN?VLaMH8>$0bbk~9~GUk&U)eV;iGk3cN)M86ql zS8O_aon%l0a9W?X4^FVDUX5PkM#{~EpS)ociXof7VKug`1(pYAaGaPYum^4zeSSk} zgKU9=d{t?FSBP~=icgMTeiwRuBG0`N_cSHMx7af z$;`^-=2P!_)XjVv?iiNh(qpNX^edGGoIqW^3IgjO2Mv;dBgE0zSHlOyiWQ|)*!T

    !gXhA{d6_OPvj%08+$D~O@p^*epL4)B~%AQ;?rVW!NB|J`5yolH)K2<;<@OU*aMr5B{Nu$Z5LzHA9JUO$7OB6Os z6{<*#JT*JY6`VQ=Z;wc# z^M!-6BCrgGwlEgAXHNv5Ul&2f@mv@g1Y$>QA|W)OKPe|L435TwNlZ1IO4kVp;Sw1V z?YSQ&MkPl}gnSv@RIqS9E;=g z4KSn^1nxs12D4cEIP?QN7AbCvioOja942gB_q zQ9{+>yutuU36XF-AvqmY8W$HwhiGGCV-Mrvm2sT@q+F>~S|Z5ZM=K3M`!{EIWeLLJ zD8Id_VqLP3%jOzU8Cs>FO?*s(n8&M549tCt)(lv)E1h-enY6<`}hB1t~Bfe^+4 z{GVC;kADaNp#H+Yz5uY^0i@Z0xmS884A#y_)y$Q?+YOEZ{BG`0|5UR_X!ettp)aP} zAkj@=-tgJkPK0bgH9LH6{sb<4uA3BdDsYdj8=~K@`8+Q(FLOWm5g>DptC_>kc2d;0>s?vRq@=|9~^X4`|Z<{Du z2`QOBdn5SXH0);M^+gMCFFdW`8zz_DvW#7uxAln-Zq*O#rb_HRw={wgMp-^pR{0Y8 z{>i2Dpvw49EBigMUK(#^wDMZ!J$>36w+j<71&{ncZuUh_H^YIqE$;3Sf9u(tvvgVa z#(8xb__~X;b8fWfQxfpk@A7O+B#OrHt)+mAUN;-4KZ$;tA6q>8I8q+;OQR-L{&AvI zgfE=b&ZniVbO<8vtV}a2mo92VxgU(sIHl z6nB@oXbm<*0aXi3^-hSLgezz`&%9qditU5g=N%h;)3U->l>rOI3=q(<W5xH6^8Nsrz$(ttA<{dGYLt zox6^(tbu|&sn|ZF>GJ1`dP1RO&W2E1d<>B@Ra~mhVsnoR&~hoeVieu zF~hniYU7D};)!^>U0q{zvMJ|}+ZaoE@sSg2<8(6_iHOS>uwV3k_YKuUiubu$(+RZh zab$FQ3|Vt?#?9d6Y;%~>bn57u`Y6jAGJ|touwML5NS=kAdArLfcjAD>se@DQc~3el ztQ%*#n5doe{IBoxbn2}KmXk$x#3X%*RUjavgLMQ)THO57`7PkU@LQ%MXJ@5+8Z_aa zltnjx+<_Y(THVoS+xg(>j-B4>&kz3K5{T)` zGLXILRt~gBrlvfdpym8zQgf&M*?SoAMrdHL%lQXt%k|!dKR*>X?Z^N$TfTgJVbENQ zjPo=5vf9q(Zs8#pmwKnUB3s0Uw8|6ACRQvrJ5=_&Fk9HLn?p`7p?II*+}iiui0Ie! zcWz@dpZ{Y!qSm4Ko#BT37QkB0NHMpC;t`Xci5?Cr+E6QjX-S~0nN!wGs!BGLptp@2 z%H0W5i@+Bq@gb%Jr=#ul8S8(v0PIV^FS;7ImI3d2uRr)!!&eS&{OLh&k+`($9ppxo zaJ)r=+;nZ|f&__-bDExE*VakJ$`yTG=qesOK?LCI>8Ad`wF=}=olQ#hom9&av$OB@ z_-Vz0JK;WXVUPud`OTB;(m$IVWxz<&PF^j&pzn8Dp1*hD35$PswzM0kqwgE*_~z?X zz8RB|&q(GuU7N{Ro@uBT$N_3?_l>Ek4lmNzIT<_-t+AZLZ8{;i zbkI3UW=P5+&;c^ynm+T&qfBqQ|3bRk2yH3|r#D0GGo0dty$;w#Zt%0K_K?)QHVH`^ zI62wt@%`ci_U1c##U$XgCCJJ(un;)RS39y<14+KmZP`TE0r+90RS4)zjd?Y25Rs#( zE~+{E`=bke#Bt>pUXR2Zy_d~F!H@-{N&i6DrOSmJe@pMB&SJpe{%dta`NXoUH|!Fd zIuXv0Hq74oa8*Fdr4wit>aJsh6&4SFu^Du%`SJMnRmpBsYJ6}zm#y8yDp1K_Qeth+l+s2KZPX&j~R7$#Ve%$ia z*A*sUb@1N-w`@8#E8J0Qw&_Ga8W&gR4IXna$R};xCVfSA8l1k`ncgyU~py|L4Z?kb!BQt zUU-FaDJ4J8&dx_eIBTFsKr=CWW>;%rWo48tSSmtwh$%R7Igf-!U|3``QBq}=L~3C? z86_UQyuW6TW<)@6JwiT5Nh)KDV@_*dC@Ln{*VbM?L>*2tL4`gWOfW)aC`CX?Z>36Z zrAZhzEleLqgNJoOZ7Y$EkUB(FG7BkIR3VFgUlku9NrNFqJ~=rQJv$#@M<638H6m_T zU4&?Crl+AnP9R~3UHts~`1trQL?T6jG-i%vb7~x6MNmdtNh&cXQdV{#DrQEGLXCq) zNli>NOe#7bBVRy7OEN8HZEsDFE3&b(RXG_GD+9z^TS8AdBPSUuRcu2xGbnCnDmP{#E@CZYlr1u79YGT_kAz2#ghpUkWk^L- zNIG6lkSkLhX=OiPIx|{JGEG|+T}*`^sjR8q-Q6@?B0DFBO>19hNjp|c5a)l{AOd(QcPd<4?e0NG(d|FH>S}8Cz zA3!QA5+8+ziy1dAE>SW@NljE>DKirwID$AgfHz{1GbK77|Ns9eFIOy0Gihu@XI>Y0 zV_^05^`4lSRy#OALO@_jPw(&VGC@2=cp&NN=~X}(Hy9u^PD@r@Qsw34S593VCr&?6 zIN{;pZ>3315IIbRAXPIuQ%z4)R7NNtGGWQVs6Wf zTYf43#iT1PW`$R_ga-XN^k{6i3iZe(JO5}WpFWi8AYQ^u#%{F28xOPqp9HJGHDg$&!k zw2YgHYy*xK2~?WNI_rRNga_Y#3+}k%ihD#Fy3A5Q0c;qPN3pq_gM}S8Y?A6J?lj_3 zgqnQwMn2hYo5{A}WLmD3;UY}#5fXs2?!yJlqRtZMNMXl0E~L7P94fB#j5ExbK*}2= zuv4kF2`FLi!YrHH4ge6xi~t)&^pJ(dE>zQMN*#=2!W8}jAWy*B!g0jPNGojaA7RAo z^tyH6yo&)VCG+zX6i~2c0C0#Mk6!WAG zO$4py8n*1vukZ%~z4N}i?qJ{&eDE@yBtzqGK+e4D2?zl_^zB@Nz4f~HzQp#x5C*j^H)2Z0l{!oX@b2nn*cD_zT4#@3mq&R0!fksk-ecE5R5<$P9XvmZb}yZ z-KYZ#t%raY#&8$qAjvblfI3o40fM5qg)SD5w7!Wg9Gvsq!)Ead4ixTiU);s~K+*^= zjN=n;7zgv5aSM6qLIwuITon@rk8V&;i|p!R7;!d&lDNYQ@OZ-~n74{s9D^3OC`K3( zHb=1~;tzO)8V|e}I-DhuBv+7vCf<+)QGnnaw7>^G!qEgWR7s9xOW4aIS%5l#YL8zW zBS@A(4QIT;8A~X`6D}aiClH_sm#|#stD4^1cM-alnl zvK0cC@WK+H+ES^CWE)ilLQc1u)tqj#n|l(05&+PJF6e*^@X!DYxX}erxb?A4XvQD- z5CtjFm8EtK$r!wl488XCv-r4YJ~^d?OBD97X0XR%7u#6Kwp9y?Q2qxn+HeV6T$UFl zL4_B#uz|hq6|}Tyf<-fGTEmhd5p&=M3s%dG7g)fxYh{BXnsEkToT0L}ElD}3FjL=Z z^|zuVB?EwJ+!v@qxyog(bD=v6MI<5^z_2cDZ3|W0lB5#m;K38T%U_&6p%_iTMk2`2 z1w6EXxywawBI4lMS=izQZK$qwfl&q$(AB;pS;HFaP)}ZF)5D_Veg8!bTb ziBp{7b};y{C`g1NERc>foT0|)Xu}^?+ENUQWD8R$MTdv{VXhXV3K~$cic`E~ZY&tG zGLFNIZH(hHR2ZstCImDxE!XuK)WEQVj#fhlH{$t^x2mw@i$~BI|j$4|B zlKg-Nt{^g)Kl}tUAi@)qXhE5kY-UZUxvg-RZaUul<~(eIrFCckNrn tio%^!)G! zn&62?Ji-!#4mCI6p^dgm_q}kY@|4fOha03I1zQ+GNh(q4UGDkT*d%~OJss#Xiy9tq zU~79Jtm+!4`o^2s#6H%f#BW#{(;&vjrd#ojNqG9aO>S~Hw6Inb*g_F;FgBxCoen-^ z!5U*Y2`YG?U(o&*5Cka3JMLi$ULP32%v}YyV+`)#GkT=93PKZYY8sF$W zf+b)}-H+Q`;OX!|x>HgK9zgWuHFZT39=`H>yra{qRqg{GU=MrDVGeFEhkN0BbB_z0 z571Hx5vUM~P=2?jD`>*Ji+&GZJi;7=@Pyhs?dcsH!VrdtaKNojcL}4R11z~hD3}3s zp$mZQD~G}(2w(;?zysKDpoA}S5Q9XRp$&8Ixa5Hmj1lZy>s)8XezSVdu>=0-!Wf0< zM=yG9@WTqLu!RMxK6yR>p#&|VbttHiXKV%+1ox;w?jf#w-48+uqv(U^h2IBZXrmDP zCc+if9O`@QANS z34>q@W)KDBfCG!*4SRqF(x?V7fEgQr45HYJ=s*B7aEdDz4%k2mUl0RuKnTpJ0@2_N z-jI%qKo0CEjUF(4ZGj4QfCbYa2?ubE^>6@o5C+0nbnn0b3@`wLpbg}(2nI=z=|~RI zD2+!zJcJ<)m4F21dS%DU_So37znc@G%hA00S?H4#)tLl5h?iumzJq z03j)oZuyo6Sp-`d3zQ&oUnvmVAeLAVlgNM!=ztDlAPJ5T4R8sU(SQbi;0T=%3vNIM zcZm>r`I3E!40>P+{m>7R5CnUm288LBhpCuB&m + + + +Qhull code + + + + + +

    Up: Home page for Qhull +
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull code: Table of Contents +(please wait while loading)
    +Dn: Qhull functions, macros, and data +structures +

    + +
    + +

    [4-d cube] Qhull code

    + +

    This section discusses the code for Qhull.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull code: Table of +Contents

    + + + +
    + +

    »Reentrant Qhull

    + +

    Qhull-2015 introduces reentrant Qhull (libqhull_r). Reentrant Qhull uses a qhT* argument instead of global data structures. +The qhT* pointer is the first argument to most Qhull routines. It allows multiple instances of Qhull to run at the same time. +It simplifies the C++ interface to Qhull. + +

    New code should be written with libqhull_r. Existing users of libqhull should consider converting to libqhull_r. +Although libqhull will be supported indefinitely, improvements may not be implemented. +Reentrant qhull is 1-2% slower than non-reentrant qhull. + +

    Note: Reentrant Qhull is not thread safe. Do not invoke Qhull routines with the same qhT* pointer from multiple threads. + +

    »How to convert code to reentrant Qhull

    + +

    C++ users need to convert to libqhull_r. +The new C++ interface does a better, but not perfect, job of hiding Qhull's C data structures. +The previous C++ interface was unusual due to Qhull's global data structures. + +

    All other users should consider converting to libqhull_r. The conversion is straight forward. +The original conversion of libqhull to libqhull_r required thousands of changes, mostly global +search and replace. The first run of Qhull (unix_r.c) produced the same +output, and nearly the same log files, as the original (unix.c). + +

    Suggestions to help with conversion. +

      +
    • Compare qconvex_r.c with qconvex.c. Define a qhT object and a pointer it. The qhT* pointer is the first argument to most Qhull functions. +Clear qh_qh-<NOerrext before calling qh_initflags(). Invoke QHULL_LIB_CHECK to check for a compatible Qhull library. +
    • Compare user_eg2_r.c with user_eg2.c +
    • Compare user_eg_r.c with user_eg.c. If you use qhT before invoking qh_init_A, call qh_zero() to clear the qhT object. +user_eg_r.c includes multiple Qhull runs. +
    • Review user_eg3_r.cpp. As with the other programs, invoke QHULL_LIB_CHECK. +Simple C++ programs should compile as is. +
    • Compare QhullFacet.cpp with the same file in Qhull-2012.1. UsingLibQhull was replaced with the macro QH_TRY_() and 'qh_qh-<NOerrext= true'. +
    • For detailed notes on libqhull_r, see "libqhull_r (reentrant Qhull)" and "Source code changes for libqhull_r" in Changes.txt. +
    • For detailed notes on libqhullcpp, see "C++ interface" and following sections in Changes.txt. +
    • For regexps and conversion notes, see README_r.txt (unedited). +
    + +

    »Qhull on 64-bit computers

    + +

    Qhull compiles for 64-bit hosts. Since the size of a pointer on a 64-bit host is double the size on a 32-bit host, +memory consumption increases about 50% for simplicial facets and up-to 100% for non-simplicial facets. + +

    You can check memory consumption with option Ts. It includes the size of +each data structure: +

      +
    • 32-bit -- merge 24 ridge 20 vertex 28 facet 88 normal 24 ridge vertices 16 facet vertices or neighbors 20 +
    • 64-bit -- merge 32 ridge 32 vertex 48 facet 120 normal 32 ridge vertices 40 facet vertices or neighbors 48 +
    + +

    For Qhull 2015, the maximum identifier for ridges, vertices, and facets was increased +from 24-bits to 32-bits. This allows for larger convex hulls, but may increase the size of +the corresponding data structures. The sizes for Qhull 2012.1 were +

      +
    • 32-bit -- merge 24 ridge 16 vertex 24 facet 88 +
    • 64-bit -- merge 32 ridge 32 vertex 40 facet 120 +
    + +

    »Calling Qhull from +C++ programs

    + +

    Qhull 2015 uses reentrant Qhull for its C++ interface. If you used +the C++ interface from qhull 2012.1, you may need to adjust how you initialize and use +the Qhull classes. See How to convert code to reentrant Qhull. + +

    +Qhull's C++ interface allows you to explore the results of running Qhull. +It provides access to Qhull's data structures. +Most of the classes derive from the corresponding qhull data structure. +For example, QhullFacet is an instance of Qhull's facetT. +

    + +

    You can retain most of the data in Qhull and use the C++ interface to explore its results. +Each object contains a reference the Qhull's data structure (via QhullQh), making the C++ representation less memory efficient. +

    + +

    Besides using the C++ interface, you can also use libqhull_r directly. For example, +the FOREACHfacet_(...) macro will visit each facet in turn. +

    + +

    The C++ interface to Qhull is incomplete. You may need to extend the interface. +If so, you will need to understand Qhull's data structures and read the code. + +Example (c.f., user_eg3 eg-100). It prints the facets generated by Qhull. + +

    +    RboxPoints rbox;
    +    rbox.appendRandomPoints("100");
    +    Qhull qhull;
    +    qhull.runQhull("", rbox);
    +    QhullFacetList facets(qhull);
    +    cout<< facets;
    +
    + +

    +The C++ iterface for RboxPoints redefines the fprintf() calls +in rboxlib.c. Instead of writing its output to stdout, RboxPoints appends +the output to a std::vector. +The same technique may be used for calling Qhull from C++. +

    +
    • +Run Qhull with option 'Ta' to annotate the +output with qh_fprintf() identifiers. +
    • +Redefine qh_fprintf() for these identifiers. +
    • +See RboxPoints.cpp for an example. +
    +

    +Since the C++ interface uses reentrant Qhull, multiple threads may run Qhull at the same time. Each thread is +one run of Qhull. +

    + +

    +Do not have two threads accessing the same Qhull instance. Qhull is not thread-safe. +

    + +

    »CoordinateIterator

    +

    +A CoordinateIterator or ConstCoordinateIterator [RboxPoints.cpp] is a std::vector<realT>::iterator for Rbox and Qhull coordinates. +It is the result type of RboxPoints.coordinates(). +

    + +

    Qhull does not use CoordinateIterator for its data structures. A point in Qhull is an array of reals instead of a std::vector. +See QhullPoint. +

    + +

    »Qhull

    +

    +Qhull is the top-level class for running Qhull. +It initializes Qhull, runs the computation, and records errors. +It provides access to the global data structure QhullQh, +Qhull's facets, and vertices. +

    + +

    »QhullError

    +

    +QhullError is derived from std::exception. It reports errors from Qhull and captures the output to stderr. +

    + +

    +If error handling is not set up, Qhull exits with a code from 1 to 5. The codes are defined by +qh_ERR* in libqhull_r.h. The exit is via qh_exit() in usermem_r.c. +The C++ interface does not report the +captured output in QhullError. Call Qhull::setErrorStream to send output to cerr instead. +

    + +

    »QhullFacet

    +

    +A QhullFacet is a facet of the convex hull, a region of the Delaunay triangulation, a vertex of a Voronoi diagram, +or an intersection of the halfspace intersection about a point. +A QhullFacet has a set of QhullVertex, a set of QhullRidge, and +a set of neighboring QhullFacets. +

    + +

    »QhullFacetList

    +

    +A QhullFacetList is a linked list of QhullFacet. The result of Qhull.runQhull is a QhullFacetList stored +in QhullQh. +

    + +

    »QhullFacetSet

    +

    +A QhullFacetSet is a QhullSet of QhullFacet. QhullFacetSet may be ordered or unordered. The neighboring facets of a QhullFacet is a QhullFacetSet. +The neighbors of a QhullFacet is a QhullFacetSet. +The neighbors are ordered for simplicial facets, matching the opposite vertex of the facet. +

    + +

    »QhullIterator

    +

    +QhullIterator contains macros for defining Java-style iterator templates from a STL-style iterator template. +

    + +

    »QhullLinkedList

    +

    +A QhullLinkedLIst is a template for linked lists with next and previous pointers. +QhullFacetList and QhullVertexList are QhullLinkedLists. +

    + +

    »QhullPoint

    +

    +A QhullPoint is an array of point coordinates, typically doubles. The length of the array is QhullQh.hull_dim. +The identifier of a QhullPoint is its 0-based index from QhullQh.first_point followed by QhullQh.other_points. +

    + +

    »QhullPointSet

    +

    +A QhullPointSet is a QhullSet of QhullPoint. The QhullPointSet of a QhullFacet is its coplanar points. +

    + +

    »QhullQh

    +

    +QhullQh is the root of Qhull's data structure. +It contains initialized constants, sets, buffers, and variables. +It contains an array and a set of QhullPoint, +a list of QhullFacet, and a list of QhullVertex. +The points are the input to Qhull. The facets and vertices are the result of running Qhull. +

    + +

    +Qhull's functions access QhullQh through the global variable, qh_qh. +The global data structures, qh_stat and qh_mem, record statistics and manage memory respectively. +

    + +

    »QhullRidge

    + +

    +A QhullRidge represents the edge between two QhullFacet's. +It is always simplicial with qh.hull_dim-1 QhullVertex)'s. +

    + +

    »QhullRidgeSet

    + +

    +A QhullRidgeSet is a QhullSet of QhullRidge. Each QhullFacet contains a QhullRidgeSet. +

    + +

    »QhullSet

    + +

    +A QhullSet is a set of pointers to objects. QhullSets may be ordered or unordered. They are the core data structure for Qhull. +

    + +

    »QhullVertex

    + +

    +A QhullVertex is a vertex of the convex hull. A simplicial QhullFacet has qh.hull_dim-1 vertices. A QhullVertex contains a QhullPoint. +It may list its neighboring QhullFacet's. +

    + +

    »QhullVertexList

    + +

    +A QhullVertexList is a QhullLinkedList of QhullVertex. +The global data structure, QhullQh contains a QhullVertexList of all +the vertices. +

    + +

    »QhullVertexSet

    + +

    +A QhullVertexSet is a QhullSet of QhullVertex. +The QhullVertexSet of a QhullFacet is the vertices of the facet. It is +ordered for simplicial facets and unordered for non-simplicial facets. +

    + +

    »RboxPoints

    + +

    +RboxPoints is a std::vector of point coordinates (QhullPoint). +It's iterator is CoordinateIterator. +

    +

    +RboxPoints.appendRandomPoints() appends points from a variety of distributions such as uniformly distributed within a cube and random points on a sphere. +It can also append a cube's vertices or specific points. +

    + +

    »Cpp questions for Qhull

    + +Developing C++ code requires many conventions, idioms, and technical details. +The following questions have either +mystified the author or do not have a clear answer. See also +C++ and Perl Guidelines. +and FIXUP notes in the code. +Please add notes to Qhull Wiki. + +
      +
    • FIXUP QH11028 Should return reference, but get reference to temporary +
      iterator Coordinates::operator++() { return iterator(++i); }
      +
    • size() as size_t, size_type, or int +
    • Should all containers have a reserve()? +
    • Qhull.feasiblePoint interface +
    • How to avoid copy constructor while logging, maybeThrowQhullMessage() +
    • How to configure Qhull output. Trace and results should go to stdout/stderr +
    • Qhull and RboxPoints messaging. e.g., ~Qhull, hasQhullMessage(). Rename them as QhullErrorMessage? +
    • How to add additional output to an error message, e.g., qh_setprint +
    • Is idx the best name for an index? It's rather cryptic, but BSD strings.h defines index(). +
    • Qhull::feasiblePoint Qhull::useOutputStream as field or getter? +
    • Define virtual functions for user customization of Qhull (e.g., qh_fprintf, qh_memfree,etc.) +
    • Figure out RoadError::global_log. clearQhullMessage currently clearGlobalLog +
    • Should the false QhullFacet be NULL or empty? e.g., QhullFacet::tricoplanarOwner() and QhullFacetSet::end() +
    • Should output format for floats be predefined (qh_REAL_1, 2.2g, 10.7g) or as currently set for stream +
    • Should cout << !point.defined() be blank or 'undefined' +
    • Infinite point as !defined() +
    • qlist and qlinkedlist define pointer, reference, size_type, difference_type, const_pointer, const_reference for the class but not for iterator and const_iterator + vector.h --
      reference operator[](difference_type _Off) const
      +
    • When forwarding an implementation is base() an approriate name (e.g., Coordinates::iterator::base() as std::vector::iterator). +
    • When forwarding an implementation, does not work "returning address of temporary" +
    • Also --, +=, and -= +
      iterator       &operator++() { return iterator(i++); }
      +
    • if vector inheritance is bad, is QhullVertexSet OK? +
    • Should QhullPointSet define pointer and reference data types? +
    + +

    »Calling Qhull from +C programs

    + +

    Warning: Qhull was not designed for calling from C +programs. You may find the C++ interface easier to use. +You will need to understand the data structures and read the code. +Most users will find it easier to call Qhull as an external +command. + +

    For examples of calling Qhull, see GNU Octave's +computational geometry code, +and Qhull's +user_eg_r.c, +user_eg2_r.c, and +user_r.c. To see how Qhull calls its library, read +unix_r.c, +qconvex.c, +qdelaun.c, +qhalf.c, and +qvoronoi.c. The '*_r.c' files are reentrant, otherwise they are non-reentrant. +Either version may be used. New code should use reentrant Qhull. + +

    See Reentrant Qhull functions, macros, and data +structures for internal documentation of Qhull. The +documentation provides an overview and index. To use the library +you will need to read and understand the code. For most users, it +is better to write data to a file, call the qhull program, and +read the results from the output file.

    + +

    If you use non-reentrant Qhull, be aware of the macros "qh" +and "qhstat", e.g., "qh hull_dim". They are +defined in libqhull.h. They allow the global data +structures to be pre-allocated (faster access) or dynamically +allocated (allows multiple copies).

    + +

    Qhull's Makefile produces a library, libqhull_r.a, +for inclusion in your programs. First review libqhull_r.h. +This defines the data structures used by Qhull and provides +prototypes for the top-level functions. +Most users will only need libqhull_r.h in their programs. For +example, the Qhull program is defined with libqhull_r.h and unix_r.c. +To access all functions, use qhull_ra.h. Include the file +with "#include <libqhull_r/qhull_ra.h>". This +avoids potential name conflicts.

    + +

    If you use the Qhull library, you are on your own as far as +bugs go. Start with small examples for which you know the output. +If you get a bug, try to duplicate it with the Qhull program. The +'Tc' option will catch many problems +as they occur. When an error occurs, use 'T4 TPn' +to trace from the last point added to the hull. Compare your +trace with the trace output from the Qhull program.

    + +

    Errors in the Qhull library are more likely than errors in the +Qhull program. These are usually due to feature interactions that +do not occur in the Qhull program. Please report all errors that +you find in the Qhull library. Please include suggestions for +improvement.

    + +

    »How to avoid exit(), fprintf(), stderr, and stdout

    + +

    Qhull sends output to qh.fout and errors, log messages, and summaries to qh.ferr. qh.fout is normally +stdout and qh.ferr is stderr. qh.fout may be redefined by option 'TO' or the caller. qh.ferr may be redirected to qh.fout by option 'Tz'.

    + +

    Qhull does not use stderr, stdout, fprintf(), or exit() directly.

    + +

    Qhull reports errors via qh_errexit() by writting a message to qh.ferr and invoking longjmp(). +This returns the caller to the corresponding setjmp() (c.f., QH_TRY_ in QhullQh.h). If +qh_errexit() is not available, Qhull functions call qh_exit(). qh_exit() normally calls exit(), +but may be redefined by the user. An example is +libqhullcpp/usermem_r-cpp.cpp. It redefines qh_exit() as a 'throw'.

    + +

    If qh_meminit() or qh_new_qhull() is called with ferr==NULL, then they set ferr to stderr. +Otherwise the Qhull libraries use qh->ferr and qh->qhmem.ferr for error output.

    + +

    If an error occurs before qh->ferr is initialized, Qhull invokes qh_fprintf_stderr(). The user +may redefine this function along with qh_exit(), qh_malloc(), and qh_free(). + +

    The Qhull libraries write output via qh_fprintf() [userprintf_r.c]. Otherwise, the Qhull +libraries do not use stdout, fprintf(), or printf(). Like qh_exit(), the user may redefine +qh_fprintf().

    + +

    »sets and quick memory +allocation

    + +

    You can use mem_r.c and qset_r.c individually. Mem_r.c +implements quick-fit memory allocation. It is faster than +malloc/free in applications that allocate and deallocate lots of +memory.

    + +

    Qset_r.c implements sets and related collections. It's +the inner loop of Qhull, so speed is more important than +abstraction. Set iteration is particularly fast. qset_r.c +just includes the functions needed for Qhull.

    + +

    »Delaunay triangulations +and point indices

    + +

    Here some unchecked code to print the point indices of each +Delaunay triangle. Use option 'QJ' if you want to avoid +non-simplicial facets. Note that upper Delaunay regions are +skipped. These facets correspond to the furthest-site Delaunay +triangulation.

    + +
    +
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  FORALLfacets {
    +    if (!facet->upperdelaunay) {
    +      printf ("%d", qh_setsize (facet->vertices);
    +      FOREACHvertex_(facet->vertices)
    +        printf (" %d", qh_pointid (vertex->point));
    +      printf ("\n");
    +    }
    +  }
    +
    +
    +
    + +

    »locate a facet with +qh_findbestfacet()

    + +

    The routine qh_findbestfacet in poly2_r.c is +particularly useful. It uses a directed search to locate the +facet that is furthest below a point. For Delaunay +triangulations, this facet is the Delaunay triangle that contains +the lifted point. For convex hulls, the distance of a point to +the convex hull is either the distance to this facet or the +distance to a subface of the facet.

    + +
    +

    Warning: If triangulated output ('Qt') and +the best facet is triangulated, qh_findbestfacet() returns one of +the corresponding 'tricoplanar' facets. The actual best facet may be a different +tricoplanar facet. +

    +See qh_nearvertex() in poly2.c for sample code to visit each +tricoplanar facet. To identify the correct tricoplanar facet, +see Devillers, et. al., ['01] +and Mucke, et al ['96]. If you +implement this test in general dimension, please notify +qhull@qhull.org. +

    + +

    qh_findbestfacet performs an exhaustive search if its directed +search returns a facet that is above the point. This occurs when +the point is inside the hull or if the curvature of the convex +hull is less than the curvature of a sphere centered at the point +(e.g., a point near a lens-shaped convex hull). When the later +occurs, the distance function is bimodal and a directed search +may return a facet on the far side of the convex hull.

    + +

    Algorithms that retain the previously constructed hulls +usually avoid an exhaustive search for the best facet. You may +use a hierarchical decomposition of the convex hull [Dobkin and +Kirkpatrick '90].

    + +

    To use qh_findbestfacet with Delaunay triangulations, lift the +point to a paraboloid by summing the squares of its coordinates +(see qh_setdelaunay in geom2_r.c). Do not scale the input with +options 'Qbk', 'QBk', 'QbB' or 'Qbb'. See Mucke, et al ['96] for a good point location +algorithm.

    + +

    The intersection of a ray with the convex hull may be found by +locating the facet closest to a distant point on the ray. +Intersecting the ray with the facet's hyperplane gives a new +point to test.

    + +

    »on-line construction with +qh_addpoint()

    + +

    The Qhull library may be used for the on-line construction of +convex hulls, Delaunay triangulations, and halfspace +intersections about a point. It may be slower than implementations that retain +intermediate convex hulls (e.g., Clarkson's hull +program). These implementations always use a directed search. +For the on-line construction of convex hulls and halfspace +intersections, Qhull may use an exhaustive search +(qh_findbestfacet).

    + +

    You may use qh_findbestfacet and qh_addpoint (libqhull.c) to add a point to +a convex hull. Do not modify the point's coordinates since +qh_addpoint does not make a copy of the coordinates. For Delaunay +triangulations, you need to lift the point to a paraboloid by +summing the squares of the coordinates (see qh_setdelaunay in +geom2.c). Do not scale the input with options 'Qbk', 'QBk', 'QbB' +or 'Qbb'. Do not deallocate the point's coordinates. You need to +provide a facet that is below the point (qh_findbestfacet). +

    + +

    You can not delete points. Another limitation is that Qhull +uses the initial set of points to determine the maximum roundoff +error (via the upper and lower bounds for each coordinate).

    + +

    For many applications, it is better to rebuild the hull from +scratch for each new point. This is especially true if the point +set is small or if many points are added at a time.

    + +

    Calling qh_addpoint from your program may be slower than +recomputing the convex hull with qh_qhull. This is especially +true if the added points are not appended to the qh first_point +array. In this case, Qhull must search a set to determine a +point's ID. [R. Weber]

    + +

    See user_eg.c for examples of the on-line construction of +convex hulls, Delaunay triangulations, and halfspace +intersections. The outline is:

    + +
    +
    +initialize qhull with an initial set of points
    +qh_qhull();
    +
    +for each additional point p
    +   append p to the end of the point array or allocate p separately
    +   lift p to the paraboloid by calling qh_setdelaunay
    +   facet= qh_findbestfacet (p, !qh_ALL, &bestdist, &isoutside);
    +   if (isoutside)
    +      if (!qh_addpoint (point, facet, False))
    +         break;  /* user requested an early exit with 'TVn' or 'TCn' */
    +
    +call qh_check_maxout() to compute outer planes
    +terminate qhull
    +
    + +

    »Constrained +Delaunay triangulation

    + +

    With a fair amount of work, Qhull is suitable for constrained +Delaunay triangulation. See Shewchuk, ACM Symposium on +Computational Geometry, Minneapolis 1998.

    + +

    Here's a quick way to add a constraint to a Delaunay +triangulation: subdivide the constraint into pieces shorter than +the minimum feature separation. You will need an independent +check of the constraint in the output since the minimum feature +separation may be incorrect. [H. Geron]

    + +

    »Tricoplanar facets and option 'Qt'

    + +

    Option 'Qt' triangulates non-simplicial +facets (e.g., a square facet in 3-d or a cubical facet in 4-d). +All facets share the same apex (i.e., the first vertex in facet->vertices). +For each triangulated facet, Qhull +sets facet->tricoplanar true and copies facet->center, facet->normal, facet->offset, and facet->maxoutside. One of +the facets owns facet->normal; its facet->keepcentrum is true. +If facet->isarea is false, facet->triowner points to the owning +facet. + +

    Qhull sets facet->degenerate if the facet's vertices belong +to the same ridge of the non-simplicial facet. + +

    To visit each tricoplanar facet of a non-simplicial facet, +either visit all neighbors of the apex or recursively visit +all neighbors of a tricoplanar facet. The tricoplanar facets +will have the same facet->center.

    + +

    See qh_detvridge for an example of ignoring tricoplanar facets.

    + +

    »Voronoi vertices of a +region

    + +

    The following code iterates over all Voronoi vertices for each +Voronoi region. Qhull computes Voronoi vertices from the convex +hull that corresponds to a Delaunay triangulation. An input site +corresponds to a vertex of the convex hull and a Voronoi vertex +corresponds to an adjacent facet. A facet is +"upperdelaunay" if it corresponds to a Voronoi vertex +"at-infinity". Qhull uses qh_printvoronoi in io.c +for 'qvoronoi o'

    + +
    +
    +/* please review this code for correctness */
    +qh_setvoronoi_all();
    +FORALLvertices {
    +   site_id = qh_pointid (vertex->point);
    +   if (qh hull_dim == 3)
    +      qh_order_vertexneighbors(vertex);
    +   infinity_seen = 0;
    +   FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay) {
    +        if (!infinity_seen) {
    +          infinity_seen = 1;
    +          ... process a Voronoi vertex "at infinity" ...
    +        }
    +      }else {
    +        voronoi_vertex = neighbor->center;
    +        ... your code goes here ...
    +      }
    +   }
    +}
    +
    +
    + +

    »Voronoi vertices of a +ridge

    + +

    Qhull uses qh_printvdiagram() in io.c to print the ridges of a +Voronoi diagram for option 'Fv'. +The helper function qh_eachvoronoi() does the real work. It calls +the callback 'printvridge' for each ridge of the Voronoi diagram. +

    + +

    You may call qh_printvdiagram2(), qh_eachvoronoi(), or +qh_eachvoronoi_all() with your own function. If you do not need +the total number of ridges, you can skip the first call to +qh_printvdiagram2(). See qh_printvridge() and qh_printvnorm() in +io.c for examples.

    + +

    »vertex neighbors of +a vertex

    + +

    To visit all of the vertices that share an edge with a vertex: +

    + +
      +
    • Generate neighbors for each vertex with + qh_vertexneighbors in poly2.c.
    • +
    • For simplicial facets, visit the vertices of each + neighbor
    • +
    • For non-simplicial facets,
        +
      • Generate ridges for neighbors with qh_makeridges + in merge.c.
      • +
      • Generate ridges for a vertex with qh_vertexridges + in merge.c.
      • +
      • Visit the vertices of these ridges.
      • +
      +
    • +
    + +

    For non-simplicial facets, the ridges form a simplicial +decomposition of the (d-2)-faces between each pair of facets -- +if you need 1-faces, you probably need to generate the full face +graph of the convex hull.

    + +

    »Performance of +Qhull

    + +

    Empirically, Qhull's performance is balanced in the sense that +the average case happens on average. This may always be true if +the precision of the input is limited to at most O(log n) +bits. Empirically, the maximum number of vertices occurs at the +end of constructing the hull.

    + +

    Let n be the number of input points, v be the +number of output vertices, and f_v be the maximum number +of facets for a convex hull of v vertices. If both +conditions hold, Qhull runs in O(n log v) in 2-d and 3-d +and O(n f_v/v) otherwise. The function f_v +increases rapidly with dimension. It is O(v^floor(d/2) / +floor(d/2)!).

    + +

    The time complexity for merging is unknown. Options 'C-0' and 'Qx' +(defaults) handle precision problems due to floating-point +arithmetic. They are optimized for simplicial outputs.

    + +

    When running large data sets, you should monitor Qhull's +performance with the 'TFn' option. +The time per facet is approximately constant. In high-d with many +merged facets, the size of the ridge sets grows rapidly. For +example the product of 8-d simplices contains 18 facets and +500,000 ridges. This will increase the time needed per facet.

    + +

    As dimension increases, the number of facets and ridges in a +convex hull grows rapidly for the same number of vertices. For +example, the convex hull of 300 cospherical points in 6-d has +30,000 facets.

    + +

    If Qhull appears to stop processing facets, check the memory +usage of Qhull. If more than 5-10% of Qhull is in virtual memory, +its performance will degrade rapidly.

    + +

    When building hulls in 20-d and higher, you can follow the +progress of Qhull with option 'T1'. +It reports each major event in processing a point.

    + +

    To reduce memory requirements, recompile Qhull for +single-precision reals (REALfloat in user.h). +Single-precision does not work with joggle ('QJ'). Check qh_MEMalign in user.h +and the match between free list sizes and data structure sizes +(see the end of the statistics report from 'Ts'). If free list sizes do not match, +you may be able to use a smaller qh_MEMalign. Setting +qh_COMPUTEfurthest saves a small amount of memory, as does +clearing qh_MAXoutside (both in user.h).

    + +

    Shewchuk is working on a 3-d version of his triangle +program. It is optimized for 3-d simplicial Delaunay triangulation +and uses less memory than Qhull.

    + +

    To reduce the size of the Qhull executable, consider +qh_NOtrace and qh_KEEPstatistics 0 in user.h. By +changing user.c you can also remove the input/output +code in io.c. If you don't need facet merging, then +version 1.01 of Qhull is much smaller. It contains some bugs that +prevent Qhull from initializing in simple test cases. It is +slower in high dimensions.

    + +

    The precision options, 'Vn', 'Wn', 'Un'. +'A-n', 'C-n', +'An', 'Cn', +and 'Qx', may have large effects on +Qhull performance. You will need to experiment to find the best +combination for your application.

    + +

    The verify option ('Tv') checks +every point after the hull is complete. If facet merging is used, +it checks that every point is inside every facet. This can take a +very long time if there are many points and many facets. You can +interrupt the verify without losing your output. If facet merging +is not used and there are many points and facets, Qhull uses a +directed search instead of an exhaustive search. This should be +fast enough for most point sets. Directed search is not used for +facet merging because directed search was already used for +updating the facets' outer planes.

    + +

    The check-frequently option ('Tc') +becomes expensive as the dimension increases. The verify option +('Tv') performs many of the same +checks before outputting the results.

    + +

    Options 'Q0' (no pre-merging), 'Q3' (no checks for redundant vertices), +'Q5' (no updates for outer planes), +and 'Q8' (no near-interior points) +increase Qhull's speed. The corresponding operations may not be +needed in your application.

    + +

    In 2-d and 3-d, a partial hull may be faster to produce. +Option 'QgGn' only builds facets +visible to point n. Option 'QgVn' +only builds facets that contain point n. In higher-dimensions, +this does not reduce the number of facets.

    + +

    User.h includes a number of performance-related +constants. Changes may improve Qhull performance on your data +sets. To understand their effect on performance, you will need to +read the corresponding code.

    + +

    GNU gprof reports that the dominate cost for 3-d +convex hull of cosperical points is qh_distplane(), mainly called +from qh_findbestnew(). The dominate cost for 3-d Delaunay triangulation +is creating new facets in qh_addpoint(), while qh_distplane() remains +the most expensive function. + +

    +

    »Enhancements to Qhull

    + +

    There are many ways in which Qhull can be improved.

    + +
    +[Jan 2016] Suggestions
    +------------
    +To do for a future verson of Qhull
    + - Add a post-merge pass for Delaunay slivers.  Merge into a neighbor with a circumsphere that includes the opposite point. [M. Treacy]
    + - Add a merge pass before cone creation to remove duplicate subridges between horizon facets
    + - Option to add a bounding box for Delaunay triangulations, e,g., nearly coincident points
    + - Report error when rbox Cn,r,m does not produce points (e.g., 'r' distributions)
    + - Rescale output to match 'QbB' on input [J. Metz, 1/30/2014 12:21p]
    + - Run through valgrind
    + - Notes to compgeom on conformant triangulation and Voronoi volume
    + - Git: Create signed tags for Qhull versions
    + - Implement weighted Delaunay triangulation and weighted Voronoi diagram [A. Liebscher]
    +   e.g., Sugihara, "Three-dimensional convex hull as a fruitful source of diagrams," Theoretical Computer Science, 2000, 235:325-337
    + - testqset: test qh_setdelnth and move-to-front
    + - Makefile: Re-review gcc/g++ warnings.  OK in 2011.
    + - Break up -Wextra into its components or figure out how to override -Wunused-but-set-variable
    +   unused-but-set-variable is reporting incorrectly.  All instances are annotated.
    + - CMakelists.txt:  Why are files duplicated for cmake build
    + - CMakeLists.txt: configure the 'ctest' target
    + - The size of maxpoints in qh_maxsimplex should be d+3 unique points to help avoid QH6154
    +
    + - Can countT be defined as 'int', 'unsigned int', or 64-bit int?
    +   countT is currently defined as 'int' in qset_r.h
    +   Vertex ID and ridge ID perhaps should be countT, They are currently 'unsigned'
    +   Check use of 'int' vs. countT in all cpp code
    +   Check use of 'int' vs. countT in all c code
    +   qset_r.h defines countT -- duplicates code in user_r.h -- need to add to qset.h/user.h
    +   countT -1 used as a flag in Coordinates.mid(), QhullFacet->id()
    +   Also QhullPoints indexOf and lastIndexOf
    +   Also QhullPointSet indexOf and lastIndexOf
    +   Coordinates.indexOf assumes countT is signed (from end)
    +   Coordinates.lastIndexOf assumes countT is signed (from end)
    +   All error messages with countT are wrong, convert to int?
    +   RboxPoints.qh_fprintf_rbox, etc. message 9393 assumes countT but may be int, va_arg(args, countT);  Need to split
    +
    +------------
    +To do for a furture version of the C++ interface
    + - Fix C++ memory leak in user_eg3 [M. Sandim]
    + - Document C++ using Doxygen conventions (//! and //!<)
    + - Should Qhull manage the output formats for doubles?  QH11010 user_r.h defines qh_REAL_1 as %6.8g
    + - Allocate memory for QhullSet using Qhull.qhmem.  Create default constructors for QhullVertexSet etc.  Also mid() etc.
    + - Add interior point for automatic translation?
    + - Add hasNext() to all next() iterators (e.g., QhullVertex)
    + - Add defineAs() to each object
    + - Write a program with concurrent Qhull
    + - Write QhullStat and QhullStat_test
    + - Add QList and vector instance of facetT*, etc.
    + - Generalize QhullPointSetIterator
    + - qh-code.htm: Document changes to C++ interface.
    +      Organize C++ documentation into collection classes, etc.
    + - Review all C++ classes and C++ tests
    + - QhullVertexSet uses QhullSetBase::referenceSetT() to free it's memory.   Probably needed elsewhere
    + - The Boost Graph Library provides C++ classes for graph data structures. It may help
    +   enhance Qhull's C++ interface [Dr. Dobb's 9/00 p. 29-38; OOPSLA '99 p. 399-414].
    +
    +[Jan 2010] Suggestions
    + - Generate vcproj from qtpro files
    +   cd qtpro && qmake -spec win32-msvc2005 -tp vc -recursive
    +   sed -i 's/C\:\/bash\/local\/qhull\/qtpro\///' qhull-all.sln
    +   Change qhullcpp to libqhull.dll
    +   Allow both builds on same host (keep /tmp separate)
    + - Make distribution -- remove tmp, news, .git, leftovers from project, change CRLF
    +     search for 2010.1, Dates
    +     qhulltest --all added to output
    +     Add md5sum
    +     Add test of user_eg3, etc.
    + - C++ class for access to statistics, accumulate vs. add
    + - Add dialog box to RoadError-- a virtual function?
    + - Option 'Gt' does not make visible all facets of the mesh example, rbox 32 M1,0,1 | qhull d Gt
    + - Option to select bounded Voronoi regions [A. Uzunovic]
    + - Merge small volume boundary cells into unbounded regions [Dominik Szczerba]
    + - Postmerge with merge options
    + - Add const to C code
    + - Add modify operators and MutablePointCoordinateIterator to PointCoordinates
    + - Add Qtest::toString() functions for QhullPoint and others.  QByteArray and qstrdup()
    + - Fix option Qt for conformant triangulations of merged facets
    + - Investigate flipped facet -- rbox 100 s D3 t1263080158 | qhull R1e-3 Tcv Qc
    + - Add doc comments to c++ code
    + - Measure performance of Qhull, seconds per point by dimension
    + - Report potential wraparound of 64-bit ints -- e.g., a large set or points
    +
    +Documentation
    +- Qhull::addPoint().  Problems with qh_findbestfacet and otherpoints see
    +   qh-code.htm#inc on-line construction with qh_addpoint()
    +- How to handle 64-bit possible loss of data.  WARN64, ptr_intT, size_t/int
    +- Show custom of qh_fprintf
    +- grep 'qh_mem ' x | sort | awk '{ print $2; }' | uniq -c | grep -vE ' (2|4|6|8|10|12|14|16|20|64|162)[^0-9]'
    +- qtpro/qhulltest contains .pro and Makefile.  Remove Makefiles by setting shadow directory to ../../tmp/projectname
    +- Rules for use of qh_qh and multi processes
    +    UsingQhull
    +    errorIfAnotherUser
    +    ~QhullPoints() needs ownership of qh_qh
    +    Does !qh_pointer work?
    +    When is qh_qh required?  Minimize the time.
    +   qhmem, qhstat.ferr
    +   qhull_inuse==1 when qhull globals active [not useful?]
    +   rbox_inuse==1 when rbox globals active
    +   - Multithreaded -- call largest dimension for infinityPoint() and origin()
    + - Better documentation for qhmem totshort, freesize, etc.
    + - how to change .h, .c, and .cpp to text/html.  OK in Opera
    + - QhullVertex.dimension() is not quite correct, epensive
    + - Check globalAngleEpsilon
    + - Deprecate save_qhull()
    +
    +[Dec 2003] Here is a partial list:
    + - fix finddelaunay() in user_eg.c for tricoplanar facets
    + - write a BGL, C++ interface to Qhull
    +     http://www.boost.org/libs/graph/doc/table_of_contents.html
    + - change qh_save_qhull to swap the qhT structure instead of using pointers
    + - change error handling and tracing to be independent of 'qh ferr'
    + - determine the maximum width for a given set of parameters
    + - prove that directed search locates all coplanar facets
    + - in high-d merging, can a loop of facets become disconnected?
    + - find a way to improve inner hulls in 5-d and higher
    + - determine the best policy for facet visibility ('Vn')
    + - determine the limitations of 'Qg'
    +
    +Precision improvements:
    + - For 'Qt', resolve cross-linked, butterfly ridges.
    +     May allow retriangulation in qh_addpoint().
    + - for Delaunay triangulations ('d' or 'v') under joggled input ('QJ'),
    +     remove vertical facets whose lowest vertex may be coplanar with convex hull
    + - review use of 'Qbb' with 'd QJ'.  Is MAXabs_coord better than MAXwidth?
    + - check Sugihara and Iri's better in-sphere test [Canadian
    +     Conf. on Comp. Geo., 1989; Univ. of Tokyo RMI 89-05]
    + - replace centrum with center of mass and facet area
    + - handle numeric overflow in qh_normalize and elsewhere
    + - merge flipped facets into non-flipped neighbors.
    +     currently they merge into best neighbor (appears ok)
    + - determine min norm for Cramer's rule (qh_sethyperplane_det).  It looks high.
    + - improve facet width for very narrow distributions
    +
    +New features:
    + - implement Matlab's tsearch() using Qhull
    + - compute volume of Voronoi regions.  You need to determine the dual face
    +   graph in all dimensions [see Clarkson's hull program]
    + - compute alpha shapes [see Clarkson's hull program]
    + - implement deletion of Delaunay vertices
    +      see Devillers, ACM Symposium on Computational Geometry, Minneapolis 1999.
    + - compute largest empty circle [see O'Rourke, chapter 5.5.3] [Hase]
    + - list redundant (i.e., coincident) vertices [Spitz]
    + - implement Mucke, et al, ['96] for point location in Delaunay triangulations
    + - implement convex hull of moving points
    + - implement constrained Delaunay diagrams
    +      see Shewchuk, ACM Symposium on Computational Geometry, Minneapolis 1998.
    + - estimate outer volume of hull
    + - automatically determine lower dimensional hulls
    + - allow "color" data for input points
    +      need to insert a coordinate for Delaunay triangulations
    +
    +Input/output improvements:
    + - Support the VTK Visualization Toolkit, http://www.kitware.com/vtk.html
    + - generate output data array for Qhull library [Gautier]
    + - need improved DOS window with screen fonts, scrollbar, cut/paste
    + - generate Geomview output for Voronoi ridges and unbounded rays
    + - generate Geomview output for halfspace intersection
    + - generate Geomview display of furthest-site Voronoi diagram
    + - use 'GDn' to view 5-d facets in 4-d
    + - convert Geomview output for other 3-d viewers
    + - add interactive output option to avoid recomputing a hull
    + - orient vertex neighbors for 'Fv' in 3-d and 2-d
    + - track total number of ridges for summary and logging
    +
    +Performance improvements:
    + - optimize Qhull for 2-d Delaunay triangulations
    + -   use O'Rourke's '94 vertex->duplicate_edge
    + -   add bucketing
    + -   better to specialize all of the code (ca. 2-3x faster w/o merging)
    + - use updated LU decomposition to speed up hyperplane construction
    + -        [Gill et al. 1974, Math. Comp. 28:505-35]
    + - construct hyperplanes from the corresponding horizon/visible facets
    + - for merging in high d, do not use vertex->neighbors
    +
    +
    + +

    Please let us know about your applications and improvements.

    + +
    + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull code: Table of Contents
    +Dn: Qhull functions, macros, and data +structures + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see changes.txt

    + + diff --git a/xs/src/qhull/html/qh-eg.htm b/xs/src/qhull/html/qh-eg.htm new file mode 100644 index 0000000000..a08f0d13f4 --- /dev/null +++ b/xs/src/qhull/html/qh-eg.htm @@ -0,0 +1,693 @@ + + + + +Examples of Qhull + + + + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull examples: Table of Contents (please wait +while loading)
    + +


    + +

    [halfspace] Examples of Qhull

    + +

    This section of the Qhull manual will introduce you to Qhull +and its options. Each example is a file for viewing with Geomview. You will need to +use a Unix computer with a copy of Geomview. +

    +If you are not running Unix, you can view pictures +for some of the examples. To understand Qhull without Geomview, try the +examples in Programs and +Programs/input. You can also try small +examples that you compute by hand. Use rbox +to generate examples. +

    +To generate the Geomview examples, execute the shell script eg/q_eg. +It uses rbox. The shell script eg/q_egtest generates +test examples, and eg/q_test exercises the code. If you +find yourself viewing the inside of a 3-d example, use Geomview's +normalization option on the 'obscure' menu.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull examples: Table of +Contents

    + + + +
    + + +
    + +

    »2-d and 3-d examples

    + +

    »rbox c D3 | qconvex G +>eg.01.cube

    + +

    The first example is a cube in 3-d. The color of each facet +indicates its normal. For example, normal [0,0,1] along the Z +axis is (r=0.5, g=0.5, b=1.0). With the 'Dn' option in rbox, +you can generate hypercubes in any dimension. Above 7-d the +number of intermediate facets grows rapidly. Use 'TFn' to track qconvex's progress. Note +that each facet is a square that qconvex merged from coplanar +triangles.

    + +

    »rbox c d G3.0 | qconvex G +>eg.02.diamond.cube

    + +

    The second example is a cube plus a diamond ('d') scaled by rbox's +'G' option. In higher dimensions, diamonds are much simpler than +hypercubes.

    + +

    »rbox s 100 D3 | qconvex G +>eg.03.sphere

    + +

    The rbox s option generates random points and +projects them to the d-sphere. All points should be on the convex +hull. Notice that random points look more clustered than you +might expect. You can get a smoother distribution by merging +facets and printing the vertices, e.g., rbox 1000 s | qconvex +A-0.95 p | qconvex G >eg.99.

    + +

    »rbox s 100 D2 | qconvex G +>eg.04.circle

    + +

    In 2-d, there are many ways to generate a convex hull. One of +the earliest algorithms, and one of the fastest, is the 2-d +Quickhull algorithm [c.f., Preparata & Shamos '85]. It was the model for +Qhull.

    + +

    »rbox 10 l | qconvex G +>eg.05.spiral

    + +

    One rotation of a spiral.

    + +

    »rbox 1000 D2 | qconvex C-0.03 +Qc Gapcv >eg.06.merge.square

    + +

    This demonstrates how Qhull handles precision errors. Option 'C-0.03' requires a clearly convex angle +between adjacent facets. Otherwise, Qhull merges the facets.

    + +

    This is the convex hull of random points in a square. The +facets have thickness because they must be outside all points and +must include their vertices. The colored lines represent the +original points and the spheres represent the vertices. Floating +in the middle of each facet is the centrum. Each centrum is at +least 0.03 below the planes of its neighbors. This guarantees +that the facets are convex.

    + +

    »rbox 1000 D3 | qconvex G +>eg.07.box

    + +

    Here's the same distribution but in 3-d with Qhull handling +machine roundoff errors. Note the large number of facets.

    + +

    »rbox c G0.4 s 500 | qconvex G +>eg.08a.cube.sphere

    + +

    The sphere is just barely poking out of the cube. Try the same +distribution with randomization turned on ('Qr'). This turns Qhull into a +randomized incremental algorithm. To compare Qhull and +randomization, look at the number of hyperplanes created and the +number of points partitioned. Don't compare CPU times since Qhull's +implementation of randomization is inefficient. The number of +hyperplanes and partitionings indicate the dominant costs for +Qhull. With randomization, you'll notice that the number of +facets created is larger than before. This is especially true as +you increase the number of points. It is because the randomized +algorithm builds most of the sphere before it adds the cube's +vertices.

    + +

    »rbox d G0.6 s 500 | qconvex G +>eg.08b.diamond.sphere

    + +

    This is a combination of the diamond distribution and the +sphere.

    + +

    »rbox 100 L3 G0.5 s | qconvex +G >eg.09.lens

    + +

    Each half of the lens distribution lies on a sphere of radius +three. A directed search for the furthest facet below a point +(e.g., qh_findbest in geom.c) may fail if started from +an arbitrary facet. For example, if the first facet is on the +opposite side of the lens, a directed search will report that the +point is inside the convex hull even though it is outside. This +problem occurs whenever the curvature of the convex hull is less +than a sphere centered at the test point.

    + +

    To prevent this problem, Qhull does not use directed search +all the time. When Qhull processes a point on the edge of the +lens, it partitions the remaining points with an exhaustive +search instead of a directed search (see qh_findbestnew in geom2.c). +

    + +

    »How Qhull adds a point

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex Ga QG0 >eg.10a.sphere.visible

    + +

    The next 4 examples show how Qhull adds a point. The point +[0.5,0.5,0.5] is at one corner of the bounding box. Qhull adds a +point using the beneath-beyond algorithm. First Qhull finds all +of the facets that are visible from the point. Qhull will replace +these facets with new facets.

    + +

    »rbox 100 s +P0.5,0.5,0.5|qconvex Ga QG-0 >eg.10b.sphere.beyond

    + +

    These are the facets that are not visible from the point. +Qhull will keep these facets.

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex PG Ga QG0 >eg.10c.sphere.horizon

    + +

    These facets are the horizon facets; they border the visible +facets. The inside edges are the horizon ridges. Each horizon +ridge will form the base for a new facet.

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex Ga QV0 PgG >eg.10d.sphere.cone

    + +

    This is the cone of points from the new point to the horizon +facets. Try combining this image with eg.10c.sphere.horizon +and eg.10a.sphere.visible. +

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qconvex Ga >eg.10e.sphere.new

    + +

    This is the convex hull after [0.5,0.5,0.5] has been added. +Note that in actual practice, the above sequence would never +happen. Unlike the randomized algorithms, Qhull always processes +a point that is furthest in an outside set. A point like +[0.5,0.5,0.5] would be one of the first points processed.

    + +

    »rbox 100 s P0.5,0.5,0.5 | +qhull Ga QV0g Q0 >eg.14.sphere.corner

    + +

    The 'QVn', 'QGn ' and 'Pdk' +options define good facets for Qhull. In this case 'QV0' defines the 0'th point +[0.5,0.5,0.5] as the good vertex, and 'Qg' +tells Qhull to only build facets that might be part of a good +facet. This technique reduces output size in low dimensions. It +does not work with facet merging.

    + +

    »Triangulated output or joggled input

    + +

    »rbox 500 W0 | qconvex QR0 Qc Gvp >eg.15a.surface

    + +

    This is the convex hull of 500 points on the surface of +a cube. Note the large, non-simplicial facet for each face. +Qhull merges non-convex facets. + +

    If the facets were not merged, Qhull +would report precision problems. For example, turn off facet merging +with option 'Q0'. Qhull may report concave +facets, flipped facets, or other precision errors: +

    +rbox 500 W0 | qhull QR0 Q0 +
    + +

    +

    »rbox 500 W0 | qconvex QR0 Qt Qc Gvp >eg.15b.triangle

    + +

    Like the previous examples, this is the convex hull of 500 points on the +surface of a cube. Option 'Qt' triangulates the +non-simplicial facets. Triangulated output is +particularly helpful for Delaunay triangulations. + +

    +

    »rbox 500 W0 | qconvex QR0 QJ5e-2 Qc Gvp >eg.15c.joggle

    + +

    This is the convex hull of 500 joggled points on the surface of +a cube. The option 'QJ5e-2' +sets a very large joggle to make the effect visible. Notice +that all of the facets are triangles. If you rotate the cube, +you'll see red-yellow lines for coplanar points. +

    +With option 'QJ', Qhull joggles the +input to avoid precision problems. It adds a small random number +to each input coordinate. If a precision +error occurs, it increases the joggle and tries again. It repeats +this process until no precision problems occur. +

    +Joggled input is a simple solution to precision problems in +computational geometry. Qhull can also merge facets to handle +precision problems. See Merged facets or joggled input. + +

    »Delaunay and Voronoi diagrams

    + +

    »qdelaunay Qt +<eg.data.17 GnraD2 >eg.17a.delaunay.2

    + +

    +The input file, eg.data.17, consists of a square, 15 random +points within the outside half of the square, and 6 co-circular +points centered on the square. + +

    The Delaunay triangulation is the triangulation with empty +circumcircles. The input for this example is unusual because it +includes six co-circular points. Every triangular subset of these +points has the same circumcircle. Option 'Qt' +triangulates the co-circular facet.

    + +

    »qdelaunay <eg.data.17 +GnraD2 >eg.17b.delaunay.2i

    + +

    This is the same example without triangulated output ('Qt'). qdelaunay +merges the non-unique Delaunay triangles into a hexagon.

    + +

    »qdelaunay <eg.data.17 +Ga >eg.17c.delaunay.2-3

    + +

    This is how Qhull generated both diagrams. Use Geomview's +'obscure' menu to turn off normalization, and Geomview's +'cameras' menu to turn off perspective. Then load this object +with one of the previous diagrams.

    + +

    The points are lifted to a paraboloid by summing the squares +of each coordinate. These are the light blue points. Then the +convex hull is taken. That's what you see here. If you look up +the Z-axis, you'll see that points and edges coincide.

    + +

    »qvoronoi QJ +<eg.data.17 Gna >eg.17d.voronoi.2

    + +

    The Voronoi diagram is the dual of the Delaunay triangulation. +Here you see the original sites and the Voronoi vertices. +Notice the each +vertex is equidistant from three sites. The edges indicate the +Voronoi region for a site. Qhull does not draw the unbounded +edges. Instead, it draws extra edges to close the unbounded +Voronoi regions. You may find it helpful to enclose the input +points in a square. You can compute the unbounded +rays from option 'Fo'. +

    + +

    Instead +of triangulated output ('Qt'), this +example uses joggled input ('QJ'). +Normally, you should use neither 'QJ' nor 'Qt' for Voronoi diagrams. + +

    »qvoronoi <eg.data.17 +Gna >eg.17e.voronoi.2i

    + +

    This looks the same as the previous diagrams, but take a look +at the data. Run 'qvoronoi p <eg/eg.data.17'. This prints +the Voronoi vertices. + +

    With 'QJ', there are four nearly identical Voronoi vertices +within 10^-11 of the origin. Option 'QJ' joggled the input. After the joggle, +the cocircular +input sites are no longer cocircular. The corresponding Voronoi vertices are +similar but not identical. + +

    This example does not use options 'Qt' or 'QJ'. The cocircular +input sites define one Voronoi vertex near the origin.

    + +

    Option 'Qt' would triangulate the corresponding Delaunay region into +four triangles. Each triangle is assigned the same Voronoi vertex.

    + +

    » rbox c G0.1 d | +qdelaunay Gt Qz <eg.17f.delaunay.3

    + +

    This is the 3-d Delaunay triangulation of a small cube inside +a prism. Since the outside ridges are transparent, it shows the +interior of the outermost facets. If you slice open the +triangulation with Geomview's ginsu, you will see that the innermost +facet is a cube. Note the use of 'Qz' +to add a point "at infinity". This avoids a degenerate +input due to cospherical points.

    + +

    »rbox 10 D2 d | qdelaunay +Qu G >eg.18a.furthest.2-3

    + +

    The furthest-site Voronoi diagram contains Voronoi regions for +points that are furthest from an input site. It is the +dual of the furthest-site Delaunay triangulation. You can +determine the furthest-site Delaunay triangulation from the +convex hull of the lifted points (eg.17c.delaunay.2-3). +The upper convex hull (blue) generates the furthest-site Delaunay +triangulation.

    + +

    »rbox 10 D2 d | qdelaunay +Qu Pd2 G >eg.18b.furthest-up.2-3

    + +

    This is the upper convex hull of the preceding example. The +furthest-site Delaunay triangulation is the projection of the +upper convex hull back to the input points. The furthest-site +Voronoi vertices are the circumcenters of the furthest-site +Delaunay triangles.

    + +

    »rbox 10 D2 d | qvoronoi +Qu Gv >eg.18c.furthest.2

    + +

    This shows an incomplete furthest-site Voronoi diagram. It +only shows regions with more than two vertices. The regions are +artificially truncated. The actual regions are unbounded. You can +print the regions' vertices with 'qvoronoi Qu o'.

    + +

    Use Geomview's 'obscure' menu to turn off normalization, and +Geomview's 'cameras' menu to turn off perspective. Then load this +with the upper convex hull.

    + +

    »rbox 10 D3 | qvoronoi QV5 +p | qconvex G >eg.19.voronoi.region.3

    + +

    This shows the Voronoi region for input site 5 of a 3-d +Voronoi diagram.

    + +

    »Facet merging for imprecision

    + +

    »rbox r s 20 Z1 G0.2 | +qconvex G >eg.20.cone

    + +

    There are two things unusual about this cone. +One is the large flat disk at one end and the other is the +rectangles about the middle. That's how the points were +generated, and if those points were exact, this is the correct +hull. But rbox used floating point arithmetic to +generate the data. So the precise convex hull should have been +triangles instead of rectangles. By requiring convexity, Qhull +has recovered the original design.

    + +

    »rbox 200 s | qhull Q0 +R0.01 Gav Po >eg.21a.roundoff.errors

    + +

    This is the convex hull of 200 cospherical points with +precision errors ignored ('Q0'). To +demonstrate the effect of roundoff error, we've added a random +perturbation ('R0.01') to every +distance and hyperplane calculation. Qhull, like all other convex +hull algorithms with floating point arithmetic, makes +inconsistent decisions and generates wildly wrong results. In +this case, one or more facets are flipped over. These facets have +the wrong color. You can also turn on 'normals' in Geomview's +appearances menu and turn off 'facing normals'. There should be +some white lines pointing in the wrong direction. These +correspond to flipped facets.

    + +

    Different machines may not produce this picture. If your +machine generated a long error message, decrease the number of +points or the random perturbation ('R0.01'). +If it did not report flipped facets, increase the number of +points or perturbation.

    + +

    »rbox 200 s | qconvex Qc +R0.01 Gpav >eg.21b.roundoff.fixed

    + +

    Qhull handles the random perturbations and returns an +imprecise sphere. +In this case, the output is a weak approximation to the points. +This is because a random perturbation of 'R0.01 ' is equivalent to losing all but +1.8 digits of precision. The outer planes float above the points +because Qhull needs to allow for the maximum roundoff error.

    +

    +If you start with a smaller random perturbation, you +can use joggle ('QJn') to avoid +precision problems. You need to set n significantly +larger than the random perturbation. For example, try +'rbox 200 s | qconvex Qc R1e-4 QJ1e-1'. + +

    »rbox 1000 s| qconvex C0.01 +Qc Gcrp >eg.22a.merge.sphere.01

    + +

    »rbox 1000 s| qconvex +C-0.01 Qc Gcrp >eg.22b.merge.sphere.-01

    + +

    »rbox 1000 s| qconvex C0.05 +Qc Gcrpv >eg.22c.merge.sphere.05

    + +

    »rbox 1000 s| qconvex +C-0.05 Qc Gcrpv >eg.22d.merge.sphere.-05

    + +

    The next four examples compare post-merging and pre-merging ('Cn' vs. 'C-n'). +Qhull uses '-' as a flag to indicate pre-merging.

    + +

    Post-merging happens after the convex hull is built. During +post-merging, Qhull repeatedly merges an independent set of +non-convex facets. For a given set of parameters, the result is +about as good as one can hope for.

    + +

    Pre-merging does the same thing as post-merging, except that +it happens after adding each point to the convex hull. With +pre-merging, Qhull guarantees a convex hull, but the facets are +wider than those from post-merging. If a pre-merge option is not +specified, Qhull handles machine round-off errors.

    + +

    You may see coplanar points appearing slightly outside +the facets of the last example. This is becomes Geomview moves +line segments forward toward the viewer. You can avoid the +effect by setting 'lines closer' to '0' in Geomview's camera menu. + +

    »rbox 1000 | qconvex s +Gcprvah C0.1 Qc >eg.23.merge.cube

    + +

    Here's the 3-d imprecise cube with all of the Geomview +options. There's spheres for the vertices, radii for the coplanar +points, dots for the interior points, hyperplane intersections, +centrums, and inner and outer planes. The radii are shorter than +the spheres because this uses post-merging ('C0.1') +instead of pre-merging. + +

    »4-d objects

    + +

    With Qhull and Geomview you can develop an intuitive sense of +4-d surfaces. When you get into trouble, think of viewing the +surface of a 3-d sphere in a 2-d plane.

    + +

    »rbox 5000 D4 | qconvex s GD0v +Pd0:0.5 C-0.02 C0.1 >eg.24.merge.cube.4d-in-3d

    + +

    Here's one facet of the imprecise cube in 4-d. It's projected +into 3-d (the 'GDn' option drops +dimension n). Each ridge consists of two triangles between this +facet and the neighboring facet. In this case, Geomview displays +the topological ridges, i.e., as triangles between three +vertices. That is why the cube looks lopsided.

    + +

    »rbox 5000 D4 | qconvex s +C-0.02 C0.1 Gh >eg.30.4d.merge.cube

    + +

    Here +is the equivalent in 4-d of the imprecise square +and imprecise cube. It's the imprecise convex +hull of 5000 random points in a hypercube. It's a full 4-d object +so Geomview's ginsu does not work. If you view it in +Geomview, you'll be inside the hypercube. To view 4-d objects +directly, either load the 4dview module or the ndview +module. For 4dview, you must have started Geomview +in the same directory as the object. For ndview, +initialize a set of windows with the prefab menu, and load the +object through Geomview. The 4dview module includes an +option for slicing along any hyperplane. If you do this in the x, +y, or z plane, you'll see the inside of a hypercube.

    + +

    The 'Gh' option prints the +geometric intersections between adjacent facets. Note the strong +convexity constraint for post-merging ('C0.1'). +It deletes the small facets.

    + +

    »rbox 20 D3 | qdelaunay G +>eg.31.4d.delaunay

    + +

    The Delaunay triangulation of 3-d sites corresponds to a 4-d +convex hull. You can't see 4-d directly but each facet is a 3-d +object that you can project to 3-d. This is exactly the same as +projecting a 2-d facet of a soccer ball onto a plane.

    + +

    Here we see all of the facets together. You can use Geomview's +ndview to look at the object from several directions. +Try turning on edges in the appearance menu. You'll notice that +some edges seem to disappear. That's because the object is +actually two sets of overlapping facets.

    + +

    You can slice the object apart using Geomview's 4dview. +With 4dview, try slicing along the w axis to get a +single set of facets and then slice along the x axis to look +inside. Another interesting picture is to slice away the equator +in the w dimension.

    + +

    »rbox 30 s D4 | qconvex s G +Pd0d1d2D3

    + +

    This is the positive octant of the convex hull of 30 4-d +points. When looking at 4-d, it's easier to look at just a few +facets at once. If you picked a facet that was directly above +you, then that facet looks exactly the same in 3-d as it looks in +4-d. If you pick several facets, then you need to imagine that +the space of a facet is rotated relative to its neighbors. Try +Geomview's ndview on this example.

    + +

    »Halfspace intersections

    + +

    »rbox 10 r s Z1 G0.3 | +qconvex G >eg.33a.cone

    + +

    »rbox 10 r s Z1 G0.3 | +qconvex FV n | qhalf G >eg.33b.cone.dual

    + +

    »rbox 10 r s Z1 G0.3 | +qconvex FV n | qhalf Fp | qconvex G >eg.33c.cone.halfspace

    + +

    These examples illustrate halfspace intersection. The first +picture is the convex hull of two 20-gons plus an apex. The +second picture is the dual of the first. Try loading both +at once. Each vertex of the second picture corresponds to a facet +or halfspace of the first. The vertices with four edges +correspond to a facet with four neighbors. Similarly the facets +correspond to vertices. A facet's normal coefficients divided by +its negative offset is the vertice's coordinates. The coordinates +are the intersection of the original halfspaces.

    + +

    The third picture shows how Qhull can go back and forth +between equivalent representations. It starts with a cone, +generates the halfspaces that define each facet of the cone, and +then intersects these halfspaces. Except for roundoff error, the +third picture is a duplicate of the first.

    + +
    + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull examples: Table of Contents (please wait +while loading)
    + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-faq.htm b/xs/src/qhull/html/qh-faq.htm new file mode 100644 index 0000000000..feda544a75 --- /dev/null +++ b/xs/src/qhull/html/qh-faq.htm @@ -0,0 +1,1547 @@ + + + + + + +Qhull FAQ + + + + + +

    Up: Home page for Qhull +(http://www.qhull.org)
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: FAQ: Table of Contents (please +wait while loading)
    + +


    + +

    [4-d cube] Frequently Asked Questions about Qhull

    +

    If your question does not appear here, see:

    + + + +

    Qhull is a general dimension code for computing convex hulls, +Delaunay triangulations, halfspace intersections about a point, +Voronoi diagrams, furthest-site Delaunay triangulations, and +furthest-site Voronoi diagrams. These structures have +applications in science, engineering, statistics, and +mathematics. For a detailed introduction, see O'Rourke ['94], Computational Geometry in C. +

    + +

    There are separate programs for each application of +Qhull. These programs disable experimental and inappropriate +options. If you prefer, you may use Qhull directly. All programs +run the same code. + +

    Version 3.1 added triangulated output ('Qt'). +It should be used for Delaunay triangulations instead of +using joggled input ('QJ'). + +

    Brad Barber, Arlington MA, +2010/01/09

    + +

    Copyright © 1998-2015 C.B. Barber

    + +
    + +

    »FAQ: Table of Contents

    + +

    Within each category, the most recently asked questions are +first. +

      +
    • Startup questions
        +
      • How do I run Qhull from Windows? +
      • How do I enter points for Qhull? +
      • How do I learn to use Qhull?
      • +
      +
    • Convex hull questions
        +
      • How do I report just the area and volume of a + convex hull? +
      • Why are there extra points in a 4-d or higher + convex hull? +
      • How do I report duplicate + vertices?
      • +
      +
    • Delaunay triangulation questions
        +
      • How do I get rid of nearly flat Delaunay + triangles? +
      • How do I find the Delaunay triangle or Voronoi + region that is closest to a point? + +
      • How do I compute the Delaunay triangulation of a + non-convex object? + +
      • How do I mesh a volume from a set of triangulated + surface points? + +
      • Can Qhull produce a triangular mesh for an + object? + +
      • For 3-d Delaunay triangulations, how do I + report the triangles of each tetrahedron? + +
      • How do I construct a 3-d Delaunay triangulation? +
      • How do I get the triangles for a 2-d Delaunay + triangulation and the vertices of its Voronoi diagram? +
      • Can Qhull triangulate a + hundred 16-d points?
      • +
      + +
    • Voronoi diagram questions
        +
      • See also "Delaunay diagram questions". Qhull computes the Voronoi diagram from the Delaunay triagulation. +
      • How do I compute the volume of a Voronoi region? +
      • How do I get the radii of the empty + spheres for each Voronoi vertex? + +
      • What is the Voronoi diagram of a square? + +
      • How do I construct the Voronoi diagram of + cospherical points? +
      • Can Qhull compute the unbounded rays of the + Voronoi diagram? +
      +
    • Approximation questions
        +
      • How do I approximate data + with a simplex?
      • +
      +
    • Halfspace questions
        +
      • How do I compute the + intersection of halfspaces with Qhull?
      • +
      +
    • Qhull library questions
        +
      • Is Qhull available for Mathematica, Matlab, or + Maple? + +
      • Why are there too few ridges? +
      • Can Qhull use coordinates without placing them in + a data file? +
      • How large are Qhull's data structures? +
      • Can Qhull construct convex hulls and Delaunay + triangulations one point at a time? +
      • How do I visit the ridges of a Delaunay + triangulation? +
      • How do I visit the Delaunay facets? +
      • When is a point outside or inside a facet? +
      • How do I find the facet that is closest to a + point? +
      • How do I find the Delaunay triangle or Voronoi + region that is closest to a point? +
      • How do I list the vertices? +
      • How do I test code that uses the Qhull library? +
      • When I compute a plane + equation from a facet, I sometimes get an + outward-pointing normal and sometimes an + inward-pointing normal
      • +
      +
    • +
    + +
    + +

    »Startup questions

    + +

    »How do I run Qhull +from Windows?

    + +

    Qhull is a console program. You will first need a command window +(i.e., a "command prompt"). You can double click on +'eg\Qhull-go.bat'.

    + +
      +
    • Type 'qconvex', 'qdelaunay', 'qhalf', 'qvoronoi, + 'qhull', and 'rbox' for a synopsis of each program. + +
    • Type 'rbox c D2 | qconvex s i' to compute the + convex hull of a square. + +
    • Type 'rbox c D2 | qconvex s i TO results.txt' to + write the results to the file 'results.txt'. A summary is still printed on + the the console. + +
    • Type 'rbox c D2' to see the input format for + qconvex. + +
    • Type 'qconvex < data.txt s i TO results.txt' to + read input data from 'data.txt'. + +
    • If you want to enter data by hand, type 'qconvex s i TO + results.txt' to read input data from the console. Type in + the numbers and end with a ctrl-D.
    • +
    + +

    »How do I enter +points for Qhull?

    + +

    Qhull takes its data from standard input. For example, create +a file named 'data.txt' with the following contents:

    + +
    +
    +2  #sample 2-d input
    +5  #number of points
    +1 2  #coordinates of points
    +-1.1 3
    +3 2.2
    +4 5
    +-10 -10
    +
    +
    + +

    Then call qconvex with 'qconvex < data.txt'. It will print a +summary of the convex hull. Use 'qconvex < data.txt o' to print +the vertices and edges. See also input +format.

    + +

    You can generate sample data with rbox, e.g., 'rbox 10' +generates 10 random points in 3-d. Use a pipe ('|') to run rbox +and qhull together, e.g.,

    + +
    +

    rbox c | qconvex o

    +
    + +

    computes the convex hull of a cube.

    + +

    »How do I learn to +use Qhull?

    + +

    First read:

    + + + +

    Look at Qhull's on-line documentation:

    + +
      +
    • 'qconvex' gives a synopsis of qconvex and its options + +
    • 'rbox' lists all of the options for generating point + sets +
    • 'qconvex - | more' lists the options for qconvex +
    • 'qconvex .' gives a concise list of options +
    • 'qdelaunay', 'qhalf', 'qvoronoi', and 'qhull' also have a synopsis and option list
    • +
    + +

    Then try out the Qhull programs on small examples.

    + +
      +
    • 'rbox c' lists the vertices of a cube +
    • 'rbox c | qconvex' is the convex hull of a cube +
    • 'rbox c | qconvex o' lists the vertices and facets of + a cube +
    • 'rbox c | qconvex Qt o' triangulates the cube +
    • 'rbox c | qconvex QJ o' joggles the input and + triangulates the cube +
    • 'rbox c D2 | qconvex' generates the convex hull of a + square +
    • 'rbox c D4 | qconvex' generates the convex hull of a + hypercube +
    • 'rbox 6 s D2 | qconvex p Fx' lists 6 random points in + a circle and lists the vertices of their convex hull in order +
    • 'rbox c D2 c G2 | qdelaunay' computes the Delaunay + triangulation of two embedded squares. It merges the cospherical facets. +
    • 'rbox c D2 c G2 | qdelaunay Qt' computes the Delaunay + triangulation of two embedded squares. It triangulates the cospherical facets. +
    • 'rbox c D2 c G2 | qvoronoi o' computes the + corresponding Voronoi vertices and regions. +
    • 'rbox c D2 c G2 | qvoronio Fv' shows the Voronoi diagram + for the previous example. Each line is one edge of the + diagram. The first number is 4, the next two numbers list + a pair of input sites, and the last two numbers list the + corresponding pair of Voronoi vertices.
    • +
    + +

    Install Geomview +if you are running SGI Irix, Solaris, SunOS, Linux, HP, IBM +RS/6000, DEC Alpha, or Next. You can then visualize the output of +Qhull. Qhull comes with Geomview examples. +

    + +

    Then try Qhull with a small example of your application. Work +out the results by hand. Then experiment with Qhull's options to +find the ones that you need.

    + +

    You will need to decide how Qhull should handle precision +problems. It can triangulate the output ('Qt'), joggle the input ('QJ'), or merge facets (the default).

    + +
      +
    • With joggle, Qhull produces simplicial (i.e., + triangular) output by joggling the input. After joggle, + no points are cocircular or cospherical. +
    • With facet merging, Qhull produces a better + approximation and does not modify the input. +
    • With triangulated output, Qhull merges facets and triangulates + the result.
    • +
    • See Merged facets or joggled input.
    • +
    + +
    +

    »Convex hull questions

    + +

    »How do I report just the area + and volume of a convex hull?

    + +Use option 'FS'. For example, + +
    +C:\qhull>rbox 10 | qconvex FS
    +0
    +2 2.192915621644613 0.2027867899638665
    +
    +C:\qhull>rbox 10 | qconvex FA
    +
    +Convex hull of 10 points in 3-d:
    +
    +  Number of vertices: 10
    +  Number of facets: 16
    +
    +Statistics for: RBOX 10 | QCONVEX FA
    +
    +  Number of points processed: 10
    +  Number of hyperplanes created: 28
    +  Number of distance tests for qhull: 44
    +  CPU seconds to compute hull (after input):  0
    +  Total facet area:   2.1929156
    +  Total volume:       0.20278679
    +
    + +

    »Why are there extra +points in a 4-d or higher convex hull?

    + +

    You may see extra points if you use options 'i' or 'Ft' + without using triangulated output ('Qt'). +The extra points occur when a facet is non-simplicial (i.e., a +facet with more than d vertices). For example, Qhull +reports the following for one facet of the convex hull of a hypercube. +Option 'Pd0:0.5' returns the facet along the positive-x axis:

    + +
    +
    +rbox c D4 | qconvex i Pd0:0.5
    +12
    +17 13 14 15
    +17 13 12 14
    +17 11 13 15
    +17 14 11 15
    +17 10 11 14
    +17 14 12 8
    +17 12 13 8
    +17 10 14 8
    +17 11 10 8
    +17 13 9 8
    +17 9 11 8
    +17 11 9 13
    +
    +
    + +

    The 4-d hypercube has 16 vertices; so point "17" was +added by qconvex. Qhull adds the point in order to report a +simplicial decomposition of the facet. The point corresponds to +the "centrum" which Qhull computes to test for +convexity.

    + +

    Triangulate the output ('Qt') to avoid the extra points. +Since the hypercube is 4-d, each simplicial facet is a tetrahedron. +

    +
    +C:\qhull3.1>rbox c D4 | qconvex i Pd0:0.5 Qt
    +9
    +9 13 14 15
    +12 9 13 14
    +9 11 13 15
    +11 9 14 15
    +9 10 11 14
    +12 9 14 8
    +9 12 13 8
    +9 10 14 8
    +10 9 11 8
    +
    +
    + +

    Use the 'Fv' option to print the +vertices of simplicial and non-simplicial facets. For example, +here is the same hypercube facet with option 'Fv' instead of 'i': +

    + +
    +
    +C:\qhull>rbox c D4 | qconvex Pd0:0.5 Fv
    +1
    +8 9 10 12 11 13 14 15 8
    +
    +
    + +

    The coordinates of the extra point are printed with the 'Ft' option.

    + +
    +
    +rbox c D4 | qconvex Pd0:0.5 Ft
    +4
    +17 12 3
    +  -0.5   -0.5   -0.5   -0.5
    +  -0.5   -0.5   -0.5    0.5
    +  -0.5   -0.5    0.5   -0.5
    +  -0.5   -0.5    0.5    0.5
    +  -0.5    0.5   -0.5   -0.5
    +  -0.5    0.5   -0.5    0.5
    +  -0.5    0.5    0.5   -0.5
    +  -0.5    0.5    0.5    0.5
    +   0.5   -0.5   -0.5   -0.5
    +   0.5   -0.5   -0.5    0.5
    +   0.5   -0.5    0.5   -0.5
    +   0.5   -0.5    0.5    0.5
    +   0.5    0.5   -0.5   -0.5
    +   0.5    0.5   -0.5    0.5
    +   0.5    0.5    0.5   -0.5
    +   0.5    0.5    0.5    0.5
    +   0.5      0      0      0
    +4 16 13 14 15
    +4 16 13 12 14
    +4 16 11 13 15
    +4 16 14 11 15
    +4 16 10 11 14
    +4 16 14 12 8
    +4 16 12 13 8
    +4 16 10 14 8
    +4 16 11 10 8
    +4 16 13 9 8
    +4 16 9 11 8
    +4 16 11 9 13
    +
    +
    + +

    »How do I report +duplicate vertices?

    + +

    There's no direct way. You can use option +'FP' to +report the distance to the nearest vertex for coplanar input +points. Select the minimum distance for a duplicated vertex, and +locate all input sites less than this distance.

    + +

    For Delaunay triangulations, all coplanar points are nearly +incident to a vertex. If you want a report of coincident input +sites, do not use option 'QJ'. By +adding a small random quantity to each input coordinate, it +prevents coincident input sites.

    + +
    +

    »Delaunay triangulation questions

    + +

    »How do I get rid of +nearly flat Delaunay triangles?

    + +

    Nearly flat triangles occur when boundary points are nearly +collinear or coplanar. They also occur for nearly coincident +points. Both events can easily occur when using joggle. For example +(rbox 10 W0 D2 | qdelaunay QJ Fa) lists the areas of the Delaunay +triangles of 10 points on the boundary of a square. Some of +these triangles are nearly flat. This occurs when one point +is joggled inside of two other points. In this case, nearly flat +triangles do not occur with triangulated output (rbox 10 W0 D2 | qdelaunay Qt Fa). + + +

    Another example, (rbox c P0 P0 D2 | qdelaunay QJ Fa), computes the +areas of the Delaunay triangles for the unit square and two +instances of the origin. Four of the triangles have an area +of 0.25 while two have an area of 2.0e-11. The later are due to +the duplicated origin. With triangulated output (rbox c P0 P0 D2 | qdelaunay Qt Fa) +there are four triangles of equal area. + +

    Nearly flat triangles also occur without using joggle. For +example, (rbox c P0 P0,0.4999999999 | qdelaunay Fa), computes +the areas of the Delaunay triangles for the unit square, +a nearly collinear point, and the origin. One triangle has an +area of 3.3e-11. + +

    Unfortunately, none of Qhull's merging options remove nearly +flat Delaunay triangles due to nearly collinear or coplanar boundary +points. +The merging options concern the empty circumsphere +property of Delaunay triangles. This is independent of the area of +the Delaunay triangles. Qhull does handle nearly coincident points. + +

    If you are calling Qhull from a program, you can merge slivers into an adjacent facet. +In d dimensions with simplicial facets (e.g., from 'Qt'), each facet has +d+1 neighbors. Each neighbor shares d vertices of the facet's d+1 vertices. Let the +other vertex be the opposite vertex. For each neighboring facet, if its circumsphere +includes the opposite.vertex, the two facets can be merged. [M. Treacy] + +

    You can handle collinear or coplanar boundary points by +enclosing the points in a box. For example, +(rbox c P0 P0,0.4999999999 c G1 | qdelaunay Fa), surrounds the +previous points with [(1,1), (1,-1), (-1,-1), (-1, 1)]. +Its Delaunay triangulation does not include a +nearly flat triangle. The box also simplifies the graphical +output from Qhull. + +

    Without joggle, Qhull lists coincident points as "coplanar" +points. For example, (rbox c P0 P0 D2 | qdelaunay Fa), ignores +the duplicated origin and lists four triangles of size 0.25. +Use 'Fc' to list the coincident points (e.g., +rbox c P0 P0 D2 | qdelaunay Fc). + +

    There is no easy way to determine coincident points with joggle. +Joggle removes all coincident, cocircular, and cospherical points +before running Qhull. Instead use facet merging (the default) +or triangulated output ('Qt'). + +

    »How do I compute +the Delaunay triangulation of a non-convex object?

    + +

    A similar question is +"How do I mesh a volume from a set of triangulated surface points?" + +

    This is an instance of the constrained Delaunay Triangulation +problem. Qhull does not handle constraints. The boundary of the +Delaunay triangulation is always convex. But if the input set +contains enough points, the triangulation will include the +boundary. The number of points needed depends on the input. + +

    Shewchuk has developed a theory of constrained Delaunay triangulations. +See his +paper at the +1998 Computational Geometry Conference. Using these ideas, constraints +could be added to Qhull. They would have many applications. + +

    There is a large literature on mesh generation and many commercial +offerings. For pointers see +Owen's International Meshing Roundtable +and Schneiders' +Finite Element Mesh Generation page.

    + +

    »Can Qhull +produce a triangular mesh for an object?

    + +

    Yes for convex objects, no for non-convex objects. For +non-convex objects, it triangulates the concavities. Unless the +object has many points on its surface, triangles may cross the +surface.

    + +

    »For 3-d Delaunay +triangulations, how do I report the triangles of each +tetrahedron?

    + +

    For points in general position, a 3-d Delaunay triangulation +generates tetrahedron. Each face of a tetrahedron is a triangle. +For example, the 3-d Delaunay triangulation of random points on +the surface of a cube, is a cellular structure of tetrahedron.

    + +

    Use triangulated output ('qdelaunay Qt i') or joggled input ('qdelaunay QJ i') +to generate the Delaunay triangulation. +Option 'i' reports each tetrahedron. The triangles are +every combination of 3 vertices. Each triangle is a +"ridge" of the Delaunay triangulation.

    + +

    For example,

    + +
    +        rbox 10 | qdelaunay Qt i
    +        14
    +        9 5 8 7
    +        0 9 8 7
    +        5 3 8 7
    +        3 0 8 7
    +        5 4 8 1
    +        4 6 8 1
    +        2 9 5 8
    +        4 2 5 8
    +        4 2 9 5
    +        6 2 4 8
    +        9 2 0 8
    +        2 6 0 8
    +        2 4 9 1
    +        2 6 4 1
    +
    + +

    is the Delaunay triangulation of 10 random points. Ridge 9-5-8 +occurs twice. Once for tetrahedron 9 5 8 7 and the other for +tetrahedron 2 9 5 8.

    + +

    You can also use the Qhull library to generate the triangles. +See "How do I visit the ridges of a +Delaunay triangulation?"

    + +

    »How do I construct a +3-d Delaunay triangulation?

    + +

    For 3-d Delaunay triangulations with cospherical input sites, +use triangulated output ('Qt') or +joggled input ('QJ'). Otherwise +option 'i' will +triangulate non-simplicial facets by adding a point to the facet. + +

    If you want non-simplicial output for cospherical sites, use +option +'Fv' or 'o'. +For option 'o', ignore the last coordinate. It is the lifted +coordinate for the corresponding convex hull in 4-d. + +

    The following example is a cube +inside a tetrahedron. The 8-vertex facet is the cube. Ignore the +last coordinates.

    + +
    +
    +C:\qhull>rbox r y c G0.1 | qdelaunay Fv
    +4
    +12 20 44
    +   0.5      0      0 0.3055555555555555
    +   0    0.5      0 0.3055555555555555
    +   0      0    0.5 0.3055555555555555
    +  -0.5   -0.5   -0.5 0.9999999999999999
    +  -0.1   -0.1   -0.1 -6.938893903907228e-018
    +  -0.1   -0.1    0.1 -6.938893903907228e-018
    +  -0.1    0.1   -0.1 -6.938893903907228e-018
    +  -0.1    0.1    0.1 -6.938893903907228e-018
    +   0.1   -0.1   -0.1 -6.938893903907228e-018
    +   0.1   -0.1    0.1 -6.938893903907228e-018
    +   0.1    0.1   -0.1 -6.938893903907228e-018
    +   0.1    0.1    0.1 -6.938893903907228e-018
    +4 2 11 1 0
    +4 10 1 0 3
    +4 11 10 1 0
    +4 2 9 0 3
    +4 9 11 2 0
    +4 7 2 1 3
    +4 11 7 2 1
    +4 8 10 0 3
    +4 9 8 0 3
    +5 8 9 10 11 0
    +4 10 6 1 3
    +4 6 7 1 3
    +5 6 8 10 4 3
    +5 6 7 10 11 1
    +4 5 9 2 3
    +4 7 5 2 3
    +5 5 8 9 4 3
    +5 5 6 7 4 3
    +8 5 6 8 7 9 10 11 4
    +5 5 7 9 11 2
    +
    +
    + +

    If you want simplicial output use options +'Qt i' or +'QJ i', e.g., +

    + +
    +
    +rbox r y c G0.1 | qdelaunay Qt i
    +31
    +2 11 1 0
    +11 10 1 0
    +9 11 2 0
    +11 7 2 1
    +8 10 0 3
    +9 8 0 3
    +10 6 1 3
    +6 7 1 3
    +5 9 2 3
    +7 5 2 3
    +9 8 10 11
    +8 10 11 0
    +9 8 11 0
    +6 8 10 4
    +8 6 10 3
    +6 8 4 3
    +6 7 10 11
    +10 6 11 1
    +6 7 11 1
    +8 5 4 3
    +5 8 9 3
    +5 6 4 3
    +6 5 7 3
    +5 9 10 11
    +8 5 9 10
    +7 5 10 11
    +5 6 7 10
    +8 5 10 4
    +5 6 10 4
    +5 9 11 2
    +7 5 11 2
    +
    +
    + +

    »How do I get the +triangles for a 2-d Delaunay triangulation and the vertices of +its Voronoi diagram?

    + +

    To compute the Delaunay triangles indexed by the indices of +the input sites, use

    + +
    +

    rbox 10 D2 | qdelaunay Qt i

    +
    + +

    To compute the Voronoi vertices and the Voronoi region for +each input site, use

    + +
    +

    rbox 10 D2 | qvoronoi o

    +
    + +

    To compute each edge ("ridge") of the Voronoi +diagram for each pair of adjacent input sites, use

    + +
    +

    rbox 10 D2 | qvoronoi Fv

    +
    + +

    To compute the area and volume of the Voronoi region for input site 5 (site 0 is the first one), +use

    + +
    +

    rbox 10 D2 | qvoronoi QV5 p | qconvex s FS

    +
    + +

    To compute the lines ("hyperplanes") that define the +Voronoi region for input site 5, use

    + +
    +

    rbox 10 D2 | qvoronoi QV5 p | qconvex n

    +
    +or +
    +

    rbox 10 D2 | qvoronoi QV5 Fi Fo

    +
    + +

    To list the extreme points of the input sites use

    + +
    +

    rbox 10 D2 | qdelaunay Fx

    +
    + +

    You will get the same point ids with

    + +
    +

    rbox 10 D2 | qconvex Fx

    +
    + +

    »Can Qhull triangulate +a hundred 16-d points?

    + +

    No. This is an immense structure. A triangulation of 19, 16-d +points has 43 simplices. If you add one point at a time, the +triangulation increased as follows: 43, 189, 523, 1289, 2830, +6071, 11410, 20487. The last triangulation for 26 points used 13 +megabytes of memory. When Qhull uses virtual memory, it becomes +too slow to use.

    + +
    +

    »Voronoi +diagram questions

    + +

    »How do I compute the volume of a Voronoi region?

    + +

    For each Voronoi region, compute the convex hull of the region's Voronoi vertices. The volume of each convex hull is the volume +of the corresponding Vornoi region.

    + +

    For example, to compute the volume of the bounded Voronoi region about [0,0,0]: output the origin's Voronoi vertices and +compute the volume of their convex hull. The last number from option 'FS' is the volume.

    +
    +rbox P0 10 | qvoronoi QV0 p | qhull FS
    +0
    +2 1.448134756744281 0.1067973560800857
    +
    + +

    For another example, see How do I get the triangles for a 2-d Delaunay + triangulation and the vertices of its Voronoi diagram?

    + +

    This approach is slow if you are using the command line. A faster approcach is to call Qhull from +a program. The fastest method is Clarkson's hull program. +It computes the volume for all Voronoi regions.

    + +

    An unbounded Voronoi region does not have a volume.

    + +

    »How do I get the radii of the empty + spheres for each Voronoi vertex?

    + +Use option 'Fi' to list each bisector (i.e. Delaunay ridge). Then compute the +minimum distance for each Voronoi vertex. + +

    There's other ways to get the same information. Let me know if you +find a better method. + +

    »What is the Voronoi diagram + of a square?

    + +

    +Consider a square, +

    +C:\qhull>rbox c D2
    +2 RBOX c D2
    +4
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +

    There's two ways to compute the Voronoi diagram: with facet merging +or with joggle. With facet merging, the +result is: + +

    +C:\qhull>rbox c D2 | qvoronoi Qz
    +
    +Voronoi diagram by the convex hull of 5 points in 3-d:
    +
    +  Number of Voronoi regions and at-infinity: 5
    +  Number of Voronoi vertices: 1
    +  Number of facets in hull: 5
    +
    +Statistics for: RBOX c D2 | QVORONOI Qz
    +
    +  Number of points processed: 5
    +  Number of hyperplanes created: 7
    +  Number of distance tests for qhull: 8
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 29
    +  CPU seconds to compute hull (after input):  0
    +
    +C:\qhull>rbox c D2 | qvoronoi Qz o
    +2
    +2 5 1
    +-10.101 -10.101
    +     0      0
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +0
    +
    +C:\qhull>rbox c D2 | qvoronoi Qz Fv
    +4
    +4 0 1 0 1
    +4 0 2 0 1
    +4 1 3 0 1
    +4 2 3 0 1
    +
    + +

    There is one Voronoi vertex at the origin and rays from the origin +along each of the coordinate axes. +The last line '4 2 3 0 1' means that there is +a ray that bisects input points #2 and #3 from infinity (vertex 0) to +the origin (vertex 1). +Option 'Qz' adds an artificial point since the input is cocircular. +Coordinates -10.101 indicate the +vertex at infinity. + +

    With triangulated output, the Voronoi vertex is +duplicated: + +

    +C:\qhull3.1>rbox c D2 | qvoronoi Qt Qz
    +
    +Voronoi diagram by the convex hull of 5 points in 3-d:
    +
    +  Number of Voronoi regions and at-infinity: 5
    +  Number of Voronoi vertices: 2
    +  Number of triangulated facets: 1
    +
    +Statistics for: RBOX c D2 | QVORONOI Qt Qz
    +
    +  Number of points processed: 5
    +  Number of hyperplanes created: 7
    +  Number of facets in hull: 6
    +  Number of distance tests for qhull: 8
    +  Number of distance tests for merging: 33
    +  Number of distance tests for checking: 30
    +  Number of merged facets: 1
    +  CPU seconds to compute hull (after input): 0.05
    +
    +C:\qhull3.1>rbox c D2 | qvoronoi Qt Qz o
    +2
    +3 5 1
    +-10.101 -10.101
    +     0      0
    +     0      0
    +3 2 0 1
    +2 1 0
    +2 2 0
    +3 2 0 1
    +0
    +
    +C:\qhull3.1>rbox c D2 | qvoronoi Qt Qz Fv
    +4
    +4 0 2 0 2
    +4 0 1 0 1
    +4 1 3 0 1
    +4 2 3 0 2
    +
    + + +

    With joggle, the input is no longer cocircular and the Voronoi vertex is +split into two: + +

    +C:\qhull>rbox c D2 | qvoronoi Qt Qz
    +
    +C:\qhull>rbox c D2 | qvoronoi QJ o
    +2
    +3 4 1
    +-10.101 -10.101
    +-4.71511718558304e-012 -1.775812830118184e-011
    +9.020340030474472e-012 -4.02267108512433e-012
    +2 0 1
    +3 2 1 0
    +3 2 0 1
    +2 2 0
    +
    +C:\qhull>rbox c D2 | qvoronoi QJ Fv
    +5
    +4 0 2 0 1
    +4 0 1 0 1
    +4 1 2 1 2
    +4 1 3 0 2
    +4 2 3 0 2
    +
    + +

    Note that the Voronoi diagram includes the same rays as + before plus a short edge between the two vertices.

    + + +

    »How do I construct +the Voronoi diagram of cospherical points?

    + +

    Three-d terrain data can be approximated with cospherical +points. The Delaunay triangulation of cospherical points is the +same as their convex hull. If the points lie on the unit sphere, +the facet normals are the Voronoi vertices [via S. Fortune].

    + +

    For example, consider the points {[1,0,0], [-1,0,0], [0,1,0], +...}. Their convex hull is:

    + +
    +rbox d G1 | qconvex o
    +3
    +6 8 12
    +     0      0     -1
    +     0      0      1
    +     0     -1      0
    +     0      1      0
    +    -1      0      0
    +     1      0      0
    +3 3 1 4
    +3 1 3 5
    +3 0 3 4
    +3 3 0 5
    +3 2 1 5
    +3 1 2 4
    +3 2 0 4
    +3 0 2 5
    +
    + +

    The facet normals are:

    + +
    +rbox d G1 | qconvex n
    +4
    +8
    +-0.5773502691896258  0.5773502691896258  0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258  0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258  0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258  0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258 -0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258 -0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258 -0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258 -0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    +
    + +

    If you drop the offset from each line (the last number), each +line is the Voronoi vertex for the corresponding facet. The +neighboring facets for each point define the Voronoi region for +each point. For example:

    + +
    +rbox d G1 | qconvex FN
    +6
    +4 7 3 2 6
    +4 5 0 1 4
    +4 7 4 5 6
    +4 3 1 0 2
    +4 6 2 0 5
    +4 7 3 1 4
    +
    + +

    The Voronoi vertices {7, 3, 2, 6} define the Voronoi region +for point 0. Point 0 is [0,0,-1]. Its Voronoi vertices are

    + +
    +-0.5773502691896258  0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258  0.5773502691896258 -0.5773502691896258
    +-0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    + 0.5773502691896258 -0.5773502691896258 -0.5773502691896258
    +
    + +

    In this case, the Voronoi vertices are oriented, but in +general they are unordered.

    + +

    By taking the dual of the Delaunay triangulation, you can +construct the Voronoi diagram. For cospherical points, the convex +hull vertices for each facet, define the input sites for each +Voronoi vertex. In 3-d, the input sites are oriented. For +example:

    + +
    +rbox d G1 | qconvex i
    +8
    +3 1 4
    +1 3 5
    +0 3 4
    +3 0 5
    +2 1 5
    +1 2 4
    +2 0 4
    +0 2 5
    +
    + +

    The convex hull vertices for facet 0 are {3, 1, 4}. So Voronoi +vertex 0 (i.e., [-0.577, 0.577, 0.577]) is the Voronoi vertex for +input sites {3, 1, 4} (i.e., {[0,1,0], [0,0,1], [-1,0,0]}).

    + +

    »Can Qhull compute the +unbounded rays of the Voronoi diagram?

    + +

    Use 'Fo' to compute the separating +hyperplanes for unbounded Voronoi regions. The corresponding ray +goes to infinity from the Voronoi vertices. If you enclose the +input sites in a large enough box, the outermost bounded regions +will represent the unbounded regions of the original points.

    + +

    If you do not box the input sites, you can identify the +unbounded regions. They list '0' as a vertex. Vertex 0 represents +"infinity". Each unbounded ray includes vertex 0 in +option 'Fv. See Voronoi graphics and Voronoi notes.

    + +
    +

    »Approximation questions

    + +

    »How do I +approximate data with a simplex

    + +

    Qhull may be used to help select a simplex that approximates a +data set. It will take experimentation. Geomview will help to +visualize the results. This task may be difficult to do in 5-d +and higher. Use rbox options 'x' and 'y' to produce random +distributions within a simplex. Your methods work if you can +recover the simplex.

    + +

    Use Qhull's precision options to get a first approximation to +the hull, say with 10 to 50 facets. For example, try 'C0.05' to +remove small facets after constructing the hull. Use 'W0.05' to +ignore points within 0.05 of a facet. Use 'PA5' to print the five +largest facets by area.

    + +

    Then use other methods to fit a simplex to this data. Remove +outlying vertices with few nearby points. Look for large facets +in different quadrants. You can use option 'Pd0d1d2' to print all +the facets in a quadrant.

    + +

    In 4-d and higher, use the outer planes (option 'Fo' or +'facet->maxoutside') since the hyperplane of an approximate +facet may be below many of the input points.

    + +

    For example, consider fitting a cube to 1000 uniformly random +points in the unit cube. In this case, the first try was good:

    + +
    +
    +rbox 1000 | qconvex W0.05 C0.05 PA6 Fo
    +4
    +6
    +0.35715408374381 0.08706467018177928 -0.9299788727015564 -0.5985514741284483
    +0.995841591359023 -0.02512604712761577 0.08756829720435189 -0.5258834069202866
    +0.02448099521570909 -0.02685210459017302 0.9993396046151313 -0.5158104982631999
    +-0.9990223929415094 -0.01261133513150079 0.04236994958247349 -0.509218270408407
    +-0.0128069014364698 -0.9998380680115362 0.01264203427283151 -0.5002512653670584
    +0.01120895057872914 0.01803671994177704 -0.9997744926535512 -0.5056824072956361
    +
    +
    + +
    +

    »Halfspace questions

    + +

    »How do I compute the + intersection of halfspaces with Qhull?

    + +

    Qhull computes the halfspace intersection about a point. The +point must be inside all of the halfspaces. Given a point, a +duality turns a halfspace intersection problem into a convex +hull problem. + +

    Use linear programming if you +do not know a point in the interior of the halfspaces. +See the notes for qhalf. You will need + a linear programming code. This may require a fair amount of work to + implement.

    + + + +
    +

    »Qhull library +questions

    + +

    »Is Qhull available for Mathematica, Matlab, or Maple?

    + +

    MATLAB + +

    Z. You of MathWorks added qhull to MATLAB 6. +See functions convhulln, + delaunayn, + griddata3, + griddatan, + tsearch, + tsearchn, and + voronoin. V. Brumberg update MATLAB R14 for Qhull 2003.1 and triangulated output. + +

    Engwirda wrote mesh2d for unstructured mesh generation in MATLAB. +It is based on the iterative method of Persson and generally results in better quality meshes than delaunay refinement. + + +

    Mathematica and Maple + +

    See qh-math +for a Delaunay interface to Mathematica. It includes projects for CodeWarrior +on the Macintosh and Visual C++ on Win32 PCs. + +

    See Mathematica ('m') and Maple ('FM') output options. + +

    +

    »Why are there too few ridges?

    + +The following sample code may produce fewer ridges than expected: + +
    +  facetT *facetp;
    +  ridgeT *ridge, **ridgep;
    +
    +  FORALLfacets {
    +    printf("facet f%d\n", facet->id);
    +    FOREACHridge_(facet->ridges) {
    +      printf("   ridge r%d between f%d and f%d\n", ridge->id, ridge->top->id, ridge->bottom->id);
    +    }
    +  }
    +
    + +

    Qhull does not create ridges for simplicial facets. +Instead it computes ridges from facet->neighbors. To make ridges for a +simplicial facet, use qh_makeridges() in merge.c. Usefacet->visit_id to visit +each ridge once (instead of twice). For example, + +

    +  facetT *facet, *neighbor;
    +  ridgeT *ridge, **ridgep;
    +
    +  qh visit_id++;
    +  FORALLfacets {
    +    printf("facet f%d\n", facet->id);
    +    qh_makeridges(facet);
    +    facet->visitId= qh visit_id;
    +    FOREACHridge_(facet->ridges) {
    +        neighbor= otherfacet_(ridge, visible);
    +        if (neighbor->visitid != qh visit_id)
    +            printf("   ridge r%d between f%d and f%d\n", ridge->id, ridge->top->id, ridge->bottom->id);
    +    }
    +  }
    +
    + +

    »Can Qhull use coordinates without placing + them in a data file?

    + +

    You may call Qhull from a program. Please use the reentrant Qhull library (libqhullstatic_r.a, libqhull_r.so, or qhull_r.dll). + +See user_eg.c and "Qhull-template" in user_r.c for examples.. + +See Qhull internals for an introduction to Qhull's reentrant library and its C++ interface. + +

    Hint: Start with a small example for which you know the + answer.

    + +

    »How large are Qhull's data structures?

    + +

    Qhull uses a general-dimension data structure. +The size depends on the dimension. Use option 'Ts' to print +out the memory statistics [e.g., 'rbox D2 10 | qconvex Ts']. + + +

    »Can Qhull construct +convex hulls and Delaunay triangulations one point at a time?

    + +

    The Qhull library may be used to construct convex hulls and +Delaunay triangulations one point at a time. It may not be used +for deleting points or moving points.

    + +

    Qhull is designed for batch processing. Neither Clarkson's +randomized incremental algorithm nor Qhull are designed for +on-line operation. For many applications, it is better to +reconstruct the convex hull or Delaunay triangulation from +scratch for each new point.

    + +

    With random point sets and on-line processing, Clarkson's +algorithm should run faster than Qhull. Clarkson uses the +intermediate facets to reject new, interior points, while Qhull, +when used on-line, visits every facet to reject such points. If +used on-line for n points, Clarkson may take O(n) times as much +memory as the average off-line case, while Qhull's space +requirement does not change.

    + +

    If you triangulate the output before adding all the points +(option 'Qt' and procedure qh_triangulate), you must set +option 'Q11'. It duplicates the +normals of triangulated facets and recomputes the centrums. +This should be avoided for regular use since triangulated facets +are not clearly convex with their neighbors. It appears to +work most of the time, but fails for cases that Qhull normally +handles well [see the test call to qh_triangulate in qh_addpoint]. + +

    »How do I visit the +ridges of a Delaunay triangulation?

    + +

    To visit the ridges of a Delaunay triangulation, visit each +facet. Each ridge will appear twice since it belongs to two +facets. In pseudo-code:

    + +
    +    for each facet of the triangulation
    +        if the facet is Delaunay (i.e., part of the lower convex hull)
    +            for each ridge of the facet
    +                if the ridge's neighboring facet has not been visited
    +                    ... process a ridge of the Delaunay triangulation ...
    +
    + +

    In undebugged, C code:

    + +
    +    qh visit_id++;
    +    FORALLfacets_(facetlist)
    +        if (!facet->upperdelaunay) {
    +            facet->visitid= qh visit_id;
    +            qh_makeridges(facet);
    +            FOREACHridge_(facet->ridges) {
    +                neighbor= otherfacet_(ridge, facet);
    +                if (neighbor->visitid != qh visit_id) {
    +                    /* Print ridge here with facet-id and neighbor-id */
    +                    /*fprintf(fp, "f%d\tf%d\t",facet->id,neighbor->ID);*/
    +                    FOREACHvertex_(ridge->vertices)
    +                        fprintf(fp,"%d ",qh_pointid (vertex->point) );
    +                    qh_printfacetNvertex_simplicial (fp, facet, format);
    +                    fprintf(fp," ");
    +                    if(neighbor->upperdelaunay)
    +                        fprintf(fp," -1 -1 -1 -1 ");
    +                    else
    +                        qh_printfacetNvertex_simplicial (fp, neighbor, format);
    +                    fprintf(fp,"\n");
    +                }
    +            }
    +        }
    +    }
    +
    + +

    Qhull should be redesigned as a class library, or at least as +an API. It currently provides everything needed, but the +programmer has to do a lot of work. Hopefully someone will write +C++ wrapper classes or a Python module for Qhull.

    + +

    »How do I visit the +Delaunay regions?

    + +

    Qhull constructs a Delaunay triangulation by lifting the +input sites to a paraboloid. The Delaunay triangulation +corresponds to the lower convex hull of the lifted points. To +visit each facet of the lower convex hull, use:

    + +
    +    facetT *facet;
    +
    +    ...
    +    FORALLfacets {
    +        if (!facet->upperdelaunay) {
    +            ... only facets for Delaunay regions ...
    +        }
    +    }
    +
    + +

    »When is a point +outside or inside a facet?

    + +

    A point is outside of a facet if it is clearly outside the +facet's outer plane. The outer plane is defined by an offset +(facet->maxoutside) from the facet's hyperplane.

    + +
    +    facetT *facet;
    +    pointT *point;
    +    realT dist;
    +
    +    ...
    +    qh_distplane(point, facet, &dist);
    +    if (dist > facet->maxoutside + 2 * qh DISTround) {
    +        /* point is clearly outside of facet */
    +    }
    +
    + +

    A point is inside of a facet if it is clearly inside the +facet's inner plane. The inner plane is computed as the maximum +distance of a vertex to the facet. It may be computed for an +individual facet, or you may use the maximum over all facets. For +example:

    + +
    +    facetT *facet;
    +    pointT *point;
    +    realT dist;
    +
    +    ...
    +    qh_distplane(point, facet, &dist);
    +    if (dist < qh min_vertex - 2 * qh DISTround) {
    +        /* point is clearly inside of facet */
    +    }
    +
    + +

    Both tests include two qh.DISTrounds because the computation +of the furthest point from a facet may be off by qh.DISTround and +the computation of the current distance to the facet may be off +by qh.DISTround.

    + +

    »How do I find the +facet that is closest to a point?

    + +

    Use qh_findbestfacet(). For example,

    + +
    +    coordT point[ DIM ];
    +    boolT isoutside;
    +    realT bestdist;
    +    facetT *facet;
    +
    +    ... set coordinates for point ...
    +
    +    facet= qh_findbestfacet (point, qh_ALL, &bestdist, &isoutside);
    +
    +    /* 'facet' is the closest facet to 'point' */
    +
    + +

    qh_findbestfacet() performs a directed search for the facet +furthest below the point. If the point lies inside this facet, +qh_findbestfacet() performs an exhaustive search of all facets. +An exhaustive search may be needed because a facet on the far +side of a lens-shaped distribution may be closer to a point than +all of the facet's neighbors. The exhaustive search may be +skipped for spherical distributions.

    + +

    Also see, "How do I find the +Delaunay triangle that is closest to a +point?"

    + +

    »How do I find the +Delaunay triangle or Voronoi region that is closest to a point?

    + +

    A Delaunay triangulation subdivides the plane, or in general +dimension, subdivides space. Given a point, how do you determine +the subdivision containing the point? Or, given a set of points, +how do you determine the subdivision containing each point of the set? +Efficiency is important -- an exhaustive search of the subdivision +is too slow. + +

    First compute the Delaunay triangle with qh_new_qhull() in user_r.c or Qhull::runQhull(). +Lift the point to the paraboloid by summing the squares of the +coordinates. Use qh_findbestfacet() [poly2.c] to find the closest Delaunay +triangle. Determine the closest vertex to find the corresponding +Voronoi region. Do not use options +'Qbb', 'QbB', +'Qbk:n', or 'QBk:n' since these scale the last +coordinate. Optimizations of qh_findbestfacet() should +be possible for Delaunay triangulations.

    + +

    You first need to lift the point to the paraboloid (i.e., the +last coordinate is the sum of the squares of the point's coordinates). +The +routine, qh_setdelaunay() [geom2.c], lifts an array of points to the +paraboloid. The following excerpt is from findclosest() in +user_eg.c.

    + +
    +    coordT point[ DIM + 1];  /* one extra coordinate for lifting the point */
    +    boolT isoutside;
    +    realT bestdist;
    +    facetT *facet;
    +
    +    ... set coordinates for point[] ...
    +
    +    qh_setdelaunay (DIM+1, 1, point);
    +    facet= qh_findbestfacet (point, qh_ALL, &bestdist, &isoutside);
    +    /* 'facet' is the closest Delaunay triangle to 'point' */
    +
    + +

    The returned facet either contains the point or it is the +closest Delaunay triangle along the convex hull of the input set. + +

    Point location is an active research area in Computational +Geometry. For a practical approach, see Mucke, et al, "Fast randomized +point location without preprocessing in two- and +three-dimensional Delaunay triangulations," Computational +Geometry '96, p. 274-283, May 1996. +For an introduction to planar point location see [O'Rourke '93]. +Also see, "How do I find the facet that is closest to a +point?"

    + +

    To locate the closest Voronoi region, determine the closest +vertex of the closest Delaunay triangle.

    + +
    +    realT dist, bestdist= REALmax;
    +        vertexT *bestvertex= NULL, *vertex, **vertexp;
    +
    +    /* 'facet' is the closest Delaunay triangle to 'point' */
    +
    +    FOREACHvertex_( facet->vertices ) {
    +        dist= qh_pointdist( point, vertex->point, DIM );
    +        if (dist < bestdist) {
    +            bestdist= dist;
    +            bestvertex= vertex;
    +        }
    +    }
    +    /* 'bestvertex' represents the Voronoi region closest to 'point'.  The corresponding
    +       input site is 'bestvertex->point' */
    +
    + +

    »How do I list the +vertices?

    + +

    To list the vertices (i.e., extreme points) of the convex hull +use

    + +
    +
    +    vertexT *vertex;
    +
    +    FORALLvertices {
    +      ...
    +      // vertex->point is the coordinates of the vertex
    +      // qh_pointid(vertex->point) is the point ID of the vertex
    +      ...
    +    }
    +    
    +
    + +

    »How do I test code +that uses the Qhull library?

    + +

    Compare the output from your program with the output from the +Qhull program. Use option 'T1' or 'T4' to trace what Qhull is +doing. Prepare a small example for which you know the +output. Run the example through the Qhull program and your code. +Compare the trace outputs. If you do everything right, the two +trace outputs should be almost the same. The trace output will +also guide you to the functions that you need to review.

    + +

    »When I compute a +plane equation from a facet, I sometimes get an outward-pointing +normal and sometimes an inward-pointing normal

    + +

    Qhull orients simplicial facets, and prints oriented output +for 'i', 'Ft', and other options. The orientation depends on both +the vertex order and the flag facet->toporient.

    + +

    Qhull does not orient + non-simplicial facets. Instead it orients the facet's ridges. These are + printed with the 'Qt' and 'Ft' option. The facet's hyperplane is oriented.

    + +
    +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: FAQ: Table of Contents
    + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: +Sept. 25, 1995 --- Last modified: see top +

    + + diff --git a/xs/src/qhull/html/qh-get.htm b/xs/src/qhull/html/qh-get.htm new file mode 100644 index 0000000000..c39ed22564 --- /dev/null +++ b/xs/src/qhull/html/qh-get.htm @@ -0,0 +1,106 @@ + + + + +Qhull Downloads + + + + +

    Up: Qhull Home Page
    +

    + +
    + +

    [CONE] Qhull Downloads

    + + + +
    + +

    Up: Qhull Home Page
    +

    + +
    + +

    [HOME] The Geometry Center Home Page

    + +

    Comments to: qhull@qhull.org
    + + diff --git a/xs/src/qhull/html/qh-impre.htm b/xs/src/qhull/html/qh-impre.htm new file mode 100644 index 0000000000..cfbe0acb82 --- /dev/null +++ b/xs/src/qhull/html/qh-impre.htm @@ -0,0 +1,826 @@ + + + + +Imprecision in Qhull + + + + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull imprecision: Table of Contents +(please wait while loading) + +


    + +

    [4-d cube] Imprecision in Qhull

    + +

    This section of the Qhull manual discusses the problems caused +by coplanar points and why Qhull uses options 'C-0' or 'Qx' +by default. If you ignore precision issues with option 'Q0', the output from Qhull can be +arbitrarily bad. Qhull avoids precision problems if you merge facets (the default) or joggle +the input ('QJ').

    + +

    Use option 'Tv' to verify the +output from Qhull. It verifies that adjacent facets are clearly +convex. It verifies that all points are on or below all facets.

    + +

    Qhull automatically tests for convexity if it detects +precision errors while constructing the hull.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »Qhull +imprecision: Table of Contents

    + + + +
    + +

    »Precision problems

    + +

    Since Qhull uses floating point arithmetic, roundoff error +occurs with each calculation. This causes problems for +geometric algorithms. Other floating point codes for convex +hulls, Delaunay triangulations, and Voronoi diagrams also suffer +from these problems. Qhull handles most of them.

    + +

    There are several kinds of precision errors:

    + +
      +
    • Representation error occurs when there are not enough + digits to represent a number, e.g., 1/3.
    • +
    • Measurement error occurs when the input coordinates are + from measurements.
    • +
    • Roundoff error occurs when a calculation is rounded to a + fixed number of digits, e.g., a floating point + calculation.
    • +
    • Approximation error occurs when the user wants an + approximate result because the exact result contains too + much detail.
    • +
    + +

    Under imprecision, calculations may return erroneous results. +For example, roundoff error can turn a small, positive number +into a small, negative number. See Milenkovic ['93] for a discussion of strict +robust geometry. Qhull does not meet Milenkovic's criterion +for accuracy. Qhull's error bound is empirical instead of +theoretical.

    + +

    Qhull 1.0 checked for precision errors but did not handle +them. The output could contain concave facets, facets with +inverted orientation ("flipped" facets), more than two +facets adjacent to a ridge, and two facets with exactly the same +set of vertices.

    + +

    Qhull 2.4 and later automatically handles errors due to +machine round-off. Option 'C-0' or 'Qx' is set by default. In 5-d and +higher, the output is clearly convex but an input point could be +outside of the hull. This may be corrected by using option 'C-0', but then the output may contain +wide facets.

    + +

    Qhull 2.5 and later provides option 'QJ' +to joggled input. Each input coordinate is modified by a +small, random quantity. If a precision error occurs, a larger +modification is tried. When no precision errors occur, Qhull is +done.

    + +

    Qhull 3.1 and later provides option 'Qt' +for triangulated output. This removes the need for +joggled input ('QJ'). +Non-simplicial facets are triangulated. +The facets may have zero area. +Triangulated output is particularly useful for Delaunay triangulations.

    + +

    By handling round-off errors, Qhull can provide a variety of +output formats. For example, it can return the halfspace that +defines each facet ('n'). The +halfspaces include roundoff error. If the halfspaces were exact, +their intersection would return the original extreme points. With +imprecise halfspaces and exact arithmetic, nearly incident points +may be returned for an original extreme point. By handling +roundoff error, Qhull returns one intersection point for each of +the original extreme points. Qhull may split or merge an extreme +point, but this appears to be unlikely.

    + +

    The following pipe implements the identity function for +extreme points (with roundoff): +

    + qconvex FV n | qhalf Fp +
    + +

    Bernd Gartner published his +Miniball +algorithm ["Fast and robust smallest enclosing balls", Algorithms - ESA '99, LNCS 1643]. +It uses floating point arithmetic and a carefully designed primitive operation. +It is practical to 20-D or higher, and identifies at least two points on the +convex hull of the input set. Like Qhull, it is an incremental algorithm that +processes points furthest from the intermediate result and ignores +points that are close to the intermediate result. + +

    »Merged facets or joggled input

    + +

    This section discusses the choice between merged facets and joggled input. +By default, Qhull uses merged facets to handle +precision problems. With option 'QJ', +the input is joggled. See examples +of joggled input and triangulated output. +

      +
    • Use merged facets (the default) +when you want non-simplicial output (e.g., the faces of a cube). +
    • Use merged facets and triangulated output ('Qt') when +you want simplicial output and coplanar facets (e.g., triangles for a Delaunay triangulation). +
    • Use joggled input ('QJ') when you need clearly-convex, +simplicial output. +
    + +

    The choice between merged facets and joggled input depends on +the application. Both run about the same speed. Joggled input may +be faster if the initial joggle is sufficiently large to avoid +precision errors. + +

    Most applications should used merged facets +with triangulated output.

    + +

    Use merged facets (the +default, 'C-0') +or triangulated output ('Qt') if

    + +
      +
    • Your application supports non-simplicial facets, or + it allows degenerate, simplicial facets (option 'Qt').
    • +
    • You do not want the input modified.
    • +
    • You want to set additional options for approximating the + hull.
    • +
    • You use single precision arithmetic (realT). +
    • +
    + +

    Use joggled input ('QJ') if

    + +
      +
    • Your application needs clearly convex, simplicial output
    • +
    • Your application supports perturbed input points and narrow triangles.
    • +
    • Seven significant digits is sufficient accuracy.
    • +
    + +

    You may use both techniques or combine joggle with +post-merging ('Cn').

    + +

    Other researchers have used techniques similar to joggled +input. Sullivan and Beichel [ref?] randomly perturb the input +before computing the Delaunay triangulation. Corkum and Wyllie +[news://comp.graphics, 1990] randomly rotate a polytope before +testing point inclusion. Edelsbrunner and Mucke [Symp. Comp. +Geo., 1988] and Yap [J. Comp. Sys. Sci., 1990] symbolically +perturb the input to remove singularities.

    + +

    Merged facets ('C-0') handles +precision problems directly. If a precision problem occurs, Qhull +merges one of the offending facets into one of its neighbors. +Since all precision problems in Qhull are associated with one or +more facets, Qhull will either fix the problem or attempt to merge the +last remaining facets.

    + +

    »Delaunay +triangulations

    + +

    Programs that use Delaunay triangulations traditionally assume +a triangulated input. By default, qdelaunay +merges regions with cocircular or cospherical input sites. +If you want a simplicial triangulation +use triangulated output ('Qt') or joggled +input ('QJ'). + +

    For Delaunay triangulations, triangulated +output should produce good results. All points are within roundoff error of +a paraboloid. If two points are nearly incident, one will be a +coplanar point. So all points are clearly separated and convex. +If qhull reports deleted vertices, the triangulation +may contain serious precision faults. Deleted vertices are reported +in the summary ('s', 'Fs'

    + +

    You should use option 'Qbb' with Delaunay +triangulations. It scales the last coordinate and may reduce +roundoff error. It is automatically set for qdelaunay, +qvoronoi, and option 'QJ'.

    + +

    Edelsbrunner, H, Geometry and Topology for Mesh Generation, Cambridge University Press, 2001. +Good mathematical treatise on Delaunay triangulation and mesh generation for 2-d +and 3-d surfaces. The chapter on surface simplification is +particularly interesting. It is similar to facet merging in Qhull. + +

    Veron and Leon published an algorithm for shape preserving polyhedral +simplification with bounded error [Computers and Graphics, 22.5:565-585, 1998]. +It remove nodes using front propagation and multiple remeshing. + +

    »Halfspace intersection

    + +

    +The identity pipe for Qhull reveals some precision questions for +halfspace intersections. The identity pipe creates the convex hull of +a set of points and intersects the facets' hyperplanes. It should return the input +points, but narrow distributions may drop points while offset distributions may add +points. It may be better to normalize the input set about the origin. +For example, compare the first results with the later two results: [T. Abraham] +

    + rbox 100 s t | tee r | qconvex FV n | qhalf Fp | cat - r | /bin/sort -n | tail +
    + rbox 100 L1e5 t | tee r | qconvex FV n | qhalf Fp | cat - r | /bin/sort -n | tail +
    + rbox 100 s O10 t | tee r | qconvex FV n | qhalf Fp | cat - r | /bin/sort -n | tail +
    + + +

    »Merged facets

    + +

    Qhull detects precision +problems when computing distances. A precision problem occurs if +the distance computation is less than the maximum roundoff error. +Qhull treats the result of a hyperplane computation as if it +were exact.

    + +

    Qhull handles precision problems by merging non-convex facets. +The result of merging two facets is a thick facet defined by an inner +plane and an outer plane. The inner and outer planes +are offsets from the facet's hyperplane. The inner plane is +clearly below the facet's vertices. At the end of Qhull, the +outer planes are clearly above all input points. Any exact convex +hull must lie between the inner and outer planes.

    + +

    Qhull tests for convexity by computing a point for each facet. +This point is called the facet's centrum. It is the +arithmetic center of the facet's vertices projected to the +facet's hyperplane. For simplicial facets with d +vertices, the centrum is equivalent to the centroid or center of +gravity.

    + +

    Two neighboring facets are convex if each centrum is clearly +below the other hyperplane. The 'Cn' +or 'C-n' options sets the centrum's +radius to n . A centrum is clearly below a hyperplane if +the computed distance from the centrum to the hyperplane is +greater than the centrum's radius plus two maximum roundoff +errors. Two are required because the centrum can be the maximum +roundoff error above its hyperplane and the distance computation +can be high by the maximum roundoff error.

    + +

    With the 'C-n' or 'A-n ' options, Qhull merges non-convex +facets while constructing the hull. The remaining facets are +clearly convex. With the 'Qx ' +option, Qhull merges coplanar facets after constructing the hull. +While constructing the hull, it merges coplanar horizon facets, +flipped facets, concave facets and duplicated ridges. With 'Qx', coplanar points may be missed, but +it appears to be unlikely.

    + +

    If the user sets the 'An' or 'A-n' option, then all neighboring +facets are clearly convex and the maximum (exact) cosine of an +angle is n.

    + +

    If 'C-0' or 'Qx' is used without other precision +options (default), Qhull tests vertices instead of centrums for +adjacent simplices. In 3-d, if simplex abc is adjacent to +simplex bcd, Qhull tests that vertex a is clearly +below simplex bcd , and vertex d is clearly below +simplex abc. When building the hull, Qhull tests vertices +if the horizon is simplicial and no merges occur.

    + +

    »How Qhull merges facets

    + +

    If two facets are not clearly convex, then Qhull removes one +or the other facet by merging the facet into a neighbor. It +selects the merge which minimizes the distance from the +neighboring hyperplane to the facet's vertices. Qhull also +performs merges when a facet has fewer than d neighbors (called a +degenerate facet), when a facet's vertices are included in a +neighboring facet's vertices (called a redundant facet), when a +facet's orientation is flipped, or when a ridge occurs between +more than two facets.

    + +

    Qhull performs merges in a series of passes sorted by merge +angle. Each pass merges those facets which haven't already been +merged in that pass. After a pass, Qhull checks for redundant +vertices. For example, if a vertex has only two neighbors in 3-d, +the vertex is redundant and Qhull merges it into an adjacent +vertex.

    + +

    Merging two simplicial facets creates a non-simplicial facet +of d+1 vertices. Additional merges create larger facets. +When merging facet A into facet B, Qhull retains facet B's +hyperplane. It merges the vertices, neighbors, and ridges of both +facets. It recomputes the centrum if a wide merge has not +occurred (qh_WIDEcoplanar) and the number of extra vertices is +smaller than a constant (qh_MAXnewcentrum).

    + + +

    »Limitations of merged +facets

    + +
      +
    • Uneven dimensions -- +If one coordinate has a larger absolute value than other +coordinates, it may dominate the effect of roundoff errors on +distance computations. You may use option 'QbB' to scale points to the unit cube. +For Delaunay triangulations and Voronoi diagrams, qdelaunay +and qvoronoi always set +option 'Qbb'. It scales the last +coordinate to [0,m] where m is the maximum width of the +other coordinates. Option 'Qbb' is +needed for Delaunay triangulations of integer coordinates +and nearly cocircular points. + +

      For example, compare +

      +        rbox 1000 W0 t | qconvex Qb2:-1e-14B2:1e-14
      +
      +with +
      +        rbox 1000 W0 t | qconvex
      +
      +The distributions are the same but the first is compressed to a 2e-14 slab. +

      +

    • Post-merging of coplanar facets -- In 5-d and higher, option 'Qx' +(default) delays merging of coplanar facets until post-merging. +This may allow "dents" to occur in the intermediate +convex hulls. A point may be poorly partitioned and force a poor +approximation. See option 'Qx' for +further discussion.

      + +

      This is difficult to produce in 5-d and higher. Option 'Q6' turns off merging of concave +facets. This is similar to 'Qx'. It may lead to serious precision errors, +for example, +

      +        rbox 10000 W1e-13  | qhull Q6  Tv
      +
      + +

      +

    • Maximum facet width -- +Qhull reports the maximum outer plane and inner planes (if +more than roundoff error apart). There is no upper bound +for either figure. This is an area for further research. Qhull +does a good job of post-merging in all dimensions. Qhull does a +good job of pre-merging in 2-d, 3-d, and 4-d. With the 'Qx' option, it does a good job in +higher dimensions. In 5-d and higher, Qhull does poorly at +detecting redundant vertices.

      + +

      In the summary ('s'), look at the +ratio between the maximum facet width and the maximum width of a +single merge, e.g., "(3.4x)". Qhull usually reports a +ratio of four or lower in 3-d and six or lower in 4-d. If it +reports a ratio greater than 10, this may indicate an +implementation error. Narrow distributions (see following) may +produce wide facets. + +

      For example, if special processing for narrow distributions is +turned off ('Q10'), qhull may produce +a wide facet:

      +
      +         rbox 1000 L100000 s G1e-16 t1002074964 | qhull Tv Q10
      +
      + +

      +

    • Narrow distribution -- In 3-d, a narrow distribution may result in a poor +approximation. For example, if you do not use qdelaunay nor option +'Qbb', the furthest-site +Delaunay triangulation of nearly cocircular points may produce a poor +approximation: +
      +         rbox s 5000 W1e-13 D2 t1002151341 | qhull d Qt
      +         rbox 1000 s W1e-13 t1002231672 | qhull d Tv
      +
      + +

      During +construction of the hull, a point may be above two +facets with opposite orientations that span the input +set. Even though the point may be nearly coplanar with both +facets, and can be distant from the precise convex +hull of the input sites. Additional facets leave the point distant from +a facet. To fix this problem, add option 'Qbb' +(it scales the last coordinate). Option 'Qbb' +is automatically set for qdelaunay and qvoronoi. + +

      Qhull generates a warning if the initial simplex is narrow. +For narrow distributions, Qhull changes how it processes coplanar +points -- it does not make a point coplanar until the hull is +finished. +Use option 'Q10' to try Qhull without +special processing for narrow distributions. +For example, special processing is needed for: +

      +         rbox 1000 L100000 s G1e-16 t1002074964 | qhull Tv Q10
      +
      + +

      You may turn off the warning message by reducing +qh_WARNnarrow in user.h or by setting option +'Pp'.

      + +

      Similar problems occur for distributions with a large flat facet surrounded +with many small facet at a sharp angle to the large facet. +Qhull 3.1 fixes most of these problems, but a poor approximation can occur. +A point may be left outside of the convex hull ('Tv'). +Examples include +the furthest-site Delaunay triangulation of nearly cocircular points plus the origin, and the convex hull of a cone of nearly cocircular points. The width of the band is 10^-13. +

      +        rbox s 1000 W1e-13 P0 D2 t996799242 | qhull d Tv
      +        rbox 1000 s Z1 G1e-13 t1002152123 | qhull Tv
      +        rbox 1000 s Z1 G1e-13 t1002231668 | qhull Tv
      +
      + +

      +

    • Quadratic running time -- If the output contains large, non-simplicial +facets, the running time for Qhull may be quadratic in the size of the triangulated +output. For example, rbox 1000 s W1e-13 c G2 | qhull d is 4 times +faster for 500 points. The convex hull contains two large nearly spherical facets and +many nearly coplanar facets. Each new point retriangulates the spherical facet and repartitions the remaining points into all of the nearly coplanar facets. +In this case, quadratic running time is avoided if you use qdelaunay, +add option 'Qbb', +or add the origin ('P0') to the input. +

      +

    • Nearly coincident points within 1e-13 -- +Multiple, nearly coincident points within a 1e-13 ball of points in the unit cube +may lead to wide facets or quadratic running time. +For example, the convex hull a 1000 coincident, cospherical points in 4-D, +or the 3-D Delaunay triangulation of nearly coincident points, may lead to very +wide facets (e.g., 2267021951.3x). + +

      For Delaunay triangulations, the problem typically occurs for extreme points of the input +set (i.e., on the edge between the upper and lower convex hull). After multiple facet merges, four +facets may share the same, duplicate ridge and must be merged. +Some of these facets may be long and narrow, leading to a very wide merged facet. +If so, error QH6271 is reported. It may be overriden with option 'Q12'. + +

      Duplicate ridges occur when the horizon facets for a new point is "pinched". +In a duplicate ridge, a subridge (e.g., a line segment in 3-d) is shared by two horizon facets. +At least two of its vertices are nearly coincident. It is easy to generate coincident points with +option 'Cn,r,m' of rbox. It generates n points within an r ball for each of m input sites. For example, +every point of the following distributions has a nearly coincident point within a 1e-13 ball. +Substantially smaller or larger balls do not lead to pinched horizons. +

      +        rbox 1000 C1,1e-13 D4 s t | qhull
      +        rbox 75 C1,1e-13 t | qhull d
      +
      +For Delaunay triangulations, a bounding box may alleviate this error (e.g., rbox 500 C1,1E-13 t c G1 | qhull d). +A later release of qhull will avoid pinched horizons by merging duplicate subridges. A subridge is +merged by merging adjacent vertices. +

      +

    • Facet with zero-area -- +It is possible for a zero-area facet to be convex with its +neighbors. This can occur if the hyperplanes of neighboring +facets are above the facet's centrum, and the facet's hyperplane +is above the neighboring centrums. Qhull computes the facet's +hyperplane so that it passes through the facet's vertices. The +vertices can be collinear.

      + +

      +

    • No more facets -- Qhull reports an error if there are d+1 facets left +and two of the facets are not clearly convex. This typically +occurs when the convexity constraints are too strong or the input +points are degenerate. The former is more likely in 5-d and +higher -- especially with option 'C-n'.

      + +

      +

    • Deleted cone -- Lots of merging can end up deleting all +of the new facets for a point. This is a rare event that has +only been seen while debugging the code. + +

      +

    • Triangulated output leads to precision problems -- With sufficient +merging, the ridges of a non-simplicial facet may have serious topological +and geometric problems. A ridge may be between more than two +neighboring facets. If so, their triangulation ('Qt') +will fail since two facets have the same vertex set. Furthermore, +a triangulated facet may have flipped orientation compared to its +neighbors.
    • + +

      The triangulation process detects degenerate facets with +only two neighbors. These are marked degenerate. They have +zero area. + +

      +

    • Coplanar points -- +Option 'Qc' is determined by +qh_check_maxout() after constructing the hull. Qhull needs to +retain all possible coplanar points in the facets' coplanar sets. +This depends on qh_RATIOnearInside in user.h. +Furthermore, the cutoff for a coplanar point is arbitrarily set +at the minimum vertex. If coplanar points are important to your +application, remove the interior points by hand (set 'Qc Qi') or +make qh_RATIOnearInside sufficiently large.

      + +

      +

    • Maximum roundoff error -- Qhull computes the maximum roundoff error from the maximum +coordinates of the point set. Usually the maximum roundoff error +is a reasonable choice for all distance computations. The maximum +roundoff error could be computed separately for each point or for +each distance computation. This is expensive and it conflicts +with option 'C-n'. + +

      +

    • All flipped or upper Delaunay -- When a lot of merging occurs for +Delaunay triangulations, a new point may lead to no good facets. For example, +try a strong convexity constraint: +
      +        rbox 1000 s t993602376 | qdelaunay C-1e-3
      +
      + +
    + +

    »Joggled input

    + +

    Joggled input is a simple work-around for precision problems +in computational geometry ["joggle: to shake or jar +slightly," Amer. Heritage Dictionary]. Other names are +jostled input or random perturbation. +Qhull joggles the +input by modifying each coordinate by a small random quantity. If +a precision problem occurs, Qhull joggles the input with a larger +quantity and the algorithm is restarted. This process continues +until no precision problems occur. Unless all inputs incur +precision problems, Qhull will terminate. Qhull adjusts the inner +and outer planes to account for the joggled input.

    + +

    Neither joggle nor merged facets has an upper bound for the width of the output +facets, but both methods work well in practice. Joggled input is +easier to justify. Precision errors occur when the points are +nearly singular. For example, four points may be coplanar or +three points may be collinear. Consider a line and an incident +point. A precision error occurs if the point is within some +epsilon of the line. Now joggle the point away from the line by a +small, uniformly distributed, random quantity. If the point is +changed by more than epsilon, the precision error is avoided. The +probability of this event depends on the maximum joggle. Once the +maximum joggle is larger than epsilon, doubling the maximum +joggle will halve the probability of a precision error.

    + +

    With actual data, an analysis would need to account for each +point changing independently and other computations. It is easier +to determine the probabilities empirically ('TRn') . For example, consider +computing the convex hull of the unit cube centered on the +origin. The arithmetic has 16 significant decimal digits.

    + +
    +

    Convex hull of unit cube

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    joggleerror prob.
    1.0e-150.983
    2.0e-150.830
    4.0e-150.561
    8.0e-150.325
    1.6e-140.185
    3.2e-140.099
    6.4e-140.051
    1.3e-130.025
    2.6e-130.010
    5.1e-130.004
    1.0e-120.002
    2.0e-120.001
    +
    + +

    A larger joggle is needed for multiple points. Since the +number of potential singularities increases, the probability of +one or more precision errors increases. Here is an example.

    + +
    +

    Convex hull of 1000 points on unit cube

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    joggleerror prob.
    1.0e-120.870
    2.0e-120.700
    4.0e-120.450
    8.0e-120.250
    1.6e-110.110
    3.2e-110.065
    6.4e-110.030
    1.3e-100.010
    2.6e-100.008
    5.1e-090.003
    +
    + +

    Other distributions behave similarly. No distribution should +behave significantly worse. In Euclidean space, the probability +measure of all singularities is zero. With floating point +numbers, the probability of a singularity is non-zero. With +sufficient digits, the probability of a singularity is extremely +small for random data. For a sufficiently large joggle, all data +is nearly random data.

    + +

    Qhull uses an initial joggle of 30,000 times the maximum +roundoff error for a distance computation. This avoids most +potential singularities. If a failure occurs, Qhull retries at +the initial joggle (in case bad luck occurred). If it occurs +again, Qhull increases the joggle by ten-fold and tries again. +This process repeats until the joggle is a hundredth of the width +of the input points. Qhull reports an error after 100 attempts. +This should never happen with double-precision arithmetic. Once +the probability of success is non-zero, the probability of +success increases about ten-fold at each iteration. The +probability of repeated failures becomes extremely small.

    + +

    Merged facets produces a significantly better approximation. +Empirically, the maximum separation between inner and outer +facets is about 30 times the maximum roundoff error for a +distance computation. This is about 2,000 times better than +joggled input. Most applications though will not notice the +difference.

    + +

    »Exact arithmetic

    + +

    Exact arithmetic may be used instead of floating point. +Singularities such as coplanar points can either be handled +directly or the input can be symbolically perturbed. Using exact +arithmetic is slower than using floating point arithmetic and the +output may take more space. Chaining a sequence of operations +increases the time and space required. Some operations are +difficult to do.

    + +

    Clarkson's hull +program and Shewchuk's triangle +program are practical implementations of exact arithmetic.

    + +

    Clarkson limits the input precision to about fifteen digits. +This reduces the number of nearly singular computations. When a +determinant is nearly singular, he uses exact arithmetic to +compute a precise result.

    + +

    »Approximating a +convex hull

    + +

    Qhull may be used for approximating a convex hull. This is +particularly valuable in 5-d and higher where hulls can be +immense. You can use 'Qx C-n' to merge facets as the hull is +being constructed. Then use 'Cn' +and/or 'An' to merge small facets +during post-processing. You can print the n largest facets +with option 'PAn'. You can print +facets whose area is at least n with option 'PFn'. You can output the outer planes +and an interior point with 'FV Fo' and then compute their intersection +with 'qhalf'.

    + +

    To approximate a convex hull in 6-d and higher, use +post-merging with 'Wn' (e.g., qhull +W1e-1 C1e-2 TF2000). Pre-merging with a convexity constraint +(e.g., qhull Qx C-1e-2) often produces a poor approximation or +terminates with a simplex. Option 'QbB' +may help to spread out the data.

    + +

    You will need to experiment to determine a satisfactory set of +options. Use rbox to generate test sets +quickly and Geomview to view +the results. You will probably want to write your own driver for +Qhull using the Qhull library. For example, you could select the +largest facet in each quadrant.

    + + +
    + +

    Up: Home +page for Qhull
    +Up: Qhull manual: Table of +Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull imprecision: Table of Contents + + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optc.htm b/xs/src/qhull/html/qh-optc.htm new file mode 100644 index 0000000000..87308180d7 --- /dev/null +++ b/xs/src/qhull/html/qh-optc.htm @@ -0,0 +1,292 @@ + + + + +Qhull precision options + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull precision options

    + +This section lists the precision options for Qhull. These options are +indicated by an upper-case letter followed by a number. + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Precision options

    + +

    Most users will not need to set these options. They are best +used for approximating a +convex hull. They may also be used for testing Qhull's handling +of precision errors.

    + +

    By default, Qhull uses options 'C-0' for +2-d, 3-d and 4-d, and 'Qx' for 5-d +and higher. These options use facet merging to handle precision +errors. You may also use joggled input 'QJ' +to avoid precision problems. +For more information see Imprecision in Qhull.

    + +
    +
     
    +
    General
    +
    Cn
    +
    centrum radius for post-merging
    +
    C-n
    +
    centrum radius for pre-merging
    +
    An
    +
    cosine of maximum angle for post-merging
    +
    A-n
    +
    cosine of maximum angle for pre-merging
    +
    Qx
    +
    exact pre-merges (allows coplanar facets)
    +
    C-0
    +
    handle all precision errors
    +
    Wn
    +
    min distance above plane for outside points
    +
    + +
    +
     
    +
    Experimental
    +
    Un
    +
    max distance below plane for a new, coplanar point
    +
    En
    +
    max roundoff error for distance computation
    +
    Vn
    +
    min distance above plane for a visible facet
    +
    Rn
    +
    randomly perturb computations by a factor of [1-n,1+n]
    +
    + +
    +
    + +
    + +

    »A-n - cosine of maximum +angle for pre-merging.

    + +

    Pre-merging occurs while Qhull constructs the hull. It is +indicated by 'C-n', 'A-n', or 'Qx'.

    + +

    If the angle between a pair of facet normals is greater than n, +Qhull merges one of the facets into a neighbor. It selects the +facet that is closest to a neighboring facet.

    + +

    For example, option 'A-0.99' merges facets during the +construction of the hull. If the cosine of the angle between +facets is greater than 0.99, one or the other facet is merged. +Qhull accounts for the maximum roundoff error.

    + +

    If 'A-n' is set without 'C-n', then 'C-0' is automatically set.

    + +

    In 5-d and higher, you should set 'Qx' +along with 'A-n'. It skips merges of coplanar facets until after +the hull is constructed and before 'An' and 'Cn' are checked.

    + +

    »An - cosine of maximum angle for +post-merging.

    + +

    Post merging occurs after the hull is constructed. For +example, option 'A0.99' merges a facet if the cosine of the angle +between facets is greater than 0.99. Qhull accounts for the +maximum roundoff error.

    + +

    If 'An' is set without 'Cn', then 'C0' is automatically set.

    + +

    »C-0 - handle all precision +errors

    + +

    Qhull handles precision errors by merging facets. The 'C-0' +option handles all precision errors in 2-d, 3-d, and 4-d. It is +set by default. It may be used in higher dimensions, but +sometimes the facet width grows rapidly. It is usually better to +use 'Qx' in 5-d and higher. +Use 'QJ' to joggle the input +instead of merging facets. +Use 'Q0' to turn both options off.

    + +

    Qhull optimizes 'C-0' ("_zero-centrum") by testing +vertices instead of centrums for adjacent simplices. This may be +slower in higher dimensions if merges decrease the number of +processed points. The optimization may be turned off by setting a +small value such as 'C-1e-30'. See How +Qhull handles imprecision.

    + +

    »C-n - centrum radius for +pre-merging

    + +

    Pre-merging occurs while Qhull constructs the hull. It is +indicated by 'C-n', 'A-n', or 'Qx'.

    + +

    The centrum of a facet is a point on the facet for +testing facet convexity. It is the average of the vertices +projected to the facet's hyperplane. Two adjacent facets are +convex if each centrum is clearly below the other facet.

    + +

    If adjacent facets are non-convex, one of the facets is merged +into a neighboring facet. Qhull merges the facet that is closest +to a neighboring facet.

    + +

    For option 'C-n', n is the centrum radius. For example, +'C-0.001' merges facets whenever the centrum is less than 0.001 +from a neighboring hyperplane. Qhull accounts for roundoff error +when testing the centrum.

    + +

    In 5-d and higher, you should set 'Qx' +along with 'C-n'. It skips merges of coplanar facets until after +the hull is constructed and before 'An' and 'Cn' are checked.

    + +

    »Cn - centrum radius for +post-merging

    + +

    Post-merging occurs after Qhull constructs the hull. It is +indicated by 'Cn' or 'An'.

    + +

    For option 'Cn', n is the centrum +radius. For example, 'C0.001' merges facets when the centrum is +less than 0.001 from a neighboring hyperplane. Qhull accounts for +roundoff error when testing the centrum.

    + +

    Both pre-merging and post-merging may be defined. If only +post-merging is used ('Q0' with +'Cn'), Qhull may fail to produce a hull due to precision errors +during the hull's construction.

    + +

    »En - max roundoff error +for distance computations

    + +

    This allows the user to change the maximum roundoff error +computed by Qhull. The value computed by Qhull may be overly +pessimistic. If 'En' is set too small, then the output may not be +convex. The statistic "max. distance of a new vertex to a +facet" (from option 'Ts') is a +reasonable upper bound for the actual roundoff error.

    + +

    »Rn - randomly perturb +computations

    + +

    This option perturbs every distance, hyperplane, and angle +computation by up to (+/- n * max_coord). It simulates the +effect of roundoff errors. Unless 'En' is +explicitly set, it is adjusted for 'Rn'. The command 'qhull Rn' +will generate a convex hull despite the perturbations. See the Examples section for an example.

    + +

    Options 'Rn C-n' have the effect of 'W2n' +and 'C-2n'. To use time as the random number +seed, use option 'QR-1'.

    + +

    »Un - max distance for a +new, coplanar point

    + +

    This allows the user to set coplanarity. When pre-merging ('C-n ', 'A-n' or 'Qx'), Qhull merges a new point into any +coplanar facets. The default value for 'Un' is 'Vn'.

    + +

    »Vn - min distance for a +visible facet

    + +

    This allows the user to set facet visibility. When adding a +point to the convex hull, Qhull determines all facets that are +visible from the point. A facet is visible if the distance from +the point to the facet is greater than 'Vn'.

    + +

    Without merging, the default value for 'Vn' is the roundoff +error ('En'). With merging, the default value +is the pre-merge centrum ('C-n') in 2-d or 3-d, +or three times that in other dimensions. If the outside width is +specified with option 'Wn ', the maximum, +default value for 'Vn' is 'Wn'.

    + +

    Qhull warns if 'Vn' is greater than 'Wn' and +furthest outside ('Qf') is not +selected; this combination usually results in flipped facets +(i.e., reversed normals).

    + +

    »Wn - min distance above +plane for outside points

    + +

    Points are added to the convex hull only if they are clearly +outside of a facet. A point is outside of a facet if its distance +to the facet is greater than 'Wn'. Without pre-merging, the +default value for 'Wn' is 'En '. If the user +specifies pre-merging and does not set 'Wn', than 'Wn' is set to +the maximum of 'C-n' and maxcoord*(1 - A-n).

    + +

    This option is good for approximating +a convex hull.

    + +

    Options 'Qc' and 'Qi' use the minimum vertex to +distinguish coplanar points from interior points.

    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optf.htm b/xs/src/qhull/html/qh-optf.htm new file mode 100644 index 0000000000..3c7a2b1db2 --- /dev/null +++ b/xs/src/qhull/html/qh-optf.htm @@ -0,0 +1,736 @@ + + + + +Qhull format options (F) + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    +
    + +

    [delaunay] Qhull format options (F)

    + +

    This section lists the format options for Qhull. These options +are indicated by 'F' followed by a letter. See Output, Print, +and Geomview for other output +options.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Additional input & output formats

    + +

    These options allow for automatic processing of Qhull output. +Options 'i', 'o', +'n', and 'p' +may also be used.

    + +
    +
    +
    Summary and control +
    FA +
    compute total area and volume for option 's' + +
    FV +
    print average vertex (interior point for 'qhalf') +
    FQ +
    print command for qhull and input +
    FO +
    print options to stderr or stdout +
    FS +
    print sizes: total area and volume +
    Fs +
    print summary: dim, #points, total vertices and + facets, #vertices, #facets, max outer and inner plane +
    Fd +
    use format for input (offset first) +
    FD +
    use cdd format for normals (offset first) +
    FM +
    print Maple output (2-d and 3-d) +
    +
    +
    Facets, points, and vertices +
    Fa +
    print area for each facet +
    FC +
    print centrum for each facet +
    Fc +
    print coplanar points for each facet +
    Fx +
    print extreme points (i.e., vertices) of convex hull. + +
    FF +
    print facets w/o ridges +
    FI +
    print ID for each facet +
    Fi +
    print inner planes for each facet +
    Fm +
    print merge count for each facet (511 max) +
    FP +
    print nearest vertex for coplanar points +
    Fn +
    print neighboring facets for each facet +
    FN +
    print neighboring facets for each point +
    Fo +
    print outer planes for each facet +
    Ft +
    print triangulation with added points +
    Fv +
    print vertices for each facet +
    +
    +
    Delaunay, Voronoi, and halfspace +
    Fx +
    print extreme input sites of Delaunay triangulation + or Voronoi diagram. +
    Fp +
    print points at halfspace intersections +
    Fi +
    print separating hyperplanes for inner, bounded + Voronoi regions +
    Fo +
    print separating hyperplanes for outer, unbounded + Voronoi regions +
    Fv +
    print Voronoi diagram as ridges for each input pair +
    FC +
    print Voronoi vertex ("center") for each facet
    +
    + +
    + +

    »Fa - print area for each +facet

    + +

    The first line is the number of facets. The remaining lines +are the area for each facet, one facet per line. See 'FA' and 'FS' for computing the total area and volume.

    + +

    Use 'PAn' for printing the n +largest facets. Use option 'PFn' +for printing facets larger than n.

    + +

    For Delaunay triangulations, the area is the area of each +Delaunay triangle. For Voronoi vertices, the area is the area of +the dual facet to each vertex.

    + +

    Qhull uses the centrum and ridges to triangulate +non-simplicial facets. The area for non-simplicial facets is the +sum of the areas for each triangle. It is an approximation of the +actual area. The ridge's vertices are projected to the facet's +hyperplane. If a vertex is far below a facet (qh_WIDEcoplanar in user.h), +the corresponding triangles are ignored.

    + +

    For non-simplicial facets, vertices are often below the +facet's hyperplane. If so, the approximation is less than the +actual value and it may be significantly less.

    + +

    »FA - compute total area +and volume for option 's'

    + +

    With option 'FA', Qhull includes the total area and volume in +the summary ('s'). Option 'FS' also includes the total area and volume. +If facets are +merged, the area and volume are approximations. Option 'FA' is +automatically set for options 'Fa', 'PAn', and 'PFn'. +

    + +

    With 'qdelaunay s FA', Qhull computes the total area of +the Delaunay triangulation. This equals the volume of the convex +hull of the data points. With options 'qdelaunay Qu +s FA', Qhull computes the +total area of the furthest-site Delaunay triangulation. This +equals of the total area of the Delaunay triangulation.

    + +

    See 'Fa' for further details. Option 'FS' also computes the total area and volume.

    + +

    »Fc - print coplanar +points for each facet

    + +

    The output starts with the number of facets. Then each facet +is printed one per line. Each line is the number of coplanar +points followed by the point ids.

    + +

    By default, option 'Fc' reports coplanar points +('Qc'). You may also use +option 'Qi'. Options 'Qi Fc' prints +interior points while 'Qci Fc' prints both coplanar and interior +points. + +

    Each coplanar point or interior point is assigned to the +facet it is furthest above (resp., least below).

    + +

    Use 'Qc p' to print vertex and +coplanar point coordinates. Use 'Fv' +to print vertices.

    + +

    »FC - print centrum or +Voronoi vertex for each facet

    + +

    The output starts with the dimension followed by the number of +facets. Then each facet centrum is printed, one per line. For +qvoronoi, Voronoi vertices are +printed instead.

    + +

    »Fd - use cdd format for +input

    + +

    The input starts with comments. The first comment is reported +in the summary. Data starts after a "begin" line. The +next line is the number of points followed by the dimension plus +one and "real" or "integer". Then the points +are listed with a leading "1" or "1.0". The +data ends with an "end" line.

    + +

    For halfspaces ('qhalf Fd'), +the input format is the same. Each halfspace starts with its +offset. The signs of the offset and coefficients are the +opposite of Qhull's +convention. The first two lines of the input may be an interior +point in 'FV' format.

    + +

    »FD - use cdd format for +normals

    + +

    Option 'FD' prints normals ('n', 'Fo', 'Fi') or points ('p') in cdd format. The first line is the +command line that invoked Qhull. Data starts with a +"begin" line. The next line is the number of normals or +points followed by the dimension plus one and "real". +Then the normals or points are listed with the offset before the +coefficients. The offset for points is 1.0. For normals, +the offset and coefficients use the opposite sign from Qhull. +The data ends with an "end" line.

    + +

    »FF - print facets w/o +ridges

    + +

    Option 'FF' prints all fields of all facets (as in 'f') without printing the ridges. This is +useful in higher dimensions where a facet may have many ridges. +For simplicial facets, options 'FF' and 'f +' are equivalent.

    + +

    »Fi - print inner planes +for each facet

    + +

    The first line is the dimension plus one. The second line is +the number of facets. The remainder is one inner plane per line. +The format is the same as option 'n'.

    + +

    The inner plane is a plane that is below the facet's vertices. +It is an offset from the facet's hyperplane. It includes a +roundoff error for computing the vertex distance.

    + +

    Note that the inner planes for Geomview output ('Gi') include an additional offset for +vertex visualization and roundoff error.

    + +

    »Fi - print separating +hyperplanes for inner, bounded Voronoi regions

    + +

    With qvoronoi, 'Fi' prints the +separating hyperplanes for inner, bounded regions of the Voronoi +diagram. The first line is the number of ridges. Then each +hyperplane is printed, one per line. A line starts with the +number of indices and floats. The first pair of indices indicates +an adjacent pair of input sites. The next d floats are the +normalized coefficients for the hyperplane, and the last float is +the offset. The hyperplane is oriented toward 'QVn' (if defined), or the first input +site of the pair.

    + +

    Use 'Fo' for unbounded regions, +and 'Fv' for the corresponding +Voronoi vertices.

    + +

    Use 'Tv' to verify that the +hyperplanes are perpendicular bisectors. It will list relevant +statistics to stderr. The hyperplane is a perpendicular bisector +if the midpoint of the input sites lies on the plane, all Voronoi +vertices in the ridge lie on the plane, and the angle between the +input sites and the plane is ninety degrees. This is true if all +statistics are zero. Roundoff and computation errors make these +non-zero. The deviations appear to be largest when the +corresponding Delaunay triangles are large and thin; for example, +the Voronoi diagram of nearly cospherical points.

    + +

    »FI - print ID for each +facet

    + +

    Print facet identifiers. These are used internally and listed +with options 'f' and 'FF'. +Options 'Fn ' and 'FN' use +facet identifiers for negative indices.

    + +

    »Fm - print merge count +for each facet

    + +

    The first line is the number of facets. The remainder is the +number of merges for each facet, one per line. At most 511 merges +are reported for a facet. See 'PMn' +for printing the facets with the most merges.

    + +

    »FM - print Maple +output

    + +

    Qhull writes a Maple file for 2-d and 3-d convex hulls, +2-d and 3-d halfspace intersections, +and 2-d Delaunay triangulations. Qhull produces a 2-d +or 3-d plot. + +

    Warning: This option has not been tested in Maple. + +

    [From T. K. Abraham with help from M. R. Feinberg and N. Platinova.] +The following steps apply while working within the +Maple worksheet environment : +

      +
    1. Generate the data and store it as an array . For example, in 3-d, data generated +in Maple is of the form : x[i],y[i],z[i] +

      +

    2. Create a single variable and assign the entire array of data points to this variable. +Use the "seq" command within square brackets as shown in the following example. +(The square brackets are essential for the rest of the steps to work.) +

      +>data:=[seq([x[i],y[i],z[i]],i=1..n)]:# here n is the number of data points + +

    3. Next we need to write the data to a file to be read by qhull. Before +writing the data to a file, make sure that the qhull executable files and +the data file lie in the same subdirectory. If the executable files are +stored in the "C:\qhull3.1\" subdirectory, then save the file in the same +subdirectory, say "C:\qhull3.1\datafile.txt". For the sake of integrity of +the data file , it is best to first ensure that the data file does not +exist before writing into the data file. This can be done by running a +delete command first . To write the data to the file, use the "writedata" +and the "writedata[APPEND]" commands as illustrated in the following example : +

      +>system("del c:\\qhull3.1\\datafile.txt");#To erase any previous versions of the file +
      >writedata("c:\\qhull3.1\\datafile.txt ",[3, nops(data)]);#writing in qhull format +
      >writedata[APPEND]("c:\\ qhull3.1\\datafile.txt ", data);#writing the data points +

    4. +Use the 'FM' option to produce Maple output. Store the output as a ".mpl" file. +For example, using the file we created above, we type the following (in DOS environment) +

      +qconvex s FM <datafile.txt >dataplot.mpl + +

    5. +To read 3-d output in Maple, we use the 'read' command followed by +a 'display3d' command. For example (in Maple environment): +

      +>with (plots): +
      >read `c:\\qhull3.1\\dataplot.mpl`:#IMPORTANT - Note that the punctuation mark used is ' and NOT '. The correct punctuation mark is the one next to the key for "1" (not the punctuation mark near the enter key) +
      > qhullplot:=%: +
      > display3d(qhullplot); +

    + +

    For Delaunay triangulation orthogonal projection is better. + +

    For halfspace intersections, Qhull produces the dual +convex hull. + +

    See Is Qhull available for Maple? +for other URLs. + +

    »Fn - print neighboring +facets for each facet

    + +

    The output starts with the number of facets. Then each facet +is printed one per line. Each line is the number of neighbors +followed by an index for each neighbor. The indices match the +other facet output formats.

    + +

    For simplicial facets, each neighbor is opposite +the corresponding vertex (option 'Fv'). +Do not compare to option 'i'. Option 'i' +orients facets by reversing the order of two vertices. For non-simplicial facets, +the neighbors are unordered. + +

    A negative index indicates an unprinted facet due to printing +only good facets ('Pg', qdelaunay, +qvoronoi). It +is the negation of the facet's ID (option 'FI'). +For example, negative indices are used for facets "at +infinity" in the Delaunay triangulation.

    + +

    »FN - print neighboring +facets for each point

    + +

    The first line is the number of points. Then each point is +printed, one per line. For unassigned points (either interior or +coplanar), the line is "0". For assigned coplanar +points ('Qc'), the line is +"1" followed by the index of the facet that is furthest +below the point. For assigned interior points ('Qi'), the line is "1" +followed by the index of the facet that is least above the point. +For vertices that do not belong to good facet, the line is +"0"

    + +

    For vertices of good facets, the line is the number of +neighboring facets followed by the facet indices. The indices +correspond to the other 'F' formats. In 4-d +and higher, the facets are sorted by index. In 3-d, the facets +are in adjacency order (not oriented).

    + +

    A negative index indicates an unprinted facet due to printing +only good facets (qdelaunay, +qvoronoi, 'Pdk', +'Pg'). It is the negation of the +facet's ID (' FI'). For example, negative +indices are used for facets "at infinity" in the +Delaunay triangulation.

    + +

    For Voronoi vertices, option 'FN' lists the vertices of the +Voronoi region for each input site. Option 'FN' lists the regions +in site ID order. Option 'FN' corresponds to the second half of +option 'o'. To convert from 'FN' to 'o', replace negative indices with zero +and increment non-negative indices by one.

    + +

    If you are using the Qhull +library or C++ interface, option 'FN' has the side effect of reordering the +neighbors for a vertex

    + +

    »Fo - print outer planes +for each facet

    + +

    The first line is the dimension plus one. The second line is +the number of facets. The remainder is one outer plane per line. +The format is the same as option 'n'.

    + +

    The outer plane is a plane that is above all points. It is an +offset from the facet's hyperplane. It includes a roundoff error +for computing the point distance. When testing the outer plane +(e.g., 'Tv'), another roundoff error +should be added for the tested point.

    + +

    If outer planes are not checked ('Q5') +or not computed (!qh_MAXoutside), the maximum, computed outside +distance is used instead. This can be much larger than the actual +outer planes.

    + +

    Note that the outer planes for Geomview output ('G') include an additional offset for +vertex/point visualization, 'lines closer,' and roundoff error.

    + +

    »Fo - print separating +hyperplanes for outer, unbounded Voronoi regions

    + +

    With qvoronoi, 'Fo' prints the +separating hyperplanes for outer, unbounded regions of the +Voronoi diagram. The first line is the number of ridges. Then +each hyperplane is printed, one per line. A line starts with the +number of indices and floats. The first pair of indices indicates +an adjacent pair of input sites. The next d floats are the +normalized coefficients for the hyperplane, and the last float is +the offset. The hyperplane is oriented toward 'QVn' (if defined), or the first input +site of the pair.

    + +

    Option 'Fo' gives the hyperplanes for the unbounded rays of +the unbounded regions of the Voronoi diagram. Each hyperplane +goes through the midpoint of the corresponding input sites. The +rays are directed away from the input sites.

    + +

    Use 'Fi' for bounded regions, +and 'Fv' for the corresponding +Voronoi vertices. Use 'Tv' to verify +that the corresponding Voronoi vertices lie on the hyperplane.

    + +

    »FO - print list of +selected options

    + +

    Lists selected options and default values to stderr. +Additional 'FO's are printed to stdout.

    + +

    »Fp - print points at +halfspace intersections

    + +

    The first line is the number of intersection points. The +remainder is one intersection point per line. A intersection +point is the intersection of d or more halfspaces from +'qhalf'. It corresponds to a +facet of the dual polytope. The "infinity" point +[-10.101,-10.101,...] indicates an unbounded intersection.

    + +

    If [x,y,z] are the dual facet's normal coefficients and b<0 +is its offset, the halfspace intersection occurs at +[x/-b,y/-b,z/-b] plus the interior point. If b>=0, the +halfspace intersection is unbounded.

    + +

    »FP - print nearest +vertex for coplanar points

    + +

    The output starts with the number of coplanar points. Then +each coplanar point is printed one per line. Each line is the +point ID of the closest vertex, the point ID of the coplanar +point, the corresponding facet ID, and the distance. Sort the +lines to list the coplanar points nearest to each vertex.

    + +

    Use options 'Qc' and/or 'Qi' with 'FP'. Options 'Qc FP' prints +coplanar points while 'Qci FP' prints coplanar and interior +points. Option 'Qc' is automatically selected if 'Qi' is not +selected. + +

    For Delaunay triangulations (qdelaunay +or qvoronoi), a coplanar point is nearly +incident to a vertex. The distance is the distance in the +original point set.

    + +

    If imprecision problems are severe, Qhull will delete input +sites when constructing the Delaunay triangulation. Option 'FP' will +list these points along with coincident points.

    + +

    If there are many coplanar or coincident points and non-simplicial +facets are triangulated ('Qt'), option +'FP' may be inefficient. It redetermines the original vertex set +for each coplanar point.

    + +

    »FQ - print command for +qhull and input

    + +

    Prints qhull and input command, e.g., "rbox 10 s | qhull +FQ". Option 'FQ' may be repeated multiple times.

    + +

    »Fs - print summary

    + +

    The first line consists of number of integers ("10") +followed by the: +

      +
    • dimension +
    • number of points +
    • number of vertices +
    • number of facets +
    • number of vertices selected for output +
    • number of facets selected for output +
    • number of coplanar points for selected facets +
    • number of nonsimplicial or merged facets selected for + output +
    • number of deleted vertices
    • +
    • number of triangulated facets ('Qt')
    • +
    + +

    The second line consists of the number of reals +("2") followed by the: +

      +
    • maximum offset to an outer plane +
    • minimum offset to an inner plane.
    • +
    +Roundoff and joggle are included. +

    + +

    For Delaunay triangulations and Voronoi diagrams, the +number of deleted vertices should be zero. If greater than zero, then the +input is highly degenerate and coplanar points are not necessarily coincident +points. For example, 'RBOX 1000 s W1e-13 t995138628 | QHULL d Qbb' reports +deleted vertices; the input is nearly cospherical.

    + +

    Later versions of Qhull may produce additional integers or reals.

    + +

    »FS - print sizes

    + +

    The first line consists of the number of integers +("0"). The second line consists of the number of reals +("2"), followed by the total facet area, and the total +volume. Later versions of Qhull may produce additional integers +or reals.

    + +

    The total volume measures the volume of the intersection of +the halfspaces defined by each facet. It is computed from the +facet area. Both area and volume are approximations for +non-simplicial facets. See option 'Fa ' for +further notes. Option 'FA ' also computes the total area and volume.

    + +

    »Ft - print triangulation

    + +

    Prints a triangulation with added points for non-simplicial +facets. The output is

    + +
      +
    • The first line is the dimension +
    • The second line is the number of points, the number + of facets, and the number of ridges. +
    • All of the input points follow, one per line. +
    • The centrums follow, one per non-simplicial facet +
    • Then the facets follow as a list of point indices + preceded by the number of points. The simplices are + oriented.
    • +
    + +

    For convex hulls with simplicial facets, the output is the +same as option 'o'.

    + +

    The added points are the centrums of the non-simplicial +facets. Except for large facets, the centrum is the average +vertex coordinate projected to the facet's hyperplane. Large +facets may use an old centrum to avoid recomputing the centrum +after each merge. In either case, the centrum is clearly below +neighboring facets. See Precision issues. +

    + +

    The new simplices will not be clearly convex with their +neighbors and they will not satisfy the Delaunay property. They +may even have a flipped orientation. Use triangulated input ('Qt') for Delaunay triangulations. + +

    For Delaunay triangulations with simplicial facets, the output is the +same as option 'o' without the lifted +coordinate. Since 'Ft' is invalid for merged Delaunay facets, option +'Ft' is not available for qdelaunay or qvoronoi. It may be used with +joggled input ('QJ') or triangulated output ('Qt'), for example, rbox 10 c G 0.01 | qhull d QJ Ft

    + +

    If you add a point-at-infinity with 'Qz', +it is printed after the input sites and before any centrums. It +will not be used in a Delaunay facet.

    + +

    »Fv - print vertices for +each facet

    + +

    The first line is the number of facets. Then each facet is +printed, one per line. Each line is the number of vertices +followed by the corresponding point ids. Vertices are listed in +the order they were added to the hull (the last one added is the +first listed). +

    +

    Option 'i' also lists the vertices, +but it orients facets by reversing the order of two +vertices. Option 'i' triangulates non-simplicial, 4-d and higher facets by +adding vertices for the centrums. +

    + +

    »Fv - print Voronoi +diagram

    + +

    With qvoronoi, 'Fv' prints the +Voronoi diagram or furthest-site Voronoi diagram. The first line +is the number of ridges. Then each ridge is printed, one per +line. The first number is the count of indices. The second pair +of indices indicates a pair of input sites. The remaining indices +list the corresponding ridge of Voronoi vertices. Vertex 0 is the +vertex-at-infinity. It indicates an unbounded ray.

    + +

    All vertices of a ridge are coplanar. If the ridge is +unbounded, add the midpoint of the pair of input sites. The +unbounded ray is directed from the Voronoi vertices to infinity.

    + +

    Use 'Fo' for separating +hyperplanes of outer, unbounded regions. Use 'Fi' for separating hyperplanes of +inner, bounded regions.

    + +

    Option 'Fv' does not list ridges that require more than one +midpoint. For example, the Voronoi diagram of cospherical points +lists zero ridges (e.g., 'rbox 10 s | qvoronoi Fv Qz'). +Other examples are the Voronoi diagrams of a rectangular mesh +(e.g., 'rbox 27 M1,0 | qvoronoi Fv') or a point set with +a rectangular corner (e.g., +'rbox P4,4,4 P4,2,4 P2,4,4 P4,4,2 10 | qvoronoi Fv'). +Both cases miss unbounded rays at the corners. +To determine these ridges, surround the points with a +large cube (e.g., 'rbox 10 s c G2.0 | qvoronoi Fv Qz'). +The cube needs to be large enough to bound all Voronoi regions of the original point set. +Please report any other cases that are missed. If you +can formally describe these cases or +write code to handle them, please send email to qhull@qhull.org.

    + +

    »FV - print average +vertex

    + +

    The average vertex is the average of all vertex coordinates. +It is an interior point for halfspace intersection. The first +line is the dimension and "1"; the second line is the +coordinates. For example,

    + +
    +

    qconvex FV n | qhalf Fp

    +
    + +

    prints the extreme points of the original point set (roundoff +included).

    + +

    »Fx - print extreme +points (vertices) of convex hulls and Delaunay triangulations

    + +

    The first line is the number of points. The following lines +give the index of the corresponding points. The first point is +'0'.

    + +

    In 2-d, the extreme points (vertices) are listed in +counterclockwise order (by qh_ORIENTclock in user.h).

    + +

    In 3-d and higher convex hulls, the extreme points (vertices) +are sorted by index. This is the same order as option 'p' when it doesn't include coplanar or +interior points.

    + +

    For Delaunay triangulations, 'Fx' lists the extreme +points of the input sites (i.e., the vertices of their convex hull). The points +are unordered.

    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: +Sept. 25, 1995 --- Last modified: see top +

    + + diff --git a/xs/src/qhull/html/qh-optg.htm b/xs/src/qhull/html/qh-optg.htm new file mode 100644 index 0000000000..a56e29df10 --- /dev/null +++ b/xs/src/qhull/html/qh-optg.htm @@ -0,0 +1,274 @@ + + + +Qhull Geomview options (G) + + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + + +

    [delaunay] Qhull Geomview options (G)

    + +This section lists the Geomview options for Qhull. These options are +indicated by 'G' followed by a letter. See +Output, Print, +and Format for other output options. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Geomview output options

    + +

    Geomview is the graphical +viewer for visualizing Qhull output in 2-d, 3-d and 4-d.

    + +

    Geomview displays each facet of the convex hull. The color of +a facet is determined by the coefficients of the facet's normal +equation. For imprecise hulls, Geomview displays the inner and +outer hull. Geomview can also display points, ridges, vertices, +coplanar points, and facet intersections.

    + +

    For 2-d Delaunay triangulations, Geomview displays the +corresponding paraboloid. Geomview displays the 2-d Voronoi +diagram. For halfspace intersections, it displays the +dual convex hull.

    + +
    +
     
    +
    General
    +
    G
    +
    display Geomview output
    +
    Gt
    +
    display transparent 3-d Delaunay triangulation
    +
    GDn
    +
    drop dimension n in 3-d and 4-d output
    + +
     
    +
     
    +
    Specific
    +
    Ga
    +
    display all points as dots
    +
    Gc
    +
    display centrums (2-d, 3-d)
    +
    Gp
    +
    display coplanar points and vertices as radii
    +
    Gh
    +
    display hyperplane intersections
    +
    Gi
    +
    display inner planes only (2-d, 3-d)
    +
    Go
    +
    display outer planes only (2-d, 3-d)
    +
    Gr
    +
    display ridges (3-d)
    +
    Gv
    +
    display vertices as spheres
    +
    Gn
    +
    do not display planes
    + +
    + +
    + +

    »G - produce output for +viewing with Geomview

    + +

    By default, option 'G' displays edges in 2-d, outer planes in +3-d, and ridges in 4-d.

    + +

    A ridge can be explicit or implicit. An explicit ridge is a (d-1)-dimensional +simplex between two facets. In 4-d, the explicit ridges are +triangles. An implicit ridge is the topological intersection of +two neighboring facets. It is the union of explicit ridges.

    + +

    For non-simplicial 4-d facets, the explicit ridges can be +quite complex. When displaying a ridge in 4-d, Qhull projects the +ridge's vertices to one of its facets' hyperplanes. Use 'Gh' to project ridges to the intersection of both +hyperplanes. This usually results in a cleaner display.

    + +

    For 2-d Delaunay triangulations, Geomview displays the +corresponding paraboloid. Geomview displays the 2-d Voronoi +diagram. For halfspace intersections, it displays the +dual convex hull. + +

    »Ga - display all +points as dots

    + +

    Each input point is displayed as a green dot.

    + +

    »Gc - display centrums +(3-d)

    + +

    The centrum is defined by a green radius sitting on a blue +plane. The plane corresponds to the facet's hyperplane. If you +sight along a facet's hyperplane, you will see that all +neighboring centrums are below the facet. The radius is defined +by 'C-n' or 'Cn'.

    + +

    »GDn - drop dimension +n in 3-d and 4-d output

    + +

    The result is a 2-d or 3-d object. In 4-d, this corresponds to +viewing the 4-d object from the nth axis without perspective. +It's best to view 4-d objects in pieces. Use the 'Pdk' 'Pg' +'PG' 'QGn' +and 'QVn' options to select a few +facets. If one of the facets is perpendicular to an axis, then +projecting along that axis will show the facet exactly as it is +in 4-d. If you generate many facets, use Geomview's ginsu +module to view the interior

    + +

    To view multiple 4-d dimensions at once, output the object +without 'GDn' and read it with Geomview's ndview. As you +rotate the object in one set of dimensions, you can see how it +changes in other sets of dimensions.

    + +

    For additional control over 4-d objects, output the object +without 'GDn' and read it with Geomview's 4dview. You +can slice the object along any 4-d plane. You can also flip the +halfspace that's deleted when slicing. By combining these +features, you can get some interesting cross sections.

    + +

    »Gh - display +hyperplane intersections (3-d, 4-d)

    + +

    In 3-d, the intersection is a black line. It lies on two +neighboring hyperplanes, c.f., the blue squares associated with +centrums ('Gc '). In 4-d, the ridges are +projected to the intersection of both hyperplanes. If you turn on +edges (Geomview's 'appearances' menu), each triangle corresponds +to one ridge. The ridges may overlap each other.

    + +

    »Gi - display inner +planes only (2-d, 3-d)

    + +

    The inner plane of a facet is below all of its vertices. It is +parallel to the facet's hyperplane. The inner plane's color is +the opposite of the outer plane's color, i.e., [1-r,1-g,1-b] . +Its edges are determined by the vertices.

    + +

    »Gn - do not display +planes

    + +

    By default, Geomview displays the precise plane (no merging) +or both inner and output planes (if merging). If merging, +Geomview does not display the inner plane if the the difference +between inner and outer is too small.

    + +

    »Go - display outer +planes only (2-d, 3-d)

    + +

    The outer plane of a facet is above all input points. It is +parallel to the facet's hyperplane. Its color is determined by +the facet's normal, and its edges are determined by the vertices.

    + +

    »Gp - display coplanar +points and vertices as radii

    + +

    Coplanar points ('Qc'), interior +points ('Qi'), outside points ('TCn' or 'TVn'), +and vertices are displayed as red and yellow radii. The radii are +perpendicular to the corresponding facet. Vertices are aligned +with an interior point. The radii define a ball which corresponds +to the imprecision of the point. The imprecision is the maximum +of the roundoff error, the centrum radius, and maxcoord * (1 - +A-n). It is at +least 1/20'th of the maximum coordinate, and ignores post merging +if pre-merging is done.

    + +

    If 'Gv' (print vertices as +spheres) is also selected, option 'Gp' displays coplanar +points as radii. Select options Qc' +and/or 'Qi'. Options 'Qc Gpv' displays +coplanar points while 'Qci Gpv' displays coplanar and interior +points. Option 'Qc' is automatically selected if 'Qi' is not +selected with options 'Gpv'. + +

    »Gr - display ridges +(3-d)

    + +

    A ridge connects the two vertices that are shared by +neighboring facets. It is displayed in green. A ridge is the +topological edge between two facets while the hyperplane +intersection is the geometric edge between two facets. Ridges are +always displayed in 4-d.

    + +

    »Gt - transparent 3-d +Delaunay

    + +

    A 3-d Delaunay triangulation looks like a convex hull with +interior facets. Option 'Gt' removes the outside ridges to reveal +the outermost facets. It automatically sets options 'Gr' and 'GDn'. See example eg.17f.delaunay.3.

    + +

    »Gv - display vertices +as spheres (2-d, 3-d)

    + +

    The radius of the sphere corresponds to the imprecision of the +data. See 'Gp' for determining the radius.

    + + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-opto.htm b/xs/src/qhull/html/qh-opto.htm new file mode 100644 index 0000000000..e7b21745c1 --- /dev/null +++ b/xs/src/qhull/html/qh-opto.htm @@ -0,0 +1,353 @@ + + + + +Qhull output options + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull output options

    + +

    This section lists the output options for Qhull. These options +are indicated by lower case characters. See Formats, Print, and Geomview for other output +options.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Output options

    + +

    Qhull prints its output to standard out. All output is printed +text. The default output is a summary (option 's'). +Other outputs may be specified as follows.

    + +
    +
    f
    +
    print all fields of all facets
    +
    n
    +
    print hyperplane normals with offsets
    +
    m
    +
    print Mathematica output (2-d and 3-d)
    +
    o
    +
    print OFF file format (dim, points and facets)
    +
    s
    +
    print summary to stderr
    +
    p
    +
    print vertex and point coordinates
    +
    i
    +
    print vertices incident to each facet
    +
     
    +
     
    +
    Related options
    +
    F
    +
    additional input/output formats
    +
    G
    +
    Geomview output
    +
    P
    +
    Print options
    +
    Ft
    +
    print triangulation with added points
    +
     
    +
    + +
    + +

    »f - print all fields of +all facets

    + +

    Print all fields of all facets. +The facet is the primary data structure for +Qhull. + +

    Option 'f' is for +debugging. Most of the fields are available via the 'F' options. If you need specialized +information from Qhull, you can use the Qhull library or C++ interface.

    + +

    Use the 'FF' option to print the +facets but not the ridges.

    + +

    »i - print vertices +incident to each facet

    + +

    The first line is the number of facets. The remaining lines +list the vertices for each facet, one facet per line. The indices +are 0-relative indices of the corresponding input points. The +facets are oriented. Option 'Fv' +displays an unoriented list of vertices with a vertex count per +line. Options 'o' and 'Ft' displays coordinates for each +vertex prior to the vertices for each facet.

    + +

    Simplicial facets (e.g., triangles in 3-d) consist of d +vertices. Non-simplicial facets in 3-d consist of 4 or more +vertices. For example, a facet of a cube consists of 4 vertices. +Use option 'Qt' to triangulate non-simplicial facets.

    + +

    For 4-d and higher convex hulls and 3-d and higher Delaunay +triangulations, d vertices are listed for all facets. A +non-simplicial facet is triangulated with its centrum and each +ridge. The index of the centrum is higher than any input point. +Use option 'Fv' to list the vertices +of non-simplicial facets as is. Use option 'Ft' to print the coordinates of the +centrums as well as those of the input points.

    + +

    »m - print Mathematica +output

    + +

    Qhull writes a Mathematica file for 2-d and 3-d convex hulls, +2-d and 3-d halfspace intersections, +and 2-d Delaunay triangulations. Qhull produces a list of +objects that you can assign to a variable in Mathematica, for +example: "list= << <outputfilename> ". +If the object is 2-d, it can be visualized by "Show[Graphics[list]] +". For 3-d objects the command is "Show[Graphics3D[list]] +". Now the object can be manipulated by commands of the +form "Show[%, <parametername> -> +<newvalue>]".

    + +

    For Delaunay triangulation orthogonal projection is better. +This can be specified, for example, by "BoxRatios: +Show[%, BoxRatios -> {1, 1, 1e-8}]". To see the +meaningful side of the 3-d object used to visualize 2-d Delaunay, +you need to change the viewpoint: "Show[%, ViewPoint +-> {0, 0, -1}]". By specifying different viewpoints +you can slowly rotate objects.

    + +

    For halfspace intersections, Qhull produces the dual +convex hull. + +

    See Is Qhull available for Mathematica? +for URLs. + +

    »n - print hyperplane +normals with offsets

    + +

    The first line is the dimension plus one. The second line is +the number of facets. The remaining lines are the normals for +each facet, one normal per line. The facet's offset follows its +normal coefficients.

    + +

    The normals point outward, i.e., the convex hull satisfies Ax +<= -b where A is the matrix of coefficients and b +is the vector of offsets.

    + +

    A point is inside or below a hyperplane if its distance +to the hyperplane is negative. A point is outside or above a hyperplane +if its distance to the hyperplane is positive. Otherwise a point is on or +coplanar to the hyperplane. + +

    If cdd output is specified ('FD'), +Qhull prints the command line, the keyword "begin", the +number of facets, the dimension (plus one), the keyword +"real", and the normals for each facet. The facet's +negative offset precedes its normal coefficients (i.e., if the +origin is an interior point, the offset is positive). Qhull ends +the output with the keyword "end".

    + +

    »o - print OFF file format +

    + +

    The output is:

    + +
      +
    • The first line is the dimension
    • +
    • The second line is the number of points, the number of + facets, and the number of ridges.
    • +
    • All of the input points follow, one per line.
    • +
    • Then Qhull prints the vertices for each facet. Each facet + is on a separate line. The first number is the number of + vertices. The remainder is the indices of the + corresponding points. The vertices are oriented in 2-d, + 3-d, and in simplicial facets.
    • +
    + +

    Option 'Ft' prints the same +information with added points for non-simplicial facets.

    + +

    Option 'i' displays vertices +without the point coordinates. Option 'p' +displays the point coordinates without vertex and facet information.

    + +

    In 3-d, Geomview can load the file directly if you delete the +first line (e.g., by piping through 'tail +2').

    + +

    For Voronoi diagrams (qvoronoi), option +'o' prints Voronoi vertices and Voronoi regions instead of input +points and facets. The first vertex is the infinity vertex +[-10.101, -10.101, ...]. Then, option 'o' lists the vertices in +the Voronoi region for each input site. The regions appear in +site ID order. In 2-d, the vertices of a Voronoi region are +sorted by adjacency (non-oriented). In 3-d and higher, the +Voronoi vertices are sorted by index. See the 'FN' option for listing Voronoi regions +without listing Voronoi vertices.

    + +

    If you are using the Qhull library, options 'v o' have the +side effect of reordering the neighbors for a vertex.

    + +

    »p - print vertex and +point coordinates

    + +

    The first line is the dimension. The second line is the number +of vertices. The remaining lines are the vertices, one vertex per +line. A vertex consists of its point coordinates

    + +

    With the 'Gc' and 'Gi' options, option 'p' also prints +coplanar and interior points respectively.

    + +

    For qvoronoi, it prints the +coordinates of each Voronoi vertex.

    + +

    For qdelaunay, it prints the +input sites as lifted to a paraboloid. For qhalf +it prints the dual points. For both, option 'p' is the same as the first +section of option 'o'.

    + +

    Use 'Fx' to list the point ids of +the extreme points (i.e., vertices).

    + +

    If a subset of the facets is selected ('Pdk', 'PDk', +'Pg' options), option 'p' only +prints vertices and points associated with those facets.

    + +

    If cdd-output format is selected ('FD'), +the first line is "begin". The second line is the +number of vertices, the dimension plus one, and "real". +The vertices follow with a leading "1". Output ends +with "end".

    + +

    »s - print summary to +stderr

    + +

    The default output of Qhull is a summary to stderr. Options 'FS' and 'Fs' +produce the same information for programs. Note: Windows 95 and 98 +treats stderr the same as stdout. Use option 'TO file' to separate +stderr and stdout.

    + +

    The summary lists the number of input points, the dimension, +the number of vertices in the convex hull, and the number of +facets in the convex hull. It lists the number of selected +("good") facets for options 'Pg', +'Pdk', qdelaunay, +or qvoronoi (Delaunay triangulations only +use the lower half of a convex hull). It lists the number of +coplanar points. For Delaunay triangulations without 'Qc', it lists the total number of +coplanar points. It lists the number of simplicial facets in +the output.

    + +

    The terminology depends on the output structure.

    + +

    The summary lists these statistics:

    + +
      +
    • number of points processed by Qhull
    • +
    • number of hyperplanes created
    • +
    • number of distance tests (not counting statistics, + summary, and checking)
    • +
    • number of merged facets (if any)
    • +
    • number of distance tests for merging (if any)
    • +
    • CPU seconds to compute the hull
    • +
    • the maximum joggle for 'QJ'
      + or, the probability of precision errors for 'QJ TRn' +
    • +
    • total area and volume (if computed, see 'FS' 'FA' + 'Fa' 'PAn')
    • +
    • max. distance of a point above a facet (if non-zero)
    • +
    • max. distance of a vertex below a facet (if non-zero)
    • +
    + +

    The statistics include intermediate hulls. For example 'rbox d +D4 | qhull' reports merged facets even though the final hull is +simplicial.

    + +

    Qhull starts counting CPU seconds after it has read and +projected the input points. It stops counting before producing +output. In the code, CPU seconds measures the execution time of +function qhull() in libqhull.c. If the number of CPU +seconds is clearly wrong, check qh_SECticks in user.h.

    + +

    The last two figures measure the maximum distance from a point +or vertex to a facet. They are not printed if less than roundoff +or if not merging. They account for roundoff error in computing +the distance (c.f., option 'Rn'). +Use 'Fs' to report the maximum outer +and inner plane.

    + +

    A number may appear in parentheses after the maximum distance +(e.g., 2.1x). It is the ratio between the maximum distance and +the worst-case distance due to merging two simplicial facets. It +should be small for 2-d, 3-d, and 4-d, and for higher dimensions +with 'Qx'. It is not printed if less +than 0.05.

    + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optp.htm b/xs/src/qhull/html/qh-optp.htm new file mode 100644 index 0000000000..9c6df90f5a --- /dev/null +++ b/xs/src/qhull/html/qh-optp.htm @@ -0,0 +1,253 @@ + + + + +Qhull print options (P) + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull print options (P)

    + +This section lists the print options for Qhull. These options are +indicated by 'P' followed by a letter. See +Output, Geomview, +and Format for other output options. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Print options

    +
    +
    +
     
    +
    General
    +
    Pp
    +
    do not report precision problems
    +
    Po
    +
    force output despite precision problems
    +
    Po
    +
    if error, output neighborhood of facet
    +
     
    +
     
    +
    Select
    +
    Pdk:n
    +
    print facets with normal[k] >= n (default 0.0)
    +
    PDk:n
    +
    print facets with normal[k] <= n
    +
    PFn
    +
    print facets whose area is at least n
    +
    Pg
    +
    print good facets only (needs 'QGn' + or 'QVn ')
    +
    PMn
    +
    print n facets with most merges
    +
    PAn
    +
    print n largest facets by area
    +
    PG
    +
    print neighbors of good facets
    +
    +
    +
    + +

    »PAn - keep n largest +facets by area

    + +

    The n largest facets are marked good for printing. This +may be useful for approximating +a hull. Unless 'PG' is set, 'Pg' +is automatically set.

    + +

    »Pdk:n - print facet if +normal[k] >= n

    + +

    For a given output, print only those facets with normal[k] >= n +and drop the others. For example, 'Pd0:0.5' prints facets with normal[0] +>= 0.5 . The default value of n is zero. For +example in 3-d, 'Pd0d1d2' prints facets in the positive octant. +

    +If no facets match, the closest facet is returned.

    +

    +On Windows 95, do not combine multiple options. A 'd' is considered +part of a number. For example, use 'Pd0:0.5 Pd1:0.5' instead of +'Pd0:0.5d1:0.5'. + +

    »PDk:n - print facet if +normal[k] <= n

    + +

    For a given output, print only those facets with normal[k] <= n +and drop the others. +For example, 'PD0:0.5' prints facets with normal[0] +<= 0.5 . The default value of n is zero. For +example in 3-d, 'PD0D1D2' displays facets in the negative octant. +

    +If no facets match, the closest facet is returned.

    + +

    In 2-d, 'd G PD2' displays the Delaunay triangulation instead +of the corresponding paraboloid.

    + +

    Be careful of placing 'Dk' or 'dk' immediately after a real +number. Some compilers treat the 'D' as a double precision +exponent.

    + +

    »PFn - keep facets whose +area is at least n

    + +

    The facets with area at least n are marked good for +printing. This may be useful for approximating a hull. Unless +'PG' is set, 'Pg' is +automatically set.

    + +

    »Pg - print good facets

    + +

    Qhull can mark facets as "good". This is used to

    + +
      +
    • mark the lower convex hull for Delaunay triangulations + and Voronoi diagrams
    • +
    • mark the facets that are visible from a point (the 'QGn ' option)
    • +
    • mark the facets that contain a point (the 'QVn' option).
    • +
    • indicate facets with a large enough area (options 'PAn' and 'PFn')
    • +
    + +

    Option 'Pg' only prints good facets that +also meet 'Pdk' and 'PDk' +options. It is automatically set for options 'PAn', +'PFn ', 'QGn', +and 'QVn'.

    + +

    »PG - print neighbors of +good facets

    + +

    Option 'PG' can be used with or without option 'Pg' +to print the neighbors of good facets. For example, options 'QGn' and 'QVn' +print the horizon facets for point n.

    + +

    »PMn - keep n facets with +most merges

    + +

    The n facets with the most merges are marked good for +printing. This may be useful for approximating a hull. Unless +'PG' is set, 'Pg' is +automatically set.

    + +

    Use option 'Fm' to print merges +per facet. + +

    »Po - force output despite +precision problems

    + +

    Use options 'Po' and 'Q0' if you +can not merge facets, triangulate the output ('Qt'), +or joggle the input (QJ). + +

    Option 'Po' can not force output when +duplicate ridges or duplicate facets occur. It may produce +erroneous results. For these reasons, merged facets, joggled input, or exact arithmetic are better.

    + +

    If you need a simplicial Delaunay triangulation, use +joggled input 'QJ' or triangulated +output 'Ft'. + +

    Option 'Po' may be used without 'Q0' +to remove some steps from Qhull or to output the neighborhood of +an error.

    + +

    Option 'Po' may be used with option 'Q5') +to skip qh_check_maxout (i.e., do not determine the maximum outside distance). +This can save a significant amount of time. + +

    If option 'Po' is used,

    + +
      +
    • most precision errors allow Qhull to continue.
    • +
    • verify ('Tv') does not check + coplanar points.
    • +
    • points are not partitioned into flipped facets and a + flipped facet is always visible to a point. This may + delete flipped facets from the output.
    • +
    + +

    »Po - if error, output +neighborhood of facet

    + +

    If an error occurs before the completion of Qhull and tracing +is not active, 'Po' outputs a neighborhood of the erroneous +facets (if any). It uses the current output options.

    + +

    See 'Po' - force output despite +precision problems. + +

    »Pp - do not report +precision problems

    + +

    With option 'Pp', Qhull does not print statistics about +precision problems, and it removes some of the warnings. It +removes the narrow hull warning.

    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optq.htm b/xs/src/qhull/html/qh-optq.htm new file mode 100644 index 0000000000..2edbb1fd43 --- /dev/null +++ b/xs/src/qhull/html/qh-optq.htm @@ -0,0 +1,731 @@ + + + + +Qhull control options (Q) + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull control options (Q)

    + +

    This section lists the control options for Qhull. These +options are indicated by 'Q' followed by a letter.

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Qhull control options

    + +
    +
     
    +
    General
    +
    Qu
    +
    compute upper hull for furthest-site Delaunay + triangulation
    +
    Qc
    +
    keep coplanar points with nearest facet
    +
    Qi
    +
    keep interior points with nearest facet
    +
    QJ
    +
    joggled input to avoid precision problems
    +
    Qt
    +
    triangulated output
    +
     
    +
     
    +
    Precision handling
    +
    Qz
    +
    add a point-at-infinity for Delaunay triangulations
    +
    Qx
    +
    exact pre-merges (allows coplanar facets)
    +
    Qs
    +
    search all points for the initial simplex
    +
    Qbb
    +
    scale last coordinate to [0,m] for Delaunay
    +
    Qv
    +
    test vertex neighbors for convexity
    +
     
    +
     
    +
    Transform input
    +
    Qbk:0Bk:0
    +
    drop dimension k from input
    +
    QRn
    +
    random rotation (n=seed, n=0 time, n=-1 time/no rotate)
    +
    Qbk:n
    +
    scale coord[k] to low bound of n (default -0.5)
    +
    QBk:n
    +
    scale coord[k] to upper bound of n (default 0.5)
    +
    QbB
    +
    scale input to fit the unit cube
    +
     
    +
     
    +
    Select facets
    +
    QVn
    +
    good facet if it includes point n, -n if not
    +
    QGn
    +
    good facet if visible from point n, -n for not visible
    +
    Qg
    +
    only build good facets (needs 'QGn', 'QVn ', or 'Pdk')
    +
     
    +
     
    +
    Experimental
    +
    Q4
    +
    avoid merging old facets into new facets
    +
    Q5
    +
    do not correct outer planes at end of qhull
    +
    Q3
    +
    do not merge redundant vertices
    +
    Q6
    +
    do not pre-merge concave or coplanar facets
    +
    Q0
    +
    do not pre-merge facets with 'C-0' or 'Qx'
    +
    Q8
    +
    ignore near-interior points
    +
    Q2
    +
    merge all non-convex at once instead of independent sets
    +
    Qf
    +
    partition point to furthest outside facet
    +
    Q7
    +
    process facets depth-first instead of breadth-first
    +
    Q9
    +
    process furthest of furthest points
    +
    Q10
    +
    no special processing for narrow distributions
    +
    Q11
    +
    copy normals and recompute centrums for tricoplanar facets
    +
    Q12
    +
    do not error on wide merge due to duplicate ridge and nearly coincident points
    +
    Qm
    +
    process points only if they would increase the max. outer + plane
    +
    Qr
    +
    process random outside points instead of furthest one
    +
    Q1
    +
    sort merges by type instead of angle
    +
    + +
    + +

    »Qbb - scale the last +coordinate to [0,m] for Delaunay

    + +

    After scaling with option 'Qbb', the lower bound of the last +coordinate will be 0 and the upper bound will be the maximum +width of the other coordinates. Scaling happens after projecting +the points to a paraboloid and scaling other coordinates.

    + +

    Option 'Qbb' is automatically set for qdelaunay +and qvoronoi. Option 'Qbb' is automatically set for joggled input 'QJ'.

    + +

    Option 'Qbb' should be used for Delaunay triangulations with +integer coordinates. Since the last coordinate is the sum of +squares, it may be much larger than the other coordinates. For +example, rbox 10000 D2 B1e8 | qhull d has precision +problems while rbox 10000 D2 B1e8 | qhull d Qbb is OK.

    + +

    »QbB - scale the input to +fit the unit cube

    + +

    After scaling with option 'QbB', the lower bound will be -0.5 +and the upper bound +0.5 in all dimensions. For different bounds +change qh_DEFAULTbox in user.h (0.5 is best for Geomview).

    + +

    For Delaunay and Voronoi diagrams, scaling happens after +projection to the paraboloid. Under precise arithmetic, scaling +does not change the topology of the convex hull. Scaling may +reduce precision errors if coordinate values vary widely.

    + +

    »Qbk:n - scale coord[k] +to low bound

    + +

    After scaling, the lower bound for dimension k of the input +points will be n. 'Qbk' scales coord[k] to -0.5.

    + +

    »QBk:n - scale coord[k] +to upper bound

    + +

    After scaling, the upper bound for dimension k of the input +points will be n. 'QBk' scales coord[k] to 0.5.

    + +

    »Qbk:0Bk:0 - drop +dimension k from the input points

    + +

    Drop dimension k from the input points. For example, +'Qb1:0B1:0' deletes the y-coordinate from all input points. This +allows the user to take convex hulls of sub-dimensional objects. +It happens before the Delaunay and Voronoi transformation. +It happens after the halfspace transformation for both the data +and the feasible point.

    + +

    »Qc - keep coplanar points +with nearest facet

    + +

    During construction of the hull, a point is coplanar if it is +between 'Wn' above and 'Un' below a facet's hyperplane. A +different definition is used for output from Qhull.

    + +

    For output, a coplanar point is above the minimum vertex +(i.e., above the inner plane). With joggle ('QJ'), a coplanar point includes points +within one joggle of the inner plane.

    + +

    With option 'Qc', output formats 'p ', +'f', 'Gp', +'Fc', 'FN', +and 'FP' will print the coplanar +points. With options 'Qc Qi' these outputs +include the interior points.

    + +

    For Delaunay triangulations (qdelaunay +or qvoronoi), a coplanar point is a point +that is nearly incident to a vertex. All input points are either +vertices of the triangulation or coplanar.

    + +

    Qhull stores coplanar points with a facet. While constructing +the hull, it retains all points within qh_RATIOnearInside +(user.h) of a facet. In qh_check_maxout(), it uses these points +to determine the outer plane for each facet. With option 'Qc', +qh_check_maxout() retains points above the minimum vertex for the +hull. Other points are removed. If qh_RATIOnearInside is wrong or +if options 'Q5 Q8' are set, a +coplanar point may be missed in the output (see Qhull limitations).

    + +

    »Qf - partition point to +furthest outside facet

    + +

    After adding a new point to the convex hull, Qhull partitions +the outside points and coplanar points of the old, visible +facets. Without the 'f ' option and +merging, it assigns a point to the first facet that it is outside +('Wn'). When merging, it assigns a +point to the first facet that is more than several times outside +(see qh_DISToutside in user.h).

    + +

    If option 'Qf' is selected, Qhull performs a directed search +(no merging) or an exhaustive search (merging) of new facets. +Option 'Qf' may reduce precision errors if pre-merging does not +occur.

    + +

    Option 'Q9' processes the furthest of all +furthest points.

    + +

    »Qg - only build good +facets (needs 'QGn' 'QVn' or 'Pdk')

    + +

    Qhull has several options for defining and printing good +facets. With the 'Qg' option, Qhull will only +build those facets that it needs to determine the good facets in +the output. This may speed up Qhull in 2-d and 3-d. It is +useful for furthest-site Delaunay +triangulations (qdelaunay Qu, +invoke with 'qhull d Qbb Qu Qg'). +It is not effective in higher +dimensions because many facets see a given point and contain a +given vertex. It is not guaranteed to work for all combinations.

    + +

    See 'QGn', 'QVn', and 'Pdk' for defining good facets, and 'Pg' and 'PG' +for printing good facets and their neighbors. If pre-merging ('C-n') is not used and there are +coplanar facets, then 'Qg Pg' may produce a different result than +'Pg'.

    + +

    »QGn - good facet if +visible from point n, -n for not visible

    + +

    With option 'QGn', a facet is good (see 'Qg' +and 'Pg') if it is visible from +point n. If n < 0, a facet is good if it is not visible +from point n. Point n is not added to the hull (unless 'TCn' or 'TPn').

    + +

    With rbox, use the 'Pn,m,r' option +to define your point; it will be point 0 ('QG0').

    + +

    »Qi - keep interior points +with nearest facet

    + +

    Normally Qhull ignores points that are clearly interior to the +convex hull. With option 'Qi', Qhull treats interior points the +same as coplanar points. Option 'Qi' does not retain coplanar +points. You will probably want 'Qc ' as well.

    + +

    Option 'Qi' is automatically set for 'qdelaunay +Qc' and 'qvoronoi +Qc'. If you use +'qdelaunay Qi' or 'qvoronoi +Qi', option 's' reports all nearly +incident points while option 'Fs' +reports the number of interior points (should always be zero).

    + +

    With option 'Qi', output formats 'p', +'f','Gp', +'Fc', 'FN', +and 'FP' include interior points.

    + +

    »QJ or QJn - joggled +input to avoid precision errors

    + +

    Option 'QJ' or 'QJn' joggles each input coordinate by adding a +random number in the range [-n,n]. If a precision error occurs, +It tries again. If precision errors still occur, Qhull increases n +ten-fold and tries again. The maximum value for increasing n +is 0.01 times the maximum width of the input. Option 'QJ' selects +a default value for n. User.h +defines these parameters and a maximum number of retries. See Merged facets or joggled input.

    + +

    Users of joggled input should consider converting to +triangulated output ('Qt'). Triangulated output is +approximately 1000 times more accurate than joggled input. + +

    Option 'QJ' also sets 'Qbb' for +Delaunay triangulations and Voronoi diagrams. It does not set +'Qbb' if 'Qbk:n' or 'QBk:n' are set.

    + +

    If 'QJn' is set, Qhull does not merge facets unless requested +to. All facets are simplicial (triangular in 2-d). This may be +important for your application. You may also use triangulated output +('Qt') or Option 'Ft'. + +

    Qhull adjusts the outer and inner planes for 'QJn' ('Fs'). They are increased by sqrt(d)*n +to account for the maximum distance between a joggled point and +the corresponding input point. Coplanar points ('Qc') require an additional sqrt(d)*n +since vertices and coplanar points may be joggled in opposite +directions.

    + +

    For Delaunay triangulations (qdelaunay), joggle +happens before lifting the input sites to a paraboloid. Instead of +'QJ', you may use triangulated output ('Qt')

    + +

    This option is deprecated for Voronoi diagrams (qvoronoi). +It triangulates cospherical points, leading to duplicated Voronoi vertices.

    + +

    By default, 'QJn' uses a fixed random number seed. To use time +as the random number seed, select 'QR-1'. +The summary ('s') will show the +selected seed as 'QR-n'. + +

    With 'QJn', Qhull does not error on degenerate hyperplane +computations. Except for Delaunay and Voronoi computations, Qhull +does not error on coplanar points.

    + +

    Use option 'FO' to display the +selected options. Option 'FO' displays the joggle and the joggle +seed. If Qhull restarts, it will redisplay the options.

    + +

    Use option 'TRn' to estimate the +probability that Qhull will fail for a given 'QJn'. + +

    »Qm - only process points +that increase the maximum outer plane

    + +

    Qhull reports the maximum outer plane in its summary ('s'). With option 'Qm', Qhull does not +process points that are below the current, maximum outer plane. +This is equivalent to always adjusting 'Wn +' to the maximum distance of a coplanar point to a facet. It +is ignored for points above the upper convex hull of a Delaunay +triangulation. Option 'Qm' is no longer important for merging.

    + +

    »Qr - process random +outside points instead of furthest ones

    + +

    Normally, Qhull processes the furthest point of a facet's +outside points. Option 'Qr' instead selects a random outside +point for processing. This makes Qhull equivalent to the +randomized incremental algorithms.

    + +

    The original randomization algorithm by Clarkson and Shor ['89] used a conflict list which +is equivalent to Qhull's outside set. Later randomized algorithms +retained the previously constructed facets.

    + +

    To compare Qhull to the randomized algorithms with option +'Qr', compare "hyperplanes constructed" and +"distance tests". Qhull does not report CPU time +because the randomization is inefficient.

    + +

    »QRn - random rotation

    + +

    Option 'QRn' randomly rotates the input. For Delaunay +triangulations (qdelaunay or qvoronoi), +it rotates the lifted points about +the last axis.

    + +

    If n=0, use time as the random number seed. If n>0, +use n as the random number seed. If n=-1, don't rotate +but use time as the random number seed. If n<-1, +don't rotate but use n as the random number seed.

    + +

    »Qs - search all points +for the initial simplex

    + +

    Qhull constructs an initial simplex from d+1 points. It +selects points with the maximum and minimum coordinates and +non-zero determinants. If this fails, it searches all other +points. In 8-d and higher, Qhull selects points with the minimum +x or maximum coordinate (see qh_initialvertices in poly2.c ). +It rejects points with nearly zero determinants. This should work +for almost all input sets.

    + +

    If Qhull can not construct an initial simplex, it reports a +descriptive message. Usually, the point set is degenerate and one +or more dimensions should be removed ('Qbk:0Bk:0'). +If not, use option 'Qs'. It performs an exhaustive search for the +best initial simplex. This is expensive is high dimensions.

    + +

    »Qt - triangulated output

    + +

    By default, qhull merges facets to handle precision errors. This +produces non-simplicial facets (e.g., the convex hull of a cube has 6 square +facets). Each facet is non-simplicial because it has four vertices. + +

    Use option 'Qt' to triangulate all non-simplicial facets before generating +results. Alternatively, use joggled input ('QJ') to +prevent non-simplical facets. Unless 'Pp' is set, +qhull produces a warning if 'QJ' and 'Qt' are used together. + +

    For Delaunay triangulations (qdelaunay), triangulation +occurs after lifting the input sites to a paraboloid and computing the convex hull. +

    + +

    Option 'Qt' is deprecated for Voronoi diagrams (qvoronoi). +It triangulates cospherical points, leading to duplicated Voronoi vertices.

    + +

    Option 'Qt' may produce degenerate facets with zero area.

    + +

    Facet area and hull volumes may differ with and without +'Qt'. The triangulations are different and different triangles +may be ignored due to precision errors. + +

    With sufficient merging, the ridges of a non-simplicial facet may share more than two neighboring facets. If so, their triangulation ('Qt') will fail since two facets have the same vertex set.

    + +

    »Qu - compute upper hull +for furthest-site Delaunay triangulation

    + +

    When computing a Delaunay triangulation (qdelaunay +or qvoronoi), +Qhull computes both the the convex hull of points on a +paraboloid. It normally prints facets of the lower hull. These +correspond to the Delaunay triangulation. With option 'Qu', Qhull +prints facets of the upper hull. These correspond to the furthest-site Delaunay triangulation +and the furthest-site Voronoi diagram.

    + +

    Option 'qhull d Qbb Qu Qg' may improve the speed of option +'Qu'. If you use the Qhull library, a faster method is 1) use +Qhull to compute the convex hull of the input sites; 2) take the +extreme points (vertices) of the convex hull; 3) add one interior +point (e.g., +'FV', the average of d extreme points); 4) run +'qhull d Qbb Qu' or 'qhull v Qbb Qu' on these points.

    + +

    »Qv - test vertex +neighbors for convexity

    + +

    Normally, Qhull tests all facet neighbors for convexity. +Non-neighboring facets which share a vertex may not satisfy the +convexity constraint. This occurs when a facet undercuts the +centrum of another facet. They should still be convex. Option +'Qv' extends Qhull's convexity testing to all neighboring facets +of each vertex. The extra testing occurs after the hull is +constructed..

    + +

    »QVn - good facet if it +includes point n, -n if not

    + +

    With option 'QVn', a facet is good ('Qg', +'Pg') if one of its vertices is +point n. If n<0, a good facet does not include point n. + +

    If options 'PG' +and 'Qg' are not set, option 'Pg' +(print only good) +is automatically set. +

    + +

    Option 'QVn' behaves oddly with options 'Fx' +and 'qvoronoi Fv'. + +

    If used with option 'Qg' (only process good facets), point n is +either in the initial simplex or it is the first +point added to the hull. Options 'QVn Qg' require either 'QJ' or +'Q0' (no merging).

    + +

    »Qx - exact pre-merges +(allows coplanar facets)

    + +

    Option 'Qx' performs exact merges while building the hull. +Option 'Qx' is set by default in 5-d and higher. Use option 'Q0' to not use 'Qx' by default. Unless otherwise +specified, option 'Qx' sets option 'C-0'. +

    + +

    The "exact" merges are merging a point into a +coplanar facet (defined by 'Vn ', 'Un', and 'C-n'), +merging concave facets, merging duplicate ridges, and merging +flipped facets. Coplanar merges and angle coplanar merges ('A-n') are not performed. Concavity +testing is delayed until a merge occurs.

    + +

    After the hull is built, all coplanar merges are performed +(defined by 'C-n' and 'A-n'), then post-merges are performed +(defined by 'Cn' and 'An'). If facet progress is logged ('TFn'), Qhull reports each phase and +prints intermediate summaries and statistics ('Ts').

    + +

    Without 'Qx' in 5-d and higher, options 'C-n' and 'A-n' +may merge too many facets. Since redundant vertices are not +removed effectively, facets become increasingly wide.

    + +

    Option 'Qx' may report a wide facet. With 'Qx', coplanar +facets are not merged. This can produce a "dent" in an +intermediate hull. If a point is partitioned into a dent and it +is below the surrounding facets but above other facets, one or +more wide facets will occur. In practice, this is unlikely. To +observe this effect, run Qhull with option 'Q6' +which doesn't pre-merge concave facets. A concave facet makes a +large dent in the intermediate hull.

    + +

    Option 'Qx' may set an outer plane below one of the input +points. A coplanar point may be assigned to the wrong facet +because of a "dent" in an intermediate hull. After +constructing the hull, Qhull double checks all outer planes with +qh_check_maxout in poly2.c . If a coplanar point is +assigned to the wrong facet, qh_check_maxout may reach a local +maximum instead of locating all coplanar facets. This appears to +be unlikely.

    + +

    »Qz - add a +point-at-infinity for Delaunay triangulations

    + +

    Option 'Qz' adds a point above the paraboloid of lifted sites +for a Delaunay triangulation. It allows the Delaunay +triangulation of cospherical sites. It reduces precision errors +for nearly cospherical sites.

    + +

    »Q0 - no merging with C-0 +and Qx

    + +

    Turn off default merge options 'C-0' +and 'Qx'.

    + +

    With 'Q0' and without other pre-merge options, Qhull ignores +precision issues while constructing the convex hull. This may +lead to precision errors. If so, a descriptive warning is +generated. See Precision issues.

    + +

    »Q1 - sort merges by type +instead of angle

    + +

    Qhull sorts the coplanar facets before picking a subset of the +facets to merge. It merges concave and flipped facets first. Then +it merges facets that meet at a steep angle. With 'Q1', Qhull +sorts merges by type (coplanar, angle coplanar, concave) instead +of by angle. This may make the facets wider.

    + +

    »Q2 - merge all non-convex +at once instead of independent sets

    + +

    With 'Q2', Qhull merges all facets at once instead of +performing merges in independent sets. This may make the facets +wider.

    + +

    »Q3 - do not merge +redundant vertices

    + +

    With 'Q3', Qhull does not remove redundant vertices. In 6-d +and higher, Qhull never removes redundant vertices (since +vertices are highly interconnected). Option 'Q3' may be faster, +but it may result in wider facets. Its effect is easiest to see +in 3-d and 4-d.

    + +

    »Q4 - avoid merging old +facets into new facets

    + +

    With 'Q4', Qhull avoids merges of an old facet into a new +facet. This sometimes improves facet width and sometimes makes it +worse.

    + +

    »Q5 - do not correct outer +planes at end of qhull

    + +

    When merging facets or approximating a hull, Qhull tests +coplanar points and outer planes after constructing the hull. It +does this by performing a directed search (qh_findbest in geom.c). +It includes points that are just inside the hull.

    + +

    With options 'Q5' or 'Po', Qhull +does not test outer planes. The maximum outer plane is used +instead. Coplanar points ('Qc') are defined by +'Un'. An input point may be outside +of the maximum outer plane (this appears to be unlikely). An +interior point may be above 'Un' +from a hyperplane.

    + +

    Option 'Q5' may be used if outer planes are not needed. Outer +planes are needed for options 's', 'G', 'Go ', +'Fs', 'Fo', +'FF', and 'f'.

    + +

    »Q6 - do not pre-merge +concave or coplanar facets

    + +

    With 'Q6', Qhull does not pre-merge concave or coplanar +facets. This demonstrates the effect of "dents" when +using 'Qx'.

    + +

    »Q7 - depth-first +processing instead of breadth-first

    + +

    With 'Q7', Qhull processes facets in depth-first order instead +of breadth-first order. This may increase the locality of +reference in low dimensions. If so, Qhull may be able to use +virtual memory effectively.

    + +

    In 5-d and higher, many facets are visible from each +unprocessed point. So each iteration may access a large +proportion of allocated memory. This makes virtual memory +ineffectual. Once real memory is used up, Qhull will spend most +of its time waiting for I/O.

    + +

    Under 'Q7', Qhull runs slower and the facets may be wider.

    + +

    »Q8 - ignore near-interior +points

    + +

    With 'Q8' and merging, Qhull does not process interior points +that are near to a facet (as defined by qh_RATIOnearInside in +user.h). This avoids partitioning steps. It may miss a coplanar +point when adjusting outer hulls in qh_check_maxout(). The best +value for qh_RATIOnearInside is not known. Options 'Q8 Qc' may be sufficient.

    + +

    »Q9 - process furthest of +furthest points

    + +

    With 'Q9', Qhull processes the furthest point of all outside +sets. This may reduce precision problems. The furthest point of +all outside sets is not necessarily the furthest point from the +convex hull.

    + +

    »Q10 - no special processing +for narrow distributions

    + +

    With 'Q10', Qhull does not special-case narrow distributions. +See Limitations of merged facets for +more information. + +

    »Q11 - copy normals and recompute +centrums for +tricoplanar facets

    + +Option 'Qt' triangulates non-simplicial facets +into "tricoplanar" facets. +Normally tricoplanar facets share the same normal, centrum, and +Voronoi vertex. They can not be merged or replaced. With +option 'Q11', Qhull duplicates the normal and Voronoi vertex. +It recomputes the centrum. + +

    Use 'Q11' if you use the Qhull library to add points +incrementally and call qh_triangulate() after each point. +Otherwise, Qhull will report an error when it tries to +merge and replace a tricoplanar facet. + +

    With sufficient merging and new points, option 'Q11' may +lead to precision problems such +as duplicate ridges and concave facets. For example, if qh_triangulate() +is added to qh_addpoint(), RBOX 1000 s W1e-12 t1001813667 P0 | QHULL d Q11 Tv, +reports an error due to a duplicate ridge. + +

    »Q12 - do not error +on wide merge due to duplicate ridge and nearly coincident points

    + +

    In 3-d and higher Delaunay Triangulations or 4-d and higher convex hulls, multiple, +nearly coincident points may lead to very wide facets. An error is reported if a +merge across a duplicate ridge would increase the facet width by 100x or more. + +

    Use option 'Q12' to log a warning instead of throwing an error. + +

    For Delaunay triangulations, a bounding box may alleviate this error (e.g., rbox 500 C1,1E-13 t c G1 | qhull d). +This avoids the ill-defined edge between upper and lower convex hulls. +The problem will be fixed in a future release of Qhull. + +

    To demonstrate the problem, use rbox option 'Cn,r,m' to generate nearly coincident points. +For more information, see "Nearly coincident points on an edge" +in Nearly coincident points on an edge. + + +


    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-optt.htm b/xs/src/qhull/html/qh-optt.htm new file mode 100644 index 0000000000..0709f58c66 --- /dev/null +++ b/xs/src/qhull/html/qh-optt.htm @@ -0,0 +1,278 @@ + + + + +Qhull trace options (T) + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    [delaunay] Qhull trace options (T)

    + +This section lists the trace options for Qhull. These options are +indicated by 'T' followed by a letter. + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    » Programs + Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    Trace options

    + +
    +
     
    +
    General
    +
    Tz
    +
    output error information to stdout instead of stderr
    +
    TI file
    +
    input data from a file
    +
    TO file
    +
    output results to a file
    +
    Ts
    +
    print statistics
    +
    TFn
    +
    report progress whenever n or more facets created
    +
    TRn
    +
    rerun qhull n times
    +
    Tv
    +
    verify result: structure, convexity, and point inclusion
    + +
     
    +
     
    +
    Debugging
    +
    Tc
    +
    check frequently during execution
    +
    TVn
    +
    stop qhull after adding point n
    +
    TCn
    +
    stop qhull after building cone for point n
    +
    TV-n
    +
    stop qhull before adding point n
    +
    T4
    +
    trace at level n, 4=all, 5=mem/gauss, -1= events
    +
    TWn
    +
    trace merge facets when width > n
    +
    TMn
    +
    turn on tracing at merge n
    +
    TPn
    +
    turn on tracing when point n added to hull
    +
    + +
    + +

    »Tc - check frequently +during execution

    + +

    Qhull includes frequent checks of its data structures. Option +'Tc' will catch most inconsistency errors. It is slow and should +not be used for production runs. Option 'Tv' +performs the same checks after the hull is constructed.

    + +

    »TCn - stop qhull after +building cone for point n

    + +

    Qhull builds a cone from the point to its horizon facets. +Option 'TCn' stops Qhull just after building the cone. The output +for 'f' includes the cone and the old +hull.'.

    + +

    »TFn - report summary +whenever n or more facets created

    + +

    Option 'TFn' reports progress whenever more than n facets are +created. The test occurs just before adding a new point to the +hull. During post-merging, 'TFn' reports progress after more than +n/2 merges.

    + +

    »TI file - input data from file

    + +

    Input data from 'file' instead of stdin. The filename may not +contain spaces or use single quotes. +You may use I/O redirection +instead (e.g., 'rbox 10 | qdelaunay >results').

    + +

    »TMn - turn on tracing at +merge n

    + +

    Turn on tracing at n'th merge.

    + +

    »Tn - trace at level n

    + +

    Qhull includes full execution tracing. 'T-1' traces events. +'T1' traces the overall execution of the program. 'T2' and 'T3' +trace overall execution and geometric and topological events. +'T4' traces the algorithm. 'T5' includes information about memory +allocation and Gaussian elimination. 'T1' is useful for logging +progress of Qhull in high dimensions.

    + +

    Option 'Tn' can produce large amounts of output. Use options 'TPn', 'TWn', and 'TMn' to selectively +turn on tracing. Since all errors report the last processed +point, option 'TPn' is particularly useful.

    + +

    Different executions of the same program may produce different +traces and different results. The reason is that Qhull uses hashing +to match ridges of non-simplicial facets. For performance reasons, +the hash computation uses +memory addresses which may change across executions. + +

    »TO file - output results to file

    + +

    Redirect stdout to 'file'. The filename may be enclosed in +single quotes. Unix and Windows NT users may use I/O redirection +instead (e.g., 'rbox 10 | qdelaunay >results').

    +

    +Windows95 users should always use 'TO file'. If they use I/O redirection, +error output is not sent to the console. Qhull uses single quotes instead +of double quotes because a missing double quote can +freeze Windows95 (e.g., do not run, rbox 10 | qhull TO "x)

    +

    + +

    »TPn - turn on tracing +when point n added to hull

    + +

    Option 'TPn' turns on tracing when point n is added to +the hull. It also traces partitions of point n. This option +reduces the output size when tracing. It is the normal +method to determine the cause of a Qhull error. All Qhull errors +report the last point added. + +

    Use options 'TPn TVn' to +trace the addition of point n to the convex hull and stop when done.

    + +

    If used with option 'TWn', +'TPn' turns off tracing after adding point n to the hull. +Use options 'TPn TWn' to +trace the addition of point n to the convex hull, partitions +of point n, and wide merges.

    + +

    »TRn - rerun qhull n times

    + +

    Option 'TRn' reruns Qhull n times. It is usually used +with 'QJn' to determine the probability +that a given joggle will fail. The summary +('s') lists the failure +rate and the precision errors that occurred. +Option 'Ts' will report statistics for +all of the runs. Trace and output options only apply to the last +run. An event trace, 'T-1' reports events for all runs. + +

    Tracing applies to the last run of Qhull. If an error +is reported, the options list the run number as "_run". +To trace this run, set 'TRn' to the same value.

    + +

    »Ts - print statistics

    + +

    Option 'Ts' collects statistics and prints them to stderr. For +Delaunay triangulations, the angle statistics are restricted to +the lower or upper envelope.

    + +

    »Tv - verify result: +structure, convexity, and point inclusion

    + +

    Option 'Tv' checks the topological structure, convexity, and +point inclusion. If precision problems occurred, facet convexity +is tested whether or not 'Tv' is selected. Option 'Tv' does not +check point inclusion if forcing output with 'Po', or if 'Q5' +is set.

    + +

    The convex hull of a set of points is the smallest polytope +that includes the points. Option 'Tv' tests point inclusion. +Qhull verifies that all points are below all outer planes +(facet->maxoutside). Point inclusion is exhaustive if merging +or if the facet-point product is small enough; otherwise Qhull +verifies each point with a directed search (qh_findbest). To +force an exhaustive test when using option 'C-0' (default), use 'C-1e-30' instead.

    + +

    Point inclusion testing occurs after producing output. It +prints a message to stderr unless option 'Pp' is used. This allows the user to +interrupt Qhull without changing the output.

    + +

    With 'qvoronoi Fi' +and 'qvoronoi Fo', +option 'Tv' collects statistics that verify all Voronoi vertices lie +on the separating hyperplane, and for bounded regions, all +separating hyperplanes are perpendicular bisectors. + +

    »TV-n - stop qhull before +adding point n

    + +

    Qhull adds one point at a time to the convex hull. See how Qhull adds a point. Option 'TV-n' +stops Qhull just before adding a new point. Output shows the hull +at this time.

    + +

    »TVn - stop qhull after +adding point n

    + +

    Option 'TVn' stops Qhull after it has added point n. Output +shows the hull at this time.

    + +

    »TWn - trace merge facets +when width > n

    + +

    Along with TMn, this option allows the user to determine the +cause of a wide merge.

    +

    »Tz - send all output to +stdout

    + +

    Redirect stderr to stdout.

    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qh-quick.htm b/xs/src/qhull/html/qh-quick.htm new file mode 100644 index 0000000000..9d52e7d750 --- /dev/null +++ b/xs/src/qhull/html/qh-quick.htm @@ -0,0 +1,495 @@ + + + + +Qhull quick reference + + + + +

    Up: Home +page for Qhull
    +Up: Qhull manual
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull internals
    +To: Qhull functions, macros, and data structures
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + +

    [cone] Qhull quick reference

    + +This section lists all programs and options in Qhull. + +

    Copyright © 1995-2015 C.B. Barber

    + +

    +  +


    +Qhull programs +

    » Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +
    +
    qconvex -- convex hull
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qdelaunay -- Delaunay triangulation
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qdelaunay Qu -- furthest-site Delaunay triangulation
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qhalf -- halfspace intersection about a point
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    qvoronoi -- Voronoi diagram
    +
    synopsis • input • outputs • + controls • graphics • notes • conventions • options
    +
     
    +
    qvoronoi Qu -- furthest-site Voronoi diagram
    +
    synopsis • input • outputs • controls • graphics • notes • conventions • options
    +
     
    +
    rbox -- generate point distributions for qhull
    +
    synopsis • outputs • examples • notes • options
    +
     
    +
    qhull -- convex hull and related structures
    +
    synopsis • input • outputs • controls • options
    +
    +  +
    +Qhull options + +

    » Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions

    + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    'Fa' +Farea +'FA' +FArea-total +'Fc' +Fcoplanars +'FC' +FCentrums + +
    'Fd' +Fd-cdd-in +'FD' +FD-cdd-out +'FF' +FF-dump-xridge +'Fi' +Finner + +
    'Fi' +Finner_bounded +'FI' +FIDs +'Fm' +Fmerges +'FM' +FMaple + +
    'Fn' +Fneighbors +'FN' +FNeigh-vertex +'Fo' +Fouter +'Fo' +Fouter_unbounded + +
    'FO' +FOptions +'Fp' +Fpoint-intersect +'FP' +FPoint_near +'FQ' +FQhull + +
    'Fs' +Fsummary +'FS' +FSize +'Ft' +Ftriangles +'Fv' +Fvertices + +
    'Fv' +Fvoronoi +'FV' +FVertex-ave +'Fx' +Fxtremes + +Merged facets or joggled input + +
     
    'PAn' +PArea-keep +'Pdk:n' +Pdrop_low +'PDk:n' +Pdrop_high +'Pg' +Pgood + +
    'PFn' +PFacet_area_keep +'PG' +PGood_neighbors +'PMn' +PMerge-keep +'Po' +Poutput_forced + +
    'Po' +Poutput_error +'Pp' +Pprecision_not + +
     
    'd' +delaunay +'v' +voronoi +'G' +Geomview +'H' +Halfspace + +
    'f' +facet_dump +'i' +incidences +'m' +mathematica +'n' +normals + +
    'o' +OFF_format +'p' +points +'s' +summary + +
     
    'Gv' +Gvertices +'Gp' +Gpoints +'Ga' +Gall_points +'Gn' +Gno_planes + +
    'Gi' +Ginner +'Gc' +Gcentrums +'Gh' +Ghyperplanes +'Gr' +Gridges + +
    'Go' +Gouter +'GDn' +GDrop_dim +'Gt' +Gtransparent + +
     
    'T4' +T4_trace +'Tc' +Tcheck_often +'Ts' +Tstatistics +'Tv' +Tverify + +
    'Tz' +Tz_stdout +'TFn' +TFacet_log +'TI file' +TInput_file +'TPn' +TPoint_trace + +
    'TMn' +TMerge_trace +'TO file' +TOutput_file +'TRn' +TRerun +'TWn' +TWide_trace + +
    'TV-n' +TVertex_stop_before +
    'TVn' +TVertex_stop_after +'TCn' +TCone_stop_after + +
     
    'A-n' +Angle_max_pre +'An' +Angle_max_post +'C-0' +Centrum_roundoff +'C-n' +Centrum_size_pre + +
    'Cn' +Centrum_size_post +'En' +Error_round +'Rn' +Random_dist +'Vn' +Visible_min + +
    'Un' +Ucoplanar_max +'Wn' +Wide_outside + +
     
    'Qbk:n' +Qbound_low +'QBk:n' +QBound_high +'Qbk:0Bk:0' +Qbound_drop +'QbB' +QbB-scale-box + +
    'Qbb' +Qbb-scale-last +'Qc' +Qcoplanar +'Qf' +Qfurthest +'Qg' +Qgood_only + +
    'QGn' +QGood_point +'Qi' +Qinterior +'Qm' +Qmax_out +'QJn' +QJoggle + +
    'Qr' +Qrandom +'QRn' +QRotate +'Qs' +Qsearch_1st +'Qt' +Qtriangulate + +
    'Qu' +QupperDelaunay +'QVn' +QVertex_good +'Qv' +Qvneighbors +'Qx' +Qxact_merge + +
    'Qz' +Qzinfinite + +
     
    'Q0' +Q0_no_premerge +'Q1' +Q1_no_angle +'Q2' +Q2_no_independ +'Q3' +Q3_no_redundant + +
    'Q4' +Q4_no_old +'Q5' +Q5_no_check_out +'Q6' +Q6_no_concave +'Q7' +Q7_depth_first + +
    'Q8' +Q8_no_near_in +'Q9' +Q9_pick_furthest +'Q10' +Q10_no_narrow +'Q11' +Q11_trinormals +
    + + +


    + +

    Up: Home +page for Qhull
    +Up: Qhull manual
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: Qhull internals
    +To: Qhull functions, macros, and data structures
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser
    + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qhalf.htm b/xs/src/qhull/html/qhalf.htm new file mode 100644 index 0000000000..c87fe719ed --- /dev/null +++ b/xs/src/qhull/html/qhalf.htm @@ -0,0 +1,626 @@ + + + + +qhalf -- halfspace intersection about a point + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    [halfspace]qhalf -- halfspace intersection about a point

    + +

    The intersection of a set of halfspaces is a polytope. The +polytope may be unbounded. See Preparata & Shamos ['85] for a discussion. In low +dimensions, halfspace intersection may be used for linear +programming. + +

    +
    +

    Example: rbox c | qconvex FQ FV + n | qhalf Fp

    +
    Print the intersection of the facets of a cube. rbox c + generates the vertices of a cube. qconvex FV n returns of average + of the cube's vertices (in this case, the origin) and the halfspaces + that define the cube. qhalf Fp computes the intersection of + the halfspaces about the origin. The intersection is the vertices + of the original cube.
    + +

    Example: rbox c d G0.55 | qconvex FQ FV + n | qhalf Fp

    +
    Print the intersection of the facets of a cube and a diamond. There + are 24 facets and 14 intersection points. Four facets define each diamond + vertex. Six facets define each cube vertex. +
    + +

    Example: rbox c d G0.55 | qconvex FQ FV + n | qhalf Fp + Qt

    +
    Same as above except triangulate before computing + the intersection points. Three facets define each intersection + point. There are two duplicates of the diamond and four duplicates of the cube. +
    + +

    Example: rbox 10 s t10 | qconvex FQ FV + n | qhalf Fp Fn

    +
    Print the intersection of the facets of the convex hull of 10 cospherical points. + Include the intersection points and the neighboring intersections. + As in the previous examples, the intersection points are nearly the same as the + original input points. +
    +
    +
    + +

    In Qhull, a halfspace is defined by the points on or below a hyperplane. +The distance of each point to the hyperplane is less than or equal to zero. + +

    Qhull computes a halfspace intersection by the geometric +duality between points and halfspaces. +See halfspace examples, +qhalf notes, and +option 'p' of qhalf outputs.

    + +

    Qhalf's outputs are the intersection +points (Fp) and +the neighboring intersection points (Fn). +For random inputs, halfspace +intersections are usually defined by more than d halfspaces. See the sphere example. + +

    You can try triangulated output ('Qt') and joggled input ('QJ'). +It demonstrates that triangulated output is more accurate than joggled input. + +

    If you use 'Qt' (triangulated output), all +halfspace intersections are simplicial (e.g., three halfspaces per +intersection in 3-d). In 3-d, if more than three halfspaces intersect +at the same point, triangulated output will produce +duplicate intersections, one for each additional halfspace. See the third example, or +add 'Qt' to the sphere example.

    + +

    If you use 'QJ' (joggled input), all halfspace +intersections are simplicial. This may lead to nearly identical +intersections. For example, either replace 'Qt' with 'QJ' above, or add +'QJ' to the sphere example. +See Merged facets or joggled input.

    + +

    The 'qhalf' program is equivalent to +'qhull H' in 2-d to 4-d, and +'qhull H Qx' +in 5-d and higher. It disables the following Qhull +options: d n v Qbb QbB Qf Qg Qm +Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    +
    + +

    »qhalf synopsis

    +
    +qhalf- halfspace intersection about a point.
    +    input (stdin): [dim, 1, interior point]
    +                   dim+1, n
    +                   halfspace coefficients + offset
    +    comments start with a non-numeric character
    +
    +options (qhalf.htm):
    +    Hn,n - specify coordinates of interior point
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Tv   - verify result: structure, convexity, and redundancy
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    Fp   - intersection coordinates
    +    Fv   - non-redundant halfspaces incident to each intersection
    +    Fx   - non-redundant halfspaces
    +    o    - OFF file format (dual convex hull)
    +    G    - Geomview output (dual convex hull)
    +    m    - Mathematica output (dual convex hull)
    +    QVn  - print intersections for halfspace n, -n if not
    +    TO file - output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox d | qconvex n | qhalf s H0,0,0 Fp
    +    rbox c | qconvex FV n | qhalf s i
    +    rbox c | qconvex FV n | qhalf s o
    +
    + +

    »qhalf input

    + +
    +

    The input data on stdin consists of:

    +
      +
    • [optional] interior point +
        +
      • dimension +
      • 1 +
      • coordinates of interior point +
      +
    • dimension + 1 +
    • number of halfspaces
    • +
    • halfspace coefficients followed by offset
    • +
    + +

    Use I/O redirection (e.g., qhalf < data.txt), a pipe (e.g., rbox c | qconvex FV n | qhalf), +or the 'TI' option (e.g., qhalf TI data.txt). + +

    Qhull needs an interior point to compute the halfspace +intersection. An interior point is clearly inside all of the halfspaces. +A point is inside a halfspace if its distance to the corresponding hyperplane is negative. + +

    The interior point may be listed at the beginning of the input (as shown above). +If not, option +'Hn,n' defines the interior point as +[n,n,0,...] where 0 is the default coordinate (e.g., +'H0' is the origin). Use linear programming if you do not know +the interior point (see halfspace notes),

    + +

    The input to qhalf is a set of halfspaces that are defined by their hyperplanes. +Each halfspace is defined by +d coefficients followed by a signed offset. This defines +a linear inequality. The coefficients define a vector that is +normal to the halfspace. +The vector may have any length. If it +has length one, the offset is the distance from the origin to the +halfspace's boundary. Points in the halfspace have a negative distance to the hyperplane. +The distance from the interior point to each +halfspace is likewise negative.

    + +

    The halfspace format is the same as Qhull's output options 'n', 'Fo', +and 'Fi'. Use option 'Fd' to use cdd format for the +halfspaces.

    + +

    For example, here is the input for computing the intersection +of halfplanes that form a cube.

    + +
    +

    rbox c | qconvex FQ FV n TO data

    +
    +RBOX c | QCONVEX FQ FV n
    +3 1
    +     0      0      0
    +4
    +6
    +     0      0     -1   -0.5
    +     0     -1      0   -0.5
    +     1      0      0   -0.5
    +    -1      0      0   -0.5
    +     0      1      0   -0.5
    +     0      0      1   -0.5
    +
    +

    qhalf s Fp < data

    +
    +
    +Halfspace intersection by the convex hull of 6 points in 3-d:
    +
    +  Number of halfspaces: 6
    +  Number of non-redundant halfspaces: 6
    +  Number of intersection points: 8
    +
    +Statistics for: RBOX c | QCONVEX FQ FV n | QHALF s Fp
    +
    +  Number of points processed: 6
    +  Number of hyperplanes created: 11
    +  Number of distance tests for qhull: 11
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 45
    +  CPU seconds to compute hull (after input):  0
    +
    +3
    +3
    +8
    +  -0.5    0.5    0.5
    +   0.5    0.5    0.5
    +  -0.5    0.5   -0.5
    +   0.5    0.5   -0.5
    +   0.5   -0.5    0.5
    +  -0.5   -0.5    0.5
    +  -0.5   -0.5   -0.5
    +   0.5   -0.5   -0.5
    +
    +
    + +
    +

    »qhalf outputs

    +
    + +

    The following options control the output for halfspace +intersection.

    +
    +
    +
     
    +
    Intersections
    +
    FN
    +
    list intersection points for each non-redundant + halfspace. The first line + is the number of non-redundant halfspaces. Each remaining + lines starts with the number of intersection points. For the cube + example, each halfspace has four intersection points.
    +
    Fn
    +
    list neighboring intersections for each intersection point. The first line + is the number of intersection points. Each remaining line + starts with the number of neighboring intersections. For the cube + example, each intersection point has three neighboring intersections. +

    + In 3-d, a non-simplicial intersection has more than three neighboring + intersections. For random data (e.g., the sphere example), non-simplicial intersections are the norm. + Option 'Qt' produces three + neighboring intersections per intersection by duplicating the intersection + points. Option QJ' produces three + neighboring intersections per intersection by joggling the hyperplanes and + hence their intersections. +

    +
    Fp
    +
    print intersection coordinates. The first line is the dimension and the + second line is the number of intersection points. The following lines are the + coordinates of each intersection.
    +
    FI
    +
    list intersection IDs. The first line is the number of + intersections. The IDs follow, one per line.
    +
     
    +
     
    +
    Halfspaces
    +
    Fx
    +
    list non-redundant halfspaces. The first line is the number of + non-redundant halfspaces. The other lines list one halfspace per line. + A halfspace is non-redundant if it + defines a facet of the intersection. Redundant halfspaces are ignored. For + the cube example, all of the halfspaces are non-redundant. +
    +
    Fv
    +
    list non-redundant halfspaces incident to each intersection point. + The first line is the number of + non-redundant halfspaces. Each remaining line starts with the number + of non-redundant halfspaces. For the + cube example, each intersection is incident to three halfspaces.
    +
    i
    +
    list non-redundant halfspaces incident to each intersection point. The first + line is the number of intersection points. Each remaining line + lists the incident, non-redundant halfspaces. For the + cube example, each intersection is incident to three halfspaces. +
    +
    Fc
    +
    list coplanar halfspaces for each intersection point. The first line is + the number of intersection points. Each remaining line starts with + the number of coplanar halfspaces. A coplanar halfspace is listed for + one intersection point even though it is coplanar to multiple intersection + points.
    +
    Qi Fc
    +
    list redundant halfspaces for each intersection point. The first line is + the number of intersection points. Each remaining line starts with + the number of redundant halfspaces. Use options 'Qc Qi Fc' to list + coplanar and redundant halfspaces.
    + +
     
    +
     
    +
    General
    +
    s
    +
    print summary for the halfspace intersection. Use 'Fs' if you need numeric data.
    +
    o
    +
    print vertices and facets of the dual convex hull. The + first line is the dimension. The second line is the number of + vertices, facets, and ridges. The vertex + coordinates are next, followed by the facets, one per line.
    +
    p
    +
    print vertex coordinates of the dual convex hull. Each vertex corresponds + to a non-redundant halfspace. Its coordinates are the negative of the hyperplane's coefficients + divided by the offset plus the inner product of the coefficients and + the interior point (-c/(b+a.p). + Options 'p Qc' includes coplanar halfspaces. + Options 'p Qi' includes redundant halfspaces.
    +
    m
    +
    Mathematica output for the dual convex hull in 2-d or 3-d.
    +
    FM
    +
    Maple output for the dual convex hull in 2-d or 3-d.
    +
    G
    +
    Geomview output for the dual convex hull in 2-d, 3-d, or 4-d.
    +
    +
    + +
    +

    »qhalf controls

    +
    + +

    These options provide additional control:

    + +
    +
    +
    Qt
    +
    triangulated output. If a 3-d intersection is defined by more than + three hyperplanes, Qhull produces duplicate intersections -- one for + each extra hyperplane.
    +
    QJ
    +
    joggle the input instead of merging facets. In 3-d, this guarantees that + each intersection is defined by three hyperplanes.
    +
    f
    +
    facet dump. Print the data structure for each intersection (i.e., + facet)
    +
    TFn
    +
    report summary after constructing n + intersections
    +
    QVn
    +
    select intersection points for halfspace n + (marked 'good')
    +
    QGn
    +
    select intersection points that are visible to halfspace n + (marked 'good'). Use -n for the remainder.
    +
    Qbk:0Bk:0
    +
    remove the k-th coordinate from the input. This computes the + halfspace intersection in one lower dimension.
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    Qs
    +
    search all points for the initial simplex. If Qhull can + not construct an initial simplex, it reports a +descriptive message. Usually, the point set is degenerate and one +or more dimensions should be removed ('Qbk:0Bk:0'). +If not, use option 'Qs'. It performs an exhaustive search for the +best initial simplex. This is expensive is high dimensions.
    +
    +
    + + +
    +

    »qhalf graphics

    +
    + +

    To view the results with Geomview, compute the convex hull of +the intersection points ('qhull FQ H0 Fp | qhull G'). See Halfspace examples.

    + +
    +

    »qhalf notes

    +
    + +

    See halfspace intersection for precision issues related to qhalf.

    + +

    If you do not know an interior point for the halfspaces, use +linear programming to find one. Assume, n halfspaces +defined by: aj*x1+bj*x2+cj*x3+dj<=0, j=1..n. Perform +the following linear program:

    + +
    +

    max(x5) aj*x1+bj*x2+cj*x3+dj*x4+x5<=0, j=1..n

    +
    + +

    Then, if [x1,x2,x3,x4,x5] is an optimal solution with +x4>0 and x5>0 we get:

    + +
    +

    aj*(x1/x4)+bj*(x2/x4)+cj*(x3/x4)+dj<=(-x5/x4) j=1..n and (-x5/x4)<0, +

    +
    + +

    and conclude that the point [x1/x4,x2/x4,x3/x4] is in +the interior of all the halfspaces. Since x5 is +optimal, this point is "way in" the interior (good +for precision errors).

    + +

    After finding an interior point, the rest of the intersection +algorithm is from Preparata & Shamos ['85, p. 316, "A simple case +..."]. Translate the halfspaces so that the interior point +is the origin. Calculate the dual polytope. The dual polytope is +the convex hull of the vertices dual to the original faces in +regard to the unit sphere (i.e., halfspaces at distance d +from the origin are dual to vertices at distance 1/d). +Then calculate the resulting polytope, which is the dual of the +dual polytope, and translate the origin back to the interior +point [S. Spitz, S. Teller, D. Strawn].

    + + +
    +

    »qhalf +conventions

    +
    + +

    The following terminology is used for halfspace intersection +in Qhull. This is the hardest structure to understand. The +underlying structure is a convex hull with one vertex per +non-redundant halfspace. See convex hull +conventions and Qhull's data structures.

    + +
      +
    • interior point - a point in the intersection of + the halfspaces. Qhull needs an interior point to compute + the intersection. See halfspace input.
    • +
    • halfspace - d coordinates for the + normal and a signed offset. The distance to an interior + point is negative.
    • +
    • non-redundant halfspace - a halfspace that + defines an output facet
    • +
    • vertex - a dual vertex in the convex hull + corresponding to a non-redundant halfspace
    • +
    • coplanar point - the dual point corresponding to + a similar halfspace
    • +
    • interior point - the dual point corresponding to + a redundant halfspace
    • +
    • intersection point- the intersection of d + or more non-redundant halfspaces
    • +
    • facet - a dual facet in the convex hull + corresponding to an intersection point
    • +
    • non-simplicial facet - more than d + halfspaces intersect at a point
    • +
    • good facet - an intersection point that + satisfies restriction 'QVn', + etc.
    • +
    + +
    +

    »qhalf options

    + +
    +qhalf- compute the intersection of halfspaces about a point
    +    http://www.qhull.org
    +
    +input (stdin):
    +    optional interior point: dimension, 1, coordinates
    +    first lines: dimension+1 and number of halfspaces
    +    other lines: halfspace coefficients followed by offset
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Hn,n - specify coordinates of interior point
    +    Qt   - triangulated ouput
    +    QJ   - joggle input instead of merging facets
    +    Qc   - keep coplanar halfspaces
    +    Qi   - keep other redundant halfspaces
    +
    +Qhull control options:
    +    QJn  - randomly joggle input in range [-n,n]
    +    Qbk:0Bk:0 - remove k-th coordinate from input
    +    Qs   - search all halfspaces for the initial simplex
    +    QGn  - print intersection if redundant to halfspace n, -n for not
    +    QVn  - print intersections for halfspace n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and redundancy
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when halfspace n added to intersection
    +    TMn  - turn on tracing at merge n
    +    TWn  - trace merge facets when width > n
    +    TVn  - stop qhull after adding halfspace n, -n for before (see TCn)
    +    TCn  - stop qhull after building cone for halfspace n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Un   - max distance below plane for a new, coplanar halfspace
    +    Wn   - min facet width for outside halfspace (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (dual convex hull)
    +    i    - non-redundant halfspaces incident to each intersection
    +    m    - Mathematica output (dual convex hull)
    +    o    - OFF format (dual convex hull: dimension, points, and facets)
    +    p    - vertex coordinates of dual convex hull (coplanars if 'Qc' or 'Qi')
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fc   - count plus redundant halfspaces for each intersection
    +         -   Qc (default) for coplanar and Qi for other redundant
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FF   - facet dump without ridges
    +    FI   - ID of each intersection
    +    Fm   - merge count for each intersection (511 max)
    +    FM   - Maple output (dual convex hull)
    +    Fn   - count plus neighboring intersections for each intersection
    +    FN   - count plus intersections for each non-redundant halfspace
    +    FO   - options and precision constants
    +    Fp   - dim, count, and intersection coordinates
    +    FP   - nearest halfspace and distance for each redundant halfspace
    +    FQ   - command used for qhalf
    +    Fs   - summary: #int (8), dim, #halfspaces, #non-redundant, #intersections
    +                      for output: #non-redundant, #intersections, #coplanar
    +                                  halfspaces, #non-simplicial intersections
    +                    #real (2), max outer plane, min vertex
    +    Fv   - count plus non-redundant halfspaces for each intersection
    +    Fx   - non-redundant halfspaces
    +
    +Geomview output (2-d, 3-d and 4-d; dual convex hull)
    +    Ga   - all points (i.e., transformed halfspaces) as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices (i.e., non-redundant halfspaces) as spheres
    +    Gi   - inner planes (i.e., halfspace intersections) only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +
    +Print options:
    +    PAn  - keep n largest facets (i.e., intersections) by area
    +    Pdk:n- drop facet if normal[k] <= n (default 0.0)
    +    PDk:n- drop facet if normal[k] >= n
    +    Pg   - print good facets (needs 'QGn' or 'QVn')
    +    PFn  - keep facets whose area is at least n
    +    PG   - print neighbors of good facets
    +    PMn  - keep n facets with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + + diff --git a/xs/src/qhull/html/qhull-cpp.xml b/xs/src/qhull/html/qhull-cpp.xml new file mode 100644 index 0000000000..ae755e8266 --- /dev/null +++ b/xs/src/qhull/html/qhull-cpp.xml @@ -0,0 +1,214 @@ + + + + +

    Qhull C++ -- C++ interface to Qhull

    + + Copyright (c) 2009-2015, C.B. Barber + + +
    +

    This draft + document records some of the design decisions for Qhull C++. Convert it to HTML by road-faq.xsl from road-faq. + + Please send comments and suggestions to bradb@shore.net +

    +
    +
    +
    + Help +
    • +
    • +
    • +
    +
    +
    +
    • +
    • +
    +
    +
    +
    + . +
    + +
    + + + + Qhull's collection APIs are modeled on Qt's collection API (QList, QVector, QHash) w/o QT_STRICT_ITERATORS. They support STL and Qt programming. + +

    Some of Qhull's collection classes derive from STL classes. If so, + please avoid additional STL functions and operators added by inheritance. + These collection classes may be rewritten to derive from Qt classes instead. + See Road's . +

    + + + Qhull's collection API (where applicable). For documentation, see Qt's QList, QMap, QListIterator, QMapIterator, QMutableListIterator, and QMutableMapIterator +
    • + STL types [list, qlinkedlist, qlist, qvector, vector] -- const_iterator, iterator +
    • + STL types describing iterators [list, qlinkedlist, qlist, qvector, vector] -- const_pointer, const_reference, difference_type, + pointer, reference, size_type, value_type. + Pointer and reference types not defined if unavailable (not needed for <algorithm>) +
    • + const_iterator, iterator types -- difference_type, iterator_category, pointer, reference, value_type +
    • + Qt types [qlinkedlist, qlist, qvector] -- ConstIterator, Iterator, QhullclassIterator, MutableQhullclassIterator. + Qt's foreach requires const_iterator. +
    • + Types for sets/maps [hash_map, QHash] -- key_compare, key_type, mapped_type +
    • + Constructor -- default constructor, copy constructor, assignment operator, destructor +
    • + Conversion -- to/from/as corresponding C, STL, and Qt constructs. Include toQList and toStdVector (may be filtered, e.g., QhullFacetSet). + Do not define fromStdList and fromQList if container is not reference counted (i.e., acts like a value) +
    • + Get/set -- configuration options for class +
    • + STL-style iterator - begin, constBegin, constEnd, end, key, value, =, *, [], ->, ++, --, +, -, ==, !=, <, + <=, >, >=, const_iterator(iterator), iterator COMPARE const_iterator. + An iterator is an abstraction of a pointer. It is not aware of its container. +
    • + Java-style iterator [qiterator.h] - countRemaining, findNext, findPrevious, hasNext, hasPrevious, next, peekNext, peekPrevious, previous, toBack, toFront, = Coordinates +
    • + Mutable Java-style iterator adds - insert, remove, setValue, value +
    • + Element access -- back, first, front, last +
    • + Element access w/ index -- [], at (const& only), constData, data, mid, value +
    • + Read-only - (int)count, empty, isEmpty, (size_t)size. Count() and size() may be filtered. If so, they may be zero when !empty(). +
    • + Read-only for sets/maps - capacity, key, keys, reserve, resize, values +
    • + Operator - ==, !=, +, +=, << +
    • + Read-write -- append, clear, erase, insert, move, prepend, pop_back, pop_front, push_back, push_front, removeAll, removeAt, removeFirst, removeLast, replace, + swap, takeAt, takeFirst, takeLast +
    • + Read-write for sets/maps -- insertMulti, squeeze, take, unite +
    • + Search -- contains(const T &), count(const T &), indexOf, lastIndexOf +
    • + Search for sets/maps -- constFind, lowerBound, upperBound +
    • + Stream I/O -- stream << +
    + + STL list and vector -- For unfiltered access to each element. +
    • + Apache: Creating your own containers -- requirements for STL containers. Iterators should define the types from 'iterator_traits'. +
    • + STL types -- allocator_type, const_iterator, const_pointer, const_reference, const_reverse_iterator, difference_type, iterator, iterator_category, pointer, reference, reverse_iterator, size_type, value_type +
    • + STL constructors -- MyType(), MyType(count), MyType(count, value), MyType(first, last), + MyType(MyType&), +
    • + STL getter/setters -- at (random_access only), back, begin, capacity, end, front, rbegin, rend, size, max_size +
    • + STL predicates -- empty +
    • + STL iterator types -- const_pointer, const_reference, difference_type, iterator_category, pointer, reference, value_type +
    • + STL iterator operators -- *, -<, ++, --, +=, -=, +, -, [], ==, !=, <, >, >=, <= +
    • + STL operators -- =, [] (random_access only), ==, !=, <, >, <=, >= +
    • + STL modifiers -- assign, clear, erase, insert, pop_back, push_back, reserve, resize, swap +
    • +
    + + Qt Qlist -- For unfiltered access to each element +
    • +
    • + Additional Qt types -- ConstIterator, Iterator, QListIterator, QMutableListIterator +
    • + Additional Qt get/set -- constBegin, constEnd, count, first, last, value (random_access only) +
    • + Additional Qt predicates -- isEmpty +
    • + Additional Qt -- mid (random_access only) +
    • + Additional Qt search -- contains, count(T&), indexOf (random_access only), lastIndeOf (random_access only) +
    • + Additional Qt modifiers -- append, insert(index,value) (random_access only), move (random_access only), pop_front, prepend, push_front, removeAll, removeAt (random_access only), removeFirst, removeLast, replace, swap by index, takeAt, takeFirst, takeLast +
    • + Additional Qt operators -- +, <<, +=, + stream << and >> +
    • + Unsupported types by Qt -- allocator_type, const_reverse_iterator, reverse_iterator +
    • + Unsupported accessors by Qt -- max_size, rbegin, rend +
    • + Unsupported constructors by Qt -- multi-value constructors +
    • + unsupported modifiers by Qt -- assign, muli-value inserts, STL's swaps +
    • +
    + + STL map and Qt QMap. These use nearly the same API as list and vector classes. They add the following. +
    • + STL types -- key_compare, key_type, mapped_type +
    • + STL search -- equal_range, find, lower_bound, upper_bound +
    • + Qt removes -- equal_range, key_compare +
    • + Qt renames -- lowerBound, upperBound +
    • + Qt adds -- constFind, insertMulti, key, keys, take, uniqueKeys, unite, values +
    • + Not applicable to map and QMap -- at, back, pop_back, pop_front, push_back, push_front, swap +
    • + Not applicable to QMap -- append, first, last, lastIndexOf, mid, move, prepend, removeAll, removeAt, removeFirst, removeLast, replace, squeeze, takeAt, takeFirst, takeLast +
    • + Not applicable to map -- assign +
    + + Qt QHash. STL extensions provide similar classes, e.g., Microsoft's stdext::hash_set. THey are nearly the same as QMap +
    • +
    • +
    • + Not applicable to Qhash -- lowerBound, unite, upperBound, +
    • + Qt adds -- squeeze +
    +
    + +
    • + check... -- Throw error on failure +
    • + try... -- Return false on failure. Do not throw errors. +
    • + ...Temporarily -- lifetime depends on source. e.g., toByteArrayTemporarily +
    • + ...p -- indicates pointer-to. +
    • + end... -- points to one beyond the last available +
    • + private functions -- No syntactic indication. They may become public later on. +
    • + Error messages -- Preceed error messages with the name of the class throwing the error (e.g. "ClassName: ..."). If this is an internal error, use "ClassName inconsistent: ..." +
    • + parameter order -- qhRunId, dimension, coordinates, count. +
    • + toClass -- Convert into a Class object (makes a deep copy) +
    • + qRunId -- Requires Qh installed. Some routines allow 0 for limited info (e.g., operator<<) +
    • + Disable methods in derived classes -- If the default constructor, copy constructor, or copy assignment is disabled, it should be also disabled in derived classes (better error messages). +
    • + Constructor order -- default constructor, other constructors, copy constructor, copy assignment, destructor +
    +
    +
    +
    diff --git a/xs/src/qhull/html/qhull.htm b/xs/src/qhull/html/qhull.htm new file mode 100644 index 0000000000..0a2aa75e06 --- /dev/null +++ b/xs/src/qhull/html/qhull.htm @@ -0,0 +1,473 @@ + + + + +qhull -- convex hull and related structures + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • input +• outputs • controls +• options +


    + +

    [cone]qhull -- convex hull and related structures

    + +

    The convex hull of a set of points is the smallest convex set +containing the points. The Delaunay triangulation and furthest-site +Delaunay triangulation are equivalent to a convex hull in one +higher dimension. Halfspace intersection about a point is +equivalent to a convex hull by polar duality. + +

    The qhull program provides options to build these +structures and to experiment with the process. Use the +qconvex, +qdelaunay, qhalf, +and qvoronoi programs +to build specific structures. You may use qhull instead. +It takes the same options and uses the same code. +

    +
    +
    Example: rbox 1000 D3 | qhull + C-1e-4 + FO + Ts +
    +
    Compute the 3-d convex hull of 1000 random + points. + Centrums must be 10^-4 below neighboring + hyperplanes. Print the options and precision constants. + When done, print statistics. These options may be + used with any of the Qhull programs.
    +
     
    +
    Example: rbox 1000 D3 | qhull d + Qbb + R1e-4 + Q0
    +
    Compute the 3-d Delaunay triangulation of 1000 random + points. Randomly perturb all calculations by + [0.9999,1.0001]. Do not correct precision problems. + This leads to serious precision errors.
    +
    +
    +

    Use the following equivalences when calling qhull in 2-d to 4-d (a 3-d +Delaunay triangulation is a 4-d convex hull): +

    + +
    + +

    Use the following equivalences when calling qhull in 5-d and higher (a 4-d +Delaunay triangulation is a 5-d convex hull): +

    + +
    + + +

    By default, Qhull merges coplanar facets. For example, the convex +hull of a cube's vertices has six facets. + +

    If you use 'Qt' (triangulated output), +all facets will be simplicial (e.g., triangles in 2-d). For the cube +example, it will have 12 facets. Some facets may be +degenerate and have zero area. + +

    If you use 'QJ' (joggled input), +all facets will be simplicial. The corresponding vertices will be +slightly perturbed. Joggled input is less accurate that triangulated +output.See Merged facets or joggled input.

    + +

    The output for 4-d convex hulls may be confusing if the convex +hull contains non-simplicial facets (e.g., a hypercube). See +Why +are there extra points in a 4-d or higher convex hull?
    +

    + +

    Copyright © 1995-2015 C.B. Barber

    + +
    + +

    »qhull synopsis

    +
    +qhull- compute convex hulls and related structures.
    +    input (stdin): dimension, n, point coordinates
    +    comments start with a non-numeric character
    +    halfspace: use dim+1 and put offsets after coefficients
    +
    +options (qh-quick.htm):
    +    d    - Delaunay triangulation by lifting points to a paraboloid
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)
    +    v    - Voronoi diagram as the dual of the Delaunay triangulation
    +    v Qu - furthest-site Voronoi diagram
    +    H1,1 - Halfspace intersection about [1,1,0,...] via polar duality
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +Output options (subset):
    +    s    - summary of results (default)
    +    i    - vertices incident to each facet
    +    n    - normals with offsets
    +    p    - vertex coordinates (if 'Qc', includes coplanar points)
    +           if 'v', Voronoi vertices
    +    Fp   - halfspace intersections
    +    Fx   - extreme points (convex hull vertices)
    +    FA   - compute total area and volume
    +    o    - OFF format (if 'v', outputs Voronoi regions)
    +    G    - Geomview output (2-d, 3-d and 4-d)
    +    m    - Mathematica output (2-d and 3-d)
    +    QVn  - print facets that include point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +    rbox c d D2 | qhull Qc s f Fx | more      rbox 1000 s | qhull Tv s FA
    +    rbox 10 D2 | qhull d QJ s i TO result     rbox 10 D2 | qhull v Qbb Qt p
    +    rbox 10 D2 | qhull d Qu QJ m              rbox 10 D2 | qhull v Qu QJ o
    +    rbox c | qhull n                          rbox c | qhull FV n | qhull H Fp
    +    rbox d D12 | qhull QR0 FA                 rbox c D7 | qhull FA TF1000
    +    rbox y 1000 W0 | qhull                    rbox 10 | qhull v QJ o Fv
    +
    + +

    »qhull input

    +
    + +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qhull < data.txt), a pipe (e.g., rbox 10 | qhull), +or the 'TI' option (e.g., qhull TI data.txt). + +

    Comments start with a non-numeric character. Error reporting is +simpler if there is one point per line. Dimension +and number of points may be reversed. For halfspace intersection, +an interior point may be prepended (see qhalf input). + +

    Here is the input for computing the convex +hull of the unit cube. The output is the normals, one +per facet.

    + +
    +

    rbox c > data

    +
    +3 RBOX c
    +8
    +  -0.5   -0.5   -0.5
    +  -0.5   -0.5    0.5
    +  -0.5    0.5   -0.5
    +  -0.5    0.5    0.5
    +   0.5   -0.5   -0.5
    +   0.5   -0.5    0.5
    +   0.5    0.5   -0.5
    +   0.5    0.5    0.5
    +
    +

    qhull s n < data

    +
    +
    +Convex hull of 8 points in 3-d:
    +
    +  Number of vertices: 8
    +  Number of facets: 6
    +  Number of non-simplicial facets: 6
    +
    +Statistics for: RBOX c | QHULL s n
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 11
    +  Number of distance tests for qhull: 35
    +  Number of merged facets: 6
    +  Number of distance tests for merging: 84
    +  CPU seconds to compute hull (after input): 0.081
    +
    +4
    +6
    +     0      0     -1   -0.5
    +     0     -1      0   -0.5
    +     1      0      0   -0.5
    +    -1      0      0   -0.5
    +     0      1      0   -0.5
    +     0      0      1   -0.5
    +
    +
    + +
    +

    »qhull outputs

    +
    + +

    These options control the output of qhull. They may be used +individually or together.

    +
    +
    +
     
    +
    General
    +
    qhull
    +
    compute the convex hull of the input points. + See qconvex.
    +
    qhull d Qbb
    +
    compute the Delaunay triangulation by lifting the points + to a paraboloid. Use option 'Qbb' + to scale the paraboloid and improve numeric precision. + See qdelaunay.
    +
    qhull v Qbb
    +
    compute the Voronoi diagram by computing the Delaunay + triangulation. Use option 'Qbb' + to scale the paraboloid and improve numeric precision. + See qvoronoi.
    +
    qhull H
    +
    compute the halfspace intersection about a point via polar + duality. The point is below the hyperplane that defines the halfspace. + See qhalf.
    +
    +
    + +

    For a full list of output options see +

    + +
    + +
    +

    »qhull controls

    +
    + +

    For a full list of control options see +

    + +
    + +
    +

    »qhull options

    + +
    +qhull- compute convex hulls and related structures.
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +    halfspaces:  use dim plus one and put offset after coefficients.
    +                 May be preceded by a single interior point ('H').
    +
    +options:
    +    d    - Delaunay triangulation by lifting points to a paraboloid
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)
    +    v    - Voronoi diagram (dual of the Delaunay triangulation)
    +    v Qu - furthest-site Voronoi diagram
    +    Hn,n,... - halfspace intersection about point [n,n,0,...]
    +    Qt   - triangulated output
    +    QJ   - joggle input instead of merging facets
    +    Qc   - keep coplanar points with nearest facet
    +    Qi   - keep interior points with nearest facet
    +
    +Qhull control options:
    +    Qbk:n   - scale coord k so that low bound is n
    +      QBk:n - scale coord k so that upper bound is n (QBk is 0.5)
    +    QbB  - scale input to unit cube centered at the origin
    +    Qbb  - scale last coordinate to [0,m] for Delaunay triangulations
    +    Qbk:0Bk:0 - remove k-th coordinate from input
    +    QJn  - randomly joggle input in range [-n,n]
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)
    +    Qf   - partition point to furthest outside facet
    +    Qg   - only build good facets (needs 'QGn', 'QVn', or 'PdD')
    +    Qm   - only process points that would increase max_outside
    +    Qr   - process random outside points instead of furthest ones
    +    Qs   - search all points for the initial simplex
    +    Qu   - for 'd' or 'v', compute upper hull without point at-infinity
    +              returns furthest-site Delaunay triangulation
    +    Qv   - test vertex neighbors for convexity
    +    Qx   - exact pre-merges (skips coplanar and anglomaniacs facets)
    +    Qz   - add point-at-infinity to Delaunay triangulation
    +    QGn  - good facet if visible from point n, -n for not visible
    +    QVn  - good facet if it includes point n, -n if not
    +    Q0   - turn off default p remerge with 'C-0'/'Qx'
    +    Q1     - sort merges by type instead of angle
    +    Q2   - merge all non-convex at once instead of independent sets
    +    Q3   - do not merge redundant vertices
    +    Q4   - avoid old>new merges
    +    Q5   - do not correct outer planes at end of qhull
    +    Q6   - do not pre-merge concave or coplanar facets
    +    Q7   - depth-first processing instead of breadth-first
    +    Q8   - do not process near-inside points
    +    Q9   - process furthest of furthest points
    +    Q10  - no special processing for narrow distributions
    +    Q11  - copy normals and recompute centrums for tricoplanar facets
    +    Q12  - do not error on wide merge due to duplicate ridge and nearly coincident points
    +
    +Towpaths Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - print statistics
    +    Tv   - verify result: structure, convexity, and point inclusion
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TRn  - rerun qhull n times.  Use with 'QJn'
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    En   - max roundoff error for distance computation
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Vn   - min distance above plane for a visible facet (default 3C-n or En)
    +    Un   - max distance below plane for a new, coplanar point (default Vn)
    +    Wn   - min facet width for outside point (before roundoff, default 2Vn)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    f    - facet dump
    +    G    - Geomview output (see below)
    +    i    - vertices incident to each facet
    +    m    - Mathematica output (2-d and 3-d)
    +    o    - OFF format (dim, points and facets; Voronoi regions)
    +    n    - normals with offsets
    +    p    - vertex coordinates or Voronoi vertices (coplanar points if 'Qc')
    +    s    - summary (stderr)
    +
    +More formats:
    +    Fa   - area for each facet
    +    FA   - compute total area and volume for option 's'
    +    Fc   - count plus coplanar points for each facet
    +           use 'Qc' (default) for coplanar and 'Qi' for interior
    +    FC   - centrum or Voronoi center for each facet
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for numeric output (offset first)
    +    FF   - facet dump without ridges
    +    Fi   - inner plane for each facet
    +           for 'v', separating hyperplanes for bounded Voronoi regions
    +    FI   - ID of each facet
    +    Fm   - merge count for each facet (511 max)
    +    FM   - Maple output (2-d and 3-d)
    +    Fn   - count plus neighboring facets for each facet
    +    FN   - count plus neighboring facets for each point
    +    Fo   - outer plane (or max_outside) for each facet
    +           for 'v', separating hyperplanes for unbounded Voronoi regions
    +    FO   - options and precision constants
    +    Fp   - dim, count, and intersection coordinates (halfspace only)
    +    FP   - nearest vertex and distance for each coplanar point
    +    FQ   - command used for qhull
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                      output: #vertices, #facets, #coplanars, #nonsimplicial
    +                    #real (2), max outer plane, min vertex
    +    FS   - sizes:   #int (0)
    +                    #real(2) tot area, tot volume
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)
    +    Fv   - count plus vertices for each facet
    +           for 'v', Voronoi diagram as Voronoi vertices for pairs of sites
    +    FV   - average of vertices (a feasible point for 'H')
    +    Fx   - extreme points (in order for 2-d)
    +
    +Geomview options (2-d, 3-d, and 4-d; 2-d Voronoi)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +    Gt   - for 3-d 'd', transparent outer ridges
    +
    +Print options:
    +    PAn  - keep n largest facets by area
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good facets (needs 'QGn' or 'QVn')
    +    PFn  - keep facets whose area is at least n
    +    PG   - print neighbors of good facets
    +    PMn  - keep n facets with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • input +• outputs • controls +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qhull.man b/xs/src/qhull/html/qhull.man new file mode 100644 index 0000000000..8d1dc08ace --- /dev/null +++ b/xs/src/qhull/html/qhull.man @@ -0,0 +1,1008 @@ +.\" This is the Unix manual page for qhull, written in nroff, the standard +.\" manual formatter for Unix systems. To format it, type +.\" +.\" nroff -man qhull.man +.\" +.\" This will print a formatted copy to standard output. If you want +.\" to ensure that the output is plain ASCII, free of any control +.\" characters that nroff uses for underlining etc, pipe the output +.\" through "col -b": +.\" +.\" nroff -man qhull.man | col -b +.\" +.\" Warning: a leading quote "'" or dot "." will not format correctly +.\" +.TH qhull 1 "2003/12/30" "Geometry Center" +.SH NAME +qhull \- convex hull, Delaunay triangulation, Voronoi diagram, +halfspace intersection about a point, hull volume, facet area +.SH SYNOPSIS +.nf +qhull- compute convex hulls and related structures + input (stdin): dimension, #points, point coordinates + first comment (non-numeric) is listed in the summary + halfspace: use dim plus one with offsets after coefficients + +options (qh-quick.htm): + d - Delaunay triangulation by lifting points to a paraboloid + v - Voronoi diagram via the Delaunay triangulation + H1,1 - Halfspace intersection about [1,1,0,...] + d Qu - Furthest-site Delaunay triangulation (upper convex hull) + v Qu - Furthest-site Voronoi diagram + Qt - triangulated output + QJ - Joggle the input to avoid precision problems + . - concise list of all options + - - one-line description of all options + +Output options (subset): + FA - compute total area and volume + Fx - extreme points (convex hull vertices) + G - Geomview output (2-d, 3-d and 4-d) + Fp - halfspace intersection coordinates + m - Mathematica output (2-d and 3-d) + n - normals with offsets + o - OFF file format (if Voronoi, outputs regions) + TO file- output results to file, may be enclosed in single quotes + f - print all fields of all facets + s - summary of results (default) + Tv - verify result: structure, convexity, and point inclusion + p - vertex coordinates (centers for Voronoi) + i - vertices incident to each facet + +example: + rbox 1000 s | qhull Tv s FA +.fi + + - html manual: index.htm + - installation: README.txt + - see also: COPYING.txt, REGISTER.txt, Changes.txt + - WWW: + - GIT: + - mirror: + - news: + - Geomview: + - news group: + - FAQ: + - email: qhull@qhull.org + - bug reports: qhull_bug@qhull.org + +The sections are: + - INTRODUCTION + - DESCRIPTION, a description of Qhull + - IMPRECISION, how Qhull handles imprecision + - OPTIONS + - Input and output options + - Additional input/output formats + - Precision options + - Geomview options + - Print options + - Qhull options + - Trace options + - BUGS + - E-MAIL + - SEE ALSO + - AUTHORS + - ACKNOWLEGEMENTS + +This man page briefly describes all Qhull options. Please report +any mismatches with Qhull's html manual (index.htm). + +.PP +.SH INTRODUCTION +Qhull is a general dimension code for computing convex hulls, Delaunay +triangulations, Voronoi diagram, furthest\[hy]site Voronoi diagram, +furthest\[hy]site Delaunay triangulations, and +halfspace intersections about a point. It implements the Quickhull algorithm for +computing the convex hull. Qhull handles round\[hy]off errors from floating +point arithmetic. It can approximate a convex hull. + +The program includes options for hull volume, facet area, partial hulls, +input transformations, randomization, tracing, multiple output formats, and +execution statistics. The program can be called from within your application. +You can view the results in 2\[hy]d, 3\[hy]d and 4\[hy]d with Geomview. +.PP +.SH DESCRIPTION +.PP +The format of input is the following: first line contains the dimension, +second line contains the number of input points, and point coordinates follow. +The dimension and number of points can be reversed. +Comments and line breaks are ignored. A comment starts with a +non\[hy]numeric character and continues to the end of line. The first comment +is reported in summaries and statistics. +Error reporting is +better if there is one point per line. +.PP +The default printout option is a short summary. There are many +other output formats. +.PP +Qhull implements the Quickhull algorithm for convex hull. This algorithm combines +the 2\[hy]d Quickhull algorithm with the n\[hy]d beneath\[hy]beyond algorithm +[c.f., Preparata & Shamos '85]. +It is similar to the randomized algorithms of Clarkson and +others [Clarkson et al. '93]. The main +advantages of Quickhull are output sensitive performance, reduced +space requirements, and automatic handling of precision problems. +.PP +The data structure produced by Qhull consists of vertices, ridges, and facets. +A vertex is a point of the input set. A ridge is a set of d vertices +and two neighboring facets. For example in 3\[hy]d, a ridge is an edge of the +polyhedron. A facet is a set of ridges, a set of neighboring facets, a set +of incident vertices, and a hyperplane equation. For simplicial facets, the +ridges are defined by the vertices and neighboring facets. When Qhull +merges two facets, it produces a non\[hy]simplicial +facet. A non\[hy]simplicial facet has more than d neighbors and may share more than +one ridge with a neighbor. +.PP +.SH IMPRECISION +.PP +Since Qhull uses floating point arithmetic, roundoff error may occur for each +calculation. This causes problems +for most geometric algorithms. +.PP +Qhull automatically sets option 'C\-0' in 2\[hy]d, 3\[hy]d, and 4\[hy]d, or +option 'Qx' in 5\[hy]d and higher. These options handle precision problems +by merging facets. Alternatively, use option 'QJ' to joggle the +input. +.PP +With 'C\-0', Qhull merges non\[hy]convex +facets while constructing the hull. The remaining facets are +clearly convex. With 'Qx', Qhull merges +coplanar horizon facets, flipped facets, concave facets and +duplicated ridges. It merges coplanar facets after constructing +the hull. +With 'Qx', coplanar points may be missed, but it +appears to be unlikely. +.PP +To guarantee triangular output, joggle the input with option 'QJ'. Facet +merging will not occur. +.SH OPTIONS +.PP +To get a list of the most important options, execute 'qhull' by itself. +To get a complete list of options, +execute 'qhull \-'. +To get a complete, concise list of options, execute 'qhull .'. + +Options can be in any order. +Capitalized options take an argument (except 'PG' and 'F' options). +Single letters are used for output formats and precision constants. The +other options are grouped into menus for other output formats ('F'), +Geomview output ('G'), +printing ('P'), Qhull control ('Q'), and tracing ('T'). +.TP +Main options: +.TP +default +Compute the convex hull of the input points. Report a summary of +the result. +.TP +d +Compute the Delaunay triangulation by lifting the input points to a +paraboloid. The 'o' option prints the input points and facets. +The 'QJ' option guarantees triangular output. The 'Ft' +option prints a triangulation. It adds points (the centrums) to non\[hy]simplicial +facets. +.TP +v +Compute the Voronoi diagram from the Delaunay triangulation. +The 'p' option prints the Voronoi vertices. +The 'o' option prints the Voronoi vertices and the +vertices in each Voronoi region. It lists regions in +site ID order. +The 'Fv' option prints each ridge of the Voronoi diagram. +The first or zero'th vertex +indicates the infinity vertex. Its coordinates are +qh_INFINITE (\-10.101). It indicates unbounded Voronoi +regions or degenerate Delaunay triangles. +.TP +Hn,n,... +Compute halfspace intersection about [n,n,0,...]. +The input is a set of halfspaces +defined in the same format as 'n', 'Fo', and 'Fi'. +Use 'Fp' to print the intersection points. Use 'Fv' +to list the intersection points for each halfspace. The +other output formats display the dual convex hull. + +The point [n,n,n,...] is a feasible point for the halfspaces, i.e., +a point that is inside all +of the halfspaces (Hx+b <= 0). The default coordinate value is 0. + +The input may start with a feasible point. If so, use 'H' by itself. +The input starts with a feasible point when the first number is the dimension, +the second number is "1", and the coordinates complete a line. The 'FV' +option produces a feasible point for a convex hull. +.TP +d Qu +Compute the furthest\[hy]site Delaunay triangulation from the upper +convex hull. The 'o' option prints the input points and facets. +The 'QJ' option guarantees triangular otuput. You can also use 'Ft' +to triangulate via the centrums of non\[hy]simplicial +facets. +.TP +v Qu +Compute the furthest\[hy]site Voronoi diagram. +The 'p' option prints the Voronoi vertices. +The 'o' option prints the Voronoi vertices and the +vertices in each Voronoi region. +The 'Fv' option prints each ridge of the Voronoi diagram. +The first or zero'th vertex +indicates the infinity vertex at infinity. Its coordinates are +qh_INFINITE (\-10.101). It indicates unbounded Voronoi regions +and degenerate Delaunay triangles. +.PP +.TP +Input/Output options: +.TP +f +Print out all facets and all fields of each facet. +.TP +G +Output the hull in Geomview format. For imprecise hulls, +Geomview displays the inner and outer hull. Geomview can also +display points, ridges, vertices, coplanar points, and +facet intersections. See below for a list of options. + +For Delaunay triangulations, 'G' displays the +corresponding paraboloid. For halfspace intersection, 'G' displays the +dual polytope. +.TP +i +Output the incident vertices for each facet. +Qhull prints the number of facets followed by the +vertices of each facet. One facet is printed per line. The numbers +are the 0\[hy]relative indices of the corresponding input points. +The facets +are oriented. + +In 4d and higher, +Qhull triangulates non\[hy]simplicial facets. Each apex (the first vertex) is +a created point that corresponds to the facet's centrum. Its index is greater +than the indices of the input points. Each base +corresponds to a simplicial ridge between two facets. +To print the vertices without triangulation, use option 'Fv'. +.TP +m +Output the hull in Mathematica format. Qhull writes a Mathematica file for 2\[hy]d and 3\[hy]d +convex hulls and for 2\[hy]d Delaunay triangulations. Qhull produces a list of objects +that you can assign to a variable in Mathematica, for example: +"list= << ". If the object is 2\[hy]d, it can be +visualized by "Show[Graphics[list]] ". For 3\[hy]d objects the command is +"Show[Graphics3D[list]]". +.TP +n +Output the normal equation for each facet. +Qhull prints the dimension (plus one), the number of facets, +and the normals for each facet. The facet's offset follows its +normal coefficients. +.TP +o +Output the facets in OFF file format. +Qhull prints the dimension, number of points, number +of facets, and number of ridges. Then it prints the coordinates of +the input points and the vertices for each facet. Each facet is on +a separate line. The first number is the number of vertices. The +remainder are the indices of the corresponding points. The vertices are +oriented in 2\[hy]d, 3\[hy]d, and in simplicial facets. + +For 2\[hy]d Voronoi diagrams, +the vertices are sorted by adjacency, but not oriented. In 3\[hy]d and higher, +the Voronoi vertices are sorted by index. +See the 'v' option for more information. +.TP +p +Output the coordinates of each vertex point. +Qhull prints the dimension, the number of points, +and the coordinates for each vertex. +With the 'Gc' and 'Gi' options, it also prints coplanar +and interior points. For Voronoi diagrams, it prints the coordinates +of each Voronoi vertex. +.TP +s +Print a summary to stderr. If no output options +are specified at all, a summary goes to stdout. The summary lists +the number of input points, the dimension, the number of vertices +in the convex hull, the number of facets in the convex hull, the +number of good facets (if 'Pg'), and statistics. + +The last two statistics (if needed) measure the maximum distance +from a point or vertex to a +facet. The number in parenthesis (e.g., 2.1x) is the ratio between the +maximum distance and the worst\[hy]case distance due to merging +two simplicial facets. +.PP +.TP +Precision options +.TP +An +Maximum angle given as a cosine. If the angle between a pair of facet +normals +is greater than n, Qhull merges one of the facets into a neighbor. +If 'n' is negative, Qhull tests angles after adding +each point to the hull (pre\[hy]merging). +If 'n' is positive, Qhull tests angles after +constructing the hull (post\[hy]merging). +Both pre\[hy] and post\[hy]merging can be defined. + +Option 'C0' or 'C\-0' is set if the corresponding 'Cn' or 'C\-n' +is not set. If 'Qx' +is set, then 'A\-n' and 'C\-n' are checked after the hull is constructed +and before 'An' and 'Cn' are checked. +.TP +Cn +Centrum radius. +If a centrum is less than n below a neighboring facet, Qhull merges one +of the facets. +If 'n' is negative or '\-0', Qhull tests and merges facets after adding +each point to the hull. This is called "pre\[hy]merging". If 'n' is positive, +Qhull tests for convexity after constructing the hull ("post\[hy]merging"). +Both pre\[hy] and post\[hy]merging can be defined. + +For 5\[hy]d and higher, 'Qx' should be used +instead of 'C\-n'. Otherwise, most or all facets may be merged +together. +.TP +En +Maximum roundoff error for distance computations. +.TP +Rn +Randomly perturb distance computations up to +/\- n * max_coord. +This option perturbs every distance, hyperplane, and angle computation. +To use time as the random number seed, use option 'QR\-1'. +.TP +Vn +Minimum distance for a facet to be visible. +A facet is visible if the distance from the point to the +facet is greater than 'Vn'. + +Without merging, the default value for 'Vn' is the round\[hy]off error ('En'). +With merging, the default value is the pre\[hy]merge centrum ('C\-n') in 2\[hy]d or +3\[hy]d, or three times that in other dimensions. If the outside width +is specified ('Wn'), the maximum, default value for 'Vn' is 'Wn'. +.TP +Un +Maximum distance below a facet for a point to be coplanar to the facet. The +default value is 'Vn'. +.TP +Wn +Minimum outside width of the hull. Points are added to the convex hull +only if they are clearly outside of a facet. A point is outside of a +facet if its distance to the facet is greater than 'Wn'. The normal +value for 'Wn' is 'En'. If the user specifies pre\[hy]merging and +does not set 'Wn', than 'Wn' is set +to the premerge 'Cn' and maxcoord*(1\-An). +.PP +.TP +Additional input/output formats +.TP +Fa +Print area for each facet. +For Delaunay triangulations, the area is the area of the triangle. +For Voronoi diagrams, the area is the area of the dual facet. +Use 'PAn' for printing the n largest facets, and option 'PFn' for +printing facets larger than 'n'. + +The area for non\[hy]simplicial facets is the sum of the +areas for each ridge to the centrum. Vertices far below +the facet's hyperplane are ignored. +The reported area may be significantly less than the actual area. +.TP +FA +Compute the total area and volume for option 's'. It is an approximation +for non\[hy]simplicial facets (see 'Fa'). +.TP +Fc +Print coplanar points for each facet. The output starts with the +number of facets. Then each facet is printed one per line. Each line +is the number of coplanar points followed by the point ids. +Option 'Qi' includes the interior points. Each coplanar point (interior point) is +assigned to the facet it is furthest above (resp., least below). +.TP +FC +Print centrums for each facet. The output starts with the +dimension followed by the number of facets. +Then each facet centrum is printed, one per line. +.TP +Fd +Read input in cdd format with homogeneous points. +The input starts with comments. The first comment is reported in +the summary. +Data starts after a "begin" line. The next line is the number of points +followed by the dimension+1 and "real" or "integer". Then the points +are listed with a leading "1" or "1.0". The data ends with an "end" line. + +For halfspaces ('Fd Hn,n,...'), the input format is the same. Each halfspace +starts with its offset. The sign of the offset is the opposite of Qhull's +convention. +.TP +FD +Print normals ('n', 'Fo', 'Fi') or points ('p') in cdd format. +The first line is the command line that invoked Qhull. +Data starts with a "begin" line. The next line is the number of normals or points +followed by the dimension+1 and "real". Then the normals or points +are listed with the offset before the coefficients. The offset for points is +1.0. The offset for normals has the opposite sign. +The data ends with an "end" line. +.TP +FF +Print facets (as in 'f') without printing the ridges. +.TP +Fi +Print inner planes for each facet. The inner plane is below all vertices. +.TP +Fi +Print separating hyperplanes for bounded, inner regions of the Voronoi +diagram. The first line is the number +of ridges. Then each hyperplane is printed, one per line. A line starts +with the number of indices and floats. The first pair lists +adjacent input +sites, the next d floats are the normalized coefficients for the hyperplane, +and the last float is the offset. The hyperplane is oriented toward 'QVn' +(if defined), or the first input site of the pair. Use 'Tv' to +verify that the hyperplanes are perpendicular bisectors. Use 'Fo' for +unbounded regions, and 'Fv' for the corresponding Voronoi vertices. +.TP +FI +Print facet identifiers. +.TP +Fm +Print number of merges for each facet. At most 511 merges are reported for +a facet. See 'PMn' for printing the facets with the most merges. +.TP +FM +Output the hull in Maple format. Qhull writes a Maple +file for 2\[hy]d and 3\[hy]d +convex hulls and for 2\[hy]d Delaunay triangulations. Qhull produces a '.mpl' +file for displaying with display3d(). +.TP +Fn +Print neighbors for each facet. The output starts with the number of facets. +Then each facet is printed one per line. Each line +is the number of neighbors followed by an index for each neighbor. The indices +match the other facet output formats. + +A negative index indicates an unprinted +facet due to printing only good facets ('Pg'). It is the negation of the facet's +ID (option 'FI'). +For example, negative indices are used for facets +"at infinity" in the Delaunay triangulation. +.TP +FN +Print vertex neighbors or coplanar facet for each point. +The first line is the number +of points. Then each point is printed, one per line. If the +point is coplanar, the line is "1" followed by the facet's ID. +If the point is +not a selected vertex, the line is "0". +Otherwise, each line is the number of +neighbors followed by the corresponding facet indices (see 'Fn'). +.TP +Fo +Print outer planes for each facet in the same format as 'n'. +The outer plane is above all points. +.TP +Fo +Print separating hyperplanes for unbounded, outer regions of the Voronoi +diagram. The first line is the number +of ridges. Then each hyperplane is printed, one per line. A line starts +with the number of indices and floats. The first pair lists +adjacent input +sites, the next d floats are the normalized coefficients for the hyperplane, +and the last float is the offset. The hyperplane is oriented toward 'QVn' +(if defined), or the first input site of the pair. Use 'Tv' to +verify that the hyperplanes are perpendicular bisectors. Use 'Fi' for +bounded regions, and 'Fv' for the corresponding Voronoi vertices. +.TP +FO +List all options to stderr, including the default values. Additional 'FO's +are printed to stdout. +.TP +Fp +Print points for halfspace intersections (option 'Hn,n,...'). Each +intersection corresponds to a facet of the dual polytope. +The "infinity" point [\-10.101,\-10.101,...] +indicates an unbounded intersection. +.TP +FP +For each coplanar point ('Qc') print the point ID of the nearest vertex, +the point ID, the facet ID, and the distance. +.TP +FQ +Print command used for qhull and input. +.TP +Fs +Print a summary. The first line consists of the number of integers ("8"), +followed by the dimension, the number of points, the number of vertices, +the number of facets, the number of vertices selected for output, the +number of facets selected for output, the number of coplanar points selected +for output, number of simplicial, unmerged facets in output + +The second line consists of the number of reals ("2"), +followed by the maxmimum offset to an outer plane and and minimum offset to +an inner plane. Roundoff is included. Later +versions of Qhull may produce additional integers or reals. +.TP +FS +Print the size of the hull. The first line consists of the number of integers ("0"). +The second line consists of the number of reals ("2"), +followed by the total facet area, and the total volume. +Later +versions of Qhull may produce additional integers or reals. + +The total volume measures the volume +of the intersection of the halfspaces defined by each facet. +Both area and volume are +approximations for non\[hy]simplicial facets. See option 'Fa'. +.TP +Ft +Print a triangulation with added points for non\[hy]simplicial +facets. The first line is the dimension and the second line is the +number of points and the number of facets. The points follow, one +per line, then the facets follow as a list of point indices. With option 'Qz', the +points include the point\[hy]at\[hy]infinity. +.TP +Fv +Print vertices for each facet. The first line is the number +of facets. Then each facet is printed, one per line. Each line is +the number of vertices followed by the corresponding point ids. Vertices +are listed in the order they were added to the hull (the last one is first). +.TP +Fv +Print all ridges of a Voronoi diagram. The first line is the number +of ridges. Then each ridge is printed, one per line. A line starts +with the number of indices. The first pair lists adjacent input +sites, the remaining indices list Voronoi vertices. Vertex '0' indicates +the vertex\[hy]at\[hy]infinity (i.e., an unbounded ray). In 3\[hy]d, the vertices +are listed in order. See 'Fi' and 'Fo' for separating hyperplanes. +.TP +FV +Print average vertex. The average vertex is a feasible point +for halfspace intersection. +.TP +Fx +List extreme points (vertices) of the convex hull. The first line +is the number of points. The other lines give the indices of the +corresponding points. The first point is '0'. In 2\[hy]d, the points +occur in counter\[hy]clockwise order; otherwise they occur in input order. +For Delaunay triangulations, 'Fx' lists the extreme points of the +input sites. The points are unordered. +.PP +.TP +Geomview options +.TP +G +Produce a file for viewing with Geomview. Without other options, +Qhull displays edges in 2\[hy]d, outer planes in 3\[hy]d, and ridges in 4\[hy]d. +A ridge can be +explicit or implicit. An explicit ridge is a dim\-1 dimensional simplex +between two facets. +In 4\[hy]d, the explicit ridges are triangles. +When displaying a ridge in 4\[hy]d, Qhull projects the ridge's vertices to +one of its facets' hyperplanes. +Use 'Gh' to +project ridges to the intersection of both hyperplanes. +.TP +Ga +Display all input points as dots. +.TP +Gc +Display the centrum for each facet in 3\[hy]d. The centrum is defined by a +green radius sitting on a blue plane. The plane corresponds to the +facet's hyperplane. +The radius is defined by 'C\-n' or 'Cn'. +.TP +GDn +Drop dimension n in 3\[hy]d or 4\[hy]d. The result is a 2\[hy]d or 3\[hy]d object. +.TP +Gh +Display hyperplane intersections in 3\[hy]d and 4\[hy]d. In 3\[hy]d, the +intersection is a black line. It lies on two neighboring hyperplanes +(c.f., the blue squares associated with centrums ('Gc')). In 4\[hy]d, +the ridges are projected to the intersection of both hyperplanes. +.TP +Gi +Display inner planes in 2\[hy]d and 3\[hy]d. The inner plane of a facet +is below all of its vertices. It is parallel to the facet's hyperplane. +The inner plane's color is the opposite (1\-r,1\-g,1\-b) of the outer +plane. Its edges are determined by the vertices. +.TP +Gn +Do not display inner or outer planes. By default, +Geomview displays the precise plane (no merging) or both +inner and output planes (merging). Under merging, Geomview does +not display the inner plane if the +the difference between inner and outer is too small. +.TP +Go +Display outer planes in 2\[hy]d and 3\[hy]d. The outer plane of a facet +is above all input points. It is parallel to the facet's hyperplane. +Its color is determined by the facet's normal, and its +edges are determined by the vertices. +.TP +Gp +Display coplanar points and vertices as radii. A radius defines a ball +which corresponds to the imprecision of the point. The imprecision is +the maximum of the roundoff error, the centrum radius, and maxcoord * +(1\-An). It is at least 1/20'th of the maximum coordinate, +and ignores post\[hy]merging if pre\[hy]merging is done. +.TP +Gr +Display ridges in 3\[hy]d. A ridge connects the two vertices that are shared +by neighboring facets. Ridges are always displayed in 4\[hy]d. +.TP +Gt +A 3\[hy]d Delaunay triangulation looks like a convex hull with interior +facets. Option 'Gt' removes the outside ridges to reveal the outermost +facets. It automatically sets options 'Gr' and 'GDn'. +.TP +Gv +Display vertices as spheres. The radius of the sphere corresponds to +the imprecision of the data. See 'Gp' for determining the radius. +.PP +.TP +Print options +.TP +PAn +Only the n largest facets are marked good for printing. +Unless 'PG' is set, 'Pg' is automatically set. +.TP +Pdk:n +Drop facet from output if normal[k] <= n. The option 'Pdk' uses the +default value of 0 for n. +.TP +PDk:n +Drop facet from output if normal[k] >= n. The option 'PDk' uses the +default value of 0 for n. +.TP +PFn +Only facets with area at least 'n' are marked good for printing. +Unless 'PG' is set, 'Pg' is automatically set. +.TP +Pg +Print only good facets. A good facet is either visible from a point +(the 'QGn' option) or includes a point (the 'QVn' option). It also meets the +requirements of 'Pdk' and 'PDk' options. Option 'Pg' is automatically +set for options 'PAn' and 'PFn'. +.TP +PG +Print neighbors of good facets. +.TP +PMn +Only the n facets with the most merges are marked good for printing. +Unless 'PG' is set, 'Pg' is automatically set. +.TP +Po +Force output despite precision problems. Verify ('Tv') does not check +coplanar points. +Flipped facets are reported and concave facets are counted. +If 'Po' is used, points are not +partitioned into flipped facets and a flipped facet is always visible +to a point. +Also, if an error occurs before the completion of Qhull and tracing is +not active, 'Po' outputs a neighborhood of the erroneous facets +(if any). +.TP +Pp +Do not report precision problems. +.PP +.TP +Qhull control options +.TP +Qbk:0Bk:0 +Drop dimension k from the input points. This allows the user to +take convex hulls of sub\[hy]dimensional objects. It happens before +the Delaunay and Voronoi transformation. +.TP +QbB +Scale the input points to fit the unit cube. After scaling, the lower +bound will be \-0.5 and the upper bound +0.5 in all dimensions. +For Delaunay and +Voronoi diagrams, scaling happens after projection to the paraboloid. +Under precise +arithmetic, scaling does not change the topology of the convex hull. +.TP +Qbb +Scale the last coordinate to [0, m] where m is the maximum absolute +value of the other coordinates. For Delaunay and +Voronoi diagrams, scaling happens after projection to the paraboloid. +It reduces roundoff error for inputs with integer coordinates. +Under precise +arithmetic, scaling does not change the topology of the convex hull. +.TP +Qbk:n +Scale the k'th coordinate of the input points. After scaling, the lower +bound of the input points will be n. 'Qbk' scales to \-0.5. +.TP +QBk:n +Scale the k'th coordinate of the input points. After scaling, the upper +bound will be n. 'QBk' scales to +0.5. +.TP +Qc +Keep coplanar points with the nearest facet. Output +formats 'p', 'f', 'Gp', 'Fc', 'FN', and 'FP' will print the points. +.TP +Qf +Partition points to the furthest outside facet. +.TP +Qg +Only build good facets. With the 'Qg' option, Qhull will only build +those facets that it needs to determine the good facets in the output. +See 'QGn', 'QVn', and 'PdD' for defining good facets, and 'Pg' and 'PG' +for printing good facets and their neighbors. +.TP +QGn +A facet is good (see 'Qg' and 'Pg') if it is visible from point n. If n < 0, a facet is +good if it is not visible from point n. Point n is not added to the +hull (unless 'TCn' or 'TPn'). +With rbox, use the 'Pn,m,r' option to define your point; it +will be point 0 (QG0). +.TP +Qi +Keep interior points with the nearest facet. +Output formats 'p', 'f', 'Gp', 'FN', 'FP', and 'Fc' will print the points. +.TP +QJn +Joggle each input coordinate by adding a random number in [\-n,n]. If a +precision error occurs, then qhull increases n and tries again. It does +not increase n beyond a certain value, and it stops after a certain number +of attempts [see user.h]. Option 'QJ' +selects a default value for n. The output will be simplicial. For +Delaunay triangulations, 'QJn' sets 'Qbb' to scale the last coordinate +(not if 'Qbk:n' or 'QBk:n' is set). +\'QJn' is deprecated for Voronoi diagrams. See also 'Qt'. +.TP +Qm +Only process points that would otherwise increase max_outside. Other +points are treated as coplanar or interior points. +.TP +Qr +Process random outside points instead of furthest ones. This makes +Qhull equivalent to the randomized incremental algorithms. CPU time +is not reported since the randomization is inefficient. +.TP +QRn +Randomly rotate the input points. If n=0, use time as the random number seed. +If n>0, use n as the random number seed. If n=\-1, don't rotate but use +time as the random number seed. For Delaunay triangulations ('d' and 'v'), +rotate about the last axis. +.TP +Qs +Search all points for the initial simplex. +.TP +Qt +Triangulated output. Triangulate all non\[hy]simplicial facets. +\'Qt' is deprecated for Voronoi diagrams. See also 'Qt'. +.TP +Qv +Test vertex neighbors for convexity after post\[hy]merging. +To use the 'Qv' option, you also need to set a merge option +(e.g., 'Qx' or 'C\-0'). +.TP +QVn +A good facet (see 'Qg' and 'Pg') includes point n. If n<0, then a good facet does not +include point n. The point is either in the initial simplex or it +is the first point added to the hull. Option 'QVn' may not be used with merging. +.TP +Qx +Perform exact merges while building the hull. The "exact" merges +are merging a point into a coplanar facet (defined by 'Vn', 'Un', +and 'C\-n'), merging concave facets, merging duplicate ridges, and +merging flipped facets. Coplanar merges and angle coplanar merges ('A\-n') +are not performed. Concavity testing is delayed until a merge occurs. + +After +the hull is built, all coplanar merges are performed (defined by 'C\-n' +and 'A\-n'), then post\[hy]merges are performed +(defined by 'Cn' and 'An'). +.TP +Qz +Add a point "at infinity" that is above the paraboloid for Delaunay triangulations +and Voronoi diagrams. This reduces precision problems and allows the triangulation +of cospherical points. +.PP +.TP +Qhull experiments and speedups +.TP +Q0 +Turn off pre\[hy]merging as a default option. +With 'Q0'/'Qx' and without explicit pre\[hy]merge options, Qhull +ignores precision issues while constructing the convex hull. This +may lead to precision errors. If so, a descriptive warning is +generated. +.TP +Q1 +With 'Q1', Qhull sorts merges by type (coplanar, angle coplanar, concave) +instead of by angle. +.TP +Q2 +With 'Q2', Qhull merges all facets at once instead of using +independent sets of merges and then retesting. +.TP +Q3 +With 'Q3', Qhull does not remove redundant vertices. +.TP +Q4 +With 'Q4', Qhull avoids merges of an old facet into a new facet. +.TP +Q5 +With 'Q5', Qhull does not correct outer planes at the end. The +maximum outer plane is used instead. +.TP +Q6 +With 'Q6', Qhull does not pre\[hy]merge concave or coplanar facets. +.TP +Q7 +With 'Q7', Qhull processes facets in depth\[hy]first order instead of +breadth\[hy]first order. +.TP +Q8 +With 'Q8' and merging, Qhull does not retain near\[hy]interior points for adjusting +outer planes. 'Qc' will probably retain +all points that adjust outer planes. +.TP +Q9 +With 'Q9', Qhull processes the furthest of all outside sets at each iteration. +.TP +Q10 +With 'Q10', Qhull does not use special processing for narrow distributions. +.TP +Q11 +With 'Q11', Qhull copies normals and recompute centrums for tricoplanar facets. +.TP +Q12 +With 'Q12', Qhull does not report a very wide merge due to a duplicated ridge with nearly coincident vertices +.PP +.TP +Trace options +.TP +Tn +Trace at level n. Qhull includes full execution tracing. 'T\-1' +traces events. 'T1' traces +the overall execution of the program. 'T2' and 'T3' trace overall +execution and geometric and topological events. 'T4' traces the +algorithm. 'T5' includes information about memory allocation and +Gaussian elimination. +.TP +Ta +Annotate output with codes that identify the +corresponding qh_fprintf() statement. +.TP +Tc +Check frequently during execution. This will catch most inconsistency +errors. +.TP +TCn +Stop Qhull after building the cone of new facets for point n. The +output for 'f' includes the cone and the old hull. +See also 'TVn'. +.TP +TFn +Report progress whenever more than n facets are created +During post\[hy]merging, 'TFn' +reports progress after more than n/2 merges. +.TP +TI file +Input data from 'file'. The filename may not include spaces or +quotes. +.TP +TO file +Output results to 'file'. The name may be enclosed in single +quotes. +.TP +TPn +Turn on tracing when point n is added to the hull. Trace +partitions of point n. If used with TWn, turn off +tracing after adding point n to the hull. +.TP +TRn +Rerun qhull n times. Usually used with 'QJn' to determine the +probability that a given joggle will fail. +.TP +Ts +Collect statistics and print to stderr at the end of execution. +.TP +Tv +Verify the convex hull. This checks the topological structure, facet +convexity, and point inclusion. +If precision problems occurred, facet convexity is tested whether or +not 'Tv' is selected. +Option 'Tv' does not check point inclusion if forcing output with 'Po', +or if 'Q5' is set. + +For point inclusion testing, Qhull verifies that all points are below +all outer planes (facet\->maxoutside). Point inclusion is exhaustive +if merging or if the facet\[hy]point product is small enough; +otherwise Qhull verifies each point with a directed +search (qh_findbest). + +Point inclusion testing occurs after producing output. It prints +a message to stderr unless option 'Pp' is used. This +allows the user to interrupt Qhull without changing the output. +.TP +TVn +Stop Qhull after adding point n. If n < 0, stop Qhull before adding +point n. Output shows the hull at this time. See also 'TCn' +.TP +TMn +Turn on tracing at n'th merge. +.TP +TWn +Trace merge facets when the width is greater than n. +.TP +Tz +Redirect stderr to stdout. +.PP +.SH BUGS +Please report bugs to Brad Barber at qhull_bug@qhull.org. + +If Qhull does not compile, it is due to an incompatibility between your +system and ours. The first thing to check is that your compiler is +ANSI standard. If it is, check the man page for the best options, or +find someone to help you. If you locate the cause of your problem, +please send email since it might help others. + +If Qhull compiles but crashes on the test case (rbox D4), there's +still incompatibility between your system and ours. Typically it's +been due to mem.c and memory alignment. You can use qh_NOmem in mem.h +to turn off memory management. Please let us know if you figure out +how to fix these problems. + +If you do find a problem, try to simplify it before reporting the +error. Try different size inputs to locate the smallest one that +causes an error. You're welcome to hunt through the code using the +execution trace as a guide. This is especially true if you're +incorporating Qhull into your own program. + +When you do report an error, please attach a data set to the +end of your message. This allows us to see the error for ourselves. +Qhull is maintained part\[hy]time. +.PP +.SH E\[hy]MAIL +Please send correspondence to qhull@qhull.org and report bugs to +qhull_bug@qhull.org. Let us know how you use Qhull. If you +mention it in a paper, please send the reference and an abstract. + +If you would like to get Qhull announcements (e.g., a new version) +and news (any bugs that get fixed, etc.), let us know and we will add you to +our mailing list. If you would like to communicate with other +Qhull users, we will add you to the qhull_users alias. +For Internet news about geometric algorithms and convex hulls, look at +comp.graphics.algorithms and sci.math.num\-analysis + +.SH SEE ALSO +rbox(1) + +Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, +"The Quickhull Algorithm for Convex Hulls," ACM +Trans. on Mathematical Software, 22(4):469\[en]483, Dec. 1996. +http://portal.acm.org/citation.cfm?doid=235815.235821 +http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.117.405 + +Clarkson, K.L., K. Mehlhorn, and R. Seidel, "Four results on randomized +incremental construction," Computational Geometry: Theory and Applications, +vol. 3, p. 185\[en]211, 1993. + +Preparata, F. and M. Shamos, Computational +Geometry, Springer\[hy]Verlag, New York, 1985. + +.PP +.SH AUTHORS +.nf + C. Bradford Barber Hannu Huhdanpaa + bradb@shore.net hannu@qhull.org + + .fi + +.SH ACKNOWLEDGEMENTS + +A special thanks to Albert Marden, Victor Milenkovic, the Geometry Center, +Harvard University, and Endocardial Solutions, Inc. for supporting this work. + +Qhull 1.0 and 2.0 were developed under National Science Foundation +grants NSF/DMS\[hy]8920161 and NSF\[hy]CCR\[hy]91\[hy]15793 750\[hy]7504. David Dobkin +guided the original work at Princeton University. +If you find it useful, please let us know. + +The Geometry Center is supported by grant DMS\[hy]8920161 from the National +Science Foundation, by grant DOE/DE\[hy]FG02\[hy]92ER25137 from the Department +of Energy, by the University of Minnesota, and by Minnesota Technology, Inc. + +Qhull is available from http://www.qhull.org diff --git a/xs/src/qhull/html/qhull.txt b/xs/src/qhull/html/qhull.txt new file mode 100644 index 0000000000..03753547e9 --- /dev/null +++ b/xs/src/qhull/html/qhull.txt @@ -0,0 +1,1263 @@ + + + +qhull(1) qhull(1) + + +NAME + qhull - convex hull, Delaunay triangulation, Voronoi dia- + gram, halfspace intersection about a point, hull volume, facet area + +SYNOPSIS + qhull- compute convex hulls and related structures + input (stdin): dimension, #points, point coordinates + first comment (non-numeric) is listed in the summary + halfspace: use dim plus one with offsets after coefficients + + options (qh-quick.htm): + d - Delaunay triangulation by lifting points to a paraboloid + v - Voronoi diagram via the Delaunay triangulation + H1,1 - Halfspace intersection about [1,1,0,...] + d Qu - Furthest-site Delaunay triangulation (upper convex hull) + v Qu - Furthest-site Voronoi diagram + QJ - Joggle the input to avoid precision problems + . - concise list of all options + - - one-line description of all options + + Output options (subset): + FA - compute total area and volume + Fx - extreme points (convex hull vertices) + G - Geomview output (2-d, 3-d and 4-d) + Fp - halfspace intersection coordinates + m - Mathematica output (2-d and 3-d) + n - normals with offsets + o - OFF file format (if Voronoi, outputs regions) + TO file- output results to file, may be enclosed in single quotes + f - print all fields of all facets + s - summary of results (default) + Tv - verify result: structure, convexity, and point inclusion + p - vertex coordinates + i - vertices incident to each facet + + example: + rbox 1000 s | qhull Tv s FA + + - html manual: index.htm + - installation: README.txt + - see also: COPYING.txt, REGISTER.txt, Changes.txt + - WWW: + - GIT: + - mirror: + - news: + - Geomview: + - news group: + - FAQ: + - email: qhull@qhull.org + - bug reports: qhull_bug@qhull.org + + + + +Geometry Center 2003/12/30 1 + + + + + +qhull(1) qhull(1) + + + The sections are: + - INTRODUCTION + - DESCRIPTION, a description of Qhull + - IMPRECISION, how Qhull handles imprecision + - OPTIONS + - Input and output options + - Additional input/output formats + - Precision options + - Geomview options + - Print options + - Qhull options + - Trace options + - BUGS + - E-MAIL + - SEE ALSO + - AUTHORS + - ACKNOWLEGEMENTS + + This man page briefly describes all Qhull options. Please + report any mismatches with Qhull's html manual (qh- + man.htm). + + + +INTRODUCTION + Qhull is a general dimension code for computing convex + hulls, Delaunay triangulations, Voronoi diagram, furthest- + site Voronoi diagram, furthest-site Delaunay triangula- + tions, and halfspace intersections about a point. It + implements the Quickhull algorithm for computing the con- + vex hull. Qhull handles round-off errors from floating + point arithmetic. It can approximate a convex hull. + + The program includes options for hull volume, facet area, + partial hulls, input transformations, randomization, trac- + ing, multiple output formats, and execution statistics. + The program can be called from within your application. + You can view the results in 2-d, 3-d and 4-d with + Geomview. + + +DESCRIPTION + The format of input is the following: first line contains + the dimension, second line contains the number of input + points, and point coordinates follow. The dimension and + number of points can be reversed. Comments and line + breaks are ignored. A comment starts with a non-numeric + character and continues to the end of line. The first + comment is reported in summaries and statistics. Error + reporting is better if there is one point per line. + + The default printout option is a short summary. There are + many other output formats. + + + + +Geometry Center 2003/12/30 2 + + + + + +qhull(1) qhull(1) + + + Qhull implements the Quickhull algorithm for convex hull. + This algorithm combines the 2-d Quickhull algorithm with + the n-d beneath-beyond algorithm [c.f., Preparata & Shamos + '85]. It is similar to the randomized algorithms of + Clarkson and others [Clarkson et al. '93]. The main + advantages of Quickhull are output sensitive performance, + reduced space requirements, and automatic handling of pre- + cision problems. + + The data structure produced by Qhull consists of vertices, + ridges, and facets. A vertex is a point of the input set. + A ridge is a set of d vertices and two neighboring facets. + For example in 3-d, a ridge is an edge of the polyhedron. + A facet is a set of ridges, a set of neighboring facets, a + set of incident vertices, and a hyperplane equation. For + simplicial facets, the ridges are defined by the vertices + and neighboring facets. When Qhull merges two facets, it + produces a non-simplicial facet. A non-simplicial facet + has more than d neighbors and may share more than one + ridge with a neighbor. + + +IMPRECISION + Since Qhull uses floating point arithmetic, roundoff error + may occur for each calculation. This causes problems for + most geometric algorithms. + + Qhull automatically sets option 'C-0' in 2-d, 3-d, and + 4-d, or option 'Qx' in 5-d and higher. These options han- + dle precision problems by merging facets. Alternatively, + use option 'QJ' to joggle the input. + + With 'C-0', Qhull merges non-convex facets while con- + structing the hull. The remaining facets are clearly con- + vex. With 'Qx', Qhull merges coplanar horizon facets, + flipped facets, concave facets and duplicated ridges. It + merges coplanar facets after constructing the hull. With + 'Qx', coplanar points may be missed, but it appears to be + unlikely. + + To guarantee triangular output, joggle the input with + option 'QJ'. Facet merging will not occur. + +OPTIONS + To get a list of the most important options, execute + 'qhull' by itself. To get a complete list of options, + execute 'qhull -'. To get a complete, concise list of + options, execute 'qhull .'. + + Options can be in any order. Capitalized options take an + argument (except 'PG' and 'F' options). Single letters + are used for output formats and precision constants. The + other options are grouped into menus for other output for- + mats ('F'), Geomview output ('G'), printing ('P'), Qhull + + + +Geometry Center 2003/12/30 3 + + + + + +qhull(1) qhull(1) + + + control ('Q'), and tracing ('T'). + + Main options: + + default + Compute the convex hull of the input points. + Report a summary of the result. + + d Compute the Delaunay triangulation by lifting the + input points to a paraboloid. The 'o' option + prints the input points and facets. The 'QJ' + option guarantees triangular output. The 'Ft' + option prints a triangulation. It adds points (the + centrums) to non-simplicial facets. + + v Compute the Voronoi diagram from the Delaunay tri- + angulation. The 'p' option prints the Voronoi ver- + tices. The 'o' option prints the Voronoi vertices + and the vertices in each Voronoi region. It lists + regions in site id order. The 'Fv' option prints + each ridge of the Voronoi diagram. The first or + zero'th vertex indicates the infinity vertex. Its + coordinates are qh_INFINITE (-10.101). It indi- + cates unbounded Voronoi regions or degenerate + Delaunay triangles. + + Hn,n,... + Compute halfspace intersection about [n,n,0,...]. + The input is a set of halfspaces defined in the + same format as 'n', 'Fo', and 'Fi'. Use 'Fp' to + print the intersection points. Use 'Fv' to list + the intersection points for each halfspace. The + other output formats display the dual convex hull. + + The point [n,n,n,...] is a feasible point for the + halfspaces, i.e., a point that is inside all of the + halfspaces (Hx+b <= 0). The default coordinate + value is 0. + + The input may start with a feasible point. If so, + use 'H' by itself. The input starts with a feasi- + ble point when the first number is the dimension, + the second number is "1", and the coordinates com- + plete a line. The 'FV' option produces a feasible + point for a convex hull. + + d Qu Compute the furthest-site Delaunay triangulation + from the upper convex hull. The 'o' option prints + the input points and facets. The 'QJ' option guar- + antees triangular otuput. You can also use facets. + + v Qu Compute the furthest-site Voronoi diagram. The 'p' + option prints the Voronoi vertices. The 'o' option + prints the Voronoi vertices and the vertices in + + + +Geometry Center 2003/12/30 4 + + + + + +qhull(1) qhull(1) + + + each Voronoi region. The 'Fv' option prints each + ridge of the Voronoi diagram. The first or zero'th + vertex indicates the infinity vertex at infinity. + Its coordinates are qh_INFINITE (-10.101). It + indicates unbounded Voronoi regions and degenerate + Delaunay triangles. + + Qt Triangulated output. + + + Input/Output options: + + f Print out all facets and all fields of each facet. + + G Output the hull in Geomview format. For imprecise + hulls, Geomview displays the inner and outer hull. + Geomview can also display points, ridges, vertices, + coplanar points, and facet intersections. See + below for a list of options. + + For Delaunay triangulations, 'G' displays the cor- + responding paraboloid. For halfspace intersection, + 'G' displays the dual polytope. + + i Output the incident vertices for each facet. Qhull + prints the number of facets followed by the ver- + tices of each facet. One facet is printed per + line. The numbers are the 0-relative indices of + the corresponding input points. The facets are + oriented. + + In 4-d and higher, Qhull triangulates non-simpli- + cial facets. Each apex (the first vertex) is a + created point that corresponds to the facet's cen- + trum. Its index is greater than the indices of the + input points. Each base corresponds to a simpli- + cial ridge between two facets. To print the ver- + tices without triangulation, use option 'Fv'. + + m Output the hull in Mathematica format. Qhull + writes a Mathematica file for 2-d and 3-d convex + hulls and for 2-d Delaunay triangulations. Qhull + produces a list of objects that you can assign to a + variable in Mathematica, for example: "list= << + ". If the object is 2-d, it can be + visualized by "Show[Graphics[list]] ". For 3-d + objects the command is "Show[Graphics3D[list]]". + + n Output the normal equation for each facet. Qhull + prints the dimension (plus one), the number of + facets, and the normals for each facet. The + facet's offset follows its normal coefficients. + + o Output the facets in OFF file format. Qhull prints + the dimension, number of points, number of facets, + and number of ridges. Then it prints the + + + +Geometry Center 2003/12/30 5 + + + + + +qhull(1) qhull(1) + + + coordinates of the input points and the vertices + for each facet. Each facet is on a separate line. + The first number is the number of vertices. The + remainder are the indices of the corresponding + points. The vertices are oriented in 2-d, 3-d, and + in simplicial facets. + + For 2-d Voronoi diagrams, the vertices are sorted + by adjacency, but not oriented. In 3-d and higher, + the Voronoi vertices are sorted by index. See the + 'v' option for more information. + + p Output the coordinates of each vertex point. Qhull + prints the dimension, the number of points, and the + coordinates for each vertex. With the 'Gc' and + 'Gi' options, it also prints coplanar and interior + points. For Voronoi diagrams, it prints the coor- + dinates of each Voronoi vertex. + + s Print a summary to stderr. If no output options + are specified at all, a summary goes to stdout. + The summary lists the number of input points, the + dimension, the number of vertices in the convex + hull, the number of facets in the convex hull, the + number of good facets (if 'Pg'), and statistics. + + The last two statistics (if needed) measure the + maximum distance from a point or vertex to a facet. + The number in parenthesis (e.g., 2.1x) is the ratio + between the maximum distance and the worst-case + distance due to merging two simplicial facets. + + + Precision options + + An Maximum angle given as a cosine. If the angle + between a pair of facet normals is greater than n, Qhull + merges one of the facets into a neighbor. If 'n' + is negative, Qhull tests angles after adding each + point to the hull (pre-merging). If 'n' is posi- + tive, Qhull tests angles after constructing the + hull (post-merging). Both pre- and post-merging + can be defined. + + Option 'C0' or 'C-0' is set if the corresponding + 'Cn' or 'C-n' is not set. If 'Qx' is set, then 'A- + n' and 'C-n' are checked after the hull is con- + structed and before 'An' and 'Cn' are checked. + + Cn Centrum radius. If a centrum is less than n below + a neighboring facet, Qhull merges one of the + facets. If 'n' is negative or '-0', Qhull tests + and merges facets after adding each point to the + hull. This is called "pre-merging". If 'n' is + + + +Geometry Center 2003/12/30 6 + + + + + +qhull(1) qhull(1) + + + positive, Qhull tests for convexity after con- + structing the hull ("post-merging"). Both pre- and + post-merging can be defined. + + For 5-d and higher, 'Qx' should be used instead of + 'C-n'. Otherwise, most or all facets may be merged + together. + + En Maximum roundoff error for distance computations. + + Rn Randomly perturb distance computations up to +/- n + * max_coord. This option perturbs every distance, + hyperplane, and angle computation. To use time as + the random number seed, use option 'QR-1'. + + Vn Minimum distance for a facet to be visible. A + facet is visible if the distance from the point to + the facet is greater than 'Vn'. + + Without merging, the default value for 'Vn' is the + round-off error ('En'). With merging, the default + value is the pre-merge centrum ('C-n') in 2-d or + 3--d, or three times that in other dimensions. If + the outside width is specified ('Wn'), the maximum, + default value for 'Vn' is 'Wn'. + + Un Maximum distance below a facet for a point to be + coplanar to the facet. The default value is 'Vn'. + + Wn Minimum outside width of the hull. Points are + added to the convex hull only if they are clearly + outside of a facet. A point is outside of a facet + if its distance to the facet is greater than 'Wn'. + The normal value for 'Wn' is 'En'. If the user + specifies pre-merging and does not set 'Wn', than + 'Wn' is set to the premerge 'Cn' and maxco- + ord*(1-An). + + + Additional input/output formats + + Fa Print area for each facet. For Delaunay triangula- + tions, the area is the area of the triangle. For + Voronoi diagrams, the area is the area of the dual + facet. Use 'PAn' for printing the n largest + facets, and option 'PFn' for printing facets larger + than 'n'. + + The area for non-simplicial facets is the sum of + the areas for each ridge to the centrum. Vertices + far below the facet's hyperplane are ignored. The + reported area may be significantly less than the + actual area. + + + + +Geometry Center 2003/12/30 7 + + + + + +qhull(1) qhull(1) + + + FA Compute the total area and volume for option 's'. + It is an approximation for non-simplicial facets + (see 'Fa'). + + Fc Print coplanar points for each facet. The output + starts with the number of facets. Then each facet + is printed one per line. Each line is the number + of coplanar points followed by the point ids. + Option 'Qi' includes the interior points. Each + coplanar point (interior point) is assigned to the + facet it is furthest above (resp., least below). + + FC Print centrums for each facet. The output starts + with the dimension followed by the number of + facets. Then each facet centrum is printed, one + per line. + + Fd Read input in cdd format with homogeneous points. + The input starts with comments. The first comment + is reported in the summary. Data starts after a + "begin" line. The next line is the number of + points followed by the dimension+1 and "real" or + "integer". Then the points are listed with a + leading "1" or "1.0". The data ends with an "end" + line. + + For halfspaces ('Fd Hn,n,...'), the input format is + the same. Each halfspace starts with its offset. + The sign of the offset is the opposite of Qhull's + convention. + + FD Print normals ('n', 'Fo', 'Fi') or points ('p') in + cdd format. The first line is the command line + that invoked Qhull. Data starts with a "begin" + line. The next line is the number of normals or + points followed by the dimension+1 and "real". + Then the normals or points are listed with the + offset before the coefficients. The offset for + points is 1.0. The offset for normals has the + opposite sign. The data ends with an "end" line. + + FF Print facets (as in 'f') without printing the + ridges. + + Fi Print inner planes for each facet. The inner plane + is below all vertices. + + Fi Print separating hyperplanes for bounded, inner + regions of the Voronoi diagram. The first line is + the number of ridges. Then each hyperplane is + printed, one per line. A line starts with the num- + ber of indices and floats. The first pair lists + adjacent input sites, the next d floats are the + normalized coefficients for the hyperplane, and the + + + +Geometry Center 2003/12/30 8 + + + + + +qhull(1) qhull(1) + + + last float is the offset. The hyperplane is ori- + ented toward verify that the hyperplanes are per- + pendicular bisectors. Use 'Fo' for unbounded + regions, and 'Fv' for the corresponding Voronoi + vertices. + + FI Print facet identifiers. + + Fm Print number of merges for each facet. At most 511 + merges are reported for a facet. See 'PMn' for + printing the facets with the most merges. + + FM Output the hull in Maple format. See 'm' + + Fn Print neighbors for each facet. The output starts + with the number of facets. Then each facet is + printed one per line. Each line is the number of + neighbors followed by an index for each neighbor. + The indices match the other facet output formats. + + A negative index indicates an unprinted facet due + to printing only good facets ('Pg'). It is the + negation of the facet's id (option 'FI'). For + example, negative indices are used for facets "at + infinity" in the Delaunay triangulation. + + FN Print vertex neighbors or coplanar facet for each + point. The first line is the number of points. + Then each point is printed, one per line. If the + point is coplanar, the line is "1" followed by the + facet's id. If the point is not a selected vertex, + the line is "0". Otherwise, each line is the num- + ber of neighbors followed by the corresponding + facet indices (see 'Fn'). + + Fo Print outer planes for each facet in the same for- + mat as 'n'. The outer plane is above all points. + + Fo Print separating hyperplanes for unbounded, outer + regions of the Voronoi diagram. The first line is + the number of ridges. Then each hyperplane is + printed, one per line. A line starts with the num- + ber of indices and floats. The first pair lists + adjacent input sites, the next d floats are the + normalized coefficients for the hyperplane, and the + last float is the offset. The hyperplane is ori- + ented toward verify that the hyperplanes are per- + pendicular bisectors. Use 'Fi' for bounded + regions, and 'Fv' for the corresponding Voronoi + vertices. + + FO List all options to stderr, including the default + values. Additional 'FO's are printed to stdout. + + Fp Print points for halfspace intersections (option + 'Hn,n,...'). Each intersection corresponds to a + + + +Geometry Center 2003/12/30 9 + + + +qhull(1) qhull(1) + + + facet of the dual polytope. The "infinity" point + [-10.101,-10.101,...] indicates an unbounded + intersection. + + FP For each coplanar point ('Qc') print the point id + of the nearest vertex, the point id, the facet id, + and the distance. + + FQ Print command used for qhull and input. + + Fs Print a summary. The first line consists of the + number of integers ("7"), followed by the dimen- + sion, the number of points, the number of vertices, + the number of facets, the number of vertices + selected for output, the number of facets selected + for output, the number of coplanar points selected + for output. + + The second line consists of the number of reals + ("2"), followed by the maxmimum offset to an outer + plane and and minimum offset to an inner plane. + Roundoff is included. Later versions of Qhull may + produce additional integers or reals. + + FS Print the size of the hull. The first line con- + sists of the number of integers ("0"). The second + line consists of the number of reals ("2"), fol- + lowed by the total facet area, and the total vol- + ume. Later versions of Qhull may produce addi- + tional integers or reals. + + The total volume measures the volume of the inter- + section of the halfspaces defined by each facet. + Both area and volume are approximations for non- + simplicial facets. See option 'Fa'. + + Ft Print a triangulation with added points for non- + simplicial facets. The first line is the dimension + and the second line is the number of points and the + number of facets. The points follow, one per line, + then the facets follow as a list of point indices. + With option points include the point-at-infinity. + + Fv Print vertices for each facet. The first line is + the number of facets. Then each facet is printed, + one per line. Each line is the number of vertices + followed by the corresponding point ids. Vertices + are listed in the order they were added to the hull + (the last one is first). + + Fv Print all ridges of a Voronoi diagram. The first + line is the number of ridges. Then each ridge is + printed, one per line. A line starts with the num- + ber of indices. The first pair lists adjacent + + + +Geometry Center 2003/12/30 10 + + + + + +qhull(1) qhull(1) + + + input sites, the remaining indices list Voronoi + vertices. Vertex '0' indicates the vertex-at- + infinity (i.e., an unbounded ray). In 3-d, the + vertices are listed in order. See 'Fi' and 'Fo' + for separating hyperplanes. + + FV Print average vertex. The average vertex is a fea- + sible point for halfspace intersection. + + Fx List extreme points (vertices) of the convex hull. + The first line is the number of points. The other + lines give the indices of the corresponding points. + The first point is '0'. In 2-d, the points occur + in counter-clockwise order; otherwise they occur in + input order. For Delaunay triangulations, 'Fx' + lists the extreme points of the input sites. The + points are unordered. + + + Geomview options + + G Produce a file for viewing with Geomview. Without + other options, Qhull displays edges in 2-d, outer + planes in 3-d, and ridges in 4-d. A ridge can be + explicit or implicit. An explicit ridge is a dim-1 + dimensional simplex between two facets. In 4-d, + the explicit ridges are triangles. When displaying + a ridge in 4-d, Qhull projects the ridge's vertices + to one of its facets' hyperplanes. Use 'Gh' to + project ridges to the intersection of both hyper- + planes. + + Ga Display all input points as dots. + + Gc Display the centrum for each facet in 3-d. The + centrum is defined by a green radius sitting on a + blue plane. The plane corresponds to the facet's + hyperplane. The radius is defined by 'C-n' or + 'Cn'. + + GDn Drop dimension n in 3-d or 4-d. The result is a + 2-d or 3-d object. + + Gh Display hyperplane intersections in 3-d and 4-d. + In 3-d, the intersection is a black line. It lies + on two neighboring hyperplanes (c.f., the blue + squares associated with centrums ('Gc')). In 4-d, + the ridges are projected to the intersection of + both hyperplanes. + + Gi Display inner planes in 2-d and 3-d. The inner + plane of a facet is below all of its vertices. It + is parallel to the facet's hyperplane. The inner + plane's color is the opposite (1-r,1-g,1-b) of the + + + +Geometry Center 2003/12/30 11 + + + + + +qhull(1) qhull(1) + + + outer plane. Its edges are determined by the ver- + tices. + + Gn Do not display inner or outer planes. By default, + Geomview displays the precise plane (no merging) or + both inner and output planes (merging). Under + merging, Geomview does not display the inner plane + if the the difference between inner and outer is + too small. + + Go Display outer planes in 2-d and 3-d. The outer + plane of a facet is above all input points. It is + parallel to the facet's hyperplane. Its color is + determined by the facet's normal, and its edges are + determined by the vertices. + + Gp Display coplanar points and vertices as radii. A + radius defines a ball which corresponds to the + imprecision of the point. The imprecision is the + maximum of the roundoff error, the centrum radius, + and maxcoord * (1-An). It is at least 1/20'th of + the maximum coordinate, and ignores post-merging if + pre-merging is done. + + Gr Display ridges in 3-d. A ridge connects the two + vertices that are shared by neighboring facets. + Ridges are always displayed in 4-d. + + Gt A 3-d Delaunay triangulation looks like a convex + hull with interior facets. Option 'Gt' removes the + outside ridges to reveal the outermost facets. It + automatically sets options 'Gr' and 'GDn'. + + Gv Display vertices as spheres. The radius of the + sphere corresponds to the imprecision of the data. + See 'Gp' for determining the radius. + + + Print options + + PAn Only the n largest facets are marked good for + printing. Unless 'PG' is set, 'Pg' is automati- + cally set. + + Pdk:n Drop facet from output if normal[k] <= n. The + option 'Pdk' uses the default value of 0 for n. + + PDk:n Drop facet from output if normal[k] >= n. The + option 'PDk' uses the default value of 0 for n. + + PFn Only facets with area at least 'n' are marked good + for printing. Unless 'PG' is set, 'Pg' is automat- + ically set. + + + + +Geometry Center 2003/12/30 12 + + + + + +qhull(1) qhull(1) + + + Pg Print only good facets. A good facet is either + visible from a point (the 'QGn' option) or includes + a point (the 'QVn' option). It also meets the + requirements of 'Pdk' and 'PDk' options. Option + 'Pg' is automatically set for options 'PAn' and + 'PFn'. + + PG Print neighbors of good facets. + + PMn Only the n facets with the most merges are marked + good for printing. Unless 'PG' is set, 'Pg' is + automatically set. + + Po Force output despite precision problems. Verify ('Tv') does not check + coplanar points. Flipped facets are reported and + concave facets are counted. If 'Po' is used, + points are not partitioned into flipped facets and + a flipped facet is always visible to a point. + Also, if an error occurs before the completion of + Qhull and tracing is not active, 'Po' outputs a + neighborhood of the erroneous facets (if any). + + Pp Do not report precision problems. + + + Qhull control options + + Qbk:0Bk:0 + Drop dimension k from the input points. This + allows the user to take convex hulls of sub-dimen- + sional objects. It happens before the Delaunay and + Voronoi transformation. + + QbB Scale the input points to fit the unit cube. After + scaling, the lower bound will be -0.5 and the upper + bound +0.5 in all dimensions. For Delaunay and + Voronoi diagrams, scaling happens after projection + to the paraboloid. Under precise arithmetic, scal- + ing does not change the topology of the convex + hull. + + Qbb Scale the last coordinate to [0, m] where m is the + maximum absolute value of the other coordinates. + For Delaunay and Voronoi diagrams, scaling happens + after projection to the paraboloid. It reduces + roundoff error for inputs with integer coordinates. + Under precise arithmetic, scaling does not change + the topology of the convex hull. + + Qbk:n Scale the k'th coordinate of the input points. + After scaling, the lower bound of the input points + will be n. 'Qbk' scales to -0.5. + + + +Geometry Center 2003/12/30 13 + + + + + +qhull(1) qhull(1) + + + QBk:n Scale the k'th coordinate of the input points. + After scaling, the upper bound will be n. 'QBk' + scales to +0.5. + + Qc Keep coplanar points with the nearest facet. Out- + put formats 'p', 'f', 'Gp', 'Fc', 'FN', and 'FP' + will print the points. + + Qf Partition points to the furthest outside facet. + + Qg Only build good facets. With the 'Qg' option, + Qhull will only build those facets that it needs to + determine the good facets in the output. See + 'QGn', 'QVn', and 'PdD' for defining good facets, + and 'Pg' and 'PG' for printing good facets and + their neighbors. + + QGn A facet is good (see 'Qg' and 'Pg') if it is visi- + ble from point n. If n < 0, a facet is good if it + is not visible from point n. Point n is not added + to the hull (unless 'TCn' or 'TPn'). With rbox, + use the 'Pn,m,r' option to define your point; it + will be point 0 (QG0). + + Qi Keep interior points with the nearest facet. Out- + put formats 'p', 'f', 'Gp', 'FN', 'FP', and 'Fc' + will print the points. + + QJn Joggle each input coordinate by adding a random + number in [-n,n]. If a precision error occurs, + then qhull increases n and tries again. It does + not increase n beyond a certain value, and it stops + after a certain number of attempts [see user.h]. + Option 'QJ' selects a default value for n. The + output will be simplicial. For Delaunay triangula- + tions, 'QJn' sets 'Qbb' to scale the last coordi- + nate (not if 'Qbk:n' or 'QBk:n' is set). 'QJn' is + deprecated for Voronoi diagrams. See also 'Qt'. + + Qm Only process points that would otherwise increase + max_outside. Other points are treated as coplanar + or interior points. + + Qr Process random outside points instead of furthest + ones. This makes Qhull equivalent to the random- + ized incremental algorithms. CPU time is not + reported since the randomization is inefficient. + + QRn Randomly rotate the input points. If n=0, use time + as the random number seed. If n>0, use n as the + random number seed. If n=-1, don't rotate but use + time as the random number seed. For Delaunay tri- + angulations ('d' and 'v'), rotate about the last + axis. + + + + +Geometry Center 2003/12/30 14 + + + + + +qhull(1) qhull(1) + + + Qs Search all points for the initial simplex. + + Qt Triangulated output. Triangulate non-simplicial + facets. 'Qt' is deprecated for Voronoi diagrams. + See also 'QJn' + + Qv Test vertex neighbors for convexity after post- + merging. To use the 'Qv' option, you also need to + set a merge option (e.g., 'Qx' or 'C-0'). + + QVn A good facet (see 'Qg' and 'Pg') includes point n. + If n<0, then a good facet does not include point n. + The point is either in the initial simplex or it is + the first point added to the hull. Option 'QVn' + may not be used with merging. + + Qx Perform exact merges while building the hull. The + "exact" merges are merging a point into a coplanar + facet (defined by 'Vn', 'Un', and 'C-n'), merging + concave facets, merging duplicate ridges, and merg- + ing flipped facets. Coplanar merges and angle + coplanar merges ('A-n') are not performed. Concav- + ity testing is delayed until a merge occurs. + + After the hull is built, all coplanar merges are + performed (defined by 'C-n' and 'A-n'), then post- + merges are performed (defined by 'Cn' and 'An'). + + Qz Add a point "at infinity" that is above the + paraboloid for Delaunay triangulations and Voronoi + diagrams. This reduces precision problems and + allows the triangulation of cospherical points. + + + Qhull experiments and speedups + + Q0 Turn off pre-merging as a default option. With + 'Q0'/'Qx' and without explicit pre-merge options, + Qhull ignores precision issues while constructing + the convex hull. This may lead to precision + errors. If so, a descriptive warning is generated. + + Q1 With 'Q1', Qhull sorts merges by type (coplanar, + angle coplanar, concave) instead of by angle. + + Q2 With 'Q2', Qhull merges all facets at once instead + of using independent sets of merges and then + retesting. + + Q3 With 'Q3', Qhull does not remove redundant ver- + tices. + + Q4 With 'Q4', Qhull avoids merges of an old facet into + a new facet. + + Q5 With 'Q5', Qhull does not correct outer planes at + the end. The maximum outer plane is used instead. + + + + +Geometry Center 2003/12/30 15 + + + + + +qhull(1) qhull(1) + + + Q6 With 'Q6', Qhull does not pre-merge concave or + coplanar facets. + + Q7 With 'Q7', Qhull processes facets in depth-first + order instead of breadth-first order. + + Q8 With 'Q8' and merging, Qhull does not retain near- + interior points for adjusting outer planes. 'Qc' + will probably retain all points that adjust outer + planes. + + Q9 With 'Q9', Qhull processes the furthest of all out- + side sets at each iteration. + + Q10 With 'Q10', Qhull does not use special processing + for narrow distributions. + + Q11 With 'Q11', Qhull copies normals and recomputes + centrums for tricoplanar facets. + + Q12 With 'Q12', Qhull does not report a very wide merge due + to a duplicated ridge with nearly coincident vertices + + Trace options + + Tn Trace at level n. Qhull includes full execution + tracing. 'T-1' traces events. 'T1' traces the + overall execution of the program. 'T2' and 'T3' + trace overall execution and geometric and topologi- + cal events. 'T4' traces the algorithm. 'T5' + includes information about memory allocation and + Gaussian elimination. + + Ta Annotate output with codes that identify the + corresponding qh_fprintf() statement. + + Tc Check frequently during execution. This will catch + most inconsistency errors. + + TCn Stop Qhull after building the cone of new facets + for point n. The output for 'f' includes the cone + and the old hull. See also 'TVn'. + + TFn Report progress whenever more than n facets are + created During post-merging, 'TFn' reports progress + after more than n/2 merges. + + TI file + Input data from 'file'. The filename may not include + spaces or quotes. + + TO file + Output results to 'file'. The name may be enclosed + in single quotes. + + TPn Turn on tracing when point n is added to the hull. + Trace partitions of point n. If used with TWn, turn off + tracing after adding point n to the hull. + + TRn Rerun qhull n times. Usually used with 'QJn' to + determine the probability that a given joggle will + fail. + + Ts Collect statistics and print to stderr at the end + of execution. + + Tv Verify the convex hull. This checks the topologi- + cal structure, facet convexity, and point inclu- + sion. If precision problems occurred, facet con- + vexity is tested whether or not 'Tv' is selected. + Option 'Tv' does not check point inclusion if + + + +Geometry Center 2003/12/30 16 + + + + + +qhull(1) qhull(1) + + + forcing output with 'Po', or if 'Q5' is set. + + For point inclusion testing, Qhull verifies that + all points are below all outer planes (facet->max- + outside). Point inclusion is exhaustive if merging + or if the facet-point product is small enough; oth- + erwise Qhull verifies each point with a directed + search (qh_findbest). + + Point inclusion testing occurs after producing out- + put. It prints a message to stderr unless option + 'Pp' is used. This allows the user to interrupt + Qhull without changing the output. + + TVn Stop Qhull after adding point n. If n < 0, stop + Qhull before adding point n. Output shows the hull + at this time. See also 'TCn' + + TMn Turn on tracing at n'th merge. + + TWn Trace merge facets when the width is greater than + n. + + Tz Redirect stderr to stdout. + + +BUGS + Please report bugs to Brad Barber at + qhull_bug@qhull.org. + + If Qhull does not compile, it is due to an incompatibility + between your system and ours. The first thing to check is + that your compiler is ANSI standard. If it is, check the + man page for the best options, or find someone to help + you. If you locate the cause of your problem, please send + email since it might help others. + + If Qhull compiles but crashes on the test case (rbox D4), + there's still incompatibility between your system and + ours. Typically it's been due to mem.c and memory align- + ment. You can use qh_NOmem in mem.h to turn off memory + management. Please let us know if you figure out how to + fix these problems. + + If you do find a problem, try to simplify it before + reporting the error. Try different size inputs to locate + the smallest one that causes an error. You're welcome to + hunt through the code using the execution trace as a + guide. This is especially true if you're incorporating + Qhull into your own program. + + When you do report an error, please attach a data set to + the end of your message. This allows us to see the error + for ourselves. Qhull is maintained part-time. + + + +Geometry Center 2003/12/30 17 + + + + + +qhull(1) qhull(1) + + +E-MAIL + Please send correspondence to qhull@qhull.org and + report bugs to qhull_bug@qhull.org. Let us know how + you use Qhull. If you mention it in a paper, please send + the reference and an abstract. + + If you would like to get Qhull announcements (e.g., a new + version) and news (any bugs that get fixed, etc.), let us + know and we will add you to our mailing list. If you + would like to communicate with other Qhull users, we will + add you to the qhull_users alias. For Internet news about + geometric algorithms and convex hulls, look at comp.graph- + ics.algorithms and sci.math.num-analysis + + +SEE ALSO + rbox(1) + + Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, "The + Quickhull Algorithm for Convex Hulls," ACM Trans. on Math- + ematical Software, 22(4):469-483, Dec. 1996. + http://portal.acm.org/citation.cfm?doid=235815.235821 + http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.117.405 + + + Clarkson, K.L., K. Mehlhorn, and R. Seidel, "Four results + on randomized incremental construction," Computational + Geometry: Theory and Applications, vol. 3, p. 185-211, + 1993. + + Preparata, F. and M. Shamos, Computational Geometry, + Springer-Verlag, New York, 1985. + + + +AUTHORS + C. Bradford Barber Hannu Huhdanpaa + bradb@shore.net hannu@qhull.org + + + +ACKNOWLEDGEMENTS + A special thanks to Albert Marden, Victor Milenkovic, the + Geometry Center, Harvard University, and Endocardial Solu- + tions, Inc. for supporting this work. + + Qhull 1.0 and 2.0 were developed under National Science Foundation + grants NSF/DMS-8920161 and NSF-CCR-91-15793 750-7504. David Dobkin + + + +Geometry Center 2003/12/30 18 + + + + + +qhull(1) qhull(1) + + + guided the original work at Princeton University. If you find it + useful, please let us know. + + The Geometry Center was supported by grant DMS-8920161 from the National + Science Foundation, by grant DOE/DE-FG02-92ER25137 from the Department + of Energy, by the University of Minnesota, and by Minnesota Technology, Inc. + + Qhull is available from http://www.qhull.org + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Geometry Center 2003/12/30 19 + + diff --git a/xs/src/qhull/html/qvoron_f.htm b/xs/src/qhull/html/qvoron_f.htm new file mode 100644 index 0000000000..db538b5ab5 --- /dev/null +++ b/xs/src/qhull/html/qvoron_f.htm @@ -0,0 +1,396 @@ + + + + +qvoronoi Qu -- furthest-site Voronoi diagram + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [delaunay]qvoronoi Qu -- furthest-site Voronoi diagram

    + +

    The furthest-site Voronoi diagram is the furthest-neighbor map for a set of +points. Each region contains those points that are further +from one input site than any other input site. See the +survey article by Aurenhammer ['91] +and the brief introduction by O'Rourke ['94]. The furthest-site Voronoi diagram is the dual of the furthest-site Delaunay triangulation. +

    + +
    +
    +
    Example: rbox 10 D2 | qvoronoi Qu s + o TO + result
    +
    Compute the 2-d, furthest-site Voronoi diagram of 10 + random points. Write a summary to the console and the Voronoi + regions and vertices to 'result'. The first vertex of the + result indicates unbounded regions. Almost all regions + are unbounded.
    +
    + +
    +
    Example: rbox r y c G1 D2 | qvoronoi Qu + s + Fn TO + result
    +
    Compute the 2-d furthest-site Voronoi diagram of a square + and a small triangle. Write a summary to the console and the Voronoi + vertices for each input site to 'result'. + The origin is the only furthest-site Voronoi vertex. The + negative indices indicate vertices-at-infinity.
    +
    +
    + +

    +Qhull computes the furthest-site Voronoi diagram via the +furthest-site Delaunay triangulation. +Each furthest-site Voronoi vertex is the circumcenter of an upper +facet of the Delaunay triangulation. Each furthest-site Voronoi +region corresponds to a vertex of the Delaunay triangulation +(i.e., an input site).

    + +

    See Qhull FAQ - Delaunay and +Voronoi diagram questions.

    + +

    The 'qvonoroi' program is equivalent to +'qhull v Qbb' in 2-d to 3-d, and +'qhull v Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n m v H U Qb +QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V Fa FA FC Fp FS Ft FV Gt Q0,etc. + + +

    Copyright © 1995-2015 C.B. Barber

    + +
    +

    »furthest-site qvoronoi synopsis

    +
    + +See qvoronoi synopsis. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Voronoi diagrams. + + +
    +

    »furthest-site qvoronoi +input

    +
    +

    The input data on stdin consists of:

    +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qvoronoi Qu < data.txt), a pipe (e.g., rbox 10 | qvoronoi Qu), +or the 'TI' option (e.g., qvoronoi TI data.txt Qu). + +

    For example, this is a square containing four random points. +Its furthest-site Voronoi diagram has on vertex and four unbounded, +separating hyperplanes (i.e., the coordinate axes) +

    +

    +rbox c 4 D2 > data +
    +2 RBOX c 4 D2
    +8
    +-0.4999921736307369 -0.3684622117955817
    +0.2556053225468894 -0.0413498678629751
    +0.0327672376602583 -0.2810408135699488
    +-0.452955383763607 0.17886471718444
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +

    qvoronoi Qu s Fo < data +

    +
    +Furthest-site Voronoi vertices by the convex hull of 8 points in 3-d:
    +
    +  Number of Voronoi regions: 8
    +  Number of Voronoi vertices: 1
    +  Number of non-simplicial Voronoi vertices: 1
    +
    +Statistics for: RBOX c 4 D2 | QVORONOI Qu s Fo
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 20
    +  Number of facets in hull: 11
    +  Number of distance tests for qhull: 34
    +  Number of merged facets: 1
    +  Number of distance tests for merging: 107
    +  CPU seconds to compute hull (after input):  0
    +
    +4
    +5 4 5      0      1      0
    +5 4 6      1      0      0
    +5 5 7      1      0      0
    +5 6 7      0      1      0
    +
    +
    + +
    +

    » furthest-site qvoronoi +outputs

    +
    + +

    These options control the output of furthest-site Voronoi diagrams.

    +
    + +
    +
     
    +
    furthest-site Voronoi vertices
    +
    p
    +
    print the coordinates of the furthest-site Voronoi vertices. The first line + is the dimension. The second line is the number of vertices. Each + remaining line is a furthest-site Voronoi vertex. The points-in-square example + has one furthest-site Voronoi vertex at the origin.
    +
    Fn
    +
    list the neighboring furthest-site Voronoi vertices for each furthest-site Voronoi + vertex. The first line is the number of Voronoi vertices. Each + remaining line starts with the number of neighboring vertices. Negative indices (e.g., -1) indicate vertices + outside of the Voronoi diagram. In the points-in-square example, the + Voronoi vertex at the origin has four neighbors-at-infinity.
    +
    FN
    +
    list the furthest-site Voronoi vertices for each furthest-site Voronoi region. The first line is + the number of Voronoi regions. Each remaining line starts with the + number of Voronoi vertices. Negative indices (e.g., -1) indicate vertices + outside of the Voronoi diagram. + In the points-in-square example, all regions share the Voronoi vertex + at the origin.
    + +
     
    +
     
    +
    furthest-site Voronoi regions
    +
    o
    +
    print the furthest-site Voronoi regions in OFF format. The first line is the + dimension. The second line is the number of vertices, the number + of input sites, and "1". The third line represents the vertex-at-infinity. + Its coordinates are "-10.101". The next lines are the coordinates + of the furthest-site Voronoi vertices. Each remaining line starts with the number + of Voronoi vertices in a Voronoi region. In 2-d, the vertices are +listed in adjacency order (unoriented). In 3-d and higher, the +vertices are listed in numeric order. In the points-in-square + example, each unbounded region includes the Voronoi vertex at + the origin. Lines consisting of 0 indicate + interior input sites.
    +
    Fi
    +
    print separating hyperplanes for inner, bounded furthest-site Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites. The next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. The are no bounded, separating hyperplanes + for the points-in-square example.
    +
    Fo
    +
    print separating hyperplanes for outer, unbounded furthest-site Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites on the convex hull. The + next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. The points-in-square example has four + unbounded, separating hyperplanes.
    +
     
    +
     
    +
    Input sites
    +
    Fv
    +
    list ridges of furthest-site Voronoi vertices for pairs of input sites. The + first line is the number of ridges. Each remaining line starts with + two plus the number of Voronoi vertices in the ridge. The next + two numbers are two adjacent input sites. The remaining numbers list + the Voronoi vertices. As with option 'o', a 0 indicates + the vertex-at-infinity + and an unbounded, separating hyperplane. + The perpendicular bisector (separating hyperplane) + of the input sites is a flat through these vertices. + In the points-in-square example, the ridge for each edge of the square + is unbounded.
    +
     
    +
     
    +
    General
    +
    s
    +
    print summary of the furthest-site Voronoi diagram. Use 'Fs' for numeric data.
    +
    i
    +
    list input sites for each furthest-site Delaunay region. Use option 'Pp' + to avoid the warning. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In the points-in-square example, the square region has four + input sites. In 3-d and higher, report cospherical sites by adding extra points. +
    +
    G
    +
    Geomview output for 2-d furthest-site Voronoi diagrams.
    +
    +
    + +
    +

    » furthest-site qvoronoi +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qu
    +
    must be used.
    +
    QVn
    +
    select furthest-site Voronoi vertices for input site n
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., + furthest-site Voronoi vertex).
    +
    + +
    +
    +

    » furthest-site qvoronoi +graphics

    +
    +

    In 2-d, Geomview output ('G') +displays a furthest-site Voronoi diagram with extra edges to +close the unbounded furthest-site Voronoi regions. All regions +will be unbounded. Since the points-in-box example has only +one furthest-site Voronoi vertex, the Geomview output is one +point.

    + +

    See the Delaunay and Voronoi +examples for a 2-d example. Turn off normalization (on +Geomview's 'obscure' menu) when comparing the furthest-site +Voronoi diagram with the corresponding Voronoi diagram.

    + +
    +

    »furthest-site qvoronoi +notes

    +
    + +

    See Voronoi notes.

    + +
    +

    »furthest-site qvoronoi conventions

    +
    + +

    The following terminology is used for furthest-site Voronoi +diagrams in Qhull. The underlying structure is a furthest-site +Delaunay triangulation from a convex hull in one higher +dimension. Upper facets of the Delaunay triangulation correspond +to vertices of the furthest-site Voronoi diagram. Vertices of the +furthest-site Delaunay triangulation correspond to input sites. +They also define regions of the furthest-site Voronoi diagram. +All vertices are extreme points of the input sites. See qconvex conventions, furthest-site delaunay +conventions, and Qhull's data structures.

    + +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - a point has d+1 coordinates. The + last coordinate is the sum of the squares of the input + site's coordinates
    • +
    • vertex - a point on the upper facets of the + paraboloid. It corresponds to a unique input site.
    • +
    • furthest-site Delaunay facet - an upper facet of the + paraboloid. The last coefficient of its normal is + clearly positive.
    • +
    • furthest-site Voronoi vertex - the circumcenter + of a furthest-site Delaunay facet
    • +
    • furthest-site Voronoi region - the region of + Euclidean space further from an input site than any other + input site. Qhull lists the furthest-site Voronoi + vertices that define each furthest-site Voronoi region.
    • +
    • furthest-site Voronoi diagram - the graph of the + furthest-site Voronoi regions with the ridges (edges) + between the regions.
    • +
    • infinity vertex - the Voronoi vertex for + unbounded furthest-site Voronoi regions in 'o' output format. Its + coordinates are -10.101.
    • +
    • good facet - an furthest-site Voronoi vertex with + optional restrictions by 'QVn', + etc.
    • +
    + +
    +

    »furthest-site qvoronoi options

    +
    + +See qvoronoi options. The same +program is used for both constructions. Use option 'Qu' +for furthest-site Voronoi diagrams. +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/qvoronoi.htm b/xs/src/qhull/html/qvoronoi.htm new file mode 100644 index 0000000000..6d81d48c15 --- /dev/null +++ b/xs/src/qhull/html/qvoronoi.htm @@ -0,0 +1,667 @@ + + + + +qvoronoi -- Voronoi diagram + + + + +Up: +Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +
    + +

    [voronoi]qvoronoi -- Voronoi diagram

    + +

    The Voronoi diagram is the nearest-neighbor map for a set of +points. Each region contains those points that are nearer +one input site than any other input site. It has many useful properties and applications. See the +survey article by Aurenhammer ['91] +and the detailed introduction by O'Rourke ['94]. The Voronoi diagram is the +dual of the Delaunay triangulation.

    + +
    +
    +
    Example: rbox 10 D3 | qvoronoi s + o TO + result
    +
    Compute the 3-d Voronoi diagram of 10 random points. Write a + summary to the console and the Voronoi vertices and + regions to 'result'. The first vertex of the result + indicates unbounded regions.
    + +
     
    +
    Example: rbox r y c G0.1 D2 | qvoronoi + s + o TO + result
    +
    Compute the 2-d Voronoi diagram of a triangle and a small + square. Write a + summary to the console and Voronoi vertices and regions + to 'result'. Report a single Voronoi vertex for + cocircular input sites. The first vertex of the result + indicates unbounded regions. The origin is the Voronoi + vertex for the square.
    + +
     
    +
    Example: rbox r y c G0.1 D2 | qvoronoi Fv + TO result
    +
    Compute the 2-d Voronoi diagram of a triangle and a small + square. Write a + summary to the console and the Voronoi ridges to + 'result'. Each ridge is the perpendicular bisector of a + pair of input sites. Vertex "0" indicates + unbounded ridges. Vertex "8" is the Voronoi + vertex for the square.
    + +
     
    +
    Example: rbox r y c G0.1 D2 | qvoronoi Fi
    +
    Print the bounded, separating hyperplanes for the 2-d Voronoi diagram of a + triangle and a small + square. Note the four hyperplanes (i.e., lines) for Voronoi vertex + "8". It is at the origin. +
    +
    +
    + +

    Qhull computes the Voronoi diagram via the Delaunay +triangulation. Each Voronoi +vertex is the circumcenter of a facet of the Delaunay +triangulation. Each Voronoi region corresponds to a vertex (i.e., input site) of the +Delaunay triangulation.

    + +

    Qhull outputs the Voronoi vertices for each Voronoi region. With +option 'Fv', +it lists all ridges of the Voronoi diagram with the corresponding +pairs of input sites. With +options 'Fi' and 'Fo', +it lists the bounded and unbounded separating hyperplanes. +You can also output a single Voronoi region +for further processing [see graphics].

    + +

    Use option 'Qz' if the input is circular, cospherical, or +nearly so. It improves precision by adding a point "at infinity," above the corresponding paraboloid. + +

    See Qhull FAQ - Delaunay and +Voronoi diagram questions.

    + +

    The 'qvonoroi' program is equivalent to +'qhull v Qbb' in 2-d to 3-d, and +'qhull v Qbb Qx' +in 4-d and higher. It disables the following Qhull +options: d n v Qbb QbB Qf Qg Qm +Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0,etc. + +

    Copyright © 1995-2015 C.B. Barber

    + +

    Voronoi image by KOOK Architecture, Silvan Oesterle and Michael Knauss. + +


    +

    »qvoronoi synopsis

    + +
    +qvoronoi- compute the Voronoi diagram.
    +    input (stdin): dimension, number of points, point coordinates
    +    comments start with a non-numeric character
    +
    +options (qh-voron.htm):
    +    Qu   - compute furthest-site Voronoi diagram
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    .    - concise list of all options
    +    -    - one-line description of all options
    +
    +output options (subset):
    +    s    - summary of results (default)
    +    p    - Voronoi vertices
    +    o    - OFF file format (dim, Voronoi vertices, and Voronoi regions)
    +    FN   - count and Voronoi vertices for each Voronoi region
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites
    +    Fi   - separating hyperplanes for bounded regions, 'Fo' for unbounded
    +    G    - Geomview output (2-d only)
    +    QVn  - Voronoi vertices for input point n, -n if not
    +    TO file- output results to file, may be enclosed in single quotes
    +
    +examples:
    +rbox c P0 D2 | qvoronoi s o         rbox c P0 D2 | qvoronoi Fi
    +rbox c P0 D2 | qvoronoi Fo          rbox c P0 D2 | qvoronoi Fv
    +rbox c P0 D2 | qvoronoi s Qu Fv     rbox c P0 D2 | qvoronoi Qu Fo
    +rbox c G1 d D2 | qvoronoi s p       rbox c P0 D2 | qvoronoi s Fv QV0
    +
    + +

    »qvoronoi input

    +
    +The input data on stdin consists of: +
      +
    • dimension +
    • number of points
    • +
    • point coordinates
    • +
    + +

    Use I/O redirection (e.g., qvoronoi < data.txt), a pipe (e.g., rbox 10 | qvoronoi), +or the 'TI' option (e.g., qvoronoi TI data.txt). + +

    For example, this is four cocircular points inside a square. Their Voronoi +diagram has nine vertices and eight regions. Notice the Voronoi vertex +at the origin, and the Voronoi vertices (on each axis) for the four +sides of the square. +

    +

    +rbox s 4 W0 c G1 D2 > data +
    +2 RBOX s 4 W0 c D2
    +8
    +-0.4941988586954018 -0.07594397977563715
    +-0.06448037284989526 0.4958248496365813
    +0.4911154367094632 0.09383830681375946
    +-0.348353580869097 -0.3586778257652367
    +    -1     -1
    +    -1      1
    +     1     -1
    +     1      1
    +
    + +

    qvoronoi s p < data +

    +
    +Voronoi diagram by the convex hull of 8 points in 3-d:
    +
    +  Number of Voronoi regions: 8
    +  Number of Voronoi vertices: 9
    +  Number of non-simplicial Voronoi vertices: 1
    +
    +Statistics for: RBOX s 4 W0 c D2 | QVORONOI s p
    +
    +  Number of points processed: 8
    +  Number of hyperplanes created: 18
    +  Number of facets in hull: 10
    +  Number of distance tests for qhull: 33
    +  Number of merged facets: 2
    +  Number of distance tests for merging: 102
    +  CPU seconds to compute hull (after input): 0.094
    +
    +2
    +9
    +4.217546450968612e-17 1.735507986399734
    +-8.402566836762659e-17 -1.364368854147395
    +0.3447488772716865 -0.6395484723719818
    +1.719446929853986 2.136555906154247e-17
    +0.4967882915039657 0.68662371396699
    +-1.729928876283549 1.343733067524222e-17
    +-0.8906163241424728 -0.4594150543829102
    +-0.6656840313875723 0.5003013793414868
    +-7.318364664277155e-19 -1.188217818408333e-16
    +
    +
    + +
    +

    » qvoronoi +outputs

    +
    + +

    These options control the output of Voronoi diagrams.

    +
    + +
    +
     
    +
    Voronoi vertices
    +
    p
    +
    print the coordinates of the Voronoi vertices. The first line + is the dimension. The second line is the number of vertices. Each + remaining line is a Voronoi vertex.
    +
    Fn
    +
    list the neighboring Voronoi vertices for each Voronoi + vertex. The first line is the number of Voronoi vertices. Each + remaining line starts with the number of neighboring vertices. + Negative vertices (e.g., -1) indicate vertices + outside of the Voronoi diagram. + In the circle-in-box example, the + Voronoi vertex at the origin has four neighbors.
    +
    FN
    +
    list the Voronoi vertices for each Voronoi region. The first line is + the number of Voronoi regions. Each remaining line starts with the + number of Voronoi vertices. Negative indices (e.g., -1) indicate vertices + outside of the Voronoi diagram. + In the circle-in-box example, the four bounded regions are defined by four + Voronoi vertices.
    + +
     
    +
     
    +
    Voronoi regions
    +
    o
    +
    print the Voronoi regions in OFF format. The first line is the + dimension. The second line is the number of vertices, the number + of input sites, and "1". The third line represents the vertex-at-infinity. + Its coordinates are "-10.101". The next lines are the coordinates + of the Voronoi vertices. Each remaining line starts with the number + of Voronoi vertices in a Voronoi region. In 2-d, the vertices are +listed in adjacency order (unoriented). In 3-d and higher, the +vertices are listed in numeric order. In the circle-in-square + example, each bounded region includes the Voronoi vertex at + the origin. Lines consisting of 0 indicate + coplanar input sites or 'Qz'.
    +
    Fi
    +
    print separating hyperplanes for inner, bounded Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites. The next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. Use 'Tv' to verify that the +hyperplanes are perpendicular bisectors. It will list relevant +statistics to stderr.
    +
    Fo
    +
    print separating hyperplanes for outer, unbounded Voronoi + regions. The first number is the number of separating + hyperplanes. Each remaining line starts with 3+dim. The + next two numbers are adjacent input sites on the convex hull. The + next dim + numbers are the coefficients of the separating hyperplane. The + last number is its offset. Use 'Tv' to verify that the +hyperplanes are perpendicular bisectors. It will list relevant +statistics to stderr,
    +
     
    +
     
    +
    Input sites
    +
    Fv
    +
    list ridges of Voronoi vertices for pairs of input sites. The + first line is the number of ridges. Each remaining line starts with + two plus the number of Voronoi vertices in the ridge. The next + two numbers are two adjacent input sites. The remaining numbers list + the Voronoi vertices. As with option 'o', a 0 indicates + the vertex-at-infinity + and an unbounded, separating hyperplane. + The perpendicular bisector (separating hyperplane) + of the input sites is a flat through these vertices. + In the circle-in-square example, the ridge for each edge of the square + is unbounded.
    +
    Fc
    +
    list coincident input sites for each Voronoi vertex. + The first line is the number of vertices. The remaining lines start with + the number of coincident sites and deleted vertices. Deleted vertices + indicate highly degenerate input (see'Fs'). + A coincident site is assigned to one Voronoi + vertex. Do not use 'QJ' with 'Fc'; the joggle will separate + coincident sites.
    +
    FP
    +
    print coincident input sites with distance to + nearest site (i.e., vertex). The first line is the + number of coincident sites. Each remaining line starts with the point ID of + an input site, followed by the point ID of a coincident point, its vertex, and distance. + Includes deleted vertices which + indicate highly degenerate input (see'Fs'). + Do not use 'QJ' with 'FP'; the joggle will separate + coincident sites.
    +
     
    +
     
    +
    General
    +
    s
    +
    print summary of the Voronoi diagram. Use 'Fs' for numeric data.
    +
    i
    +
    list input sites for each Delaunay region. Use option 'Pp' + to avoid the warning. The first line is the number of regions. The + remaining lines list the input sites for each region. The regions are + oriented. In the circle-in-square example, the cocircular region has four + edges. In 3-d and higher, report cospherical sites by adding extra points. +
    +
    G
    +
    Geomview output for 2-d Voronoi diagrams.
    +
    +
    +
    +

    » qvoronoi +controls

    +
    + +

    These options provide additional control:

    +
    + +
    +
    Qu
    +
    compute the furthest-site Voronoi diagram.
    +
    QVn
    +
    select Voronoi vertices for input site n
    +
    Qz
    +
    add a point above the paraboloid to reduce precision + errors. Use it for nearly cocircular/cospherical input + (e.g., 'rbox c | qvoronoi Qz').
    +
    Tv
    +
    verify result
    +
    TI file
    +
    input data from file. The filename may not use spaces or quotes.
    +
    TO file
    +
    output results to file. Use single quotes if the filename + contains spaces (e.g., TO 'file with spaces.txt'
    +
    TFn
    +
    report progress after constructing n facets
    +
    PDk:1
    +
    include upper and lower facets in the output. Set k + to the last dimension (e.g., 'PD2:1' for 2-d inputs).
    +
    f
    +
    facet dump. Print the data structure for each facet (i.e., + Voronoi vertex).
    +
    + +
    +
    +

    » qvoronoi +graphics

    +
    + +

    In 2-d, Geomview output ('G') +displays a Voronoi diagram with extra edges to close the +unbounded Voronoi regions. To view the unbounded rays, enclose +the input points in a square.

    + +

    You can also view individual Voronoi regions in 3-d. To +view the Voronoi region for site 3 in Geomview, execute

    + +
    +

    qvoronoi <data QV3 p | qconvex s G >output

    +
    + +

    The qvoronoi command returns the Voronoi vertices +for input site 3. The qconvex command computes their convex hull. +This is the Voronoi region for input site 3. Its +hyperplane normals (qconvex 'n') are the same as the separating hyperplanes +from options 'Fi' +and 'Fo' (up to roundoff error). + +

    See the Delaunay and Voronoi +examples for 2-d and 3-d examples. Turn off normalization (on +Geomview's 'obscure' menu) when comparing the Voronoi diagram +with the corresponding Delaunay triangulation.

    + +
    +

    »qvoronoi +notes

    +
    + +

    You can simplify the Voronoi diagram by enclosing the input +sites in a large square or cube. This is particularly recommended +for cocircular or cospherical input data.

    + +

    See Voronoi graphics for computing +the convex hull of a Voronoi region.

    + +

    Voronoi diagrams do not include facets that are +coplanar with the convex hull of the input sites. A facet is +coplanar if the last coefficient of its normal is +nearly zero (see qh_ZEROdelaunay). + +

    Unbounded regions can be confusing. For example, 'rbox c | +qvoronoi Qz o' produces the Voronoi regions for the vertices +of a cube centered at the origin. All regions are unbounded. The +output is

    + +
    +
    3
    +2 9 1
    +-10.101 -10.101 -10.101
    +     0      0      0
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +2 0 1
    +0
    +
    +
    + +

    The first line is the dimension. The second line is the number +of vertices and the number of regions. There is one region per +input point plus a region for the point-at-infinity added by +option 'Qz'. The next two lines +lists the Voronoi vertices. The first vertex is the infinity +vertex. It is indicate by the coordinates -10.101. The +second vertex is the origin. The next nine lines list the +regions. Each region lists two vertices -- the infinity vertex +and the origin. The last line is "0" because no region +is associated with the point-at-infinity. A "0" would +also be listed for nearly incident input sites.

    + +

    To use option 'Fv', add an +interior point. For example,

    + +
    +
    +rbox c P0 | qvoronoi Fv
    +20
    +5 0 7 1 3 5
    +5 0 3 1 4 5
    +5 0 5 1 2 3
    +5 0 1 1 2 4
    +5 0 6 2 3 6
    +5 0 2 2 4 6
    +5 0 4 4 5 6
    +5 0 8 5 3 6
    +5 1 2 0 2 4
    +5 1 3 0 1 4
    +5 1 5 0 1 2
    +5 2 4 0 4 6
    +5 2 6 0 2 6
    +5 3 4 0 4 5
    +5 3 7 0 1 5
    +5 4 8 0 6 5
    +5 5 6 0 2 3
    +5 5 7 0 1 3
    +5 6 8 0 6 3
    +5 7 8 0 3 5
    +
    +
    + +

    The output consists of 20 ridges and each ridge lists a pair +of input sites and a triplet of Voronoi vertices. The first eight +ridges connect the origin ('P0'). The remainder list the edges of +the cube. Each edge generates an unbounded ray through the +midpoint. The corresponding separating planes ('Fo') follow each +pair of coordinate axes.

    + +

    Options 'Qt' (triangulated output) +and 'QJ' (joggled input) are deprecated. They may produce +unexpected results. If you use these options, cocircular and cospherical input sites will +produce duplicate or nearly duplicate Voronoi vertices. See also Merged facets or joggled input.

    + +
    +

    »qvoronoi conventions

    +
    + +

    The following terminology is used for Voronoi diagrams in +Qhull. The underlying structure is a Delaunay triangulation from +a convex hull in one higher dimension. Facets of the Delaunay +triangulation correspond to vertices of the Voronoi diagram. +Vertices of the Delaunay triangulation correspond to input sites. +They also correspond to regions of the Voronoi diagram. See convex hull conventions, Delaunay conventions, and +Qhull's data structures.

    +
    + +
      +
    • input site - a point in the input (one dimension + lower than a point on the convex hull)
    • +
    • point - a point has d+1 coordinates. The + last coordinate is the sum of the squares of the input + site's coordinates
    • +
    • coplanar point - a nearly incident + input site
    • +
    • vertex - a point on the paraboloid. It + corresponds to a unique input site.
    • +
    • point-at-infinity - a point added above the + paraboloid by option 'Qz'
    • +
    • Delaunay facet - a lower facet of the + paraboloid. The last coefficient of its normal is + clearly negative.
    • +
    • Voronoi vertex - the circumcenter of a Delaunay + facet
    • +
    • Voronoi region - the Voronoi vertices for an + input site. The region of Euclidean space nearest to an + input site.
    • +
    • Voronoi diagram - the graph of the Voronoi + regions. It includes the ridges (i.e., edges) between the + regions.
    • +
    • vertex-at-infinity - the Voronoi vertex that + indicates unbounded Voronoi regions in 'o' output format. Its + coordinates are -10.101.
    • +
    • good facet - a Voronoi vertex with optional + restrictions by 'QVn', etc.
    • +
    + +
    +
    +

    »qvoronoi options

    + +
    +qvoronoi- compute the Voronoi diagram
    +    http://www.qhull.org
    +
    +input (stdin):
    +    first lines: dimension and number of points (or vice-versa).
    +    other lines: point coordinates, best if one point per line
    +    comments:    start with a non-numeric character
    +
    +options:
    +    Qu   - compute furthest-site Voronoi diagram
    +
    +Qhull control options:
    +    QJn  - randomly joggle input in range [-n,n]
    +    Qs   - search all points for the initial simplex
    +    Qz   - add point-at-infinity to Voronoi diagram
    +    QGn  - Voronoi vertices if visible from point n, -n if not
    +    QVn  - Voronoi vertices for input point n, -n if not
    +
    +Trace options:
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events
    +    Tc   - check frequently during execution
    +    Ts   - statistics
    +    Tv   - verify result: structure, convexity, and in-circle test
    +    Tz   - send all output to stdout
    +    TFn  - report summary when n or more facets created
    +    TI file - input data from file, no spaces or single quotes
    +    TO file - output results to file, may be enclosed in single quotes
    +    TPn  - turn on tracing when point n added to hull
    +     TMn - turn on tracing at merge n
    +     TWn - trace merge facets when width > n
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)
    +     TCn - stop qhull after building cone for point n (see TVn)
    +
    +Precision options:
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]
    +    Wn   - min facet width for non-coincident point (before roundoff)
    +
    +Output formats (may be combined; if none, produces a summary to stdout):
    +    s    - summary to stderr
    +    p    - Voronoi vertices
    +    o    - OFF format (dim, Voronoi vertices, and Voronoi regions)
    +    i    - Delaunay regions (use 'Pp' to avoid warning)
    +    f    - facet dump
    +
    +More formats:
    +    Fc   - count plus coincident points (by Voronoi vertex)
    +    Fd   - use cdd format for input (homogeneous with offset first)
    +    FD   - use cdd format for output (offset first)
    +    FF   - facet dump without ridges
    +    Fi   - separating hyperplanes for bounded Voronoi regions
    +    FI   - ID for each Voronoi vertex
    +    Fm   - merge count for each Voronoi vertex (511 max)
    +    Fn   - count plus neighboring Voronoi vertices for each Voronoi vertex
    +    FN   - count and Voronoi vertices for each Voronoi region
    +    Fo   - separating hyperplanes for unbounded Voronoi regions
    +    FO   - options and precision constants
    +    FP   - nearest point and distance for each coincident point
    +    FQ   - command used for qvoronoi
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,
    +                    for output: #Voronoi regions, #Voronoi vertices,
    +                                #coincident points, #non-simplicial regions
    +                    #real (2), max outer plane and min vertex
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)
    +
    +Geomview options (2-d only)
    +    Ga   - all points as dots
    +     Gp  -  coplanar points and vertices as radii
    +     Gv  -  vertices as spheres
    +    Gi   - inner planes only
    +     Gn  -  no planes
    +     Go  -  outer planes only
    +    Gc   - centrums
    +    Gh   - hyperplane intersections
    +    Gr   - ridges
    +    GDn  - drop dimension n in 3-d and 4-d output
    +
    +Print options:
    +    PAn  - keep n largest Voronoi vertices by 'area'
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)
    +    PDk:n - drop facet if normal[k] >= n
    +    Pg   - print good Voronoi vertices (needs 'QGn' or 'QVn')
    +    PFn  - keep Voronoi vertices whose 'area' is at least n
    +    PG   - print neighbors of good Voronoi vertices
    +    PMn  - keep n Voronoi vertices with most merges
    +    Po   - force output.  If error, output neighborhood of facet
    +    Pp   - do not report precision problems
    +
    +    .    - list of all options
    +    -    - one line descriptions of all options
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis +• input • outputs +• controls • graphics +• notes • conventions +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: see top

    + + diff --git a/xs/src/qhull/html/rbox.htm b/xs/src/qhull/html/rbox.htm new file mode 100644 index 0000000000..9c28face56 --- /dev/null +++ b/xs/src/qhull/html/rbox.htm @@ -0,0 +1,277 @@ + + + + +rbox -- generate point distributions + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • outputs +• examples • notes +• options +


    + +

    [CONE]rbox -- generate point distributions

    + +
    + rbox generates random or regular points according to the + options given, and outputs the points to stdout. The + points are generated in a cube, unless 's', 'x', or 'y' + are given. + +
    +

    »rbox synopsis

    +
    +rbox- generate various point distributions.  Default is random in cube.
    +
    +args (any order, space separated):
    +  3000    number of random points in cube, lens, spiral, sphere or grid
    +  D3      dimension 3-d
    +  c       add a unit cube to the output ('c G2.0' sets size)
    +  d       add a unit diamond to the output ('d G2.0' sets size)
    +  l       generate a regular 3-d spiral
    +  r       generate a regular polygon, ('r s Z1 G0.1' makes a cone)
    +  s       generate cospherical points
    +  x       generate random points in simplex, may use 'r' or 'Wn'
    +  y       same as 'x', plus simplex
    +  Cn,r,m  add n nearly coincident points within radius r of m points
    +  Pn,m,r  add point [n,m,r] first, pads with 0
    +
    +  Ln      lens distribution of radius n.  Also 's', 'r', 'G', 'W'.
    +  Mn,m,r  lattice (Mesh) rotated by [n,-m,0], [m,n,0], [0,0,r], ...
    +          '27 M1,0,1' is {0,1,2} x {0,1,2} x {0,1,2}.  Try 'M3,4 z'.
    +  W0.1    random distribution within 0.1 of the cube's or sphere's surface
    +  Z0.5 s  random points in a 0.5 disk projected to a sphere
    +  Z0.5 s G0.6 same as Z0.5 within a 0.6 gap
    +
    +  Bn      bounding box coordinates, default 0.5
    +  h       output as homogeneous coordinates for cdd
    +  n       remove command line from the first line of output
    +  On      offset coordinates by n
    +  t       use time as the random number seed (default is command line)
    +  tn      use n as the random number seed
    +  z       print integer coordinates, default 'Bn' is 1e+06
    +
    + +

    »rbox outputs

    +
    + +The format of the output is the following: first line contains + the dimension and a comment, second line contains the + number of points, and the following lines contain the points, + one point per line. Points are represented by their coordinate values. + +

    For example, rbox c 10 D2 generates +

    +
    +2 RBOX c 10 D2
    +14
    +-0.4999921736307369 -0.3684622117955817
    +0.2556053225468894 -0.0413498678629751
    +0.0327672376602583 -0.2810408135699488
    +-0.452955383763607 0.17886471718444
    +0.1792964061529342 0.4346928963760779
    +-0.1164979223315585 0.01941637230982666
    +0.3309653464993139 -0.4654278894564396
    +-0.4465383649305798 0.02970019358182344
    +0.1711493843897706 -0.4923018137852678
    +-0.1165843490665633 -0.433157762450313
    +  -0.5   -0.5
    +  -0.5    0.5
    +   0.5   -0.5
    +   0.5    0.5
    +
    + +
    + +
    +

    »rbox examples

    + +
    +       rbox 10
    +              10 random points in the unit cube centered  at  the
    +              origin.
    +
    +       rbox 10 s D2
    +              10 random points on a 2-d circle.
    +
    +       rbox 100 W0
    +              100 random points on the surface of a cube.
    +
    +       rbox 1000 s D4
    +              1000 random points on a 4-d sphere.
    +
    +       rbox c D5 O0.5
    +              a 5-d hypercube with one corner at the origin.
    +
    +       rbox d D10
    +              a 10-d diamond.
    +
    +       rbox x 1000 r W0
    +              100 random points on the surface of a fixed simplex
    +
    +       rbox y D12
    +              a 12-d simplex.
    +
    +       rbox l 10
    +              10 random points along a spiral
    +
    +       rbox l 10 r
    +              10 regular points  along  a  spiral  plus  two  end
    +              points
    +
    +       rbox 1000 L10000 D4 s
    +              1000 random points on the surface of a narrow lens.
    +
    +           rbox 1000 L100000 s G1e-6
    +                  1000 random points near the edge of a narrow lens
    +
    +       rbox c G2 d G3
    +              a cube with coordinates +2/-2 and  a  diamond  with
    +              coordinates +3/-3.
    +
    +       rbox 64 M3,4 z
    +              a  rotated,  {0,1,2,3} x {0,1,2,3} x {0,1,2,3} lat-
    +              tice (Mesh) of integer points.
    +
    +       rbox P0 P0 P0 P0 P0
    +              5 copies of the origin in 3-d.  Try 'rbox P0 P0  P0
    +              P0 P0 | qhull QJ'.
    +
    +       r 100 s Z1 G0.1
    +              two  cospherical  100-gons plus another cospherical
    +              point.
    +
    +       100 s Z1
    +              a cone of points.
    +
    +       100 s Z1e-7
    +              a narrow cone of points with many precision errors.
    +
    + +

    »rbox notes

    +
    +Some combinations of arguments generate odd results. + +
    +

    »rbox options

    + +
    +       n      number of points
    +
    +       Dn     dimension n-d (default 3-d)
    +
    +       Bn     bounding box coordinates (default 0.5)
    +
    +       l      spiral distribution, available only in 3-d
    +
    +       Ln     lens  distribution  of  radius n.  May be used with
    +              's', 'r', 'G', and 'W'.
    +
    +       Mn,m,r lattice  (Mesh)  rotated  by  {[n,-m,0],   [m,n,0],
    +              [0,0,r],  ...}.   Use  'Mm,n'  for a rigid rotation
    +              with r = sqrt(n^2+m^2).  'M1,0'  is  an  orthogonal
    +              lattice.   For  example,  '27  M1,0'  is  {0,1,2} x
    +              {0,1,2} x {0,1,2}.
    +
    +       s      cospherical points randomly generated in a cube and
    +              projected to the unit sphere
    +
    +       x      simplicial  distribution.   It  is fixed for option
    +              'r'.  May be used with 'W'.
    +
    +       y      simplicial distribution plus a simplex.   Both  'x'
    +              and 'y' generate the same points.
    +
    +       Wn     restrict  points  to distance n of the surface of a
    +              sphere or a cube
    +
    +       c      add a unit cube to the output
    +
    +       c Gm   add a cube with all combinations of +m  and  -m  to
    +              the output
    +
    +       d      add a unit diamond to the output.
    +
    +       d Gm   add a diamond made of 0, +m and -m to the output
    +
    +       Cn,r,m add n nearly coincident points within radius r of m points
    +
    +       Pn,m,r add point [n,m,r] to the output first.  Pad coordi-
    +              nates with 0.0.
    +
    +       n      Remove the command line from the first line of out-
    +              put.
    +
    +       On     offset the data by adding n to each coordinate.
    +
    +       t      use  time  in  seconds  as  the  random number seed
    +              (default is command line).
    +
    +       tn     set the random number seed to n.
    +
    +       z      generate integer coordinates.  Use 'Bn'  to  change
    +              the  range.   The  default  is 'B1e6' for six-digit
    +              coordinates.  In R^4, seven-digit coordinates  will
    +              overflow hyperplane normalization.
    +
    +       Zn s   restrict points to a disk about the z+ axis and the
    +              sphere (default Z1.0).  Includes the opposite pole.
    +              'Z1e-6'  generates  degenerate  points under single
    +              precision.
    +
    +       Zn Gm s
    +              same as Zn with an empty center (default G0.5).
    +
    +       r s D2 generate a regular polygon
    +
    +       r s Z1 G0.1
    +              generate a regular cone
    +
    + + +
    + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +To: synopsis • outputs +• examples • notes +• options + +


    + +

    The Geometry Center +Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: Sept. 25, 1995 --- Last modified: August 12, 1998

    + + diff --git a/xs/src/qhull/html/rbox.man b/xs/src/qhull/html/rbox.man new file mode 100644 index 0000000000..3ea6395e69 --- /dev/null +++ b/xs/src/qhull/html/rbox.man @@ -0,0 +1,176 @@ +.\" This is the Unix manual page for rbox, written in nroff, the standard +.\" manual formatter for Unix systems. To format it, type +.\" +.\" nroff -man rbox.man +.\" +.\" This will print a formatted copy to standard output. If you want +.\" to ensure that the output is plain ascii, free of any control +.\" characters that nroff uses for underlining etc, pipe the output +.\" through "col -b": +.\" +.\" nroff -man rbox.man | col -b +.\" +.TH rbox 1 "August 10, 1998" "Geometry Center" +.SH NAME +rbox \- generate point distributions for qhull +.SH SYNOPSIS +Command "rbox" (w/o arguments) lists the options. +.SH DESCRIPTION +.PP +rbox generates random or regular points according to the options given, and +outputs +the points to stdout. The points are generated in a cube, unless 's' or 'k' +option is +given. The format of the output is the following: first line +contains the dimension and a comment, +second line contains the number of points, and the +following lines contain the points, one point per line. Points are represented +by their coordinate values. +.SH EXAMPLES +.TP +rbox 10 +10 random points in the unit cube centered at the origin. +.TP +rbox 10 s D2 +10 random points on a 2\[hy]d circle. +.TP +rbox 100 W0 +100 random points on the surface of a cube. +.TP +rbox 1000 s D4 +1000 random points on a 4\[hy]d sphere. +.TP +rbox c D5 O0.5 +a 5\[hy]d hypercube with one corner at the origin. +.TP +rbox d D10 +a 10\[hy]d diamond. +.TP +rbox x 1000 r W0 +100 random points on the surface of a fixed simplex +.TP +rbox y D12 +a 12\[hy]d simplex. +.TP +rbox l 10 +10 random points along a spiral +.TP +rbox l 10 r +10 regular points along a spiral plus two end points +.TP +rbox 1000 L10000 D4 s +1000 random points on the surface of a narrow lens. +.TP +rbox c G2 d G3 +a cube with coordinates +2/\-2 and a diamond with coordinates +3/\-3. +.TP +rbox 64 M3,4 z +a rotated, {0,1,2,3} x {0,1,2,3} x {0,1,2,3} lattice (Mesh) of integer +points. 'rbox 64 M1,0' is orthogonal. +.TP +rbox P0 P0 P0 P0 P0 +5 copies of the origin in 3\-d. Try 'rbox P0 P0 P0 P0 P0 | qhull QJ'. +.TP +r 100 s Z1 G0.1 +two cospherical 100\-gons plus another cospherical point. +.TP +100 s Z1 +a cone of points. +.TP +100 s Z1e\-7 +a narrow cone of points with many precision errors. +.SH OPTIONS +.TP +n +number of points +.TP +Dn +dimension n\[hy]d (default 3\[hy]d) +.TP +Bn +bounding box coordinates (default 0.5) +.TP +l +spiral distribution, available only in 3\[hy]d +.TP +Ln +lens distribution of radius n. May be used with 's', 'r', 'G', and 'W'. +.TP +Mn,m,r +lattice (Mesh) rotated by {[n,\-m,0], [m,n,0], [0,0,r], ...}. +Use 'Mm,n' for a rigid rotation with r = sqrt(n^2+m^2). 'M1,0' is an +orthogonal lattice. For example, '27 M1,0' is {0,1,2} x {0,1,2} x +{0,1,2}. '27 M3,4 z' is a rotated integer lattice. +.TP +s +cospherical points randomly generated in a cube and projected to the unit sphere +.TP +x +simplicial distribution. It is fixed for option 'r'. May be used with 'W'. +.TP +y +simplicial distribution plus a simplex. Both 'x' and 'y' generate the same points. +.TP +Wn +restrict points to distance n of the surface of a sphere or a cube +.TP +c +add a unit cube to the output +.TP +c Gm +add a cube with all combinations of +m and \-m to the output +.TP +d +add a unit diamond to the output. +.TP +d Gm +add a diamond made of 0, +m and \-m to the output +.TP +Cn,r,m +add n nearly coincident points within radius r of m points +.TP +Pn,m,r +add point [n,m,r] to the output first. Pad coordinates with 0.0. +.TP +n +Remove the command line from the first line of output. +.TP +On +offset the data by adding n to each coordinate. +.TP +t +use time in seconds as the random number seed (default is command line). +.TP +tn +set the random number seed to n. +.TP +z +generate integer coordinates. Use 'Bn' to change the range. +The default is 'B1e6' for six\[hy]digit coordinates. In R^4, seven\[hy]digit +coordinates will overflow hyperplane normalization. +.TP +Zn s +restrict points to a disk about the z+ axis and the sphere (default Z1.0). +Includes the opposite pole. 'Z1e\-6' generates degenerate points under +single precision. +.TP +Zn Gm s +same as Zn with an empty center (default G0.5). +.TP +r s D2 +generate a regular polygon +.TP +r s Z1 G0.1 +generate a regular cone +.SH BUGS +Some combinations of arguments generate odd results. + +Report bugs to qhull_bug@qhull.org, other correspondence to qhull@qhull.org +.SH SEE ALSO +qhull(1) +.SH AUTHOR +.nf +C. Bradford Barber +bradb@shore.net +.fi + diff --git a/xs/src/qhull/html/rbox.txt b/xs/src/qhull/html/rbox.txt new file mode 100644 index 0000000000..e3cf721892 --- /dev/null +++ b/xs/src/qhull/html/rbox.txt @@ -0,0 +1,195 @@ + + + +rbox(1) rbox(1) + + +NAME + rbox - generate point distributions for qhull + +SYNOPSIS + Command "rbox" (w/o arguments) lists the options. + +DESCRIPTION + rbox generates random or regular points according to the + options given, and outputs the points to stdout. The + points are generated in a cube, unless 's' or given. The + format of the output is the following: first line contains + the dimension and a comment, second line contains the num- + ber of points, and the following lines contain the points, + one point per line. Points are represented by their coor- + dinate values. + +EXAMPLES + rbox 10 + 10 random points in the unit cube centered at the + origin. + + rbox 10 s D2 + 10 random points on a 2-d circle. + + rbox 100 W0 + 100 random points on the surface of a cube. + + rbox 1000 s D4 + 1000 random points on a 4-d sphere. + + rbox c D5 O0.5 + a 5-d hypercube with one corner at the origin. + + rbox d D10 + a 10-d diamond. + + rbox x 1000 r W0 + 100 random points on the surface of a fixed simplex + + rbox y D12 + a 12-d simplex. + + rbox l 10 + 10 random points along a spiral + + rbox l 10 r + 10 regular points along a spiral plus two end + points + + rbox 1000 L10000 D4 s + 1000 random points on the surface of a narrow lens. + + rbox c G2 d G3 + a cube with coordinates +2/-2 and a diamond with + + + +Geometry Center August 10, 1998 1 + + + + + +rbox(1) rbox(1) + + + coordinates +3/-3. + + rbox 64 M3,4 z + a rotated, {0,1,2,3} x {0,1,2,3} x {0,1,2,3} lat- + tice (Mesh) of integer points. + + rbox P0 P0 P0 P0 P0 + 5 copies of the origin in 3-d. Try 'rbox P0 P0 P0 + P0 P0 | qhull QJ'. + + r 100 s Z1 G0.1 + two cospherical 100-gons plus another cospherical + point. + + 100 s Z1 + a cone of points. + + 100 s Z1e-7 + a narrow cone of points with many precision errors. + +OPTIONS + n number of points + + Dn dimension n-d (default 3-d) + + Bn bounding box coordinates (default 0.5) + + l spiral distribution, available only in 3-d + + Ln lens distribution of radius n. May be used with + 's', 'r', 'G', and 'W'. + + Mn,m,r lattice (Mesh) rotated by {[n,-m,0], [m,n,0], + [0,0,r], ...}. Use 'Mm,n' for a rigid rotation + with r = sqrt(n^2+m^2). 'M1,0' is an orthogonal + lattice. For example, '27 M1,0' is {0,1,2} x + {0,1,2} x {0,1,2}. + + s cospherical points randomly generated in a cube and + projected to the unit sphere + + x simplicial distribution. It is fixed for option + 'r'. May be used with 'W'. + + y simplicial distribution plus a simplex. Both 'x' + and 'y' generate the same points. + + Wn restrict points to distance n of the surface of a + sphere or a cube + + c add a unit cube to the output + + c Gm add a cube with all combinations of +m and -m to + the output + + + +Geometry Center August 10, 1998 2 + + + + + +rbox(1) rbox(1) + + + d add a unit diamond to the output. + + d Gm add a diamond made of 0, +m and -m to the output + + Cn,r,m add n nearly coincident points within radius r of m points + + Pn,m,r add point [n,m,r] to the output first. Pad coordi- + nates with 0.0. + + n Remove the command line from the first line of out- + put. + + On offset the data by adding n to each coordinate. + + t use time in seconds as the random number seed + (default is command line). + + tn set the random number seed to n. + + z generate integer coordinates. Use 'Bn' to change + the range. The default is 'B1e6' for six-digit + coordinates. In R^4, seven-digit coordinates will + overflow hyperplane normalization. + + Zn s restrict points to a disk about the z+ axis and the + sphere (default Z1.0). Includes the opposite pole. + 'Z1e-6' generates degenerate points under single + precision. + + Zn Gm s + same as Zn with an empty center (default G0.5). + + r s D2 generate a regular polygon + + r s Z1 G0.1 + generate a regular cone + +BUGS + Some combinations of arguments generate odd results. + + Report bugs to qhull_bug@qhull.org, other correspon- + dence to qhull@qhull.org + +SEE ALSO + qhull(1) + +AUTHOR + C. Bradford Barber + bradb@shore.net + + + + + +Geometry Center August 10, 1998 3 + + diff --git a/xs/src/qhull/index.htm b/xs/src/qhull/index.htm new file mode 100644 index 0000000000..4ea7806c93 --- /dev/null +++ b/xs/src/qhull/index.htm @@ -0,0 +1,284 @@ + + + + +Qhull code for Convex Hull, Delaunay Triangulation, Voronoi Diagram, and Halfspace Intersection about a Point + + + + +URL: http://www.qhull.org +
    To: +News +• Download +• CiteSeer +• Images +• Manual +• FAQ +• Programs +• Options +

    + +
    + + +
    +

    Qhull

    + [CONE] +
    +Qhull computes the convex hull, Delaunay triangulation, Voronoi diagram, +halfspace intersection about a point, furthest-site Delaunay +triangulation, and furthest-site Voronoi diagram. The source code runs in +2-d, 3-d, 4-d, and higher dimensions. Qhull implements the Quickhull +algorithm for computing the convex hull. It handles roundoff +errors from floating point arithmetic. It computes volumes, +surface areas, and approximations to the convex hull.

    + + +

    Qhull does not support triangulation of non-convex surfaces, mesh +generation of non-convex objects, medium-sized inputs in 9-D +and higher, alpha shapes, weighted Voronoi diagrams, Voronoi volumes, or +constrained Delaunay triangulations,

    + +

    Qhull 2015.2 introduces reentrant Qhull. It allows concurrent Qhull runs and simplifies the C++ interface to Qhull. +If you call Qhull from your program, you should use reentrant Qhull (libqhull_r) instead of qh_QHpointer (libqhull). +If you use Qhull 2003.1. please upgrade or apply poly.c-qh_gethash.patch. +

    +
    + +
    +
    + + + +
    + +

    Introduction +

      +
    • Fukuda's introduction to convex hulls, Delaunay + triangulations, Voronoi diagrams, and linear programming
    • +
    • Lambert's Java visualization of convex hull algorithms
    • +
    • LEDA Guide to geometry algorithms +
    • MathWorld's Computational Geometry from Wolfram Research +
    • Skiena's Computational Geometry from his Algorithm Design Manual. +
    • Stony Brook Algorithm Repository, computational geometry
    • +
    + +

    Qhull Documentation and Support +

    + +

    Related URLs +

    + +

    FAQs and Newsgroups +

    + +
    + +

    The program includes options for input transformations, +randomization, tracing, multiple output formats, and execution +statistics. The program can be called from within your +application.

    + +

    You can view the results in 2-d, 3-d and 4-d with Geomview. An alternative +is VTK.

    + +

    For an article about Qhull, download from + ACM or CiteSeer: +

    + +
    +

    Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The + Quickhull algorithm for convex hulls," ACM Trans. on + Mathematical Software, 22(4):469-483, Dec 1996, http://www.qhull.org

    +
    + +

    Abstract:

    + +
    +

    The convex hull of a set of points is the smallest convex + set that contains the points. This article presents a + practical convex hull algorithm that combines the + two-dimensional Quickhull Algorithm with the general + dimension Beneath-Beyond Algorithm. It is similar to the + randomized, incremental algorithms for convex hull and + Delaunay triangulation. We provide empirical evidence that + the algorithm runs faster when the input contains non-extreme + points, and that it uses less memory.

    +

    Computational geometry algorithms have traditionally + assumed that input sets are well behaved. When an algorithm + is implemented with floating point arithmetic, this + assumption can lead to serious errors. We briefly describe a + solution to this problem when computing the convex hull in + two, three, or four dimensions. The output is a set of + "thick" facets that contain all possible exact convex hulls + of the input. A variation is effective in five or more + dimensions.

    +
    + +
    + +

    Up: Past Software +Projects of the Geometry Center
    +URL: http://www.qhull.org +
    To: +News +• Download +• CiteSeer +• Images +• Manual +• FAQ +• Programs +• Options +

    + +
    + +

    [HOME] The Geometry Center Home Page

    + +

    Comments to: qhull@qhull.org +
    +Created: May 17 1995 --- + + diff --git a/xs/src/qhull/origCMakeLists.txt b/xs/src/qhull/origCMakeLists.txt new file mode 100644 index 0000000000..1034d1dea9 --- /dev/null +++ b/xs/src/qhull/origCMakeLists.txt @@ -0,0 +1,426 @@ +# CMakeLists.txt -- CMake configuration file for qhull, qhull6, and related programs +# +# To install CMake +# Download from http://www.cmake.org/download/ +# +# To find the available targets for CMake -G "..." +# cmake --help +# +# To build with MSYS/mingw +# cd build && cmake -G "MSYS Makefiles" .. && cmake .. +# make +# make install +# +# To uninstall on unix or MSYS/mingw +# xargs rm [B. Boeckel] + - Moved include file for each C++ source file to the top of the includes + - Prepend cpp includes with "libqhullcpp/" + - RoadLogEvent includes RoadLogEvent.h + - QhullIterator.h: Only QHULL_DECLARE_SEQUENTIAL_ITERATOR is used. + + - Compared src/libqhull/* to src/libqhull_r/* and resolved differences + - qh_printpoint in io.c skips qh_IDnone like io_r.c + - qhull_p-exports.def: Added three missing exports + - set_r.h: Removed countT. Too many issues + + - libqhull_r/Makefile: Add help prompts to 'make qtest' + - libqhull.pro: Add '../libqhull/' to sources and headers + - libqhull/Makefile: Fixed -I,./,,/src + + - qhull-zip.sh: Add CMakeModules to tarball [C. Rosenvik] + - CMakeLists.txt: Add targets qhullp and user_egp for qh_QHpointer and libqhull_p + - Reorganized 'make help' + - Makefile cleanall: Delete testqset and qhulltest from bin/ + - Fix filetype of Unix-only files + - Fix Unix line endings for Makefile and check in qhull-zip.sh + - Fix Windows line-endings and check in qhull-zip.sh + - qhull-zip.sh: Check for Unix text files + + ------------ +Qhull 2015.1 2016/01/03 (7.1.0) + - Add Rbox option 'Cn,r,m' to add nearly coincident points. Trigger for duplicate ridges + - Add Qhull option 'Q12' to ignore error on wide merge due to duplicate ridge + + - qh_findbestlower: Call qh_findfacet_all to fix rare "flipped or upper Delaunay" error QH6228. + QH6228 input provided by J. Metz. Reported (date order): L. Fiaschi, N. Bowler, A. Liebscher, V. Vieira, N. Rhinehart, N. Vance, P. Shafer + - qh_check_dupridge: Check if wide merge due to duplicate ridge from nearly coincident points + - qh_initialhull: Fix error messages for initial simplex is flat + - qh_determinant: increased 2-d and 3-d nearzero by 10x due to a counter-example + - rbox: Add qh_outcoord() to output coordinates w/ or w/o iscdd + - qh_meminit (mem.c): Add call to qh_memcheck + - Compare libqhull/... to libqhull_r/... and resolve differences + - Update builds for DevStudio (qhull.sln for msdev 2005..2009, qhull-32.sln and qhull-64.sln for recent releases) + + - qh-impre.htm: Add a section about precision errors for 'Nearly coincident points on an edge' + - html/index.htm#geomview: Document how to install, build, and use Geomview. + - html/index.htm: Emphasize program links and move related urls to end + - qhull/index.htm: Emphasize manual, geomview, and imprecision + - Fix documentation links in libqhull_r/index.htm + - Add 'Functions' link to documentation headers + - Change '...' to '...' + - libqhull_r/index.htm -- Add instructions for configuring web browsers for source links. + - libqhull_r/ -- Fix source links for ..._r.htm files + +------------ +Qhull 2015.0.7 2015/11/09 (7.0.7) + - Fix return type of operator-> in QhullLinkedList and other collection classes [F. Jares] + - Fix return types for QhullLinkedList + - Fix return types for QhullPoints + - Simplify return type for Coordinates::operator[] (same as QList) + - Add const to operators for QhullSet::iterator and add documentation + - Coordinates.h: Fix return types for operations of iterator and const_iterator + - Drop use of Perforce changelist number in qhull_VERSION of CMakeLists.txt + - Rename the md5sum files as *.tgz.md5sum instead of *-tgz.md5sum + - Fix build dependency for testqset_r [asekez] + - rbox.c depends on Qhull due to qh_lib_check which uses qh_version2 for error messages + - QhullFacet_test.cpp: Annotate Qhull invocations. Allows their repetition. + - QhullFacet_test.cpp: Adjust epsilon on distance tests + - Do not create libqhullcpp as a shared library. Qhull C++ classes may change layout and size. + - qhull-cpp.xml: Make a relative path to road-faq.xsl + +------------ +Qhull 2015.0.6 2015/10/20 (7.0.6.2013) + - In the libraries, exit() is only called from qh_exit(). qh_exit may be redefined. + - Add qh_fprintf_stderr to usermem.c. May be overridden to avoid use of stderr [D. Sterratt] + Add usermem to testqset builds + Used by qh_fprintf_rbox + - Remove most instances of stderr/stdout from libqhull, libqhull_r, and libqhullcpp [D. Sterratt] + qh_fprintf_stderr may be redefined. qh_meminit and qh_new_qhull use stderr as the default ferr + - qh_initflags: Use qh.fout instead of stdout for 'TO file'. A library caller may define a different qh.fout. + - qh_settemppush: Call qh_fprintf() instead of fprintf() on error. + - Rename qh_call_qhull as "Qhull-template" from user.c. Updated its references. + + - qh-code.htm: "How to avoid exit(), fprintf(), stderr, and stdout" + - html/index.htm: Fix table of contents for qh-code + - libqhull_r/index.htm: Rewrite introduction to Reentrant Qhull + - qh-faq.htm: Rewrite "Can Qhull use coordinates without placing them in a data file?" + - qh-get.html: Link to github + - Remove qhull_interface.cpp from the documentation + +------------ +Qhull 2015.0.5 2015/10/12 (7.0.5.1995) +- qh_new_qhull: default 'errfile' is 'stderr'. outfile and errfile are optional [B. Pearlmutter] +- qh_new_qhull: returns qh_ERRinput instead of exit() if qhull_cmd is not "qhull ..." [B. Pearlmutter] +- qhalf_r.c,etc: Add clear of qh.NOerrexit +- global.c: gcc 4.4.0 mingw32 segfault cleared by adding comment +- usermem_r-cpp.cpp: Optional file to redefine qh_exit() as throw "QH10003.." [B. Pearlmutter] + qh_exit() is called by qhull_r when qh_errexit() is not available. + +- html/index.htm: Add bibliographic reference to Golub & van Loan and other references [R. Gaul] +- qhalf.htm: A halfspace is the points on or below a hyperplane [D. Strawn] +- qh-opto.htm#n: Defined inside, outside, on, above, and below a hyperplane [D. Strawn] +- qhalf.htm#notes: Recast the linear program using negative halfspaces (as used by Qhull) [D. Strawn] +- qhull_a.h: Fix comment '#include "libqhull/qhull_a.h" [fe rew] + +- build/qhull*.pc.in: Templates for pkg-config (derived from Fedorra) [P. McMunn] + https://bitbucket.org/mgorny/pkg-config-spec/src/c1bf12afe0df6d95f2fe3f5e1ffb4c50f018825d/pkg-config-spec.txt?at=master&fileviewer=file-view-default +- Makefile: Remove user_eg3.o from LIBQHULLCPP_OBJS +- Makefile: Add .h dependencies for unix_r.o, etc. +- libqhull/Makefile: Fix build of rbox +- libqhull_r/Makefile: Fix build -I +- qhull.sln/user_eg3: Add dependency on libcpp +- Removed bin/libqhull_r.dll (should be qhull_r.dll) +- Removed build/qhulltest.vcproj (see build/qhulltest/qhulltest.vcproj) + +------------ +Qhull 2015.0.4 2015/9/30 (7.0.4.1984) + - qh-get.htm: Unix tarball includes version number (e.g., qhull-2015-src-7.1.0.1940.tgz) [Hauptman] + - qglobal.c: Add qh_version2 with Unix version for "-V" option [Hauptman] + - build/qhull-32.sln, *-32.vcxproj: Add Visual Studio 32-bit build for 2010+ + - build/qhull-64.sln, *-64.vcxproj: Add Visual Studio 64-bit build for 2010+ [G. Lodron] + - make-vcproj.sh: Restore to eg/... It is required for Visual Studio builds + - README.txt: updated builds and reentrant Qhull + - Add documentation for QHULL_LIB_CHECK + - qh_lib_check: Check for unknown QHULL_LIB_TYPE + - qh-code.htm: Add memory requirements for 32- and 64-bit + +------------ +Qhull 2015.0.3 2015/9/22 + - qh_mem, qh_merge: Log before 'delete' instead of afterwards [Coverity, K. Schwehr] + - qh_merge: Test for NULL horizon in qh_checkzero [Coverity, K. Schwehr] + - qh_matchneighbor: Check for matchfacet not a neighbor of facet [Coverity, K. Schwehr] + - qh_triangulate: Explicit check for visible==NULL [Coverity, K. Schwehr] + - qh_findbestfacet (unused by qhull): Fix test of isoutside [Coverity, K. Schwehr] + - qh_check_maxout: Check bestfacet!=0 for logging its id [Coverity, K. Schwehr] + - qh_nearvertex: Check for bestvertex not found [Coverity, K. Schwehr] + - qh_checkfacet: Check for missing neighbors of simplicial facets [Coverity, K. Schwehr] + - qh_setdelnth: Check 'nth' before using it [Coverity, K. Schwehr] + - Annotate code for Coverity warnings (most of these protected by qh_errexit) [K. Schwehr] + + - qh_printfacet3math: explicit format string (duplicates change to io.c) [B. Pearlmutter] + - libqhull_r.h: fix spelling error (duplicates change to libqhull.h) [B. Pearlmutter] + - unix_r.c: fix spelling error (duplicates change to unix.c) [B. Pearlmutter] + - qhull_a.h: define qhullUnused() only if defined(__cplusplus) [R. Stogner] + - qh_version: Use const char str[]= "string" instead of const char * str= "string" [U. Drepper, p. 27] + - qh_newvertex: Use UINT_MAX instead of 0xFFFFFFFF + - qh_newridge: Use UINT_MAX instead of 0xFFFFFFFF + - Reviewed FIXUP notes + + - QhullRidge_test: t_foreach use 'foreach(const QhullVertex &v, vertices) + - Made '#include "RoadTest.h" consistent across all C++ tests + + - qh-code.htm: May also use libqhull_r (e.g., FOREACHfacet_(...)) + - qh-get.htm: Add list of download build repositories + + - Add CMakeModules/CheckLFS.cmake: Enables Large File Support [B. Pearlmutter] + - Makefile: Use -fpic at all times instead of -fPIC, [U. Drepper p. 15] + +------------ +Qhull 2015.0.2 2015/9/1 + - global_r.c: Fixed spelling of /* duplicated in...qh_clear_outputflags */ [K. Schwehr] + - Replaced Gitorious with GitHub + - Moved 'to do' comments into Changes.txt + +------------ +Qhull 2015.0.1 2015/8/31 + + Source code changes + - Increased size of vertexT.id and ridgeT.id to 2^32 [H. Strandenes, C. Carson, K. Nguyen] + Reworded the warning message for ridgeT.id overflow. It does not affect Qhull output + - Add qh_lib_check to check for a compatible Qhull library. + Programs should call QHULL_LIB_CHECK before calling Qhull. + - Include headers prefixed with libqhull/, libqhull_r/, or libqhullcpp/ + - Renamed debugging routines dfacet/dvertex to qh_dfacet/qh_dvertex + - Rewrote user_eg, user_eg2, and user_eg3 as reentrant code + - Renamed 'qh_rand_seed' to 'qh_last_random'. Declare it as DATA + - qh_initqhull_start2 sets qh->NOerrexit on initialization + User must clear NOerrexit after setjmp() + + Other source code changes + - Define ptr_intT as 'long long' for __MINGW64__ [A. Voskov] + - poly_r.c: initialize horizon_skip [K. Schwehr] + - Removed vertexT.dim and MAX_vdim. It is not used by reentrant Qhull. + - Removed qhull_inuse. Not used by C++ + - Removed old __MWERKS__/__POWERPC__ code that speed up SIOUX I/O + - Moved #include libqhull/... before system includes (e.g., + - Comment-out _isatty declaration. Avoids "C4273 ... inconsistent dll linkage" + - Add random.h/random_r.h as an include file to random.c/random_r.c + - Rename rbox routines to qh_roundi/qh_out1/qh_out2n/qh_out3n + - Rename dfacet and dvertex to qh_dfacet and qh_dvertex + - Replace 'qhmem .zzz' with 'qhmem.zzz' + - Removed spaces between function name and parentheses + - Rename 'enum statistics' to 'enum qh_statistics' + - Declare rbox as DATA in qhull-exports.def and qhull_p-exports.def + - In comments, use 'qh.zzz' to reference qhT fields + - In qh_fprintf, use qhmem.ferr to report errors + - qh_fprintf may be called for errors in qh_initstatistics and qh_meminit + - qh_pointid returns qh_IDnone, qh_IDinterior, qh_IDunknown in place of -3, -2, -1 resp. + - getid_() returns qh_IDunknown in place of -1 + - After qh_meminit, qhmem.ferr is non-zero (stderr is the default) + - Update qh_MEMalign in testqset.c to user.h (with realT and void*) + - Split rboxT into a header file + - Add rboxlib.h to libqhull_a.h + - Rename PI to qh_PI and extend to 30 digits + - Rename MAXdim to qh_MAXdim + - Change spacing for type annotations '*' and '&' in C++ header files + - Test for !rbox_output/cpp_object in qh_fprintf_rbox + - Remove 'inline' annotation from explicit inline declarations + - Column 25 formatting for iterators, etc. + - Use '#//!\name' for section headers + - QhullFacet.cpp: zinc_(Zdistio); + - Clear qhT.ALLOWrestart in qh_errexit + - Replace longjmp with qh_errexit_rbox in qh_rboxpoints + - Add jmpExtra after rbox_errexit to protect against compiler errors + - Add qh.ISqhullQh to indicate initialization by QhullQh() + - Add library warnings to 'rbox D4', user_eg, user_eg2, user_eg3 + - Add headers to q_eg, q_egtest, and q_test + - Check that qh.NOerrexit is cleared before call to qh_initflags + +Qhull documentation + - README.txt: Added references to qh-code.htm + - README.txt: Added section 'Calling Qhull from C programs' + - qh-code.htm: Moved Performance after C++ and C interface + - qh-code.htm: Moved Cpp Questions to end of the C++ section + - qh-code.htm: Fixed documentation for 'include' path. It should be include/libqhull + - qconvex.htm: Fixed documentation for 'i'. It triangulates in 4-d and higher [ref] + - Clarified qhalf space documentation for the interior point [J. Santos] + - rbox.c: Version is same date as qh_version in global.c + - gobal_r.c: Version includes a '.r' suffix to indicate 'reentrant' + +Qhull builds + - Development moved to http://github.com/qhull/qhull + git clone git@github.com:qhull/qhull.git + - Exchanged make targets for testing. + 'make test' is a quick test of qhull programs. + 'make testall' is a thorough test + - Added 'make help' and 'make test' to libqhull and libqhull_r Makefiles + - CMakeLists.txt: Remove libqhull, libqhull_r, and libqhullcpp from include_directories + - CMakeLists.txt: Add qhull_SHAREDR for qhull_r + - CMakeLists.txt: Retain qhull_SHARED and qhull_SHAREDP (qh_QHpointer) + - CMakeLists.txt: Move qhull_SHARED and qhull_SHAREDP (qh_QHpointer) to qhull_TARGETS_OLD + Drop qhull_STATICP (use qhull_SHAREDP or qhull_STATIC) + Set SOVERSION and VERSION for shared libraries + - Move qhull_p-exports.def back to libqhull + - Switched to mingw-w64-install for gcc + - Improved prompts for 'make' + - qhull-all.pro: Remove user_eg3.cpp from OTHER_FILES + - libqhull.pro: Ordered object files by frequency of execution, as done before + - Add the folder name to C++ includes and remove libqhullcpp from INCLUDEPATH + - Changed CONFIG+=qtestlib to QT+=testlib + - Changed Makefile to gcc -O3 (was -O2) + - Changed libqhull/libqhull_r Makefiles to both produce rbox, qhull, ..., user_eg, and user_eg2 + - Removed Debian 'config/...'. It was needed for Qhull 2012. + +libqhull_r (reentrant Qhull) + - Replaced qh_qh with a parameter to each procedure [P. Klosterman] + No more globally defined data structures in Qhull + Simplified multithreading and C++ user interface + All functions are reentrant (Qt: "A reentrant function can ... be called simultaneously from multiple threads, but only if each invocation uses its own data.") + No more qh_QHpointer. + See user_eg3 and qhulltest + New libraries + libqhull_r -- Shared library with reentrant sources (e.g., poly_r.h and poly_r.c which replace libqhull's poly.h and poly.c) + libqhullstatic_r -- Static library with the same sources as libqhull_r + libqhullcpp -- The C++ interface using libqhullstatic_r (further notes below) + New executables + testqset_r -- Test qset_r.c (the reentrant version of qset.c + + Source code changes for libqhull_r + - Add qh_zero() to initialize and zero memory for qh_new_qhull + - Remove qh_save_qhull(), qh_restore_qhull(), and qh.old_qhstat from global_r.c + - Remove qh_freeqhull2() (global_r.c) + - Remove qh_freestatistics() (stat_r.c) + - Remove qh_compare_vertexpoint (qhT is not available, unused code) + - Remove conditional code for __POWERPC__ from unix_r.c and rbox_r.c + - Move qh_last_random into qh->last_random (random_r.c) + - Rename sources files with a '_r' suffix. qhull_a.h becomes qhull_ra.h + - Replace 'qh' macro with 'qh->' + - Replace global qhT with parameter-0 + - Add qhmemT to beginning of qhT. It may not be used standalone. + - Add qhstatT to end of qhT + - Remove qhull_inuse + - Change qhmem.zzz to qh->qhmem.zzz + - Replace qh_qhstat with qh->qhstat + - Remove qh_freestatistics + - Replace qh_last_random with qh->last_random + - Replace rboxT with qh->rbox_errexit, rbox_isinteger, rbox_out_offset + - Replace rbox.ferr/fout with qh->ferr/fout + - No qh for qh_exit, qh_free, qh_malloc, qh_strtod, qh_strtol, qh_stddev + - New qmake include files qhull-app-c_r.pri, qhull-app-shared_r.pri, qhull-libqhull-src_r.pri + - Replace 'int' with 'countT' and 'COUNTmax' for large counts and identifiers + - qhset converted to countT + - Removed vertexT.dim -- No longer needed by cpp + Removed MAX_vdim + - Guarantee that qh->run_id!=0. Old code assumed that qh_RANDOMint was 31 bits + +Changes to libqhullcpp + - Added QhullVertexSet.h to libqhullcpp.pro and libqhullpcpp.pro + - QhullVertexSet: error if qhsettemp_defined at copy constructor/assignment (otherwise double free) + - Enable QhullSet.operator=. Copy constructor and assignment only copies pointers + - Changed QhullPoint.operator==() to sqrt(distanceEpsilon) + - Added assignment of base class QhullPoints to PointCoordinates.operator= + - Enable QhullPoints.operator= + - Rename PointCoordinates.point_comment to describe_points + - Add 'typename T' to definition of QhullSet::value() + +C++ interface + - Reimplemented C++ interface on reentrant libqhull_r instead of libqhull + - Prepend include files with libqhullcpp/ + - Replaced UsingLibQhull with QhullQh and macro QH_TRY + Removed UsingLibQhull.currentAngleEpsilon and related routines + Removed UsingLibQhull_test.cpp + Replaced globalDistanceEpsilon with QhullQh.distanceEpsilon + Replaced globalAngleEpsilon with QhullQh.angleEpsilon + Moved UsingQhullLib.checkQhullMemoryEmpty to QhullQh.checkAndFreeQhullMemory + Replaced FACTORepsilon=10 with QhullQh.factor_epsilon=1.0 + - To avoid -Wshadow for QhullQh*, use 'qqh' for parameters and 'qh()' for methods + - Moved messaging from Qhull to QhullQh + - Add check of RboxPoints* in qh_fprintf_rbox + - Renamed Qhull.initializeQhull to Qhull.allocateQhullQh + Added qh_freeqhull(!qh_ALL) as done by unix.c and other programs + - Moved QhullPoints.extraCoordinatesCount into QhullPoints.cpp + - Replaced section tags with '#//!\name ...' + - Removed qhRunId from print() to ostream. + - Removed print() to ostream. Use '<< qhullPoint' or '<< qhullPoint.print("message")' + +C++ interface for most classes + - Remove qhRunId + - Add QhullQh *qh_qh to all types + Pointer comparisons of facetT,etc. do not test corresponding qh_qh + Added to end of type for debugging information, unless wasteful alignment + - Add QhullQh * to all constructors + - All constructors may use Qhull & instead of QhullQh * + - For inherited QhullQh types, change to 'protected' + - Renamed 'o' to 'other' except where used extensively in iterators + - Except for conditional code, merged the Conversion section into GetSet + - Removed empty(). Use isEmpty() instead + - Add operator= instead of keeping it private + - print_message=0 not allowed. Use "" instead. + - Rename isDefined() to isValid() to match Qt conventions + +C++ interface by class + - Coordinates + Removed empty(). Use isEmpty() instead + Added append(dim, coordT*) + Reformated the iterators + Convert to countT + - PointCoordinates + Added constructors for Qhull or QhullQh* (provides access to QhullPoint.operator==) + Removed PointCoordinates(int pointDimension) since PointCoordinates should have a comment. Also, it is ambiguous with PointCoordinates(QhullQh*) + Renamed point_comment to describe_points + Convert to countT + - Qhull + Remove qhull_run_i + Remove qh_active + Replace property feasiblePoint with field feasible_point and methods setFeasiblePoint/feasiblePoint + Returns qh.feasible_point if defined + Moved useOutputStream to QhullQh use_output_stream + Renamed useOutputStream() to hasOutputStream() + Replaced qhull_dimension with qh->input_dim //! Dimension of result (qh.hull_dim or one less for Delaunay/Voronoi) + Removed global s_qhull_output= 0; + Move qhull_status, qhull_message, error_stream, output_stream to QhullQh + Renamed qhullQh() to qh() + Added check of base address to allocateQhullQh(), Was not needed for qhullpcpp + - QhullFacet + Constructor requires Qhull or QhullQh* pointer + Convert to countT + Dropped implicit conversion from facetT + Dropped runId + Add print("message") to replace print() + - QhullFacetList + Constructor requires Qhull or QhullQh* pointer + Convert to countT + Dropped runId + - QhullFacetSet + Removed empty(). Use isEmpty() instead + Constructor requires Qhull or QhullQh* pointer + Convert to countT + Dropped runId + Add operator= + Implement print("message") + - QhullHyperplane + Add hyperplaneAngle() method + Rewrite operator== to use hyperplaneAngle() + Reorganize fields to keep pointers aligned + Except for default constructor requires Qhull or QhullQh* pointer + Enable copy assignment + Reorganized header + - QhullLinkedList + Add operator= + Removed empty(). Use isEmpty() instead + Convert to countT + iterator(T) made iterator(const T &) + const_iterator(T) made const_iterator(const T &) + const_iterator(iterator) made const_iterator(const iterator &) + - QhullPoint + Add constructors for Qhull or QhullQh* pointer (for id() and operator==) + Add defineAs(coordT*) + Add getBaseT() and base_type for QhullSet + Added checks for point_coordinates==0 + Removed static QhullPoint::id(), use QhullPoint.id() instead + distance() throws an error if dimension doesn't agree or if a point is undefined + Convert to countT + If !qh_qh, operator==() requires equal coordinates + Use cout<

    [R. Richter, S. Pasko] + - Remove deprecated libqhull/qhull.h + Use libqhull/libqhull.h instead. Avoids confusion with libqhullcpp/Qhull.h + - Makefile: Add LIBDIR, INCDIR, and DESTDIR to install [L.H. de Mello] + Separate MAN install from DOC install + Create install directories + Installs headers to include/libqhull, include/libqhullcpp, include/road + - CMakeLists.txt: Add MAN_INSTALL_DIR for qhull.1 and rbox.1 man pages + Add RoadTest.h to include/road for Qt users (road_HEADERS) + - Renamed md5sum files to avoid two extensions + - qh-get.htm: Add Readme links and 2009.1 note. + - qh-optf.htm: Fix link + - index.htm: Updated Google Scholar link + - qhull-zip.sh: Improved error message. + +------------ +Qhull 2011.1 2011/04/17 6.2.0.1373 + +Changes to deliverables + - qvoronoi: Deprecated 'Qt' and 'QJn'. Removed from documentation and prompts. + These options produced duplicate Voronoi vertices for cospherical data. + - Removed doskey from Qhull-go.bat. It is incompatible with Windows 7 + - Added 'facets' argument to user_eg3.cpp + - user_eg links with shared library + - qhulltest.cpp: Add closing prompt. + +Changes to build system + - Reorganized source directories + - Moved executables to bin directory + - Add CMake build for all targets (CMakeFiles.txt) [M. Moll assisted] + - Add gcc build for all targets (Makefile) + - Fixed location of qhull.man and rbox.man [M. Moll] + - Add DevStudio builds for all targets (build/*.vcproj) + - Added shared library (lib/qhull6.dll) + Added qh_QHpointer_dllimport to work around problems with MSVC + - Added static libraries with and without qh_QHpointer (lib/qhullstatic.lib) + - Added eg/make-vcproj.sh to create vcproj/sln files from cmake and qmake + - Document location of qh_QHpointer + - Use shadow build directory + - Made -fno-strict-aliasing conditional on gcc version + - Added src/qhull-app-cpp.pri, src/qhull-app-c.pri, etc. for common settings + - Add .gitignore with ignored files and directories. + - Use .git/info/exclude for locally excluded files. + - Fixed MBorland for new directory structure + - cleanall (Makefile): Delete 'linked' programs due to libqhull_r and libqhull/Makefile + +Changes to documentation + - qvoronoi.htm: Remove quotes from qvoronoi example + - qhull-cpp.xml: Add naming conventions + - index.htm: Add Google Scholar references + - qh-optf.htm: Add note about order of 'Fn' matching 'Fv' order [Q. Pan] + - Add patch for old builds in qh-get.htm + - Added C++ compiling instructions to README.txt + - Add instructions for fixing the DOS window + - Changed DOS window to command window + - Fixed html links + - qh-get.htm: Dropped the Spanish mirror site. It was disabled. + +Changes to C code + - mem.h: Define ptr_intT as 'long long' for Microsoft Windows _win64 builds. + On Linux and Mac, 'long' is 64-bits on a 64-bit host + - Added qh_QHpointer_dllimport to work around MSVC problem + - qconvex.c,etc.: Define prototype for _isatty + - Define MSG_QHULL_ERROR in user.h + - Move MSG_FIXUP to 11000 and updated FIXUP QH11... + +Changes to test code + - Add note to q_test than R1e-3 may error (qh-code.htm, Enhancements) + - Add test for executables to q_eg, etc. + - Fixed Qhull-go.bat. QHULL-GO invokes it with command.com, + +Changes to C++ interface + - QhullFacet: Added isSimplicial, isTopOrient, isTriCoplanar, isUpperDelaunay + - Added Qhull::defineVertexFacetNeighbors() for facetNeighbors of vertices. + Automatically called for facet merging and Voronoi diagrams + Do not print QhullVertex::facetNeighbors is !facetNeighborsDefined() + - Assigned FIXUP identifiers + - QhullError: Add copy constructor, assignment operator, and destructor + - Add throw() specifiers to RoadError and QhullError + - Renamed RoadError::defined() to RoadError::isDefined() + - Add #error to Qhull.h if qh_QHpointer is not defined + +Changes to C++ code + - Fixed bug reported by renangms. Vertex output throws error QH10034 + and defineVertexNeighbors() does not exist. + - Define QHULL_USES_QT for qt-qhull.cpp [renangms] + - Reviewed all copy constructors and copy assignments. Updated comments. + Defined Qhull copy constructor and copy assignment [G. Rivet-Sabourin] + Disabled UsingQhullLib default constructor, copy construct, and copy assign + - Merged changes from J. Obermayr in gitorious/jobermayrs-qhull:next + - Fix strncat limit in rboxlib.c and global.c + - Changes to CMakeLists.txt for openSUSE + - Fixed additional uses of strncat + - Fixed QhullFacet::PrintRidges to check hasNextRidge3d() + - Removed gcc warnings for shadowing from code (src/qhull-warn.pri) + - Removed semicolon after extern "C" {...} + - Removed experimental QhullEvent/QhullLog + - Use fabs() instead of abs() to avoid accidental conversions to int + - Fixed type of vertex->neighbors in qh_printvoronoi [no effect on results] + - Removed unnecessary if statement in qh_printvoronoi + +------------ +qhull 2010.1 2010/01/14 +- Fixed quote for #include in qhull.h [U.Hergenhahn, K.Roland] +- Add qt-qhull.cpp with Qt conditional code +- Add libqhullp.proj +- Add libqhull5 to Readme, Announce, download +- Reviewed #pragma +- Reviewed FIXUP and assigned QH tags +- All projects compile with warnings enabled +- Replaced 'up' glyphs with » +- Moved cpp questions to qh-code.htm#questions-cpp +- Moved suggestions to qh-code.htm#enhance +- Moved documentation requests to qh-code.htm#enhance +- Add md5sum file to distributions +- Switched to DevStudio builds to avoid dependent libraries, 10% slower + Removed user_eg3.exe and qhullcpp.dll from Windows build + Fix qhull.sln and project files for qh_QHpointer +- Add eg/qhull-zip.sh to build qhull distribution files + +------------ +qhull 2010.1 2010/01/10 +- Test for NULL fp in qh_eachvoronoi [D. Szczerba] + +qhull 2010.1 2010/01/09 + +Changes to build and distribution +- Use qh_QHpointer=0 for libqhull.a, qhull, rbox, etc. + Use -Dqh_QHpointer for libqhullp.a, qhullcpp.dll, etc. + qh_QHpointer [2010, gcc] 4% time 4% space, [2003, msvc] 8% time 2% space +- Add config/ and project/debian/ for Autoconf build [R. Laboissiere] + from debian branch in git and http://savannah.nongnu.org/cvs/?group=qhull +- Add CMakeLists.txt [kwilliams] +- Fix tabs in Makefile.txt [mschamschula] +- Add -fno-strict-aliasing to Makefile for gcc 4.1, 4.2, and 4.3 qset segfault +- Remove user_eg.exe and user_eg2.exe from Windows distribution +- Order object files by frequency of execution for better locality. + +Changes to source +- Remove ptr_intT from qh_matchvertices. It was int since the beginning. +- user.h requires for CLOCKS_PER_SEC +- Move ostream<

      ---------------------------------
    +
    +   geom.c
    +   geometric routines of qhull
    +
    +   see qh-geom.htm and geom.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/geom.c#2 $$Change: 1995 $
    +   $DateTime: 2015/10/13 21:59:42 $$Author: bbarber $
    +
    +   infrequent code goes into geom2.c
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*---------------------------------
    +
    +  qh_distplane( point, facet, dist )
    +    return distance from point to facet
    +
    +  returns:
    +    dist
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    dist > 0 if point is above facet (i.e., outside)
    +    does not error (for qh_sortfacets, qh_outerinner)
    +
    +  see:
    +    qh_distnorm in geom2.c
    +    qh_distplane [geom.c], QhullFacet::distance, and QhullHyperplane::distance are copies
    +*/
    +void qh_distplane(pointT *point, facetT *facet, realT *dist) {
    +  coordT *normal= facet->normal, *coordp, randr;
    +  int k;
    +
    +  switch (qh hull_dim){
    +  case 2:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1];
    +    break;
    +  case 3:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
    +    break;
    +  case 4:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
    +    break;
    +  case 5:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
    +    break;
    +  case 6:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
    +    break;
    +  case 7:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
    +    break;
    +  case 8:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
    +    break;
    +  default:
    +    *dist= facet->offset;
    +    coordp= point;
    +    for (k=qh hull_dim; k--; )
    +      *dist += *coordp++ * *normal++;
    +    break;
    +  }
    +  zinc_(Zdistplane);
    +  if (!qh RANDOMdist && qh IStracing < 4)
    +    return;
    +  if (qh RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    *dist += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh RANDOMfactor * qh MAXabs_coord;
    +  }
    +  if (qh IStracing >= 4) {
    +    qh_fprintf(qh ferr, 8001, "qh_distplane: ");
    +    qh_fprintf(qh ferr, 8002, qh_REAL_1, *dist);
    +    qh_fprintf(qh ferr, 8003, "from p%d to f%d\n", qh_pointid(point), facet->id);
    +  }
    +  return;
    +} /* distplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbest( point, startfacet, bestoutside, qh_ISnewfacets, qh_NOupper, dist, isoutside, numpart )
    +    find facet that is furthest below a point
    +    for upperDelaunay facets
    +      returns facet only if !qh_NOupper and clearly above
    +
    +  input:
    +    starts search at 'startfacet' (can not be flipped)
    +    if !bestoutside(qh_ALL), stops at qh.MINoutside
    +
    +  returns:
    +    best facet (reports error if NULL)
    +    early out if isoutside defined and bestdist > qh.MINoutside
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart counts the number of distance tests
    +
    +  see also:
    +    qh_findbestnew()
    +
    +  notes:
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    after qh_distplane, this and qh_partitionpoint are the most expensive in 3-d
    +      avoid calls to distplane, function calls, and real number operations.
    +    caller traces result
    +    Optimized for outside points.   Tried recording a search set for qh_findhorizon.
    +    Made code more complicated.
    +
    +  when called by qh_partitionvisible():
    +    indicated by qh_ISnewfacets
    +    qh.newfacet_list is list of simplicial, new facets
    +    qh_findbestnew set if qh_sharpnewfacets returns True (to use qh_findbestnew)
    +    qh.bestfacet_notsharp set if qh_sharpnewfacets returns False
    +
    +  when called by qh_findfacet(), qh_partitionpoint(), qh_partitioncoplanar(),
    +                 qh_check_bestdist(), qh_addpoint()
    +    indicated by !qh_ISnewfacets
    +    returns best facet in neighborhood of given facet
    +      this is best facet overall if dist > -   qh.MAXcoplanar
    +        or hull has at least a "spherical" curvature
    +
    +  design:
    +    initialize and test for early exit
    +    repeat while there are better facets
    +      for each neighbor of facet
    +        exit if outside facet found
    +        test for better facet
    +    if point is inside and partitioning
    +      test for new facets with a "sharp" intersection
    +      if so, future calls go to qh_findbestnew()
    +    test horizon facets
    +*/
    +facetT *qh_findbest(pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *bestfacet= NULL, *lastfacet= NULL;
    +  int oldtrace= qh IStracing;
    +  unsigned int visitid= ++qh visit_id;
    +  int numpartnew=0;
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  zinc_(Zfindbest);
    +  if (qh IStracing >= 3 || (qh TRACElevel && qh TRACEpoint >= 0 && qh TRACEpoint == qh_pointid(point))) {
    +    if (qh TRACElevel > qh IStracing)
    +      qh IStracing= qh TRACElevel;
    +    qh_fprintf(qh ferr, 8004, "qh_findbest: point p%d starting at f%d isnewfacets? %d, unless %d exit if > %2.2g\n",
    +             qh_pointid(point), startfacet->id, isnewfacets, bestoutside, qh MINoutside);
    +    qh_fprintf(qh ferr, 8005, "  testhorizon? %d noupper? %d", testhorizon, noupper);
    +    qh_fprintf(qh ferr, 8006, "  Last point added was p%d.", qh furthest_id);
    +    qh_fprintf(qh ferr, 8007, "  Last merge was #%d.  max_outside %2.2g\n", zzval_(Ztotmerge), qh max_outside);
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  if (!startfacet->flipped) {  /* test startfacet */
    +    *numpart= 1;
    +    qh_distplane(point, startfacet, dist);  /* this code is duplicated below */
    +    if (!bestoutside && *dist >= qh MINoutside
    +    && (!startfacet->upperdelaunay || !noupper)) {
    +      bestfacet= startfacet;
    +      goto LABELreturn_best;
    +    }
    +    bestdist= *dist;
    +    if (!startfacet->upperdelaunay) {
    +      bestfacet= startfacet;
    +    }
    +  }else
    +    *numpart= 0;
    +  startfacet->visitid= visitid;
    +  facet= startfacet;
    +  while (facet) {
    +    trace4((qh ferr, 4001, "qh_findbest: neighbors of f%d, bestdist %2.2g f%d\n",
    +                facet->id, bestdist, getid_(bestfacet)));
    +    lastfacet= facet;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->newfacet && isnewfacets)
    +        continue;
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {  /* code duplicated above */
    +        (*numpart)++;
    +        qh_distplane(point, neighbor, dist);
    +        if (*dist > bestdist) {
    +          if (!bestoutside && *dist >= qh MINoutside
    +          && (!neighbor->upperdelaunay || !noupper)) {
    +            bestfacet= neighbor;
    +            goto LABELreturn_best;
    +          }
    +          if (!neighbor->upperdelaunay) {
    +            bestfacet= neighbor;
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }else if (!bestfacet) {
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }
    +        } /* end of *dist>bestdist */
    +      } /* end of !flipped */
    +    } /* end of FOREACHneighbor */
    +    facet= neighbor;  /* non-NULL only if *dist>bestdist */
    +  } /* end of while facet (directed search) */
    +  if (isnewfacets) {
    +    if (!bestfacet) {
    +      bestdist= -REALmax/2;
    +      bestfacet= qh_findbestnew(point, startfacet->next, &bestdist, bestoutside, isoutside, &numpartnew);
    +      testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +    }else if (!qh findbest_notsharp && bestdist < - qh DISTround) {
    +      if (qh_sharpnewfacets()) {
    +        /* seldom used, qh_findbestnew will retest all facets */
    +        zinc_(Zfindnewsharp);
    +        bestfacet= qh_findbestnew(point, bestfacet, &bestdist, bestoutside, isoutside, &numpartnew);
    +        testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +        qh findbestnew= True;
    +      }else
    +        qh findbest_notsharp= True;
    +    }
    +  }
    +  if (!bestfacet)
    +    bestfacet= qh_findbestlower(lastfacet, point, &bestdist, numpart);
    +  if (testhorizon)
    +    bestfacet= qh_findbesthorizon(!qh_IScheckmax, point, bestfacet, noupper, &bestdist, &numpartnew);
    +  *dist= bestdist;
    +  if (isoutside && bestdist < qh MINoutside)
    +    *isoutside= False;
    +LABELreturn_best:
    +  zadd_(Zfindbesttot, *numpart);
    +  zmax_(Zfindbestmax, *numpart);
    +  (*numpart) += numpartnew;
    +  qh IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbest */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbesthorizon( qh_IScheckmax, point, startfacet, qh_NOupper, &bestdist, &numpart )
    +    search coplanar and better horizon facets from startfacet/bestdist
    +    ischeckmax turns off statistics and minsearch update
    +    all arguments must be initialized
    +  returns(ischeckmax):
    +    best facet
    +  returns(!ischeckmax):
    +    best facet that is not upperdelaunay
    +    allows upperdelaunay that is clearly outside
    +  returns:
    +    bestdist is distance to bestfacet
    +    numpart -- updates number of distance tests
    +
    +  notes:
    +    no early out -- use qh_findbest() or qh_findbestnew()
    +    Searches coplanar or better horizon facets
    +
    +  when called by qh_check_maxout() (qh_IScheckmax)
    +    startfacet must be closest to the point
    +      Otherwise, if point is beyond and below startfacet, startfacet may be a local minimum
    +      even though other facets are below the point.
    +    updates facet->maxoutside for good, visited facets
    +    may return NULL
    +
    +    searchdist is qh.max_outside + 2 * DISTround
    +      + max( MINvisible('Vn'), MAXcoplanar('Un'));
    +    This setting is a guess.  It must be at least max_outside + 2*DISTround
    +    because a facet may have a geometric neighbor across a vertex
    +
    +  design:
    +    for each horizon facet of coplanar best facets
    +      continue if clearly inside
    +      unless upperdelaunay or clearly outside
    +         update best facet
    +*/
    +facetT *qh_findbesthorizon(boolT ischeckmax, pointT* point, facetT *startfacet, boolT noupper, realT *bestdist, int *numpart) {
    +  facetT *bestfacet= startfacet;
    +  realT dist;
    +  facetT *neighbor, **neighborp, *facet;
    +  facetT *nextfacet= NULL; /* optimize last facet of coplanarfacetset */
    +  int numpartinit= *numpart, coplanarfacetset_size;
    +  unsigned int visitid= ++qh visit_id;
    +  boolT newbest= False; /* for tracing */
    +  realT minsearch, searchdist;  /* skip facets that are too far from point */
    +
    +  if (!ischeckmax) {
    +    zinc_(Zfindhorizon);
    +  }else {
    +#if qh_MAXoutside
    +    if ((!qh ONLYgood || startfacet->good) && *bestdist > startfacet->maxoutside)
    +      startfacet->maxoutside= *bestdist;
    +#endif
    +  }
    +  searchdist= qh_SEARCHdist; /* multiple of qh.max_outside and precision constants */
    +  minsearch= *bestdist - searchdist;
    +  if (ischeckmax) {
    +    /* Always check coplanar facets.  Needed for RBOX 1000 s Z1 G1e-13 t996564279 | QHULL Tv */
    +    minimize_(minsearch, -searchdist);
    +  }
    +  coplanarfacetset_size= 0;
    +  facet= startfacet;
    +  while (True) {
    +    trace4((qh ferr, 4002, "qh_findbesthorizon: neighbors of f%d bestdist %2.2g f%d ischeckmax? %d noupper? %d minsearch %2.2g searchdist %2.2g\n",
    +                facet->id, *bestdist, getid_(bestfacet), ischeckmax, noupper,
    +                minsearch, searchdist));
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {
    +        qh_distplane(point, neighbor, &dist);
    +        (*numpart)++;
    +        if (dist > *bestdist) {
    +          if (!neighbor->upperdelaunay || ischeckmax || (!noupper && dist >= qh MINoutside)) {
    +            bestfacet= neighbor;
    +            *bestdist= dist;
    +            newbest= True;
    +            if (!ischeckmax) {
    +              minsearch= dist - searchdist;
    +              if (dist > *bestdist + searchdist) {
    +                zinc_(Zfindjump);  /* everything in qh.coplanarfacetset at least searchdist below */
    +                coplanarfacetset_size= 0;
    +              }
    +            }
    +          }
    +        }else if (dist < minsearch)
    +          continue;  /* if ischeckmax, dist can't be positive */
    +#if qh_MAXoutside
    +        if (ischeckmax && dist > neighbor->maxoutside)
    +          neighbor->maxoutside= dist;
    +#endif
    +      } /* end of !flipped */
    +      if (nextfacet) {
    +        if (!coplanarfacetset_size++) {
    +          SETfirst_(qh coplanarfacetset)= nextfacet;
    +          SETtruncate_(qh coplanarfacetset, 1);
    +        }else
    +          qh_setappend(&qh coplanarfacetset, nextfacet); /* Was needed for RBOX 1000 s W1e-13 P0 t996547055 | QHULL d Qbb Qc Tv
    +                                                 and RBOX 1000 s Z1 G1e-13 t996564279 | qhull Tv  */
    +      }
    +      nextfacet= neighbor;
    +    } /* end of EACHneighbor */
    +    facet= nextfacet;
    +    if (facet)
    +      nextfacet= NULL;
    +    else if (!coplanarfacetset_size)
    +      break;
    +    else if (!--coplanarfacetset_size) {
    +      facet= SETfirstt_(qh coplanarfacetset, facetT);
    +      SETtruncate_(qh coplanarfacetset, 0);
    +    }else
    +      facet= (facetT*)qh_setdellast(qh coplanarfacetset);
    +  } /* while True, for each facet in qh.coplanarfacetset */
    +  if (!ischeckmax) {
    +    zadd_(Zfindhorizontot, *numpart - numpartinit);
    +    zmax_(Zfindhorizonmax, *numpart - numpartinit);
    +    if (newbest)
    +      zinc_(Zparthorizon);
    +  }
    +  trace4((qh ferr, 4003, "qh_findbesthorizon: newbest? %d bestfacet f%d bestdist %2.2g\n", newbest, getid_(bestfacet), *bestdist));
    +  return bestfacet;
    +}  /* findbesthorizon */
    +
    +/*---------------------------------
    +
    +  qh_findbestnew( point, startfacet, dist, isoutside, numpart )
    +    find best newfacet for point
    +    searches all of qh.newfacet_list starting at startfacet
    +    searches horizon facets of coplanar best newfacets
    +    searches all facets if startfacet == qh.facet_list
    +  returns:
    +    best new or horizon facet that is not upperdelaunay
    +    early out if isoutside and not 'Qf'
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart is number of distance tests
    +
    +  notes:
    +    Always used for merged new facets (see qh_USEfindbestnew)
    +    Avoids upperdelaunay facet unless (isoutside and outside)
    +
    +    Uses qh.visit_id, qh.coplanarfacetset.
    +    If share visit_id with qh_findbest, coplanarfacetset is incorrect.
    +
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    a point maybe coplanar to the bestfacet, below its horizon facet,
    +    and above a horizon facet of a coplanar newfacet.  For example,
    +      rbox 1000 s Z1 G1e-13 | qhull
    +      rbox 1000 s W1e-13 P0 t992110337 | QHULL d Qbb Qc
    +
    +    qh_findbestnew() used if
    +       qh_sharpnewfacets -- newfacets contains a sharp angle
    +       if many merges, qh_premerge found a merge, or 'Qf' (qh.findbestnew)
    +
    +  see also:
    +    qh_partitionall() and qh_findbest()
    +
    +  design:
    +    for each new facet starting from startfacet
    +      test distance from point to facet
    +      return facet if clearly outside
    +      unless upperdelaunay and a lowerdelaunay exists
    +         update best facet
    +    test horizon facets
    +*/
    +facetT *qh_findbestnew(pointT *point, facetT *startfacet,
    +           realT *dist, boolT bestoutside, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2;
    +  facetT *bestfacet= NULL, *facet;
    +  int oldtrace= qh IStracing, i;
    +  unsigned int visitid= ++qh visit_id;
    +  realT distoutside= 0.0;
    +  boolT isdistoutside; /* True if distoutside is defined */
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  if (!startfacet) {
    +    if (qh MERGING)
    +      qh_fprintf(qh ferr, 6001, "qhull precision error (qh_findbestnew): merging has formed and deleted a cone of new facets.  Can not continue.\n");
    +    else
    +      qh_fprintf(qh ferr, 6002, "qhull internal error (qh_findbestnew): no new facets for point p%d\n",
    +              qh furthest_id);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  zinc_(Zfindnew);
    +  if (qh BESToutside || bestoutside)
    +    isdistoutside= False;
    +  else {
    +    isdistoutside= True;
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  *numpart= 0;
    +  if (qh IStracing >= 3 || (qh TRACElevel && qh TRACEpoint >= 0 && qh TRACEpoint == qh_pointid(point))) {
    +    if (qh TRACElevel > qh IStracing)
    +      qh IStracing= qh TRACElevel;
    +    qh_fprintf(qh ferr, 8008, "qh_findbestnew: point p%d facet f%d. Stop? %d if dist > %2.2g\n",
    +             qh_pointid(point), startfacet->id, isdistoutside, distoutside);
    +    qh_fprintf(qh ferr, 8009, "  Last point added p%d visitid %d.",  qh furthest_id, visitid);
    +    qh_fprintf(qh ferr, 8010, "  Last merge was #%d.\n", zzval_(Ztotmerge));
    +  }
    +  /* visit all new facets starting with startfacet, maybe qh facet_list */
    +  for (i=0, facet=startfacet; i < 2; i++, facet= qh newfacet_list) {
    +    FORALLfacet_(facet) {
    +      if (facet == startfacet && i)
    +        break;
    +      facet->visitid= visitid;
    +      if (!facet->flipped) {
    +        qh_distplane(point, facet, dist);
    +        (*numpart)++;
    +        if (*dist > bestdist) {
    +          if (!facet->upperdelaunay || *dist >= qh MINoutside) {
    +            bestfacet= facet;
    +            if (isdistoutside && *dist >= distoutside)
    +              goto LABELreturn_bestnew;
    +            bestdist= *dist;
    +          }
    +        }
    +      } /* end of !flipped */
    +    } /* FORALLfacet from startfacet or qh newfacet_list */
    +  }
    +  if (testhorizon || !bestfacet) /* testhorizon is always True.  Keep the same code as qh_findbest */
    +    bestfacet= qh_findbesthorizon(!qh_IScheckmax, point, bestfacet ? bestfacet : startfacet,
    +                                        !qh_NOupper, &bestdist, numpart);
    +  *dist= bestdist;
    +  if (isoutside && *dist < qh MINoutside)
    +    *isoutside= False;
    +LABELreturn_bestnew:
    +  zadd_(Zfindnewtot, *numpart);
    +  zmax_(Zfindnewmax, *numpart);
    +  trace4((qh ferr, 4004, "qh_findbestnew: bestfacet f%d bestdist %2.2g\n", getid_(bestfacet), *dist));
    +  qh IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbestnew */
    +
    +/* ============ hyperplane functions -- keep code together [?] ============ */
    +
    +/*---------------------------------
    +
    +  qh_backnormal( rows, numrow, numcol, sign, normal, nearzero )
    +    given an upper-triangular rows array and a sign,
    +    solve for normal equation x using back substitution over rows U
    +
    +  returns:
    +     normal= x
    +
    +     if will not be able to divzero() when normalized(qh.MINdenom_2 and qh.MINdenom_1_2),
    +       if fails on last row
    +         this means that the hyperplane intersects [0,..,1]
    +         sets last coordinate of normal to sign
    +       otherwise
    +         sets tail of normal to [...,sign,0,...], i.e., solves for b= [0...0]
    +         sets nearzero
    +
    +  notes:
    +     assumes numrow == numcol-1
    +
    +     see Golub & van Loan, 1983, Eq. 4.4-9 for "Gaussian elimination with complete pivoting"
    +
    +     solves Ux=b where Ax=b and PA=LU
    +     b= [0,...,0,sign or 0]  (sign is either -1 or +1)
    +     last row of A= [0,...,0,1]
    +
    +     1) Ly=Pb == y=b since P only permutes the 0's of   b
    +
    +  design:
    +    for each row from end
    +      perform back substitution
    +      if near zero
    +        use qh_divzero for division
    +        if zero divide and not last row
    +          set tail of normal to 0
    +*/
    +void qh_backnormal(realT **rows, int numrow, int numcol, boolT sign,
    +        coordT *normal, boolT *nearzero) {
    +  int i, j;
    +  coordT *normalp, *normal_tail, *ai, *ak;
    +  realT diagonal;
    +  boolT waszero;
    +  int zerocol= -1;
    +
    +  normalp= normal + numcol - 1;
    +  *normalp--= (sign ? -1.0 : 1.0);
    +  for (i=numrow; i--; ) {
    +    *normalp= 0.0;
    +    ai= rows[i] + i + 1;
    +    ak= normalp+1;
    +    for (j=i+1; j < numcol; j++)
    +      *normalp -= *ai++ * *ak++;
    +    diagonal= (rows[i])[i];
    +    if (fabs_(diagonal) > qh MINdenom_2)
    +      *(normalp--) /= diagonal;
    +    else {
    +      waszero= False;
    +      *normalp= qh_divzero(*normalp, diagonal, qh MINdenom_1_2, &waszero);
    +      if (waszero) {
    +        zerocol= i;
    +        *(normalp--)= (sign ? -1.0 : 1.0);
    +        for (normal_tail= normalp+2; normal_tail < normal + numcol; normal_tail++)
    +          *normal_tail= 0.0;
    +      }else
    +        normalp--;
    +    }
    +  }
    +  if (zerocol != -1) {
    +    zzinc_(Zback0);
    +    *nearzero= True;
    +    trace4((qh ferr, 4005, "qh_backnormal: zero diagonal at column %d.\n", i));
    +    qh_precision("zero diagonal on back substitution");
    +  }
    +} /* backnormal */
    +
    +/*---------------------------------
    +
    +  qh_gausselim( rows, numrow, numcol, sign )
    +    Gaussian elimination with partial pivoting
    +
    +  returns:
    +    rows is upper triangular (includes row exchanges)
    +    flips sign for each row exchange
    +    sets nearzero if pivot[k] < qh.NEARzero[k], else clears it
    +
    +  notes:
    +    if nearzero, the determinant's sign may be incorrect.
    +    assumes numrow <= numcol
    +
    +  design:
    +    for each row
    +      determine pivot and exchange rows if necessary
    +      test for near zero
    +      perform gaussian elimination step
    +*/
    +void qh_gausselim(realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero) {
    +  realT *ai, *ak, *rowp, *pivotrow;
    +  realT n, pivot, pivot_abs= 0.0, temp;
    +  int i, j, k, pivoti, flip=0;
    +
    +  *nearzero= False;
    +  for (k=0; k < numrow; k++) {
    +    pivot_abs= fabs_((rows[k])[k]);
    +    pivoti= k;
    +    for (i=k+1; i < numrow; i++) {
    +      if ((temp= fabs_((rows[i])[k])) > pivot_abs) {
    +        pivot_abs= temp;
    +        pivoti= i;
    +      }
    +    }
    +    if (pivoti != k) {
    +      rowp= rows[pivoti];
    +      rows[pivoti]= rows[k];
    +      rows[k]= rowp;
    +      *sign ^= 1;
    +      flip ^= 1;
    +    }
    +    if (pivot_abs <= qh NEARzero[k]) {
    +      *nearzero= True;
    +      if (pivot_abs == 0.0) {   /* remainder of column == 0 */
    +        if (qh IStracing >= 4) {
    +          qh_fprintf(qh ferr, 8011, "qh_gausselim: 0 pivot at column %d. (%2.2g < %2.2g)\n", k, pivot_abs, qh DISTround);
    +          qh_printmatrix(qh ferr, "Matrix:", rows, numrow, numcol);
    +        }
    +        zzinc_(Zgauss0);
    +        qh_precision("zero pivot for Gaussian elimination");
    +        goto LABELnextcol;
    +      }
    +    }
    +    pivotrow= rows[k] + k;
    +    pivot= *pivotrow++;  /* signed value of pivot, and remainder of row */
    +    for (i=k+1; i < numrow; i++) {
    +      ai= rows[i] + k;
    +      ak= pivotrow;
    +      n= (*ai++)/pivot;   /* divzero() not needed since |pivot| >= |*ai| */
    +      for (j= numcol - (k+1); j--; )
    +        *ai++ -= n * *ak++;
    +    }
    +  LABELnextcol:
    +    ;
    +  }
    +  wmin_(Wmindenom, pivot_abs);  /* last pivot element */
    +  if (qh IStracing >= 5)
    +    qh_printmatrix(qh ferr, "qh_gausselem: result", rows, numrow, numcol);
    +} /* gausselim */
    +
    +
    +/*---------------------------------
    +
    +  qh_getangle( vect1, vect2 )
    +    returns the dot product of two vectors
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    the angle may be > 1.0 or < -1.0 because of roundoff errors
    +
    +*/
    +realT qh_getangle(pointT *vect1, pointT *vect2) {
    +  realT angle= 0, randr;
    +  int k;
    +
    +  for (k=qh hull_dim; k--; )
    +    angle += *vect1++ * *vect2++;
    +  if (qh RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    angle += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh RANDOMfactor;
    +  }
    +  trace4((qh ferr, 4006, "qh_getangle: %2.2g\n", angle));
    +  return(angle);
    +} /* getangle */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcenter( vertices )
    +    returns arithmetic center of a set of vertices as a new point
    +
    +  notes:
    +    allocates point array for center
    +*/
    +pointT *qh_getcenter(setT *vertices) {
    +  int k;
    +  pointT *center, *coord;
    +  vertexT *vertex, **vertexp;
    +  int count= qh_setsize(vertices);
    +
    +  if (count < 2) {
    +    qh_fprintf(qh ferr, 6003, "qhull internal error (qh_getcenter): not defined for %d points\n", count);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  center= (pointT *)qh_memalloc(qh normal_size);
    +  for (k=0; k < qh hull_dim; k++) {
    +    coord= center+k;
    +    *coord= 0.0;
    +    FOREACHvertex_(vertices)
    +      *coord += vertex->point[k];
    +    *coord /= count;  /* count>=2 by QH6003 */
    +  }
    +  return(center);
    +} /* getcenter */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcentrum( facet )
    +    returns the centrum for a facet as a new point
    +
    +  notes:
    +    allocates the centrum
    +*/
    +pointT *qh_getcentrum(facetT *facet) {
    +  realT dist;
    +  pointT *centrum, *point;
    +
    +  point= qh_getcenter(facet->vertices);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(point, facet, &dist);
    +  centrum= qh_projectpoint(point, facet, dist);
    +  qh_memfree(point, qh normal_size);
    +  trace4((qh ferr, 4007, "qh_getcentrum: for f%d, %d vertices dist= %2.2g\n",
    +          facet->id, qh_setsize(facet->vertices), dist));
    +  return centrum;
    +} /* getcentrum */
    +
    +
    +/*---------------------------------
    +
    +  qh_getdistance( facet, neighbor, mindist, maxdist )
    +    returns the maxdist and mindist distance of any vertex from neighbor
    +
    +  returns:
    +    the max absolute value
    +
    +  design:
    +    for each vertex of facet that is not in neighbor
    +      test the distance from vertex to neighbor
    +*/
    +realT qh_getdistance(facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist) {
    +  vertexT *vertex, **vertexp;
    +  realT dist, maxd, mind;
    +
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHvertex_(neighbor->vertices)
    +    vertex->seen= True;
    +  mind= 0.0;
    +  maxd= 0.0;
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      zzinc_(Zbestdist);
    +      qh_distplane(vertex->point, neighbor, &dist);
    +      if (dist < mind)
    +        mind= dist;
    +      else if (dist > maxd)
    +        maxd= dist;
    +    }
    +  }
    +  *mindist= mind;
    +  *maxdist= maxd;
    +  mind= -mind;
    +  if (maxd > mind)
    +    return maxd;
    +  else
    +    return mind;
    +} /* getdistance */
    +
    +
    +/*---------------------------------
    +
    +  qh_normalize( normal, dim, toporient )
    +    normalize a vector and report if too small
    +    does not use min norm
    +
    +  see:
    +    qh_normalize2
    +*/
    +void qh_normalize(coordT *normal, int dim, boolT toporient) {
    +  qh_normalize2( normal, dim, toporient, NULL, NULL);
    +} /* normalize */
    +
    +/*---------------------------------
    +
    +  qh_normalize2( normal, dim, toporient, minnorm, ismin )
    +    normalize a vector and report if too small
    +    qh.MINdenom/MINdenom1 are the upper limits for divide overflow
    +
    +  returns:
    +    normalized vector
    +    flips sign if !toporient
    +    if minnorm non-NULL,
    +      sets ismin if normal < minnorm
    +
    +  notes:
    +    if zero norm
    +       sets all elements to sqrt(1.0/dim)
    +    if divide by zero (divzero())
    +       sets largest element to   +/-1
    +       bumps Znearlysingular
    +
    +  design:
    +    computes norm
    +    test for minnorm
    +    if not near zero
    +      normalizes normal
    +    else if zero norm
    +      sets normal to standard value
    +    else
    +      uses qh_divzero to normalize
    +      if nearzero
    +        sets norm to direction of maximum value
    +*/
    +void qh_normalize2(coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin) {
    +  int k;
    +  realT *colp, *maxp, norm= 0, temp, *norm1, *norm2, *norm3;
    +  boolT zerodiv;
    +
    +  norm1= normal+1;
    +  norm2= normal+2;
    +  norm3= normal+3;
    +  if (dim == 2)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1));
    +  else if (dim == 3)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2));
    +  else if (dim == 4) {
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3));
    +  }else if (dim > 4) {
    +    norm= (*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3);
    +    for (k=dim-4, colp=normal+4; k--; colp++)
    +      norm += (*colp) * (*colp);
    +    norm= sqrt(norm);
    +  }
    +  if (minnorm) {
    +    if (norm < *minnorm)
    +      *ismin= True;
    +    else
    +      *ismin= False;
    +  }
    +  wmin_(Wmindenom, norm);
    +  if (norm > qh MINdenom) {
    +    if (!toporient)
    +      norm= -norm;
    +    *normal /= norm;
    +    *norm1 /= norm;
    +    if (dim == 2)
    +      ; /* all done */
    +    else if (dim == 3)
    +      *norm2 /= norm;
    +    else if (dim == 4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +    }else if (dim >4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +      for (k=dim-4, colp=normal+4; k--; )
    +        *colp++ /= norm;
    +    }
    +  }else if (norm == 0.0) {
    +    temp= sqrt(1.0/dim);
    +    for (k=dim, colp=normal; k--; )
    +      *colp++ = temp;
    +  }else {
    +    if (!toporient)
    +      norm= -norm;
    +    for (k=dim, colp=normal; k--; colp++) { /* k used below */
    +      temp= qh_divzero(*colp, norm, qh MINdenom_1, &zerodiv);
    +      if (!zerodiv)
    +        *colp= temp;
    +      else {
    +        maxp= qh_maxabsval(normal, dim);
    +        temp= ((*maxp * norm >= 0.0) ? 1.0 : -1.0);
    +        for (k=dim, colp=normal; k--; colp++)
    +          *colp= 0.0;
    +        *maxp= temp;
    +        zzinc_(Znearlysingular);
    +        trace0((qh ferr, 1, "qh_normalize: norm=%2.2g too small during p%d\n",
    +               norm, qh furthest_id));
    +        return;
    +      }
    +    }
    +  }
    +} /* normalize */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoint( point, facet, dist )
    +    project point onto a facet by dist
    +
    +  returns:
    +    returns a new point
    +
    +  notes:
    +    if dist= distplane(point,facet)
    +      this projects point to hyperplane
    +    assumes qh_memfree_() is valid for normal_size
    +*/
    +pointT *qh_projectpoint(pointT *point, facetT *facet, realT dist) {
    +  pointT *newpoint, *np, *normal;
    +  int normsize= qh normal_size;
    +  int k;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(normsize, freelistp, newpoint, pointT);
    +  np= newpoint;
    +  normal= facet->normal;
    +  for (k=qh hull_dim; k--; )
    +    *(np++)= *point++ - dist * *normal++;
    +  return(newpoint);
    +} /* projectpoint */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfacetplane( facet )
    +    sets the hyperplane for a facet
    +    if qh.RANDOMdist, joggles hyperplane
    +
    +  notes:
    +    uses global buffers qh.gm_matrix and qh.gm_row
    +    overwrites facet->normal if already defined
    +    updates Wnewvertex if PRINTstatistics
    +    sets facet->upperdelaunay if upper envelope of Delaunay triangulation
    +
    +  design:
    +    copy vertex coordinates to qh.gm_matrix/gm_row
    +    compute determinate
    +    if nearzero
    +      recompute determinate with gaussian elimination
    +      if nearzero
    +        force outside orientation by testing interior point
    +*/
    +void qh_setfacetplane(facetT *facet) {
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int normsize= qh normal_size;
    +  int k,i, oldtrace= 0;
    +  realT dist;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +  coordT *coord, *gmcoord;
    +  pointT *point0= SETfirstt_(facet->vertices, vertexT)->point;
    +  boolT nearzero= False;
    +
    +  zzinc_(Zsetplane);
    +  if (!facet->normal)
    +    qh_memalloc_(normsize, freelistp, facet->normal, coordT);
    +  if (facet == qh tracefacet) {
    +    oldtrace= qh IStracing;
    +    qh IStracing= 5;
    +    qh_fprintf(qh ferr, 8012, "qh_setfacetplane: facet f%d created.\n", facet->id);
    +    qh_fprintf(qh ferr, 8013, "  Last point added to hull was p%d.", qh furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh ferr, 8014, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    qh_fprintf(qh ferr, 8015, "\n\nCurrent summary is:\n");
    +      qh_printsummary(qh ferr);
    +  }
    +  if (qh hull_dim <= 4) {
    +    i= 0;
    +    if (qh RANDOMdist) {
    +      gmcoord= qh gm_matrix;
    +      FOREACHvertex_(facet->vertices) {
    +        qh gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        for (k=qh hull_dim; k--; )
    +          *(gmcoord++)= *coord++ * qh_randomfactor(qh RANDOMa, qh RANDOMb);
    +      }
    +    }else {
    +      FOREACHvertex_(facet->vertices)
    +       qh gm_row[i++]= vertex->point;
    +    }
    +    qh_sethyperplane_det(qh hull_dim, qh gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +  }
    +  if (qh hull_dim > 4 || nearzero) {
    +    i= 0;
    +    gmcoord= qh gm_matrix;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        qh gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        point= point0;
    +        for (k=qh hull_dim; k--; )
    +          *(gmcoord++)= *coord++ - *point++;
    +      }
    +    }
    +    qh gm_row[i]= gmcoord;  /* for areasimplex */
    +    if (qh RANDOMdist) {
    +      gmcoord= qh gm_matrix;
    +      for (i=qh hull_dim-1; i--; ) {
    +        for (k=qh hull_dim; k--; )
    +          *(gmcoord++) *= qh_randomfactor(qh RANDOMa, qh RANDOMb);
    +      }
    +    }
    +    qh_sethyperplane_gauss(qh hull_dim, qh gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +    if (nearzero) {
    +      if (qh_orientoutside(facet)) {
    +        trace0((qh ferr, 2, "qh_setfacetplane: flipped orientation after testing interior_point during p%d\n", qh furthest_id));
    +      /* this is part of using Gaussian Elimination.  For example in 5-d
    +           1 1 1 1 0
    +           1 1 1 1 1
    +           0 0 0 1 0
    +           0 1 0 0 0
    +           1 0 0 0 0
    +           norm= 0.38 0.38 -0.76 0.38 0
    +         has a determinate of 1, but g.e. after subtracting pt. 0 has
    +         0's in the diagonal, even with full pivoting.  It does work
    +         if you subtract pt. 4 instead. */
    +      }
    +    }
    +  }
    +  facet->upperdelaunay= False;
    +  if (qh DELAUNAY) {
    +    if (qh UPPERdelaunay) {     /* matches qh_triangulate_facet and qh.lower_threshold in qh_initbuild */
    +      if (facet->normal[qh hull_dim -1] >= qh ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }else {
    +      if (facet->normal[qh hull_dim -1] > -qh ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }
    +  }
    +  if (qh PRINTstatistics || qh IStracing || qh TRACElevel || qh JOGGLEmax < REALmax) {
    +    qh old_randomdist= qh RANDOMdist;
    +    qh RANDOMdist= False;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        boolT istrace= False;
    +        zinc_(Zdiststat);
    +        qh_distplane(vertex->point, facet, &dist);
    +        dist= fabs_(dist);
    +        zinc_(Znewvertex);
    +        wadd_(Wnewvertex, dist);
    +        if (dist > wwval_(Wnewvertexmax)) {
    +          wwval_(Wnewvertexmax)= dist;
    +          if (dist > qh max_outside) {
    +            qh max_outside= dist;  /* used by qh_maxouter() */
    +            if (dist > qh TRACEdist)
    +              istrace= True;
    +          }
    +        }else if (-dist > qh TRACEdist)
    +          istrace= True;
    +        if (istrace) {
    +          qh_fprintf(qh ferr, 8016, "qh_setfacetplane: ====== vertex p%d(v%d) increases max_outside to %2.2g for new facet f%d last p%d\n",
    +                qh_pointid(vertex->point), vertex->id, dist, facet->id, qh furthest_id);
    +          qh_errprint("DISTANT", facet, NULL, NULL, NULL);
    +        }
    +      }
    +    }
    +    qh RANDOMdist= qh old_randomdist;
    +  }
    +  if (qh IStracing >= 3) {
    +    qh_fprintf(qh ferr, 8017, "qh_setfacetplane: f%d offset %2.2g normal: ",
    +             facet->id, facet->offset);
    +    for (k=0; k < qh hull_dim; k++)
    +      qh_fprintf(qh ferr, 8018, "%2.2g ", facet->normal[k]);
    +    qh_fprintf(qh ferr, 8019, "\n");
    +  }
    +  if (facet == qh tracefacet)
    +    qh IStracing= oldtrace;
    +} /* setfacetplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_det( dim, rows, point0, toporient, normal, offset, nearzero )
    +    given dim X dim array indexed by rows[], one row per point,
    +        toporient(flips all signs),
    +        and point0 (any row)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +    sets nearzero if hyperplane not through points
    +
    +  notes:
    +    only defined for dim == 2..4
    +    rows[] is not modified
    +    solves det(P-V_0, V_n-V_0, ..., V_1-V_0)=0, i.e. every point is on hyperplane
    +    see Bower & Woodworth, A programmer's geometry, Butterworths 1983.
    +
    +  derivation of 3-d minnorm
    +    Goal: all vertices V_i within qh.one_merge of hyperplane
    +    Plan: exactly translate the facet so that V_0 is the origin
    +          exactly rotate the facet so that V_1 is on the x-axis and y_2=0.
    +          exactly rotate the effective perturbation to only effect n_0
    +             this introduces a factor of sqrt(3)
    +    n_0 = ((y_2-y_0)*(z_1-z_0) - (z_2-z_0)*(y_1-y_0)) / norm
    +    Let M_d be the max coordinate difference
    +    Let M_a be the greater of M_d and the max abs. coordinate
    +    Let u be machine roundoff and distround be max error for distance computation
    +    The max error for n_0 is sqrt(3) u M_a M_d / norm.  n_1 is approx. 1 and n_2 is approx. 0
    +    The max error for distance of V_1 is sqrt(3) u M_a M_d M_d / norm.  Offset=0 at origin
    +    Then minnorm = 1.8 u M_a M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 45.5 u M_a and norm is usually about M_d M_d
    +
    +  derivation of 4-d minnorm
    +    same as above except rotate the facet so that V_1 on x-axis and w_2, y_3, w_3=0
    +     [if two vertices fixed on x-axis, can rotate the other two in yzw.]
    +    n_0 = det3_(...) = y_2 det2_(z_1, w_1, z_3, w_3) = - y_2 w_1 z_3
    +     [all other terms contain at least two factors nearly zero.]
    +    The max error for n_0 is sqrt(4) u M_a M_d M_d / norm
    +    Then minnorm = 2 u M_a M_d M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 82 u M_a and norm is usually about M_d M_d M_d
    +*/
    +void qh_sethyperplane_det(int dim, coordT **rows, coordT *point0,
    +          boolT toporient, coordT *normal, realT *offset, boolT *nearzero) {
    +  realT maxround, dist;
    +  int i;
    +  pointT *point;
    +
    +
    +  if (dim == 2) {
    +    normal[0]= dY(1,0);
    +    normal[1]= dX(0,1);
    +    qh_normalize2(normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0]+point0[1]*normal[1]);
    +    *nearzero= False;  /* since nearzero norm => incident points */
    +  }else if (dim == 3) {
    +    normal[0]= det2_(dY(2,0), dZ(2,0),
    +                     dY(1,0), dZ(1,0));
    +    normal[1]= det2_(dX(1,0), dZ(1,0),
    +                     dX(2,0), dZ(2,0));
    +    normal[2]= det2_(dX(2,0), dY(2,0),
    +                     dX(1,0), dY(1,0));
    +    qh_normalize2(normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2]);
    +    maxround= qh DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }else if (dim == 4) {
    +    normal[0]= - det3_(dY(2,0), dZ(2,0), dW(2,0),
    +                        dY(1,0), dZ(1,0), dW(1,0),
    +                        dY(3,0), dZ(3,0), dW(3,0));
    +    normal[1]=   det3_(dX(2,0), dZ(2,0), dW(2,0),
    +                        dX(1,0), dZ(1,0), dW(1,0),
    +                        dX(3,0), dZ(3,0), dW(3,0));
    +    normal[2]= - det3_(dX(2,0), dY(2,0), dW(2,0),
    +                        dX(1,0), dY(1,0), dW(1,0),
    +                        dX(3,0), dY(3,0), dW(3,0));
    +    normal[3]=   det3_(dX(2,0), dY(2,0), dZ(2,0),
    +                        dX(1,0), dY(1,0), dZ(1,0),
    +                        dX(3,0), dY(3,0), dZ(3,0));
    +    qh_normalize2(normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2] + point0[3]*normal[3]);
    +    maxround= qh DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2] + point[3]*normal[3]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  if (*nearzero) {
    +    zzinc_(Zminnorm);
    +    trace0((qh ferr, 3, "qh_sethyperplane_det: degenerate norm during p%d.\n", qh furthest_id));
    +    zzinc_(Znearlysingular);
    +  }
    +} /* sethyperplane_det */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_gauss( dim, rows, point0, toporient, normal, offset, nearzero )
    +    given(dim-1) X dim array of rows[i]= V_{i+1} - V_0 (point0)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +
    +  notes:
    +    if nearzero
    +      orientation may be incorrect because of incorrect sign flips in gausselim
    +    solves [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0 .. 0 1]
    +        or [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0]
    +    i.e., N is normal to the hyperplane, and the unnormalized
    +        distance to [0 .. 1] is either 1 or   0
    +
    +  design:
    +    perform gaussian elimination
    +    flip sign for negative values
    +    perform back substitution
    +    normalize result
    +    compute offset
    +*/
    +void qh_sethyperplane_gauss(int dim, coordT **rows, pointT *point0,
    +                boolT toporient, coordT *normal, coordT *offset, boolT *nearzero) {
    +  coordT *pointcoord, *normalcoef;
    +  int k;
    +  boolT sign= toporient, nearzero2= False;
    +
    +  qh_gausselim(rows, dim-1, dim, &sign, nearzero);
    +  for (k=dim-1; k--; ) {
    +    if ((rows[k])[k] < 0)
    +      sign ^= 1;
    +  }
    +  if (*nearzero) {
    +    zzinc_(Znearlysingular);
    +    trace0((qh ferr, 4, "qh_sethyperplane_gauss: nearly singular or axis parallel hyperplane during p%d.\n", qh furthest_id));
    +    qh_backnormal(rows, dim-1, dim, sign, normal, &nearzero2);
    +  }else {
    +    qh_backnormal(rows, dim-1, dim, sign, normal, &nearzero2);
    +    if (nearzero2) {
    +      zzinc_(Znearlysingular);
    +      trace0((qh ferr, 5, "qh_sethyperplane_gauss: singular or axis parallel hyperplane at normalization during p%d.\n", qh furthest_id));
    +    }
    +  }
    +  if (nearzero2)
    +    *nearzero= True;
    +  qh_normalize2(normal, dim, True, NULL, NULL);
    +  pointcoord= point0;
    +  normalcoef= normal;
    +  *offset= -(*pointcoord++ * *normalcoef++);
    +  for (k=dim-1; k--; )
    +    *offset -= *pointcoord++ * *normalcoef++;
    +} /* sethyperplane_gauss */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/geom.h b/xs/src/qhull/src/libqhull/geom.h
    new file mode 100644
    index 0000000000..16ef48d2d7
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/geom.h
    @@ -0,0 +1,176 @@
    +/*
      ---------------------------------
    +
    +  geom.h
    +    header file for geometric routines
    +
    +   see qh-geom.htm and geom.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/geom.h#1 $$Change: 1981 $
    +   $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFgeom
    +#define qhDEFgeom 1
    +
    +#include "libqhull.h"
    +
    +/* ============ -macros- ======================== */
    +
    +/*----------------------------------
    +
    +  fabs_(a)
    +    returns the absolute value of a
    +*/
    +#define fabs_( a ) ((( a ) < 0 ) ? -( a ):( a ))
    +
    +/*----------------------------------
    +
    +  fmax_(a,b)
    +    returns the maximum value of a and b
    +*/
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  fmin_(a,b)
    +    returns the minimum value of a and b
    +*/
    +#define fmin_( a,b )  ( ( a ) > ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  maximize_(maxval, val)
    +    set maxval to val if val is greater than maxval
    +*/
    +#define maximize_( maxval, val ) { if (( maxval ) < ( val )) ( maxval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  minimize_(minval, val)
    +    set minval to val if val is less than minval
    +*/
    +#define minimize_( minval, val ) { if (( minval ) > ( val )) ( minval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  det2_(a1, a2,
    +        b1, b2)
    +
    +    compute a 2-d determinate
    +*/
    +#define det2_( a1,a2,b1,b2 ) (( a1 )*( b2 ) - ( a2 )*( b1 ))
    +
    +/*----------------------------------
    +
    +  det3_(a1, a2, a3,
    +       b1, b2, b3,
    +       c1, c2, c3)
    +
    +    compute a 3-d determinate
    +*/
    +#define det3_( a1,a2,a3,b1,b2,b3,c1,c2,c3 ) ( ( a1 )*det2_( b2,b3,c2,c3 ) \
    +                - ( b1 )*det2_( a2,a3,c2,c3 ) + ( c1 )*det2_( a2,a3,b2,b3 ) )
    +
    +/*----------------------------------
    +
    +  dX( p1, p2 )
    +  dY( p1, p2 )
    +  dZ( p1, p2 )
    +
    +    given two indices into rows[],
    +
    +    compute the difference between X, Y, or Z coordinates
    +*/
    +#define dX( p1,p2 )  ( *( rows[p1] ) - *( rows[p2] ))
    +#define dY( p1,p2 )  ( *( rows[p1]+1 ) - *( rows[p2]+1 ))
    +#define dZ( p1,p2 )  ( *( rows[p1]+2 ) - *( rows[p2]+2 ))
    +#define dW( p1,p2 )  ( *( rows[p1]+3 ) - *( rows[p2]+3 ))
    +
    +/*============= prototypes in alphabetical order, infrequent at end ======= */
    +
    +void    qh_backnormal(realT **rows, int numrow, int numcol, boolT sign, coordT *normal, boolT *nearzero);
    +void    qh_distplane(pointT *point, facetT *facet, realT *dist);
    +facetT *qh_findbest(pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbesthorizon(boolT ischeckmax, pointT *point,
    +                     facetT *startfacet, boolT noupper, realT *bestdist, int *numpart);
    +facetT *qh_findbestnew(pointT *point, facetT *startfacet, realT *dist,
    +                     boolT bestoutside, boolT *isoutside, int *numpart);
    +void    qh_gausselim(realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero);
    +realT   qh_getangle(pointT *vect1, pointT *vect2);
    +pointT *qh_getcenter(setT *vertices);
    +pointT *qh_getcentrum(facetT *facet);
    +realT   qh_getdistance(facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist);
    +void    qh_normalize(coordT *normal, int dim, boolT toporient);
    +void    qh_normalize2(coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin);
    +pointT *qh_projectpoint(pointT *point, facetT *facet, realT dist);
    +
    +void    qh_setfacetplane(facetT *newfacets);
    +void    qh_sethyperplane_det(int dim, coordT **rows, coordT *point0,
    +              boolT toporient, coordT *normal, realT *offset, boolT *nearzero);
    +void    qh_sethyperplane_gauss(int dim, coordT **rows, pointT *point0,
    +             boolT toporient, coordT *normal, coordT *offset, boolT *nearzero);
    +boolT   qh_sharpnewfacets(void);
    +
    +/*========= infrequently used code in geom2.c =============*/
    +
    +coordT *qh_copypoints(coordT *points, int numpoints, int dimension);
    +void    qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]);
    +realT   qh_determinant(realT **rows, int dim, boolT *nearzero);
    +realT   qh_detjoggle(pointT *points, int numpoints, int dimension);
    +void    qh_detroundoff(void);
    +realT   qh_detsimplex(pointT *apex, setT *points, int dim, boolT *nearzero);
    +realT   qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp);
    +realT   qh_distround(int dimension, realT maxabs, realT maxsumabs);
    +realT   qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv);
    +realT   qh_facetarea(facetT *facet);
    +realT   qh_facetarea_simplex(int dim, coordT *apex, setT *vertices,
    +          vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset);
    +pointT *qh_facetcenter(setT *vertices);
    +facetT *qh_findgooddist(pointT *point, facetT *facetA, realT *distp, facetT **facetlist);
    +void    qh_getarea(facetT *facetlist);
    +boolT   qh_gram_schmidt(int dim, realT **rows);
    +boolT   qh_inthresholds(coordT *normal, realT *angle);
    +void    qh_joggleinput(void);
    +realT  *qh_maxabsval(realT *normal, int dim);
    +setT   *qh_maxmin(pointT *points, int numpoints, int dimension);
    +realT   qh_maxouter(void);
    +void    qh_maxsimplex(int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex);
    +realT   qh_minabsval(realT *normal, int dim);
    +int     qh_mindiff(realT *vecA, realT *vecB, int dim);
    +boolT   qh_orientoutside(facetT *facet);
    +void    qh_outerinner(facetT *facet, realT *outerplane, realT *innerplane);
    +coordT  qh_pointdist(pointT *point1, pointT *point2, int dim);
    +void    qh_printmatrix(FILE *fp, const char *string, realT **rows, int numrow, int numcol);
    +void    qh_printpoints(FILE *fp, const char *string, setT *points);
    +void    qh_projectinput(void);
    +void    qh_projectpoints(signed char *project, int n, realT *points,
    +             int numpoints, int dim, realT *newpoints, int newdim);
    +void    qh_rotateinput(realT **rows);
    +void    qh_rotatepoints(realT *points, int numpoints, int dim, realT **rows);
    +void    qh_scaleinput(void);
    +void    qh_scalelast(coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh);
    +void    qh_scalepoints(pointT *points, int numpoints, int dim,
    +                realT *newlows, realT *newhighs);
    +boolT   qh_sethalfspace(int dim, coordT *coords, coordT **nextp,
    +              coordT *normal, coordT *offset, coordT *feasible);
    +coordT *qh_sethalfspace_all(int dim, int count, coordT *halfspaces, pointT *feasible);
    +pointT *qh_voronoi_center(int dim, setT *points);
    +
    +#endif /* qhDEFgeom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/geom2.c b/xs/src/qhull/src/libqhull/geom2.c
    new file mode 100644
    index 0000000000..82ec4936ef
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/geom2.c
    @@ -0,0 +1,2094 @@
    +/*
      ---------------------------------
    +
    +
    +   geom2.c
    +   infrequently used geometric routines of qhull
    +
    +   see qh-geom.htm and geom.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/geom2.c#6 $$Change: 2065 $
    +   $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +
    +   frequently used code goes into geom.c
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*================== functions in alphabetic order ============*/
    +
    +/*---------------------------------
    +
    +  qh_copypoints( points, numpoints, dimension)
    +    return qh_malloc'd copy of points
    +  notes:
    +    qh_free the returned points to avoid a memory leak
    +*/
    +coordT *qh_copypoints(coordT *points, int numpoints, int dimension) {
    +  int size;
    +  coordT *newpoints;
    +
    +  size= numpoints * dimension * (int)sizeof(coordT);
    +  if (!(newpoints=(coordT*)qh_malloc((size_t)size))) {
    +    qh_fprintf(qh ferr, 6004, "qhull error: insufficient memory to copy %d points\n",
    +        numpoints);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  memcpy((char *)newpoints, (char *)points, (size_t)size); /* newpoints!=0 by QH6004 */
    +  return newpoints;
    +} /* copypoints */
    +
    +/*---------------------------------
    +
    +  qh_crossproduct( dim, vecA, vecB, vecC )
    +    crossproduct of 2 dim vectors
    +    C= A x B
    +
    +  notes:
    +    from Glasner, Graphics Gems I, p. 639
    +    only defined for dim==3
    +*/
    +void qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]){
    +
    +  if (dim == 3) {
    +    vecC[0]=   det2_(vecA[1], vecA[2],
    +                     vecB[1], vecB[2]);
    +    vecC[1]= - det2_(vecA[0], vecA[2],
    +                     vecB[0], vecB[2]);
    +    vecC[2]=   det2_(vecA[0], vecA[1],
    +                     vecB[0], vecB[1]);
    +  }
    +} /* vcross */
    +
    +/*---------------------------------
    +
    +  qh_determinant( rows, dim, nearzero )
    +    compute signed determinant of a square matrix
    +    uses qh.NEARzero to test for degenerate matrices
    +
    +  returns:
    +    determinant
    +    overwrites rows and the matrix
    +    if dim == 2 or 3
    +      nearzero iff determinant < qh NEARzero[dim-1]
    +      (!quite correct, not critical)
    +    if dim >= 4
    +      nearzero iff diagonal[k] < qh NEARzero[k]
    +*/
    +realT qh_determinant(realT **rows, int dim, boolT *nearzero) {
    +  realT det=0;
    +  int i;
    +  boolT sign= False;
    +
    +  *nearzero= False;
    +  if (dim < 2) {
    +    qh_fprintf(qh ferr, 6005, "qhull internal error (qh_determinate): only implemented for dimension >= 2\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }else if (dim == 2) {
    +    det= det2_(rows[0][0], rows[0][1],
    +                 rows[1][0], rows[1][1]);
    +    if (fabs_(det) < 10*qh NEARzero[1])  /* not really correct, what should this be? */
    +      *nearzero= True;
    +  }else if (dim == 3) {
    +    det= det3_(rows[0][0], rows[0][1], rows[0][2],
    +                 rows[1][0], rows[1][1], rows[1][2],
    +                 rows[2][0], rows[2][1], rows[2][2]);
    +    if (fabs_(det) < 10*qh NEARzero[2])  /* what should this be?  det 5.5e-12 was flat for qh_maxsimplex of qdelaunay 0,0 27,27 -36,36 -9,63  */
    +      *nearzero= True;
    +  }else {
    +    qh_gausselim(rows, dim, dim, &sign, nearzero);  /* if nearzero, diagonal still ok*/
    +    det= 1.0;
    +    for (i=dim; i--; )
    +      det *= (rows[i])[i];
    +    if (sign)
    +      det= -det;
    +  }
    +  return det;
    +} /* determinant */
    +
    +/*---------------------------------
    +
    +  qh_detjoggle( points, numpoints, dimension )
    +    determine default max joggle for point array
    +      as qh_distround * qh_JOGGLEdefault
    +
    +  returns:
    +    initial value for JOGGLEmax from points and REALepsilon
    +
    +  notes:
    +    computes DISTround since qh_maxmin not called yet
    +    if qh SCALElast, last dimension will be scaled later to MAXwidth
    +
    +    loop duplicated from qh_maxmin
    +*/
    +realT qh_detjoggle(pointT *points, int numpoints, int dimension) {
    +  realT abscoord, distround, joggle, maxcoord, mincoord;
    +  pointT *point, *pointtemp;
    +  realT maxabs= -REALmax;
    +  realT sumabs= 0;
    +  realT maxwidth= 0;
    +  int k;
    +
    +  for (k=0; k < dimension; k++) {
    +    if (qh SCALElast && k == dimension-1)
    +      abscoord= maxwidth;
    +    else if (qh DELAUNAY && k == dimension-1) /* will qh_setdelaunay() */
    +      abscoord= 2 * maxabs * maxabs;  /* may be low by qh hull_dim/2 */
    +    else {
    +      maxcoord= -REALmax;
    +      mincoord= REALmax;
    +      FORALLpoint_(points, numpoints) {
    +        maximize_(maxcoord, point[k]);
    +        minimize_(mincoord, point[k]);
    +      }
    +      maximize_(maxwidth, maxcoord-mincoord);
    +      abscoord= fmax_(maxcoord, -mincoord);
    +    }
    +    sumabs += abscoord;
    +    maximize_(maxabs, abscoord);
    +  } /* for k */
    +  distround= qh_distround(qh hull_dim, maxabs, sumabs);
    +  joggle= distround * qh_JOGGLEdefault;
    +  maximize_(joggle, REALepsilon * qh_JOGGLEdefault);
    +  trace2((qh ferr, 2001, "qh_detjoggle: joggle=%2.2g maxwidth=%2.2g\n", joggle, maxwidth));
    +  return joggle;
    +} /* detjoggle */
    +
    +/*---------------------------------
    +
    +  qh_detroundoff()
    +    determine maximum roundoff errors from
    +      REALepsilon, REALmax, REALmin, qh.hull_dim, qh.MAXabs_coord,
    +      qh.MAXsumcoord, qh.MAXwidth, qh.MINdenom_1
    +
    +    accounts for qh.SETroundoff, qh.RANDOMdist, qh MERGEexact
    +      qh.premerge_cos, qh.postmerge_cos, qh.premerge_centrum,
    +      qh.postmerge_centrum, qh.MINoutside,
    +      qh_RATIOnearinside, qh_COPLANARratio, qh_WIDEcoplanar
    +
    +  returns:
    +    sets qh.DISTround, etc. (see below)
    +    appends precision constants to qh.qhull_options
    +
    +  see:
    +    qh_maxmin() for qh.NEARzero
    +
    +  design:
    +    determine qh.DISTround for distance computations
    +    determine minimum denominators for qh_divzero
    +    determine qh.ANGLEround for angle computations
    +    adjust qh.premerge_cos,... for roundoff error
    +    determine qh.ONEmerge for maximum error due to a single merge
    +    determine qh.NEARinside, qh.MAXcoplanar, qh.MINvisible,
    +      qh.MINoutside, qh.WIDEfacet
    +    initialize qh.max_vertex and qh.minvertex
    +*/
    +void qh_detroundoff(void) {
    +
    +  qh_option("_max-width", NULL, &qh MAXwidth);
    +  if (!qh SETroundoff) {
    +    qh DISTround= qh_distround(qh hull_dim, qh MAXabs_coord, qh MAXsumcoord);
    +    if (qh RANDOMdist)
    +      qh DISTround += qh RANDOMfactor * qh MAXabs_coord;
    +    qh_option("Error-roundoff", NULL, &qh DISTround);
    +  }
    +  qh MINdenom= qh MINdenom_1 * qh MAXabs_coord;
    +  qh MINdenom_1_2= sqrt(qh MINdenom_1 * qh hull_dim) ;  /* if will be normalized */
    +  qh MINdenom_2= qh MINdenom_1_2 * qh MAXabs_coord;
    +                                              /* for inner product */
    +  qh ANGLEround= 1.01 * qh hull_dim * REALepsilon;
    +  if (qh RANDOMdist)
    +    qh ANGLEround += qh RANDOMfactor;
    +  if (qh premerge_cos < REALmax/2) {
    +    qh premerge_cos -= qh ANGLEround;
    +    if (qh RANDOMdist)
    +      qh_option("Angle-premerge-with-random", NULL, &qh premerge_cos);
    +  }
    +  if (qh postmerge_cos < REALmax/2) {
    +    qh postmerge_cos -= qh ANGLEround;
    +    if (qh RANDOMdist)
    +      qh_option("Angle-postmerge-with-random", NULL, &qh postmerge_cos);
    +  }
    +  qh premerge_centrum += 2 * qh DISTround;    /*2 for centrum and distplane()*/
    +  qh postmerge_centrum += 2 * qh DISTround;
    +  if (qh RANDOMdist && (qh MERGEexact || qh PREmerge))
    +    qh_option("Centrum-premerge-with-random", NULL, &qh premerge_centrum);
    +  if (qh RANDOMdist && qh POSTmerge)
    +    qh_option("Centrum-postmerge-with-random", NULL, &qh postmerge_centrum);
    +  { /* compute ONEmerge, max vertex offset for merging simplicial facets */
    +    realT maxangle= 1.0, maxrho;
    +
    +    minimize_(maxangle, qh premerge_cos);
    +    minimize_(maxangle, qh postmerge_cos);
    +    /* max diameter * sin theta + DISTround for vertex to its hyperplane */
    +    qh ONEmerge= sqrt((realT)qh hull_dim) * qh MAXwidth *
    +      sqrt(1.0 - maxangle * maxangle) + qh DISTround;
    +    maxrho= qh hull_dim * qh premerge_centrum + qh DISTround;
    +    maximize_(qh ONEmerge, maxrho);
    +    maxrho= qh hull_dim * qh postmerge_centrum + qh DISTround;
    +    maximize_(qh ONEmerge, maxrho);
    +    if (qh MERGING)
    +      qh_option("_one-merge", NULL, &qh ONEmerge);
    +  }
    +  qh NEARinside= qh ONEmerge * qh_RATIOnearinside; /* only used if qh KEEPnearinside */
    +  if (qh JOGGLEmax < REALmax/2 && (qh KEEPcoplanar || qh KEEPinside)) {
    +    realT maxdist;             /* adjust qh.NEARinside for joggle */
    +    qh KEEPnearinside= True;
    +    maxdist= sqrt((realT)qh hull_dim) * qh JOGGLEmax + qh DISTround;
    +    maxdist= 2*maxdist;        /* vertex and coplanar point can joggle in opposite directions */
    +    maximize_(qh NEARinside, maxdist);  /* must agree with qh_nearcoplanar() */
    +  }
    +  if (qh KEEPnearinside)
    +    qh_option("_near-inside", NULL, &qh NEARinside);
    +  if (qh JOGGLEmax < qh DISTround) {
    +    qh_fprintf(qh ferr, 6006, "qhull error: the joggle for 'QJn', %.2g, is below roundoff for distance computations, %.2g\n",
    +         qh JOGGLEmax, qh DISTround);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh MINvisible > REALmax/2) {
    +    if (!qh MERGING)
    +      qh MINvisible= qh DISTround;
    +    else if (qh hull_dim <= 3)
    +      qh MINvisible= qh premerge_centrum;
    +    else
    +      qh MINvisible= qh_COPLANARratio * qh premerge_centrum;
    +    if (qh APPROXhull && qh MINvisible > qh MINoutside)
    +      qh MINvisible= qh MINoutside;
    +    qh_option("Visible-distance", NULL, &qh MINvisible);
    +  }
    +  if (qh MAXcoplanar > REALmax/2) {
    +    qh MAXcoplanar= qh MINvisible;
    +    qh_option("U-coplanar-distance", NULL, &qh MAXcoplanar);
    +  }
    +  if (!qh APPROXhull) {             /* user may specify qh MINoutside */
    +    qh MINoutside= 2 * qh MINvisible;
    +    if (qh premerge_cos < REALmax/2)
    +      maximize_(qh MINoutside, (1- qh premerge_cos) * qh MAXabs_coord);
    +    qh_option("Width-outside", NULL, &qh MINoutside);
    +  }
    +  qh WIDEfacet= qh MINoutside;
    +  maximize_(qh WIDEfacet, qh_WIDEcoplanar * qh MAXcoplanar);
    +  maximize_(qh WIDEfacet, qh_WIDEcoplanar * qh MINvisible);
    +  qh_option("_wide-facet", NULL, &qh WIDEfacet);
    +  if (qh MINvisible > qh MINoutside + 3 * REALepsilon
    +  && !qh BESToutside && !qh FORCEoutput)
    +    qh_fprintf(qh ferr, 7001, "qhull input warning: minimum visibility V%.2g is greater than \nminimum outside W%.2g.  Flipped facets are likely.\n",
    +             qh MINvisible, qh MINoutside);
    +  qh max_vertex= qh DISTround;
    +  qh min_vertex= -qh DISTround;
    +  /* numeric constants reported in printsummary */
    +} /* detroundoff */
    +
    +/*---------------------------------
    +
    +  qh_detsimplex( apex, points, dim, nearzero )
    +    compute determinant of a simplex with point apex and base points
    +
    +  returns:
    +     signed determinant and nearzero from qh_determinant
    +
    +  notes:
    +     uses qh.gm_matrix/qh.gm_row (assumes they're big enough)
    +
    +  design:
    +    construct qm_matrix by subtracting apex from points
    +    compute determinate
    +*/
    +realT qh_detsimplex(pointT *apex, setT *points, int dim, boolT *nearzero) {
    +  pointT *coorda, *coordp, *gmcoord, *point, **pointp;
    +  coordT **rows;
    +  int k,  i=0;
    +  realT det;
    +
    +  zinc_(Zdetsimplex);
    +  gmcoord= qh gm_matrix;
    +  rows= qh gm_row;
    +  FOREACHpoint_(points) {
    +    if (i == dim)
    +      break;
    +    rows[i++]= gmcoord;
    +    coordp= point;
    +    coorda= apex;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *coordp++ - *coorda++;
    +  }
    +  if (i < dim) {
    +    qh_fprintf(qh ferr, 6007, "qhull internal error (qh_detsimplex): #points %d < dimension %d\n",
    +               i, dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  det= qh_determinant(rows, dim, nearzero);
    +  trace2((qh ferr, 2002, "qh_detsimplex: det=%2.2g for point p%d, dim %d, nearzero? %d\n",
    +          det, qh_pointid(apex), dim, *nearzero));
    +  return det;
    +} /* detsimplex */
    +
    +/*---------------------------------
    +
    +  qh_distnorm( dim, point, normal, offset )
    +    return distance from point to hyperplane at normal/offset
    +
    +  returns:
    +    dist
    +
    +  notes:
    +    dist > 0 if point is outside of hyperplane
    +
    +  see:
    +    qh_distplane in geom.c
    +*/
    +realT qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp) {
    +  coordT *normalp= normal, *coordp= point;
    +  realT dist;
    +  int k;
    +
    +  dist= *offsetp;
    +  for (k=dim; k--; )
    +    dist += *(coordp++) * *(normalp++);
    +  return dist;
    +} /* distnorm */
    +
    +/*---------------------------------
    +
    +  qh_distround(dimension, maxabs, maxsumabs )
    +    compute maximum round-off error for a distance computation
    +      to a normalized hyperplane
    +    maxabs is the maximum absolute value of a coordinate
    +    maxsumabs is the maximum possible sum of absolute coordinate values
    +
    +  returns:
    +    max dist round for REALepsilon
    +
    +  notes:
    +    calculate roundoff error according to Golub & van Loan, 1983, Lemma 3.2-1, "Rounding Errors"
    +    use sqrt(dim) since one vector is normalized
    +      or use maxsumabs since one vector is < 1
    +*/
    +realT qh_distround(int dimension, realT maxabs, realT maxsumabs) {
    +  realT maxdistsum, maxround;
    +
    +  maxdistsum= sqrt((realT)dimension) * maxabs;
    +  minimize_( maxdistsum, maxsumabs);
    +  maxround= REALepsilon * (dimension * maxdistsum * 1.01 + maxabs);
    +              /* adds maxabs for offset */
    +  trace4((qh ferr, 4008, "qh_distround: %2.2g maxabs %2.2g maxsumabs %2.2g maxdistsum %2.2g\n",
    +                 maxround, maxabs, maxsumabs, maxdistsum));
    +  return maxround;
    +} /* distround */
    +
    +/*---------------------------------
    +
    +  qh_divzero( numer, denom, mindenom1, zerodiv )
    +    divide by a number that's nearly zero
    +    mindenom1= minimum denominator for dividing into 1.0
    +
    +  returns:
    +    quotient
    +    sets zerodiv and returns 0.0 if it would overflow
    +
    +  design:
    +    if numer is nearly zero and abs(numer) < abs(denom)
    +      return numer/denom
    +    else if numer is nearly zero
    +      return 0 and zerodiv
    +    else if denom/numer non-zero
    +      return numer/denom
    +    else
    +      return 0 and zerodiv
    +*/
    +realT qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv) {
    +  realT temp, numerx, denomx;
    +
    +
    +  if (numer < mindenom1 && numer > -mindenom1) {
    +    numerx= fabs_(numer);
    +    denomx= fabs_(denom);
    +    if (numerx < denomx) {
    +      *zerodiv= False;
    +      return numer/denom;
    +    }else {
    +      *zerodiv= True;
    +      return 0.0;
    +    }
    +  }
    +  temp= denom/numer;
    +  if (temp > mindenom1 || temp < -mindenom1) {
    +    *zerodiv= False;
    +    return numer/denom;
    +  }else {
    +    *zerodiv= True;
    +    return 0.0;
    +  }
    +} /* divzero */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetarea( facet )
    +    return area for a facet
    +
    +  notes:
    +    if non-simplicial,
    +      uses centrum to triangulate facet and sums the projected areas.
    +    if (qh DELAUNAY),
    +      computes projected area instead for last coordinate
    +    assumes facet->normal exists
    +    projecting tricoplanar facets to the hyperplane does not appear to make a difference
    +
    +  design:
    +    if simplicial
    +      compute area
    +    else
    +      for each ridge
    +        compute area from centrum to ridge
    +    negate area if upper Delaunay facet
    +*/
    +realT qh_facetarea(facetT *facet) {
    +  vertexT *apex;
    +  pointT *centrum;
    +  realT area= 0.0;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->simplicial) {
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    area= qh_facetarea_simplex(qh hull_dim, apex->point, facet->vertices,
    +                    apex, facet->toporient, facet->normal, &facet->offset);
    +  }else {
    +    if (qh CENTERtype == qh_AScentrum)
    +      centrum= facet->center;
    +    else
    +      centrum= qh_getcentrum(facet);
    +    FOREACHridge_(facet->ridges)
    +      area += qh_facetarea_simplex(qh hull_dim, centrum, ridge->vertices,
    +                 NULL, (boolT)(ridge->top == facet),  facet->normal, &facet->offset);
    +    if (qh CENTERtype != qh_AScentrum)
    +      qh_memfree(centrum, qh normal_size);
    +  }
    +  if (facet->upperdelaunay && qh DELAUNAY)
    +    area= -area;  /* the normal should be [0,...,1] */
    +  trace4((qh ferr, 4009, "qh_facetarea: f%d area %2.2g\n", facet->id, area));
    +  return area;
    +} /* facetarea */
    +
    +/*---------------------------------
    +
    +  qh_facetarea_simplex( dim, apex, vertices, notvertex, toporient, normal, offset )
    +    return area for a simplex defined by
    +      an apex, a base of vertices, an orientation, and a unit normal
    +    if simplicial or tricoplanar facet,
    +      notvertex is defined and it is skipped in vertices
    +
    +  returns:
    +    computes area of simplex projected to plane [normal,offset]
    +    returns 0 if vertex too far below plane (qh WIDEfacet)
    +      vertex can't be apex of tricoplanar facet
    +
    +  notes:
    +    if (qh DELAUNAY),
    +      computes projected area instead for last coordinate
    +    uses qh gm_matrix/gm_row and qh hull_dim
    +    helper function for qh_facetarea
    +
    +  design:
    +    if Notvertex
    +      translate simplex to apex
    +    else
    +      project simplex to normal/offset
    +      translate simplex to apex
    +    if Delaunay
    +      set last row/column to 0 with -1 on diagonal
    +    else
    +      set last row to Normal
    +    compute determinate
    +    scale and flip sign for area
    +*/
    +realT qh_facetarea_simplex(int dim, coordT *apex, setT *vertices,
    +        vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset) {
    +  pointT *coorda, *coordp, *gmcoord;
    +  coordT **rows, *normalp;
    +  int k,  i=0;
    +  realT area, dist;
    +  vertexT *vertex, **vertexp;
    +  boolT nearzero;
    +
    +  gmcoord= qh gm_matrix;
    +  rows= qh gm_row;
    +  FOREACHvertex_(vertices) {
    +    if (vertex == notvertex)
    +      continue;
    +    rows[i++]= gmcoord;
    +    coorda= apex;
    +    coordp= vertex->point;
    +    normalp= normal;
    +    if (notvertex) {
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *coordp++ - *coorda++;
    +    }else {
    +      dist= *offset;
    +      for (k=dim; k--; )
    +        dist += *coordp++ * *normalp++;
    +      if (dist < -qh WIDEfacet) {
    +        zinc_(Znoarea);
    +        return 0.0;
    +      }
    +      coordp= vertex->point;
    +      normalp= normal;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= (*coordp++ - dist * *normalp++) - *coorda++;
    +    }
    +  }
    +  if (i != dim-1) {
    +    qh_fprintf(qh ferr, 6008, "qhull internal error (qh_facetarea_simplex): #points %d != dim %d -1\n",
    +               i, dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  rows[i]= gmcoord;
    +  if (qh DELAUNAY) {
    +    for (i=0; i < dim-1; i++)
    +      rows[i][dim-1]= 0.0;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= 0.0;
    +    rows[dim-1][dim-1]= -1.0;
    +  }else {
    +    normalp= normal;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *normalp++;
    +  }
    +  zinc_(Zdetsimplex);
    +  area= qh_determinant(rows, dim, &nearzero);
    +  if (toporient)
    +    area= -area;
    +  area *= qh AREAfactor;
    +  trace4((qh ferr, 4010, "qh_facetarea_simplex: area=%2.2g for point p%d, toporient %d, nearzero? %d\n",
    +          area, qh_pointid(apex), toporient, nearzero));
    +  return area;
    +} /* facetarea_simplex */
    +
    +/*---------------------------------
    +
    +  qh_facetcenter( vertices )
    +    return Voronoi center (Voronoi vertex) for a facet's vertices
    +
    +  returns:
    +    return temporary point equal to the center
    +
    +  see:
    +    qh_voronoi_center()
    +*/
    +pointT *qh_facetcenter(setT *vertices) {
    +  setT *points= qh_settemp(qh_setsize(vertices));
    +  vertexT *vertex, **vertexp;
    +  pointT *center;
    +
    +  FOREACHvertex_(vertices)
    +    qh_setappend(&points, vertex->point);
    +  center= qh_voronoi_center(qh hull_dim-1, points);
    +  qh_settempfree(&points);
    +  return center;
    +} /* facetcenter */
    +
    +/*---------------------------------
    +
    +  qh_findgooddist( point, facetA, dist, facetlist )
    +    find best good facet visible for point from facetA
    +    assumes facetA is visible from point
    +
    +  returns:
    +    best facet, i.e., good facet that is furthest from point
    +      distance to best facet
    +      NULL if none
    +
    +    moves good, visible facets (and some other visible facets)
    +      to end of qh facet_list
    +
    +  notes:
    +    uses qh visit_id
    +
    +  design:
    +    initialize bestfacet if facetA is good
    +    move facetA to end of facetlist
    +    for each facet on facetlist
    +      for each unvisited neighbor of facet
    +        move visible neighbors to end of facetlist
    +        update best good neighbor
    +        if no good neighbors, update best facet
    +*/
    +facetT *qh_findgooddist(pointT *point, facetT *facetA, realT *distp,
    +               facetT **facetlist) {
    +  realT bestdist= -REALmax, dist;
    +  facetT *neighbor, **neighborp, *bestfacet=NULL, *facet;
    +  boolT goodseen= False;
    +
    +  if (facetA->good) {
    +    zzinc_(Zcheckpart);  /* calls from check_bestdist occur after print stats */
    +    qh_distplane(point, facetA, &bestdist);
    +    bestfacet= facetA;
    +    goodseen= True;
    +  }
    +  qh_removefacet(facetA);
    +  qh_appendfacet(facetA);
    +  *facetlist= facetA;
    +  facetA->visitid= ++qh visit_id;
    +  FORALLfacet_(*facetlist) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh visit_id)
    +        continue;
    +      neighbor->visitid= qh visit_id;
    +      if (goodseen && !neighbor->good)
    +        continue;
    +      zzinc_(Zcheckpart);
    +      qh_distplane(point, neighbor, &dist);
    +      if (dist > 0) {
    +        qh_removefacet(neighbor);
    +        qh_appendfacet(neighbor);
    +        if (neighbor->good) {
    +          goodseen= True;
    +          if (dist > bestdist) {
    +            bestdist= dist;
    +            bestfacet= neighbor;
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    *distp= bestdist;
    +    trace2((qh ferr, 2003, "qh_findgooddist: p%d is %2.2g above good facet f%d\n",
    +      qh_pointid(point), bestdist, bestfacet->id));
    +    return bestfacet;
    +  }
    +  trace4((qh ferr, 4011, "qh_findgooddist: no good facet for p%d above f%d\n",
    +      qh_pointid(point), facetA->id));
    +  return NULL;
    +}  /* findgooddist */
    +
    +/*---------------------------------
    +
    +  qh_getarea( facetlist )
    +    set area of all facets in facetlist
    +    collect statistics
    +    nop if hasAreaVolume
    +
    +  returns:
    +    sets qh totarea/totvol to total area and volume of convex hull
    +    for Delaunay triangulation, computes projected area of the lower or upper hull
    +      ignores upper hull if qh ATinfinity
    +
    +  notes:
    +    could compute outer volume by expanding facet area by rays from interior
    +    the following attempt at perpendicular projection underestimated badly:
    +      qh.totoutvol += (-dist + facet->maxoutside + qh DISTround)
    +                            * area/ qh hull_dim;
    +  design:
    +    for each facet on facetlist
    +      compute facet->area
    +      update qh.totarea and qh.totvol
    +*/
    +void qh_getarea(facetT *facetlist) {
    +  realT area;
    +  realT dist;
    +  facetT *facet;
    +
    +  if (qh hasAreaVolume)
    +    return;
    +  if (qh REPORTfreq)
    +    qh_fprintf(qh ferr, 8020, "computing area of each facet and volume of the convex hull\n");
    +  else
    +    trace1((qh ferr, 1001, "qh_getarea: computing volume and area for each facet\n"));
    +  qh totarea= qh totvol= 0.0;
    +  FORALLfacet_(facetlist) {
    +    if (!facet->normal)
    +      continue;
    +    if (facet->upperdelaunay && qh ATinfinity)
    +      continue;
    +    if (!facet->isarea) {
    +      facet->f.area= qh_facetarea(facet);
    +      facet->isarea= True;
    +    }
    +    area= facet->f.area;
    +    if (qh DELAUNAY) {
    +      if (facet->upperdelaunay == qh UPPERdelaunay)
    +        qh totarea += area;
    +    }else {
    +      qh totarea += area;
    +      qh_distplane(qh interior_point, facet, &dist);
    +      qh totvol += -dist * area/ qh hull_dim;
    +    }
    +    if (qh PRINTstatistics) {
    +      wadd_(Wareatot, area);
    +      wmax_(Wareamax, area);
    +      wmin_(Wareamin, area);
    +    }
    +  }
    +  qh hasAreaVolume= True;
    +} /* getarea */
    +
    +/*---------------------------------
    +
    +  qh_gram_schmidt( dim, row )
    +    implements Gram-Schmidt orthogonalization by rows
    +
    +  returns:
    +    false if zero norm
    +    overwrites rows[dim][dim]
    +
    +  notes:
    +    see Golub & van Loan, 1983, Algorithm 6.2-2, "Modified Gram-Schmidt"
    +    overflow due to small divisors not handled
    +
    +  design:
    +    for each row
    +      compute norm for row
    +      if non-zero, normalize row
    +      for each remaining rowA
    +        compute inner product of row and rowA
    +        reduce rowA by row * inner product
    +*/
    +boolT qh_gram_schmidt(int dim, realT **row) {
    +  realT *rowi, *rowj, norm;
    +  int i, j, k;
    +
    +  for (i=0; i < dim; i++) {
    +    rowi= row[i];
    +    for (norm= 0.0, k= dim; k--; rowi++)
    +      norm += *rowi * *rowi;
    +    norm= sqrt(norm);
    +    wmin_(Wmindenom, norm);
    +    if (norm == 0.0)  /* either 0 or overflow due to sqrt */
    +      return False;
    +    for (k=dim; k--; )
    +      *(--rowi) /= norm;
    +    for (j=i+1; j < dim; j++) {
    +      rowj= row[j];
    +      for (norm= 0.0, k=dim; k--; )
    +        norm += *rowi++ * *rowj++;
    +      for (k=dim; k--; )
    +        *(--rowj) -= *(--rowi) * norm;
    +    }
    +  }
    +  return True;
    +} /* gram_schmidt */
    +
    +
    +/*---------------------------------
    +
    +  qh_inthresholds( normal, angle )
    +    return True if normal within qh.lower_/upper_threshold
    +
    +  returns:
    +    estimate of angle by summing of threshold diffs
    +      angle may be NULL
    +      smaller "angle" is better
    +
    +  notes:
    +    invalid if qh.SPLITthresholds
    +
    +  see:
    +    qh.lower_threshold in qh_initbuild()
    +    qh_initthresholds()
    +
    +  design:
    +    for each dimension
    +      test threshold
    +*/
    +boolT qh_inthresholds(coordT *normal, realT *angle) {
    +  boolT within= True;
    +  int k;
    +  realT threshold;
    +
    +  if (angle)
    +    *angle= 0.0;
    +  for (k=0; k < qh hull_dim; k++) {
    +    threshold= qh lower_threshold[k];
    +    if (threshold > -REALmax/2) {
    +      if (normal[k] < threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +    if (qh upper_threshold[k] < REALmax/2) {
    +      threshold= qh upper_threshold[k];
    +      if (normal[k] > threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +  }
    +  return within;
    +} /* inthresholds */
    +
    +
    +/*---------------------------------
    +
    +  qh_joggleinput()
    +    randomly joggle input to Qhull by qh.JOGGLEmax
    +    initial input is qh.first_point/qh.num_points of qh.hull_dim
    +      repeated calls use qh.input_points/qh.num_points
    +
    +  returns:
    +    joggles points at qh.first_point/qh.num_points
    +    copies data to qh.input_points/qh.input_malloc if first time
    +    determines qh.JOGGLEmax if it was zero
    +    if qh.DELAUNAY
    +      computes the Delaunay projection of the joggled points
    +
    +  notes:
    +    if qh.DELAUNAY, unnecessarily joggles the last coordinate
    +    the initial 'QJn' may be set larger than qh_JOGGLEmaxincrease
    +
    +  design:
    +    if qh.DELAUNAY
    +      set qh.SCALElast for reduced precision errors
    +    if first call
    +      initialize qh.input_points to the original input points
    +      if qh.JOGGLEmax == 0
    +        determine default qh.JOGGLEmax
    +    else
    +      increase qh.JOGGLEmax according to qh.build_cnt
    +    joggle the input by adding a random number in [-qh.JOGGLEmax,qh.JOGGLEmax]
    +    if qh.DELAUNAY
    +      sets the Delaunay projection
    +*/
    +void qh_joggleinput(void) {
    +  int i, seed, size;
    +  coordT *coordp, *inputp;
    +  realT randr, randa, randb;
    +
    +  if (!qh input_points) { /* first call */
    +    qh input_points= qh first_point;
    +    qh input_malloc= qh POINTSmalloc;
    +    size= qh num_points * qh hull_dim * sizeof(coordT);
    +    if (!(qh first_point=(coordT*)qh_malloc((size_t)size))) {
    +      qh_fprintf(qh ferr, 6009, "qhull error: insufficient memory to joggle %d points\n",
    +          qh num_points);
    +      qh_errexit(qh_ERRmem, NULL, NULL);
    +    }
    +    qh POINTSmalloc= True;
    +    if (qh JOGGLEmax == 0.0) {
    +      qh JOGGLEmax= qh_detjoggle(qh input_points, qh num_points, qh hull_dim);
    +      qh_option("QJoggle", NULL, &qh JOGGLEmax);
    +    }
    +  }else {                 /* repeated call */
    +    if (!qh RERUN && qh build_cnt > qh_JOGGLEretry) {
    +      if (((qh build_cnt-qh_JOGGLEretry-1) % qh_JOGGLEagain) == 0) {
    +        realT maxjoggle= qh MAXwidth * qh_JOGGLEmaxincrease;
    +        if (qh JOGGLEmax < maxjoggle) {
    +          qh JOGGLEmax *= qh_JOGGLEincrease;
    +          minimize_(qh JOGGLEmax, maxjoggle);
    +        }
    +      }
    +    }
    +    qh_option("QJoggle", NULL, &qh JOGGLEmax);
    +  }
    +  if (qh build_cnt > 1 && qh JOGGLEmax > fmax_(qh MAXwidth/4, 0.1)) {
    +      qh_fprintf(qh ferr, 6010, "qhull error: the current joggle for 'QJn', %.2g, is too large for the width\nof the input.  If possible, recompile Qhull with higher-precision reals.\n",
    +                qh JOGGLEmax);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  /* for some reason, using qh ROTATErandom and qh_RANDOMseed does not repeat the run. Use 'TRn' instead */
    +  seed= qh_RANDOMint;
    +  qh_option("_joggle-seed", &seed, NULL);
    +  trace0((qh ferr, 6, "qh_joggleinput: joggle input by %2.2g with seed %d\n",
    +    qh JOGGLEmax, seed));
    +  inputp= qh input_points;
    +  coordp= qh first_point;
    +  randa= 2.0 * qh JOGGLEmax/qh_RANDOMmax;
    +  randb= -qh JOGGLEmax;
    +  size= qh num_points * qh hull_dim;
    +  for (i=size; i--; ) {
    +    randr= qh_RANDOMint;
    +    *(coordp++)= *(inputp++) + (randr * randa + randb);
    +  }
    +  if (qh DELAUNAY) {
    +    qh last_low= qh last_high= qh last_newhigh= REALmax;
    +    qh_setdelaunay(qh hull_dim, qh num_points, qh first_point);
    +  }
    +} /* joggleinput */
    +
    +/*---------------------------------
    +
    +  qh_maxabsval( normal, dim )
    +    return pointer to maximum absolute value of a dim vector
    +    returns NULL if dim=0
    +*/
    +realT *qh_maxabsval(realT *normal, int dim) {
    +  realT maxval= -REALmax;
    +  realT *maxp= NULL, *colp, absval;
    +  int k;
    +
    +  for (k=dim, colp= normal; k--; colp++) {
    +    absval= fabs_(*colp);
    +    if (absval > maxval) {
    +      maxval= absval;
    +      maxp= colp;
    +    }
    +  }
    +  return maxp;
    +} /* maxabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_maxmin( points, numpoints, dimension )
    +    return max/min points for each dimension
    +    determine max and min coordinates
    +
    +  returns:
    +    returns a temporary set of max and min points
    +      may include duplicate points. Does not include qh.GOODpoint
    +    sets qh.NEARzero, qh.MAXabs_coord, qh.MAXsumcoord, qh.MAXwidth
    +         qh.MAXlastcoord, qh.MINlastcoord
    +    initializes qh.max_outside, qh.min_vertex, qh.WAScoplanar, qh.ZEROall_ok
    +
    +  notes:
    +    loop duplicated in qh_detjoggle()
    +
    +  design:
    +    initialize global precision variables
    +    checks definition of REAL...
    +    for each dimension
    +      for each point
    +        collect maximum and minimum point
    +      collect maximum of maximums and minimum of minimums
    +      determine qh.NEARzero for Gaussian Elimination
    +*/
    +setT *qh_maxmin(pointT *points, int numpoints, int dimension) {
    +  int k;
    +  realT maxcoord, temp;
    +  pointT *minimum, *maximum, *point, *pointtemp;
    +  setT *set;
    +
    +  qh max_outside= 0.0;
    +  qh MAXabs_coord= 0.0;
    +  qh MAXwidth= -REALmax;
    +  qh MAXsumcoord= 0.0;
    +  qh min_vertex= 0.0;
    +  qh WAScoplanar= False;
    +  if (qh ZEROcentrum)
    +    qh ZEROall_ok= True;
    +  if (REALmin < REALepsilon && REALmin < REALmax && REALmin > -REALmax
    +  && REALmax > 0.0 && -REALmax < 0.0)
    +    ; /* all ok */
    +  else {
    +    qh_fprintf(qh ferr, 6011, "qhull error: floating point constants in user.h are wrong\n\
    +REALepsilon %g REALmin %g REALmax %g -REALmax %g\n",
    +             REALepsilon, REALmin, REALmax, -REALmax);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  set= qh_settemp(2*dimension);
    +  for (k=0; k < dimension; k++) {
    +    if (points == qh GOODpointp)
    +      minimum= maximum= points + dimension;
    +    else
    +      minimum= maximum= points;
    +    FORALLpoint_(points, numpoints) {
    +      if (point == qh GOODpointp)
    +        continue;
    +      if (maximum[k] < point[k])
    +        maximum= point;
    +      else if (minimum[k] > point[k])
    +        minimum= point;
    +    }
    +    if (k == dimension-1) {
    +      qh MINlastcoord= minimum[k];
    +      qh MAXlastcoord= maximum[k];
    +    }
    +    if (qh SCALElast && k == dimension-1)
    +      maxcoord= qh MAXwidth;
    +    else {
    +      maxcoord= fmax_(maximum[k], -minimum[k]);
    +      if (qh GOODpointp) {
    +        temp= fmax_(qh GOODpointp[k], -qh GOODpointp[k]);
    +        maximize_(maxcoord, temp);
    +      }
    +      temp= maximum[k] - minimum[k];
    +      maximize_(qh MAXwidth, temp);
    +    }
    +    maximize_(qh MAXabs_coord, maxcoord);
    +    qh MAXsumcoord += maxcoord;
    +    qh_setappend(&set, maximum);
    +    qh_setappend(&set, minimum);
    +    /* calculation of qh NEARzero is based on Golub & van Loan, 1983,
    +       Eq. 4.4-13 for "Gaussian elimination with complete pivoting".
    +       Golub & van Loan say that n^3 can be ignored and 10 be used in
    +       place of rho */
    +    qh NEARzero[k]= 80 * qh MAXsumcoord * REALepsilon;
    +  }
    +  if (qh IStracing >=1)
    +    qh_printpoints(qh ferr, "qh_maxmin: found the max and min points(by dim):", set);
    +  return(set);
    +} /* maxmin */
    +
    +/*---------------------------------
    +
    +  qh_maxouter()
    +    return maximum distance from facet to outer plane
    +    normally this is qh.max_outside+qh.DISTround
    +    does not include qh.JOGGLEmax
    +
    +  see:
    +    qh_outerinner()
    +
    +  notes:
    +    need to add another qh.DISTround if testing actual point with computation
    +
    +  for joggle:
    +    qh_setfacetplane() updated qh.max_outer for Wnewvertexmax (max distance to vertex)
    +    need to use Wnewvertexmax since could have a coplanar point for a high
    +      facet that is replaced by a low facet
    +    need to add qh.JOGGLEmax if testing input points
    +*/
    +realT qh_maxouter(void) {
    +  realT dist;
    +
    +  dist= fmax_(qh max_outside, qh DISTround);
    +  dist += qh DISTround;
    +  trace4((qh ferr, 4012, "qh_maxouter: max distance from facet to outer plane is %2.2g max_outside is %2.2g\n", dist, qh max_outside));
    +  return dist;
    +} /* maxouter */
    +
    +/*---------------------------------
    +
    +  qh_maxsimplex( dim, maxpoints, points, numpoints, simplex )
    +    determines maximum simplex for a set of points
    +    starts from points already in simplex
    +    skips qh.GOODpointp (assumes that it isn't in maxpoints)
    +
    +  returns:
    +    simplex with dim+1 points
    +
    +  notes:
    +    assumes at least pointsneeded points in points
    +    maximizes determinate for x,y,z,w, etc.
    +    uses maxpoints as long as determinate is clearly non-zero
    +
    +  design:
    +    initialize simplex with at least two points
    +      (find points with max or min x coordinate)
    +    for each remaining dimension
    +      add point that maximizes the determinate
    +        (use points from maxpoints first)
    +*/
    +void qh_maxsimplex(int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex) {
    +  pointT *point, **pointp, *pointtemp, *maxpoint, *minx=NULL, *maxx=NULL;
    +  boolT nearzero, maxnearzero= False;
    +  int k, sizinit;
    +  realT maxdet= -REALmax, det, mincoord= REALmax, maxcoord= -REALmax;
    +
    +  sizinit= qh_setsize(*simplex);
    +  if (sizinit < 2) {
    +    if (qh_setsize(maxpoints) >= 2) {
    +      FOREACHpoint_(maxpoints) {
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }else {
    +      FORALLpoint_(points, numpoints) {
    +        if (point == qh GOODpointp)
    +          continue;
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }
    +    qh_setunique(simplex, minx);
    +    if (qh_setsize(*simplex) < 2)
    +      qh_setunique(simplex, maxx);
    +    sizinit= qh_setsize(*simplex);
    +    if (sizinit < 2) {
    +      qh_precision("input has same x coordinate");
    +      if (zzval_(Zsetplane) > qh hull_dim+1) {
    +        qh_fprintf(qh ferr, 6012, "qhull precision error (qh_maxsimplex for voronoi_center):\n%d points with the same x coordinate.\n",
    +                 qh_setsize(maxpoints)+numpoints);
    +        qh_errexit(qh_ERRprec, NULL, NULL);
    +      }else {
    +        qh_fprintf(qh ferr, 6013, "qhull input error: input is less than %d-dimensional since it has the same x coordinate\n", qh hull_dim);
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +    }
    +  }
    +  for (k=sizinit; k < dim+1; k++) {
    +    maxpoint= NULL;
    +    maxdet= -REALmax;
    +    FOREACHpoint_(maxpoints) {
    +      if (!qh_setin(*simplex, point)) {
    +        det= qh_detsimplex(point, *simplex, k, &nearzero);
    +        if ((det= fabs_(det)) > maxdet) {
    +          maxdet= det;
    +          maxpoint= point;
    +          maxnearzero= nearzero;
    +        }
    +      }
    +    }
    +    if (!maxpoint || maxnearzero) {
    +      zinc_(Zsearchpoints);
    +      if (!maxpoint) {
    +        trace0((qh ferr, 7, "qh_maxsimplex: searching all points for %d-th initial vertex.\n", k+1));
    +      }else {
    +        trace0((qh ferr, 8, "qh_maxsimplex: searching all points for %d-th initial vertex, better than p%d det %2.2g\n",
    +                k+1, qh_pointid(maxpoint), maxdet));
    +      }
    +      FORALLpoint_(points, numpoints) {
    +        if (point == qh GOODpointp)
    +          continue;
    +        if (!qh_setin(*simplex, point)) {
    +          det= qh_detsimplex(point, *simplex, k, &nearzero);
    +          if ((det= fabs_(det)) > maxdet) {
    +            maxdet= det;
    +            maxpoint= point;
    +            maxnearzero= nearzero;
    +          }
    +        }
    +      }
    +    } /* !maxpoint */
    +    if (!maxpoint) {
    +      qh_fprintf(qh ferr, 6014, "qhull internal error (qh_maxsimplex): not enough points available\n");
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_setappend(simplex, maxpoint);
    +    trace1((qh ferr, 1002, "qh_maxsimplex: selected point p%d for %d`th initial vertex, det=%2.2g\n",
    +            qh_pointid(maxpoint), k+1, maxdet));
    +  } /* k */
    +} /* maxsimplex */
    +
    +/*---------------------------------
    +
    +  qh_minabsval( normal, dim )
    +    return minimum absolute value of a dim vector
    +*/
    +realT qh_minabsval(realT *normal, int dim) {
    +  realT minval= 0;
    +  realT maxval= 0;
    +  realT *colp;
    +  int k;
    +
    +  for (k=dim, colp=normal; k--; colp++) {
    +    maximize_(maxval, *colp);
    +    minimize_(minval, *colp);
    +  }
    +  return fmax_(maxval, -minval);
    +} /* minabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_mindif( vecA, vecB, dim )
    +    return index of min abs. difference of two vectors
    +*/
    +int qh_mindiff(realT *vecA, realT *vecB, int dim) {
    +  realT mindiff= REALmax, diff;
    +  realT *vecAp= vecA, *vecBp= vecB;
    +  int k, mink= 0;
    +
    +  for (k=0; k < dim; k++) {
    +    diff= *vecAp++ - *vecBp++;
    +    diff= fabs_(diff);
    +    if (diff < mindiff) {
    +      mindiff= diff;
    +      mink= k;
    +    }
    +  }
    +  return mink;
    +} /* mindiff */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_orientoutside( facet  )
    +    make facet outside oriented via qh.interior_point
    +
    +  returns:
    +    True if facet reversed orientation.
    +*/
    +boolT qh_orientoutside(facetT *facet) {
    +  int k;
    +  realT dist;
    +
    +  qh_distplane(qh interior_point, facet, &dist);
    +  if (dist > 0) {
    +    for (k=qh hull_dim; k--; )
    +      facet->normal[k]= -facet->normal[k];
    +    facet->offset= -facet->offset;
    +    return True;
    +  }
    +  return False;
    +} /* orientoutside */
    +
    +/*---------------------------------
    +
    +  qh_outerinner( facet, outerplane, innerplane  )
    +    if facet and qh.maxoutdone (i.e., qh_check_maxout)
    +      returns outer and inner plane for facet
    +    else
    +      returns maximum outer and inner plane
    +    accounts for qh.JOGGLEmax
    +
    +  see:
    +    qh_maxouter(), qh_check_bestdist(), qh_check_points()
    +
    +  notes:
    +    outerplaner or innerplane may be NULL
    +    facet is const
    +    Does not error (QhullFacet)
    +
    +    includes qh.DISTround for actual points
    +    adds another qh.DISTround if testing with floating point arithmetic
    +*/
    +void qh_outerinner(facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT dist, mindist;
    +  vertexT *vertex, **vertexp;
    +
    +  if (outerplane) {
    +    if (!qh_MAXoutside || !facet || !qh maxoutdone) {
    +      *outerplane= qh_maxouter();       /* includes qh.DISTround */
    +    }else { /* qh_MAXoutside ... */
    +#if qh_MAXoutside
    +      *outerplane= facet->maxoutside + qh DISTround;
    +#endif
    +
    +    }
    +    if (qh JOGGLEmax < REALmax/2)
    +      *outerplane += qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +  }
    +  if (innerplane) {
    +    if (facet) {
    +      mindist= REALmax;
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(vertex->point, facet, &dist);
    +        minimize_(mindist, dist);
    +      }
    +      *innerplane= mindist - qh DISTround;
    +    }else
    +      *innerplane= qh min_vertex - qh DISTround;
    +    if (qh JOGGLEmax < REALmax/2)
    +      *innerplane -= qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +  }
    +} /* outerinner */
    +
    +/*---------------------------------
    +
    +  qh_pointdist( point1, point2, dim )
    +    return distance between two points
    +
    +  notes:
    +    returns distance squared if 'dim' is negative
    +*/
    +coordT qh_pointdist(pointT *point1, pointT *point2, int dim) {
    +  coordT dist, diff;
    +  int k;
    +
    +  dist= 0.0;
    +  for (k= (dim > 0 ? dim : -dim); k--; ) {
    +    diff= *point1++ - *point2++;
    +    dist += diff * diff;
    +  }
    +  if (dim > 0)
    +    return(sqrt(dist));
    +  return dist;
    +} /* pointdist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printmatrix( fp, string, rows, numrow, numcol )
    +    print matrix to fp given by row vectors
    +    print string as header
    +
    +  notes:
    +    print a vector by qh_printmatrix(fp, "", &vect, 1, len)
    +*/
    +void qh_printmatrix(FILE *fp, const char *string, realT **rows, int numrow, int numcol) {
    +  realT *rowp;
    +  realT r; /*bug fix*/
    +  int i,k;
    +
    +  qh_fprintf(fp, 9001, "%s\n", string);
    +  for (i=0; i < numrow; i++) {
    +    rowp= rows[i];
    +    for (k=0; k < numcol; k++) {
    +      r= *rowp++;
    +      qh_fprintf(fp, 9002, "%6.3g ", r);
    +    }
    +    qh_fprintf(fp, 9003, "\n");
    +  }
    +} /* printmatrix */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpoints( fp, string, points )
    +    print pointids to fp for a set of points
    +    if string, prints string and 'p' point ids
    +*/
    +void qh_printpoints(FILE *fp, const char *string, setT *points) {
    +  pointT *point, **pointp;
    +
    +  if (string) {
    +    qh_fprintf(fp, 9004, "%s", string);
    +    FOREACHpoint_(points)
    +      qh_fprintf(fp, 9005, " p%d", qh_pointid(point));
    +    qh_fprintf(fp, 9006, "\n");
    +  }else {
    +    FOREACHpoint_(points)
    +      qh_fprintf(fp, 9007, " %d", qh_pointid(point));
    +    qh_fprintf(fp, 9008, "\n");
    +  }
    +} /* printpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectinput()
    +    project input points using qh.lower_bound/upper_bound and qh DELAUNAY
    +    if qh.lower_bound[k]=qh.upper_bound[k]= 0,
    +      removes dimension k
    +    if halfspace intersection
    +      removes dimension k from qh.feasible_point
    +    input points in qh first_point, num_points, input_dim
    +
    +  returns:
    +    new point array in qh first_point of qh hull_dim coordinates
    +    sets qh POINTSmalloc
    +    if qh DELAUNAY
    +      projects points to paraboloid
    +      lowbound/highbound is also projected
    +    if qh ATinfinity
    +      adds point "at-infinity"
    +    if qh POINTSmalloc
    +      frees old point array
    +
    +  notes:
    +    checks that qh.hull_dim agrees with qh.input_dim, PROJECTinput, and DELAUNAY
    +
    +
    +  design:
    +    sets project[k] to -1 (delete), 0 (keep), 1 (add for Delaunay)
    +    determines newdim and newnum for qh hull_dim and qh num_points
    +    projects points to newpoints
    +    projects qh.lower_bound to itself
    +    projects qh.upper_bound to itself
    +    if qh DELAUNAY
    +      if qh ATINFINITY
    +        projects points to paraboloid
    +        computes "infinity" point as vertex average and 10% above all points
    +      else
    +        uses qh_setdelaunay to project points to paraboloid
    +*/
    +void qh_projectinput(void) {
    +  int k,i;
    +  int newdim= qh input_dim, newnum= qh num_points;
    +  signed char *project;
    +  int projectsize= (qh input_dim+1)*sizeof(*project);
    +  pointT *newpoints, *coord, *infinity;
    +  realT paraboloid, maxboloid= 0;
    +
    +  project= (signed char*)qh_memalloc(projectsize);
    +  memset((char*)project, 0, (size_t)projectsize);
    +  for (k=0; k < qh input_dim; k++) {   /* skip Delaunay bound */
    +    if (qh lower_bound[k] == 0 && qh upper_bound[k] == 0) {
    +      project[k]= -1;
    +      newdim--;
    +    }
    +  }
    +  if (qh DELAUNAY) {
    +    project[k]= 1;
    +    newdim++;
    +    if (qh ATinfinity)
    +      newnum++;
    +  }
    +  if (newdim != qh hull_dim) {
    +    qh_memfree(project, projectsize);
    +    qh_fprintf(qh ferr, 6015, "qhull internal error (qh_projectinput): dimension after projection %d != hull_dim %d\n", newdim, qh hull_dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (!(newpoints= qh temp_malloc= (coordT*)qh_malloc(newnum*newdim*sizeof(coordT)))){
    +    qh_memfree(project, projectsize);
    +    qh_fprintf(qh ferr, 6016, "qhull error: insufficient memory to project %d points\n",
    +           qh num_points);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  /* qh_projectpoints throws error if mismatched dimensions */
    +  qh_projectpoints(project, qh input_dim+1, qh first_point,
    +                    qh num_points, qh input_dim, newpoints, newdim);
    +  trace1((qh ferr, 1003, "qh_projectinput: updating lower and upper_bound\n"));
    +  qh_projectpoints(project, qh input_dim+1, qh lower_bound,
    +                    1, qh input_dim+1, qh lower_bound, newdim+1);
    +  qh_projectpoints(project, qh input_dim+1, qh upper_bound,
    +                    1, qh input_dim+1, qh upper_bound, newdim+1);
    +  if (qh HALFspace) {
    +    if (!qh feasible_point) {
    +      qh_memfree(project, projectsize);
    +      qh_fprintf(qh ferr, 6017, "qhull internal error (qh_projectinput): HALFspace defined without qh.feasible_point\n");
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_projectpoints(project, qh input_dim, qh feasible_point,
    +                      1, qh input_dim, qh feasible_point, newdim);
    +  }
    +  qh_memfree(project, projectsize);
    +  if (qh POINTSmalloc)
    +    qh_free(qh first_point);
    +  qh first_point= newpoints;
    +  qh POINTSmalloc= True;
    +  qh temp_malloc= NULL;
    +  if (qh DELAUNAY && qh ATinfinity) {
    +    coord= qh first_point;
    +    infinity= qh first_point + qh hull_dim * qh num_points;
    +    for (k=qh hull_dim-1; k--; )
    +      infinity[k]= 0.0;
    +    for (i=qh num_points; i--; ) {
    +      paraboloid= 0.0;
    +      for (k=0; k < qh hull_dim-1; k++) {
    +        paraboloid += *coord * *coord;
    +        infinity[k] += *coord;
    +        coord++;
    +      }
    +      *(coord++)= paraboloid;
    +      maximize_(maxboloid, paraboloid);
    +    }
    +    /* coord == infinity */
    +    for (k=qh hull_dim-1; k--; )
    +      *(coord++) /= qh num_points;
    +    *(coord++)= maxboloid * 1.1;
    +    qh num_points++;
    +    trace0((qh ferr, 9, "qh_projectinput: projected points to paraboloid for Delaunay\n"));
    +  }else if (qh DELAUNAY)  /* !qh ATinfinity */
    +    qh_setdelaunay( qh hull_dim, qh num_points, qh first_point);
    +} /* projectinput */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoints( project, n, points, numpoints, dim, newpoints, newdim )
    +    project points/numpoints/dim to newpoints/newdim
    +    if project[k] == -1
    +      delete dimension k
    +    if project[k] == 1
    +      add dimension k by duplicating previous column
    +    n is size of project
    +
    +  notes:
    +    newpoints may be points if only adding dimension at end
    +
    +  design:
    +    check that 'project' and 'newdim' agree
    +    for each dimension
    +      if project == -1
    +        skip dimension
    +      else
    +        determine start of column in newpoints
    +        determine start of column in points
    +          if project == +1, duplicate previous column
    +        copy dimension (column) from points to newpoints
    +*/
    +void qh_projectpoints(signed char *project, int n, realT *points,
    +        int numpoints, int dim, realT *newpoints, int newdim) {
    +  int testdim= dim, oldk=0, newk=0, i,j=0,k;
    +  realT *newp, *oldp;
    +
    +  for (k=0; k < n; k++)
    +    testdim += project[k];
    +  if (testdim != newdim) {
    +    qh_fprintf(qh ferr, 6018, "qhull internal error (qh_projectpoints): newdim %d should be %d after projection\n",
    +      newdim, testdim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  for (j=0; j= dim)
    +          continue;
    +        oldp= points+oldk;
    +      }else
    +        oldp= points+oldk++;
    +      for (i=numpoints; i--; ) {
    +        *newp= *oldp;
    +        newp += newdim;
    +        oldp += dim;
    +      }
    +    }
    +    if (oldk >= dim)
    +      break;
    +  }
    +  trace1((qh ferr, 1004, "qh_projectpoints: projected %d points from dim %d to dim %d\n",
    +    numpoints, dim, newdim));
    +} /* projectpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_rotateinput( rows )
    +    rotate input using row matrix
    +    input points given by qh first_point, num_points, hull_dim
    +    assumes rows[dim] is a scratch buffer
    +    if qh POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    rotated input
    +    sets qh POINTSmalloc
    +
    +  design:
    +    see qh_rotatepoints
    +*/
    +void qh_rotateinput(realT **rows) {
    +
    +  if (!qh POINTSmalloc) {
    +    qh first_point= qh_copypoints(qh first_point, qh num_points, qh hull_dim);
    +    qh POINTSmalloc= True;
    +  }
    +  qh_rotatepoints(qh first_point, qh num_points, qh hull_dim, rows);
    +}  /* rotateinput */
    +
    +/*---------------------------------
    +
    +  qh_rotatepoints( points, numpoints, dim, row )
    +    rotate numpoints points by a d-dim row matrix
    +    assumes rows[dim] is a scratch buffer
    +
    +  returns:
    +    rotated points in place
    +
    +  design:
    +    for each point
    +      for each coordinate
    +        use row[dim] to compute partial inner product
    +      for each coordinate
    +        rotate by partial inner product
    +*/
    +void qh_rotatepoints(realT *points, int numpoints, int dim, realT **row) {
    +  realT *point, *rowi, *coord= NULL, sum, *newval;
    +  int i,j,k;
    +
    +  if (qh IStracing >= 1)
    +    qh_printmatrix(qh ferr, "qh_rotatepoints: rotate points by", row, dim, dim);
    +  for (point= points, j= numpoints; j--; point += dim) {
    +    newval= row[dim];
    +    for (i=0; i < dim; i++) {
    +      rowi= row[i];
    +      coord= point;
    +      for (sum= 0.0, k= dim; k--; )
    +        sum += *rowi++ * *coord++;
    +      *(newval++)= sum;
    +    }
    +    for (k=dim; k--; )
    +      *(--coord)= *(--newval);
    +  }
    +} /* rotatepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_scaleinput()
    +    scale input points using qh low_bound/high_bound
    +    input points given by qh first_point, num_points, hull_dim
    +    if qh POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    scales coordinates of points to low_bound[k], high_bound[k]
    +    sets qh POINTSmalloc
    +
    +  design:
    +    see qh_scalepoints
    +*/
    +void qh_scaleinput(void) {
    +
    +  if (!qh POINTSmalloc) {
    +    qh first_point= qh_copypoints(qh first_point, qh num_points, qh hull_dim);
    +    qh POINTSmalloc= True;
    +  }
    +  qh_scalepoints(qh first_point, qh num_points, qh hull_dim,
    +       qh lower_bound, qh upper_bound);
    +}  /* scaleinput */
    +
    +/*---------------------------------
    +
    +  qh_scalelast( points, numpoints, dim, low, high, newhigh )
    +    scale last coordinate to [0,m] for Delaunay triangulations
    +    input points given by points, numpoints, dim
    +
    +  returns:
    +    changes scale of last coordinate from [low, high] to [0, newhigh]
    +    overwrites last coordinate of each point
    +    saves low/high/newhigh in qh.last_low, etc. for qh_setdelaunay()
    +
    +  notes:
    +    when called by qh_setdelaunay, low/high may not match actual data
    +
    +  design:
    +    compute scale and shift factors
    +    apply to last coordinate of each point
    +*/
    +void qh_scalelast(coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh) {
    +  realT scale, shift;
    +  coordT *coord;
    +  int i;
    +  boolT nearzero= False;
    +
    +  trace4((qh ferr, 4013, "qh_scalelast: scale last coordinate from [%2.2g, %2.2g] to [0,%2.2g]\n",
    +    low, high, newhigh));
    +  qh last_low= low;
    +  qh last_high= high;
    +  qh last_newhigh= newhigh;
    +  scale= qh_divzero(newhigh, high - low,
    +                  qh MINdenom_1, &nearzero);
    +  if (nearzero) {
    +    if (qh DELAUNAY)
    +      qh_fprintf(qh ferr, 6019, "qhull input error: can not scale last coordinate.  Input is cocircular\n   or cospherical.   Use option 'Qz' to add a point at infinity.\n");
    +    else
    +      qh_fprintf(qh ferr, 6020, "qhull input error: can not scale last coordinate.  New bounds [0, %2.2g] are too wide for\nexisting bounds [%2.2g, %2.2g] (width %2.2g)\n",
    +                newhigh, low, high, high-low);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  shift= - low * newhigh / (high-low);
    +  coord= points + dim - 1;
    +  for (i=numpoints; i--; coord += dim)
    +    *coord= *coord * scale + shift;
    +} /* scalelast */
    +
    +/*---------------------------------
    +
    +  qh_scalepoints( points, numpoints, dim, newlows, newhighs )
    +    scale points to new lowbound and highbound
    +    retains old bound when newlow= -REALmax or newhigh= +REALmax
    +
    +  returns:
    +    scaled points
    +    overwrites old points
    +
    +  design:
    +    for each coordinate
    +      compute current low and high bound
    +      compute scale and shift factors
    +      scale all points
    +      enforce new low and high bound for all points
    +*/
    +void qh_scalepoints(pointT *points, int numpoints, int dim,
    +        realT *newlows, realT *newhighs) {
    +  int i,k;
    +  realT shift, scale, *coord, low, high, newlow, newhigh, mincoord, maxcoord;
    +  boolT nearzero= False;
    +
    +  for (k=0; k < dim; k++) {
    +    newhigh= newhighs[k];
    +    newlow= newlows[k];
    +    if (newhigh > REALmax/2 && newlow < -REALmax/2)
    +      continue;
    +    low= REALmax;
    +    high= -REALmax;
    +    for (i=numpoints, coord=points+k; i--; coord += dim) {
    +      minimize_(low, *coord);
    +      maximize_(high, *coord);
    +    }
    +    if (newhigh > REALmax/2)
    +      newhigh= high;
    +    if (newlow < -REALmax/2)
    +      newlow= low;
    +    if (qh DELAUNAY && k == dim-1 && newhigh < newlow) {
    +      qh_fprintf(qh ferr, 6021, "qhull input error: 'Qb%d' or 'QB%d' inverts paraboloid since high bound %.2g < low bound %.2g\n",
    +               k, k, newhigh, newlow);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    scale= qh_divzero(newhigh - newlow, high - low,
    +                  qh MINdenom_1, &nearzero);
    +    if (nearzero) {
    +      qh_fprintf(qh ferr, 6022, "qhull input error: %d'th dimension's new bounds [%2.2g, %2.2g] too wide for\nexisting bounds [%2.2g, %2.2g]\n",
    +              k, newlow, newhigh, low, high);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    shift= (newlow * high - low * newhigh)/(high-low);
    +    coord= points+k;
    +    for (i=numpoints; i--; coord += dim)
    +      *coord= *coord * scale + shift;
    +    coord= points+k;
    +    if (newlow < newhigh) {
    +      mincoord= newlow;
    +      maxcoord= newhigh;
    +    }else {
    +      mincoord= newhigh;
    +      maxcoord= newlow;
    +    }
    +    for (i=numpoints; i--; coord += dim) {
    +      minimize_(*coord, maxcoord);  /* because of roundoff error */
    +      maximize_(*coord, mincoord);
    +    }
    +    trace0((qh ferr, 10, "qh_scalepoints: scaled %d'th coordinate [%2.2g, %2.2g] to [%.2g, %.2g] for %d points by %2.2g and shifted %2.2g\n",
    +      k, low, high, newlow, newhigh, numpoints, scale, shift));
    +  }
    +} /* scalepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelaunay( dim, count, points )
    +    project count points to dim-d paraboloid for Delaunay triangulation
    +
    +    dim is one more than the dimension of the input set
    +    assumes dim is at least 3 (i.e., at least a 2-d Delaunay triangulation)
    +
    +    points is a dim*count realT array.  The first dim-1 coordinates
    +    are the coordinates of the first input point.  array[dim] is
    +    the first coordinate of the second input point.  array[2*dim] is
    +    the first coordinate of the third input point.
    +
    +    if qh.last_low defined (i.e., 'Qbb' called qh_scalelast)
    +      calls qh_scalelast to scale the last coordinate the same as the other points
    +
    +  returns:
    +    for each point
    +      sets point[dim-1] to sum of squares of coordinates
    +    scale points to 'Qbb' if needed
    +
    +  notes:
    +    to project one point, use
    +      qh_setdelaunay(qh hull_dim, 1, point)
    +
    +    Do not use options 'Qbk', 'QBk', or 'QbB' since they scale
    +    the coordinates after the original projection.
    +
    +*/
    +void qh_setdelaunay(int dim, int count, pointT *points) {
    +  int i, k;
    +  coordT *coordp, coord;
    +  realT paraboloid;
    +
    +  trace0((qh ferr, 11, "qh_setdelaunay: project %d points to paraboloid for Delaunay triangulation\n", count));
    +  coordp= points;
    +  for (i=0; i < count; i++) {
    +    coord= *coordp++;
    +    paraboloid= coord*coord;
    +    for (k=dim-2; k--; ) {
    +      coord= *coordp++;
    +      paraboloid += coord*coord;
    +    }
    +    *coordp++ = paraboloid;
    +  }
    +  if (qh last_low < REALmax/2)
    +    qh_scalelast(points, count, dim, qh last_low, qh last_high, qh last_newhigh);
    +} /* setdelaunay */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace( dim, coords, nextp, normal, offset, feasible )
    +    set point to dual of halfspace relative to feasible point
    +    halfspace is normal coefficients and offset.
    +
    +  returns:
    +    false and prints error if feasible point is outside of hull
    +    overwrites coordinates for point at dim coords
    +    nextp= next point (coords)
    +    does not call qh_errexit
    +
    +  design:
    +    compute distance from feasible point to halfspace
    +    divide each normal coefficient by -dist
    +*/
    +boolT qh_sethalfspace(int dim, coordT *coords, coordT **nextp,
    +         coordT *normal, coordT *offset, coordT *feasible) {
    +  coordT *normp= normal, *feasiblep= feasible, *coordp= coords;
    +  realT dist;
    +  realT r; /*bug fix*/
    +  int k;
    +  boolT zerodiv;
    +
    +  dist= *offset;
    +  for (k=dim; k--; )
    +    dist += *(normp++) * *(feasiblep++);
    +  if (dist > 0)
    +    goto LABELerroroutside;
    +  normp= normal;
    +  if (dist < -qh MINdenom) {
    +    for (k=dim; k--; )
    +      *(coordp++)= *(normp++) / -dist;
    +  }else {
    +    for (k=dim; k--; ) {
    +      *(coordp++)= qh_divzero(*(normp++), -dist, qh MINdenom_1, &zerodiv);
    +      if (zerodiv)
    +        goto LABELerroroutside;
    +    }
    +  }
    +  *nextp= coordp;
    +  if (qh IStracing >= 4) {
    +    qh_fprintf(qh ferr, 8021, "qh_sethalfspace: halfspace at offset %6.2g to point: ", *offset);
    +    for (k=dim, coordp=coords; k--; ) {
    +      r= *coordp++;
    +      qh_fprintf(qh ferr, 8022, " %6.2g", r);
    +    }
    +    qh_fprintf(qh ferr, 8023, "\n");
    +  }
    +  return True;
    +LABELerroroutside:
    +  feasiblep= feasible;
    +  normp= normal;
    +  qh_fprintf(qh ferr, 6023, "qhull input error: feasible point is not clearly inside halfspace\nfeasible point: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh ferr, 8024, qh_REAL_1, r=*(feasiblep++));
    +  qh_fprintf(qh ferr, 8025, "\n     halfspace: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh ferr, 8026, qh_REAL_1, r=*(normp++));
    +  qh_fprintf(qh ferr, 8027, "\n     at offset: ");
    +  qh_fprintf(qh ferr, 8028, qh_REAL_1, *offset);
    +  qh_fprintf(qh ferr, 8029, " and distance: ");
    +  qh_fprintf(qh ferr, 8030, qh_REAL_1, dist);
    +  qh_fprintf(qh ferr, 8031, "\n");
    +  return False;
    +} /* sethalfspace */
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace_all( dim, count, halfspaces, feasible )
    +    generate dual for halfspace intersection with feasible point
    +    array of count halfspaces
    +      each halfspace is normal coefficients followed by offset
    +      the origin is inside the halfspace if the offset is negative
    +    feasible is a point inside all halfspaces (http://www.qhull.org/html/qhalf.htm#notes)
    +
    +  returns:
    +    malloc'd array of count X dim-1 points
    +
    +  notes:
    +    call before qh_init_B or qh_initqhull_globals
    +    free memory when done
    +    unused/untested code: please email bradb@shore.net if this works ok for you
    +    if using option 'Fp', qh->feasible_point must be set (e.g., to 'feasible')
    +    qh->feasible_point is a malloc'd array that is freed by qh_freebuffers.
    +
    +  design:
    +    see qh_sethalfspace
    +*/
    +coordT *qh_sethalfspace_all(int dim, int count, coordT *halfspaces, pointT *feasible) {
    +  int i, newdim;
    +  pointT *newpoints;
    +  coordT *coordp, *normalp, *offsetp;
    +
    +  trace0((qh ferr, 12, "qh_sethalfspace_all: compute dual for halfspace intersection\n"));
    +  newdim= dim - 1;
    +  if (!(newpoints=(coordT*)qh_malloc(count*newdim*sizeof(coordT)))){
    +    qh_fprintf(qh ferr, 6024, "qhull error: insufficient memory to compute dual of %d halfspaces\n",
    +          count);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  coordp= newpoints;
    +  normalp= halfspaces;
    +  for (i=0; i < count; i++) {
    +    offsetp= normalp + newdim;
    +    if (!qh_sethalfspace(newdim, coordp, &coordp, normalp, offsetp, feasible)) {
    +      qh_free(newpoints);  /* feasible is not inside halfspace as reported by qh_sethalfspace */
    +      qh_fprintf(qh ferr, 8032, "The halfspace was at index %d\n", i);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    normalp= offsetp + 1;
    +  }
    +  return newpoints;
    +} /* sethalfspace_all */
    +
    +
    +/*---------------------------------
    +
    +  qh_sharpnewfacets()
    +
    +  returns:
    +    true if could be an acute angle (facets in different quadrants)
    +
    +  notes:
    +    for qh_findbest
    +
    +  design:
    +    for all facets on qh.newfacet_list
    +      if two facets are in different quadrants
    +        set issharp
    +*/
    +boolT qh_sharpnewfacets(void) {
    +  facetT *facet;
    +  boolT issharp = False;
    +  int *quadrant, k;
    +
    +  quadrant= (int*)qh_memalloc(qh hull_dim * sizeof(int));
    +  FORALLfacet_(qh newfacet_list) {
    +    if (facet == qh newfacet_list) {
    +      for (k=qh hull_dim; k--; )
    +        quadrant[ k]= (facet->normal[ k] > 0);
    +    }else {
    +      for (k=qh hull_dim; k--; ) {
    +        if (quadrant[ k] != (facet->normal[ k] > 0)) {
    +          issharp= True;
    +          break;
    +        }
    +      }
    +    }
    +    if (issharp)
    +      break;
    +  }
    +  qh_memfree( quadrant, qh hull_dim * sizeof(int));
    +  trace3((qh ferr, 3001, "qh_sharpnewfacets: %d\n", issharp));
    +  return issharp;
    +} /* sharpnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_voronoi_center( dim, points )
    +    return Voronoi center for a set of points
    +    dim is the orginal dimension of the points
    +    gh.gm_matrix/qh.gm_row are scratch buffers
    +
    +  returns:
    +    center as a temporary point (qh_memalloc)
    +    if non-simplicial,
    +      returns center for max simplex of points
    +
    +  notes:
    +    only called by qh_facetcenter
    +    from Bowyer & Woodwark, A Programmer's Geometry, 1983, p. 65
    +
    +  design:
    +    if non-simplicial
    +      determine max simplex for points
    +    translate point0 of simplex to origin
    +    compute sum of squares of diagonal
    +    compute determinate
    +    compute Voronoi center (see Bowyer & Woodwark)
    +*/
    +pointT *qh_voronoi_center(int dim, setT *points) {
    +  pointT *point, **pointp, *point0;
    +  pointT *center= (pointT*)qh_memalloc(qh center_size);
    +  setT *simplex;
    +  int i, j, k, size= qh_setsize(points);
    +  coordT *gmcoord;
    +  realT *diffp, sum2, *sum2row, *sum2p, det, factor;
    +  boolT nearzero, infinite;
    +
    +  if (size == dim+1)
    +    simplex= points;
    +  else if (size < dim+1) {
    +    qh_memfree(center, qh center_size);
    +    qh_fprintf(qh ferr, 6025, "qhull internal error (qh_voronoi_center):\n  need at least %d points to construct a Voronoi center\n",
    +             dim+1);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +    simplex= points;  /* never executed -- avoids warning */
    +  }else {
    +    simplex= qh_settemp(dim+1);
    +    qh_maxsimplex(dim, points, NULL, 0, &simplex);
    +  }
    +  point0= SETfirstt_(simplex, pointT);
    +  gmcoord= qh gm_matrix;
    +  for (k=0; k < dim; k++) {
    +    qh gm_row[k]= gmcoord;
    +    FOREACHpoint_(simplex) {
    +      if (point != point0)
    +        *(gmcoord++)= point[k] - point0[k];
    +    }
    +  }
    +  sum2row= gmcoord;
    +  for (i=0; i < dim; i++) {
    +    sum2= 0.0;
    +    for (k=0; k < dim; k++) {
    +      diffp= qh gm_row[k] + i;
    +      sum2 += *diffp * *diffp;
    +    }
    +    *(gmcoord++)= sum2;
    +  }
    +  det= qh_determinant(qh gm_row, dim, &nearzero);
    +  factor= qh_divzero(0.5, det, qh MINdenom, &infinite);
    +  if (infinite) {
    +    for (k=dim; k--; )
    +      center[k]= qh_INFINITE;
    +    if (qh IStracing)
    +      qh_printpoints(qh ferr, "qh_voronoi_center: at infinity for ", simplex);
    +  }else {
    +    for (i=0; i < dim; i++) {
    +      gmcoord= qh gm_matrix;
    +      sum2p= sum2row;
    +      for (k=0; k < dim; k++) {
    +        qh gm_row[k]= gmcoord;
    +        if (k == i) {
    +          for (j=dim; j--; )
    +            *(gmcoord++)= *sum2p++;
    +        }else {
    +          FOREACHpoint_(simplex) {
    +            if (point != point0)
    +              *(gmcoord++)= point[k] - point0[k];
    +          }
    +        }
    +      }
    +      center[i]= qh_determinant(qh gm_row, dim, &nearzero)*factor + point0[i];
    +    }
    +#ifndef qh_NOtrace
    +    if (qh IStracing >= 3) {
    +      qh_fprintf(qh ferr, 8033, "qh_voronoi_center: det %2.2g factor %2.2g ", det, factor);
    +      qh_printmatrix(qh ferr, "center:", ¢er, 1, dim);
    +      if (qh IStracing >= 5) {
    +        qh_printpoints(qh ferr, "points", simplex);
    +        FOREACHpoint_(simplex)
    +          qh_fprintf(qh ferr, 8034, "p%d dist %.2g, ", qh_pointid(point),
    +                   qh_pointdist(point, center, dim));
    +        qh_fprintf(qh ferr, 8035, "\n");
    +      }
    +    }
    +#endif
    +  }
    +  if (simplex != points)
    +    qh_settempfree(&simplex);
    +  return center;
    +} /* voronoi_center */
    +
    diff --git a/xs/src/qhull/src/libqhull/global.c b/xs/src/qhull/src/libqhull/global.c
    new file mode 100644
    index 0000000000..0328fea7b9
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/global.c
    @@ -0,0 +1,2217 @@
    +
    +/*
      ---------------------------------
    +
    +   global.c
    +   initializes all the globals of the qhull application
    +
    +   see README
    +
    +   see libqhull.h for qh.globals and function prototypes
    +
    +   see qhull_a.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/global.c#17 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    + */
    +
    +#include "qhull_a.h"
    +
    +/*========= qh definition -- globals defined in libqhull.h =======================*/
    +
    +#if qh_QHpointer
    +qhT *qh_qh= NULL;       /* pointer to all global variables */
    +#else
    +qhT qh_qh;              /* all global variables.
    +                           Add "= {0}" if this causes a compiler error.
    +                           Also qh_qhstat in stat.c and qhmem in mem.c.  */
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_version
    +    version string by year and date
    +    qh_version2 for Unix users and -V
    +
    +    the revision increases on code changes only
    +
    +  notes:
    +    change date:    Changes.txt, Announce.txt, index.htm, README.txt,
    +                    qhull-news.html, Eudora signatures, CMakeLists.txt
    +    change version: README.txt, qh-get.htm, File_id.diz, Makefile.txt, CMakeLists.txt
    +    check that CmakeLists @version is the same as qh_version2
    +    change year:    Copying.txt
    +    check download size
    +    recompile user_eg.c, rbox.c, libqhull.c, qconvex.c, qdelaun.c qvoronoi.c, qhalf.c, testqset.c
    +*/
    +
    +const char qh_version[]= "2015.2 2016/01/18";
    +const char qh_version2[]= "qhull 7.2.0 (2015.2 2016/01/18)";
    +
    +/*---------------------------------
    +
    +  qh_appendprint( printFormat )
    +    append printFormat to qh.PRINTout unless already defined
    +*/
    +void qh_appendprint(qh_PRINT format) {
    +  int i;
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    if (qh PRINTout[i] == format && format != qh_PRINTqhull)
    +      break;
    +    if (!qh PRINTout[i]) {
    +      qh PRINTout[i]= format;
    +      break;
    +    }
    +  }
    +} /* appendprint */
    +
    +/*---------------------------------
    +
    +  qh_checkflags( commandStr, hiddenFlags )
    +    errors if commandStr contains hiddenFlags
    +    hiddenFlags starts and ends with a space and is space delimited (checked)
    +
    +  notes:
    +    ignores first word (e.g., "qconvex i")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initflags() initializes Qhull according to commandStr
    +*/
    +void qh_checkflags(char *command, char *hiddenflags) {
    +  char *s= command, *t, *chkerr; /* qh_skipfilename is non-const */
    +  char key, opt, prevopt;
    +  char chkkey[]= "   ";
    +  char chkopt[]=  "    ";
    +  char chkopt2[]= "     ";
    +  boolT waserr= False;
    +
    +  if (*hiddenflags != ' ' || hiddenflags[strlen(hiddenflags)-1] != ' ') {
    +    qh_fprintf(qh ferr, 6026, "qhull error (qh_checkflags): hiddenflags must start and end with a space: \"%s\"", hiddenflags);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (strpbrk(hiddenflags, ",\n\r\t")) {
    +    qh_fprintf(qh ferr, 6027, "qhull error (qh_checkflags): hiddenflags contains commas, newlines, or tabs: \"%s\"", hiddenflags);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    key = *s++;
    +    chkerr = NULL;
    +    if (key == 'T' && (*s == 'I' || *s == 'O')) {  /* TI or TO 'file name' */
    +      s= qh_skipfilename(++s);
    +      continue;
    +    }
    +    chkkey[1]= key;
    +    if (strstr(hiddenflags, chkkey)) {
    +      chkerr= chkkey;
    +    }else if (isupper(key)) {
    +      opt= ' ';
    +      prevopt= ' ';
    +      chkopt[1]= key;
    +      chkopt2[1]= key;
    +      while (!chkerr && *s && !isspace(*s)) {
    +        opt= *s++;
    +        if (isalpha(opt)) {
    +          chkopt[2]= opt;
    +          if (strstr(hiddenflags, chkopt))
    +            chkerr= chkopt;
    +          if (prevopt != ' ') {
    +            chkopt2[2]= prevopt;
    +            chkopt2[3]= opt;
    +            if (strstr(hiddenflags, chkopt2))
    +              chkerr= chkopt2;
    +          }
    +        }else if (key == 'Q' && isdigit(opt) && prevopt != 'b'
    +              && (prevopt == ' ' || islower(prevopt))) {
    +            chkopt[2]= opt;
    +            if (strstr(hiddenflags, chkopt))
    +              chkerr= chkopt;
    +        }else {
    +          qh_strtod(s-1, &t);
    +          if (s < t)
    +            s= t;
    +        }
    +        prevopt= opt;
    +      }
    +    }
    +    if (chkerr) {
    +      *chkerr= '\'';
    +      chkerr[strlen(chkerr)-1]=  '\'';
    +      qh_fprintf(qh ferr, 6029, "qhull error: option %s is not used with this program.\n             It may be used with qhull.\n", chkerr);
    +      waserr= True;
    +    }
    +  }
    +  if (waserr)
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +} /* checkflags */
    +
    +/*---------------------------------
    +
    +  qh_clear_outputflags()
    +    Clear output flags for QhullPoints
    +*/
    +void qh_clear_outputflags(void) {
    +  int i,k;
    +
    +  qh ANNOTATEoutput= False;
    +  qh DOintersections= False;
    +  qh DROPdim= -1;
    +  qh FORCEoutput= False;
    +  qh GETarea= False;
    +  qh GOODpoint= 0;
    +  qh GOODpointp= NULL;
    +  qh GOODthreshold= False;
    +  qh GOODvertex= 0;
    +  qh GOODvertexp= NULL;
    +  qh IStracing= 0;
    +  qh KEEParea= False;
    +  qh KEEPmerge= False;
    +  qh KEEPminArea= REALmax;
    +  qh PRINTcentrums= False;
    +  qh PRINTcoplanar= False;
    +  qh PRINTdots= False;
    +  qh PRINTgood= False;
    +  qh PRINTinner= False;
    +  qh PRINTneighbors= False;
    +  qh PRINTnoplanes= False;
    +  qh PRINToptions1st= False;
    +  qh PRINTouter= False;
    +  qh PRINTprecision= True;
    +  qh PRINTridges= False;
    +  qh PRINTspheres= False;
    +  qh PRINTstatistics= False;
    +  qh PRINTsummary= False;
    +  qh PRINTtransparent= False;
    +  qh SPLITthresholds= False;
    +  qh TRACElevel= 0;
    +  qh TRInormals= False;
    +  qh USEstdout= False;
    +  qh VERIFYoutput= False;
    +  for (k=qh input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh lower_threshold[k]= -REALmax;
    +    qh upper_threshold[k]= REALmax;
    +    qh lower_bound[k]= -REALmax;
    +    qh upper_bound[k]= REALmax;
    +  }
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    qh PRINTout[i]= qh_PRINTnone;
    +  }
    +
    +  if (!qh qhull_commandsiz2)
    +      qh qhull_commandsiz2= (int)strlen(qh qhull_command); /* WARN64 */
    +  else {
    +      qh qhull_command[qh qhull_commandsiz2]= '\0';
    +  }
    +  if (!qh qhull_optionsiz2)
    +    qh qhull_optionsiz2= (int)strlen(qh qhull_options);  /* WARN64 */
    +  else {
    +    qh qhull_options[qh qhull_optionsiz2]= '\0';
    +    qh qhull_optionlen= qh_OPTIONline;  /* start a new line */
    +  }
    +} /* clear_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_clock()
    +    return user CPU time in 100ths (qh_SECtick)
    +    only defined for qh_CLOCKtype == 2
    +
    +  notes:
    +    use first value to determine time 0
    +    from Stevens '92 8.15
    +*/
    +unsigned long qh_clock(void) {
    +
    +#if (qh_CLOCKtype == 2)
    +  struct tms time;
    +  static long clktck;  /* initialized first call and never updated */
    +  double ratio, cpu;
    +  unsigned long ticks;
    +
    +  if (!clktck) {
    +    if ((clktck= sysconf(_SC_CLK_TCK)) < 0) {
    +      qh_fprintf(qh ferr, 6030, "qhull internal error (qh_clock): sysconf() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  if (times(&time) == -1) {
    +    qh_fprintf(qh ferr, 6031, "qhull internal error (qh_clock): times() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  ratio= qh_SECticks / (double)clktck;
    +  ticks= time.tms_utime * ratio;
    +  return ticks;
    +#else
    +  qh_fprintf(qh ferr, 6032, "qhull internal error (qh_clock): use qh_CLOCKtype 2 in user.h\n");
    +  qh_errexit(qh_ERRqhull, NULL, NULL); /* never returns */
    +  return 0;
    +#endif
    +} /* clock */
    +
    +/*---------------------------------
    +
    +  qh_freebuffers()
    +    free up global memory buffers
    +
    +  notes:
    +    must match qh_initbuffers()
    +*/
    +void qh_freebuffers(void) {
    +
    +  trace5((qh ferr, 5001, "qh_freebuffers: freeing up global memory buffers\n"));
    +  /* allocated by qh_initqhull_buffers */
    +  qh_memfree(qh NEARzero, qh hull_dim * sizeof(realT));
    +  qh_memfree(qh lower_threshold, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh upper_threshold, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh lower_bound, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh upper_bound, (qh input_dim+1) * sizeof(realT));
    +  qh_memfree(qh gm_matrix, (qh hull_dim+1) * qh hull_dim * sizeof(coordT));
    +  qh_memfree(qh gm_row, (qh hull_dim+1) * sizeof(coordT *));
    +  qh NEARzero= qh lower_threshold= qh upper_threshold= NULL;
    +  qh lower_bound= qh upper_bound= NULL;
    +  qh gm_matrix= NULL;
    +  qh gm_row= NULL;
    +  qh_setfree(&qh other_points);
    +  qh_setfree(&qh del_vertices);
    +  qh_setfree(&qh coplanarfacetset);
    +  if (qh line)                /* allocated by qh_readinput, freed if no error */
    +    qh_free(qh line);
    +  if (qh half_space)
    +    qh_free(qh half_space);
    +  if (qh temp_malloc)
    +    qh_free(qh temp_malloc);
    +  if (qh feasible_point)      /* allocated by qh_readfeasible */
    +    qh_free(qh feasible_point);
    +  if (qh feasible_string)     /* allocated by qh_initflags */
    +    qh_free(qh feasible_string);
    +  qh line= qh feasible_string= NULL;
    +  qh half_space= qh feasible_point= qh temp_malloc= NULL;
    +  /* usually allocated by qh_readinput */
    +  if (qh first_point && qh POINTSmalloc) {
    +    qh_free(qh first_point);
    +    qh first_point= NULL;
    +  }
    +  if (qh input_points && qh input_malloc) { /* set by qh_joggleinput */
    +    qh_free(qh input_points);
    +    qh input_points= NULL;
    +  }
    +  trace5((qh ferr, 5002, "qh_freebuffers: finished\n"));
    +} /* freebuffers */
    +
    +
    +/*---------------------------------
    +
    +  qh_freebuild( allmem )
    +    free global memory used by qh_initbuild and qh_buildhull
    +    if !allmem,
    +      does not free short memory (e.g., facetT, freed by qh_memfreeshort)
    +
    +  design:
    +    free centrums
    +    free each vertex
    +    mark unattached ridges
    +    for each facet
    +      free ridges
    +      free outside set, coplanar set, neighbor set, ridge set, vertex set
    +      free facet
    +    free hash table
    +    free interior point
    +    free merge set
    +    free temporary sets
    +*/
    +void qh_freebuild(boolT allmem) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  ridgeT *ridge, **ridgep;
    +  mergeT *merge, **mergep;
    +
    +  trace1((qh ferr, 1005, "qh_freebuild: free memory from qh_inithull and qh_buildhull\n"));
    +  if (qh del_vertices)
    +    qh_settruncate(qh del_vertices, 0);
    +  if (allmem) {
    +    while ((vertex= qh vertex_list)) {
    +      if (vertex->next)
    +        qh_delvertex(vertex);
    +      else {
    +        qh_memfree(vertex, (int)sizeof(vertexT));
    +        qh newvertex_list= qh vertex_list= NULL;
    +      }
    +    }
    +  }else if (qh VERTEXneighbors) {
    +    FORALLvertices
    +      qh_setfreelong(&(vertex->neighbors));
    +  }
    +  qh VERTEXneighbors= False;
    +  qh GOODclosest= NULL;
    +  if (allmem) {
    +    FORALLfacets {
    +      FOREACHridge_(facet->ridges)
    +        ridge->seen= False;
    +    }
    +    FORALLfacets {
    +      if (facet->visible) {
    +        FOREACHridge_(facet->ridges) {
    +          if (!otherfacet_(ridge, facet)->visible)
    +            ridge->seen= True;  /* an unattached ridge */
    +        }
    +      }
    +    }
    +    while ((facet= qh facet_list)) {
    +      FOREACHridge_(facet->ridges) {
    +        if (ridge->seen) {
    +          qh_setfree(&(ridge->vertices));
    +          qh_memfree(ridge, (int)sizeof(ridgeT));
    +        }else
    +          ridge->seen= True;
    +      }
    +      qh_setfree(&(facet->outsideset));
    +      qh_setfree(&(facet->coplanarset));
    +      qh_setfree(&(facet->neighbors));
    +      qh_setfree(&(facet->ridges));
    +      qh_setfree(&(facet->vertices));
    +      if (facet->next)
    +        qh_delfacet(facet);
    +      else {
    +        qh_memfree(facet, (int)sizeof(facetT));
    +        qh visible_list= qh newfacet_list= qh facet_list= NULL;
    +      }
    +    }
    +  }else {
    +    FORALLfacets {
    +      qh_setfreelong(&(facet->outsideset));
    +      qh_setfreelong(&(facet->coplanarset));
    +      if (!facet->simplicial) {
    +        qh_setfreelong(&(facet->neighbors));
    +        qh_setfreelong(&(facet->ridges));
    +        qh_setfreelong(&(facet->vertices));
    +      }
    +    }
    +  }
    +  qh_setfree(&(qh hash_table));
    +  qh_memfree(qh interior_point, qh normal_size);
    +  qh interior_point= NULL;
    +  FOREACHmerge_(qh facet_mergeset)  /* usually empty */
    +    qh_memfree(merge, (int)sizeof(mergeT));
    +  qh facet_mergeset= NULL;  /* temp set */
    +  qh degen_mergeset= NULL;  /* temp set */
    +  qh_settempfree_all();
    +} /* freebuild */
    +
    +/*---------------------------------
    +
    +  qh_freeqhull( allmem )
    +    see qh_freeqhull2
    +    if qh_QHpointer, frees qh_qh
    +*/
    +void qh_freeqhull(boolT allmem) {
    +    qh_freeqhull2(allmem);
    +#if qh_QHpointer
    +    qh_free(qh_qh);
    +    qh_qh= NULL;
    +#endif
    +}
    +
    +/*---------------------------------
    +
    +qh_freeqhull2( allmem )
    +  free global memory and set qhT to 0
    +  if !allmem,
    +    does not free short memory (freed by qh_memfreeshort unless qh_NOmem)
    +
    +notes:
    +  sets qh.NOerrexit in case caller forgets to
    +  Does not throw errors
    +
    +see:
    +  see qh_initqhull_start2()
    +
    +design:
    +  free global and temporary memory from qh_initbuild and qh_buildhull
    +  free buffers
    +  free statistics
    +*/
    +void qh_freeqhull2(boolT allmem) {
    +
    +  qh NOerrexit= True;  /* no more setjmp since called at exit and ~QhullQh */
    +  trace1((qh ferr, 1006, "qh_freeqhull: free global memory\n"));
    +  qh_freebuild(allmem);
    +  qh_freebuffers();
    +  qh_freestatistics();
    +#if qh_QHpointer
    +  memset((char *)qh_qh, 0, sizeof(qhT));
    +  /* qh_qh freed by caller, qh_freeqhull() */
    +#else
    +  memset((char *)&qh_qh, 0, sizeof(qhT));
    +#endif
    +  qh NOerrexit= True;
    +} /* freeqhull2 */
    +
    +/*---------------------------------
    +
    +  qh_init_A( infile, outfile, errfile, argc, argv )
    +    initialize memory and stdio files
    +    convert input options to option string (qh.qhull_command)
    +
    +  notes:
    +    infile may be NULL if qh_readpoints() is not called
    +
    +    errfile should always be defined.  It is used for reporting
    +    errors.  outfile is used for output and format options.
    +
    +    argc/argv may be 0/NULL
    +
    +    called before error handling initialized
    +    qh_errexit() may not be used
    +*/
    +void qh_init_A(FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]) {
    +  qh_meminit(errfile);
    +  qh_initqhull_start(infile, outfile, errfile);
    +  qh_init_qhull_command(argc, argv);
    +} /* init_A */
    +
    +/*---------------------------------
    +
    +  qh_init_B( points, numpoints, dim, ismalloc )
    +    initialize globals for points array
    +
    +    points has numpoints dim-dimensional points
    +      points[0] is the first coordinate of the first point
    +      points[1] is the second coordinate of the first point
    +      points[dim] is the first coordinate of the second point
    +
    +    ismalloc=True
    +      Qhull will call qh_free(points) on exit or input transformation
    +    ismalloc=False
    +      Qhull will allocate a new point array if needed for input transformation
    +
    +    qh.qhull_command
    +      is the option string.
    +      It is defined by qh_init_B(), qh_qhull_command(), or qh_initflags
    +
    +  returns:
    +    if qh.PROJECTinput or (qh.DELAUNAY and qh.PROJECTdelaunay)
    +      projects the input to a new point array
    +
    +        if qh.DELAUNAY,
    +          qh.hull_dim is increased by one
    +        if qh.ATinfinity,
    +          qh_projectinput adds point-at-infinity for Delaunay tri.
    +
    +    if qh.SCALEinput
    +      changes the upper and lower bounds of the input, see qh_scaleinput()
    +
    +    if qh.ROTATEinput
    +      rotates the input by a random rotation, see qh_rotateinput()
    +      if qh.DELAUNAY
    +        rotates about the last coordinate
    +
    +  notes:
    +    called after points are defined
    +    qh_errexit() may be used
    +*/
    +void qh_init_B(coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  qh_initqhull_globals(points, numpoints, dim, ismalloc);
    +  if (qhmem.LASTsize == 0)
    +    qh_initqhull_mem();
    +  /* mem.c and qset.c are initialized */
    +  qh_initqhull_buffers();
    +  qh_initthresholds(qh qhull_command);
    +  if (qh PROJECTinput || (qh DELAUNAY && qh PROJECTdelaunay))
    +    qh_projectinput();
    +  if (qh SCALEinput)
    +    qh_scaleinput();
    +  if (qh ROTATErandom >= 0) {
    +    qh_randommatrix(qh gm_matrix, qh hull_dim, qh gm_row);
    +    if (qh DELAUNAY) {
    +      int k, lastk= qh hull_dim-1;
    +      for (k=0; k < lastk; k++) {
    +        qh gm_row[k][lastk]= 0.0;
    +        qh gm_row[lastk][k]= 0.0;
    +      }
    +      qh gm_row[lastk][lastk]= 1.0;
    +    }
    +    qh_gram_schmidt(qh hull_dim, qh gm_row);
    +    qh_rotateinput(qh gm_row);
    +  }
    +} /* init_B */
    +
    +/*---------------------------------
    +
    +  qh_init_qhull_command( argc, argv )
    +    build qh.qhull_command from argc/argv
    +    Calls qh_exit if qhull_command is too short
    +
    +  returns:
    +    a space-delimited string of options (just as typed)
    +
    +  notes:
    +    makes option string easy to input and output
    +
    +    argc/argv may be 0/NULL
    +*/
    +void qh_init_qhull_command(int argc, char *argv[]) {
    +
    +  if (!qh_argv_to_command(argc, argv, qh qhull_command, (int)sizeof(qh qhull_command))){
    +    /* Assumes qh.ferr is defined. */
    +    qh_fprintf(qh ferr, 6033, "qhull input error: more than %d characters in command line\n",
    +          (int)sizeof(qh qhull_command));
    +    qh_exit(qh_ERRinput);  /* error reported, can not use qh_errexit */
    +  }
    +} /* init_qhull_command */
    +
    +/*---------------------------------
    +
    +  qh_initflags( commandStr )
    +    set flags and initialized constants from commandStr
    +    calls qh_exit() if qh->NOerrexit
    +
    +  returns:
    +    sets qh.qhull_command to command if needed
    +
    +  notes:
    +    ignores first word (e.g., "qhull d")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initthresholds() continues processing of 'Pdn' and 'PDn'
    +    'prompt' in unix.c for documentation
    +
    +  design:
    +    for each space-delimited option group
    +      if top-level option
    +        check syntax
    +        append appropriate option to option string
    +        set appropriate global variable or append printFormat to print options
    +      else
    +        for each sub-option
    +          check syntax
    +          append appropriate option to option string
    +          set appropriate global variable or append printFormat to print options
    +*/
    +void qh_initflags(char *command) {
    +  int k, i, lastproject;
    +  char *s= command, *t, *prev_s, *start, key;
    +  boolT isgeom= False, wasproject;
    +  realT r;
    +
    +  if(qh NOerrexit){/* without this comment, segfault in gcc 4.4.0 mingw32 */
    +    qh_fprintf(qh ferr, 6245, "qhull initflags error: qh.NOerrexit was not cleared before calling qh_initflags().  It should be cleared after setjmp().  Exit qhull.");
    +    qh_exit(6245);
    +  }
    +  if (command <= &qh qhull_command[0] || command > &qh qhull_command[0] + sizeof(qh qhull_command)) {
    +    if (command != &qh qhull_command[0]) {
    +      *qh qhull_command= '\0';
    +      strncat(qh qhull_command, command, sizeof(qh qhull_command)-strlen(qh qhull_command)-1);
    +    }
    +    while (*s && !isspace(*s))  /* skip program name */
    +      s++;
    +  }
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    prev_s= s;
    +    switch (*s++) {
    +    case 'd':
    +      qh_option("delaunay", NULL, NULL);
    +      qh DELAUNAY= True;
    +      break;
    +    case 'f':
    +      qh_option("facets", NULL, NULL);
    +      qh_appendprint(qh_PRINTfacets);
    +      break;
    +    case 'i':
    +      qh_option("incidence", NULL, NULL);
    +      qh_appendprint(qh_PRINTincidences);
    +      break;
    +    case 'm':
    +      qh_option("mathematica", NULL, NULL);
    +      qh_appendprint(qh_PRINTmathematica);
    +      break;
    +    case 'n':
    +      qh_option("normals", NULL, NULL);
    +      qh_appendprint(qh_PRINTnormals);
    +      break;
    +    case 'o':
    +      qh_option("offFile", NULL, NULL);
    +      qh_appendprint(qh_PRINToff);
    +      break;
    +    case 'p':
    +      qh_option("points", NULL, NULL);
    +      qh_appendprint(qh_PRINTpoints);
    +      break;
    +    case 's':
    +      qh_option("summary", NULL, NULL);
    +      qh PRINTsummary= True;
    +      break;
    +    case 'v':
    +      qh_option("voronoi", NULL, NULL);
    +      qh VORONOI= True;
    +      qh DELAUNAY= True;
    +      break;
    +    case 'A':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh ferr, 7002, "qhull warning: no maximum cosine angle given for option 'An'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh premerge_cos= -qh_strtod(s, &s);
    +          qh_option("Angle-premerge-", NULL, &qh premerge_cos);
    +          qh PREmerge= True;
    +        }else {
    +          qh postmerge_cos= qh_strtod(s, &s);
    +          qh_option("Angle-postmerge", NULL, &qh postmerge_cos);
    +          qh POSTmerge= True;
    +        }
    +        qh MERGING= True;
    +      }
    +      break;
    +    case 'C':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh ferr, 7003, "qhull warning: no centrum radius given for option 'Cn'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh premerge_centrum= -qh_strtod(s, &s);
    +          qh_option("Centrum-premerge-", NULL, &qh premerge_centrum);
    +          qh PREmerge= True;
    +        }else {
    +          qh postmerge_centrum= qh_strtod(s, &s);
    +          qh_option("Centrum-postmerge", NULL, &qh postmerge_centrum);
    +          qh POSTmerge= True;
    +        }
    +        qh MERGING= True;
    +      }
    +      break;
    +    case 'E':
    +      if (*s == '-')
    +        qh_fprintf(qh ferr, 7004, "qhull warning: negative maximum roundoff given for option 'An'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh ferr, 7005, "qhull warning: no maximum roundoff given for option 'En'.  Ignored.\n");
    +      else {
    +        qh DISTround= qh_strtod(s, &s);
    +        qh_option("Distance-roundoff", NULL, &qh DISTround);
    +        qh SETroundoff= True;
    +      }
    +      break;
    +    case 'H':
    +      start= s;
    +      qh HALFspace= True;
    +      qh_strtod(s, &t);
    +      while (t > s)  {
    +        if (*t && !isspace(*t)) {
    +          if (*t == ',')
    +            t++;
    +          else
    +            qh_fprintf(qh ferr, 7006, "qhull warning: origin for Halfspace intersection should be 'Hn,n,n,...'\n");
    +        }
    +        s= t;
    +        qh_strtod(s, &t);
    +      }
    +      if (start < t) {
    +        if (!(qh feasible_string= (char*)calloc((size_t)(t-start+1), (size_t)1))) {
    +          qh_fprintf(qh ferr, 6034, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +          qh_errexit(qh_ERRmem, NULL, NULL);
    +        }
    +        strncpy(qh feasible_string, start, (size_t)(t-start));
    +        qh_option("Halfspace-about", NULL, NULL);
    +        qh_option(qh feasible_string, NULL, NULL);
    +      }else
    +        qh_option("Halfspace", NULL, NULL);
    +      break;
    +    case 'R':
    +      if (!isdigit(*s))
    +        qh_fprintf(qh ferr, 7007, "qhull warning: missing random perturbation for option 'Rn'.  Ignored\n");
    +      else {
    +        qh RANDOMfactor= qh_strtod(s, &s);
    +        qh_option("Random_perturb", NULL, &qh RANDOMfactor);
    +        qh RANDOMdist= True;
    +      }
    +      break;
    +    case 'V':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh ferr, 7008, "qhull warning: missing visible distance for option 'Vn'.  Ignored\n");
    +      else {
    +        qh MINvisible= qh_strtod(s, &s);
    +        qh_option("Visible", NULL, &qh MINvisible);
    +      }
    +      break;
    +    case 'U':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh ferr, 7009, "qhull warning: missing coplanar distance for option 'Un'.  Ignored\n");
    +      else {
    +        qh MAXcoplanar= qh_strtod(s, &s);
    +        qh_option("U-coplanar", NULL, &qh MAXcoplanar);
    +      }
    +      break;
    +    case 'W':
    +      if (*s == '-')
    +        qh_fprintf(qh ferr, 7010, "qhull warning: negative outside width for option 'Wn'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh ferr, 7011, "qhull warning: missing outside width for option 'Wn'.  Ignored\n");
    +      else {
    +        qh MINoutside= qh_strtod(s, &s);
    +        qh_option("W-outside", NULL, &qh MINoutside);
    +        qh APPROXhull= True;
    +      }
    +      break;
    +    /************  sub menus ***************/
    +    case 'F':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option("Farea", NULL, NULL);
    +          qh_appendprint(qh_PRINTarea);
    +          qh GETarea= True;
    +          break;
    +        case 'A':
    +          qh_option("FArea-total", NULL, NULL);
    +          qh GETarea= True;
    +          break;
    +        case 'c':
    +          qh_option("Fcoplanars", NULL, NULL);
    +          qh_appendprint(qh_PRINTcoplanars);
    +          break;
    +        case 'C':
    +          qh_option("FCentrums", NULL, NULL);
    +          qh_appendprint(qh_PRINTcentrums);
    +          break;
    +        case 'd':
    +          qh_option("Fd-cdd-in", NULL, NULL);
    +          qh CDDinput= True;
    +          break;
    +        case 'D':
    +          qh_option("FD-cdd-out", NULL, NULL);
    +          qh CDDoutput= True;
    +          break;
    +        case 'F':
    +          qh_option("FFacets-xridge", NULL, NULL);
    +          qh_appendprint(qh_PRINTfacets_xridge);
    +          break;
    +        case 'i':
    +          qh_option("Finner", NULL, NULL);
    +          qh_appendprint(qh_PRINTinner);
    +          break;
    +        case 'I':
    +          qh_option("FIDs", NULL, NULL);
    +          qh_appendprint(qh_PRINTids);
    +          break;
    +        case 'm':
    +          qh_option("Fmerges", NULL, NULL);
    +          qh_appendprint(qh_PRINTmerges);
    +          break;
    +        case 'M':
    +          qh_option("FMaple", NULL, NULL);
    +          qh_appendprint(qh_PRINTmaple);
    +          break;
    +        case 'n':
    +          qh_option("Fneighbors", NULL, NULL);
    +          qh_appendprint(qh_PRINTneighbors);
    +          break;
    +        case 'N':
    +          qh_option("FNeighbors-vertex", NULL, NULL);
    +          qh_appendprint(qh_PRINTvneighbors);
    +          break;
    +        case 'o':
    +          qh_option("Fouter", NULL, NULL);
    +          qh_appendprint(qh_PRINTouter);
    +          break;
    +        case 'O':
    +          if (qh PRINToptions1st) {
    +            qh_option("FOptions", NULL, NULL);
    +            qh_appendprint(qh_PRINToptions);
    +          }else
    +            qh PRINToptions1st= True;
    +          break;
    +        case 'p':
    +          qh_option("Fpoint-intersect", NULL, NULL);
    +          qh_appendprint(qh_PRINTpointintersect);
    +          break;
    +        case 'P':
    +          qh_option("FPoint-nearest", NULL, NULL);
    +          qh_appendprint(qh_PRINTpointnearest);
    +          break;
    +        case 'Q':
    +          qh_option("FQhull", NULL, NULL);
    +          qh_appendprint(qh_PRINTqhull);
    +          break;
    +        case 's':
    +          qh_option("Fsummary", NULL, NULL);
    +          qh_appendprint(qh_PRINTsummary);
    +          break;
    +        case 'S':
    +          qh_option("FSize", NULL, NULL);
    +          qh_appendprint(qh_PRINTsize);
    +          qh GETarea= True;
    +          break;
    +        case 't':
    +          qh_option("Ftriangles", NULL, NULL);
    +          qh_appendprint(qh_PRINTtriangles);
    +          break;
    +        case 'v':
    +          /* option set in qh_initqhull_globals */
    +          qh_appendprint(qh_PRINTvertices);
    +          break;
    +        case 'V':
    +          qh_option("FVertex-average", NULL, NULL);
    +          qh_appendprint(qh_PRINTaverage);
    +          break;
    +        case 'x':
    +          qh_option("Fxtremes", NULL, NULL);
    +          qh_appendprint(qh_PRINTextremes);
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7012, "qhull warning: unknown 'F' output option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'G':
    +      isgeom= True;
    +      qh_appendprint(qh_PRINTgeom);
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option("Gall-points", NULL, NULL);
    +          qh PRINTdots= True;
    +          break;
    +        case 'c':
    +          qh_option("Gcentrums", NULL, NULL);
    +          qh PRINTcentrums= True;
    +          break;
    +        case 'h':
    +          qh_option("Gintersections", NULL, NULL);
    +          qh DOintersections= True;
    +          break;
    +        case 'i':
    +          qh_option("Ginner", NULL, NULL);
    +          qh PRINTinner= True;
    +          break;
    +        case 'n':
    +          qh_option("Gno-planes", NULL, NULL);
    +          qh PRINTnoplanes= True;
    +          break;
    +        case 'o':
    +          qh_option("Gouter", NULL, NULL);
    +          qh PRINTouter= True;
    +          break;
    +        case 'p':
    +          qh_option("Gpoints", NULL, NULL);
    +          qh PRINTcoplanar= True;
    +          break;
    +        case 'r':
    +          qh_option("Gridges", NULL, NULL);
    +          qh PRINTridges= True;
    +          break;
    +        case 't':
    +          qh_option("Gtransparent", NULL, NULL);
    +          qh PRINTtransparent= True;
    +          break;
    +        case 'v':
    +          qh_option("Gvertices", NULL, NULL);
    +          qh PRINTspheres= True;
    +          break;
    +        case 'D':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6035, "qhull input error: missing dimension for option 'GDn'\n");
    +          else {
    +            if (qh DROPdim >= 0)
    +              qh_fprintf(qh ferr, 7013, "qhull warning: can only drop one dimension.  Previous 'GD%d' ignored\n",
    +                   qh DROPdim);
    +            qh DROPdim= qh_strtol(s, &s);
    +            qh_option("GDrop-dim", &qh DROPdim, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7014, "qhull warning: unknown 'G' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'P':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'd': case 'D':  /* see qh_initthresholds() */
    +          key= s[-1];
    +          i= qh_strtol(s, &s);
    +          r= 0;
    +          if (*s == ':') {
    +            s++;
    +            r= qh_strtod(s, &s);
    +          }
    +          if (key == 'd')
    +            qh_option("Pdrop-facets-dim-less", &i, &r);
    +          else
    +            qh_option("PDrop-facets-dim-more", &i, &r);
    +          break;
    +        case 'g':
    +          qh_option("Pgood-facets", NULL, NULL);
    +          qh PRINTgood= True;
    +          break;
    +        case 'G':
    +          qh_option("PGood-facet-neighbors", NULL, NULL);
    +          qh PRINTneighbors= True;
    +          break;
    +        case 'o':
    +          qh_option("Poutput-forced", NULL, NULL);
    +          qh FORCEoutput= True;
    +          break;
    +        case 'p':
    +          qh_option("Pprecision-ignore", NULL, NULL);
    +          qh PRINTprecision= False;
    +          break;
    +        case 'A':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6036, "qhull input error: missing facet count for keep area option 'PAn'\n");
    +          else {
    +            qh KEEParea= qh_strtol(s, &s);
    +            qh_option("PArea-keep", &qh KEEParea, NULL);
    +            qh GETarea= True;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6037, "qhull input error: missing facet area for option 'PFn'\n");
    +          else {
    +            qh KEEPminArea= qh_strtod(s, &s);
    +            qh_option("PFacet-area-keep", NULL, &qh KEEPminArea);
    +            qh GETarea= True;
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6038, "qhull input error: missing merge count for option 'PMn'\n");
    +          else {
    +            qh KEEPmerge= qh_strtol(s, &s);
    +            qh_option("PMerge-keep", &qh KEEPmerge, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7015, "qhull warning: unknown 'P' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'Q':
    +      lastproject= -1;
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'b': case 'B':  /* handled by qh_initthresholds */
    +          key= s[-1];
    +          if (key == 'b' && *s == 'B') {
    +            s++;
    +            r= qh_DEFAULTbox;
    +            qh SCALEinput= True;
    +            qh_option("QbBound-unit-box", NULL, &r);
    +            break;
    +          }
    +          if (key == 'b' && *s == 'b') {
    +            s++;
    +            qh SCALElast= True;
    +            qh_option("Qbbound-last", NULL, NULL);
    +            break;
    +          }
    +          k= qh_strtol(s, &s);
    +          r= 0.0;
    +          wasproject= False;
    +          if (*s == ':') {
    +            s++;
    +            if ((r= qh_strtod(s, &s)) == 0.0) {
    +              t= s;            /* need true dimension for memory allocation */
    +              while (*t && !isspace(*t)) {
    +                if (toupper(*t++) == 'B'
    +                 && k == qh_strtol(t, &t)
    +                 && *t++ == ':'
    +                 && qh_strtod(t, &t) == 0.0) {
    +                  qh PROJECTinput++;
    +                  trace2((qh ferr, 2004, "qh_initflags: project dimension %d\n", k));
    +                  qh_option("Qb-project-dim", &k, NULL);
    +                  wasproject= True;
    +                  lastproject= k;
    +                  break;
    +                }
    +              }
    +            }
    +          }
    +          if (!wasproject) {
    +            if (lastproject == k && r == 0.0)
    +              lastproject= -1;  /* doesn't catch all possible sequences */
    +            else if (key == 'b') {
    +              qh SCALEinput= True;
    +              if (r == 0.0)
    +                r= -qh_DEFAULTbox;
    +              qh_option("Qbound-dim-low", &k, &r);
    +            }else {
    +              qh SCALEinput= True;
    +              if (r == 0.0)
    +                r= qh_DEFAULTbox;
    +              qh_option("QBound-dim-high", &k, &r);
    +            }
    +          }
    +          break;
    +        case 'c':
    +          qh_option("Qcoplanar-keep", NULL, NULL);
    +          qh KEEPcoplanar= True;
    +          break;
    +        case 'f':
    +          qh_option("Qfurthest-outside", NULL, NULL);
    +          qh BESToutside= True;
    +          break;
    +        case 'g':
    +          qh_option("Qgood-facets-only", NULL, NULL);
    +          qh ONLYgood= True;
    +          break;
    +        case 'i':
    +          qh_option("Qinterior-keep", NULL, NULL);
    +          qh KEEPinside= True;
    +          break;
    +        case 'm':
    +          qh_option("Qmax-outside-only", NULL, NULL);
    +          qh ONLYmax= True;
    +          break;
    +        case 'r':
    +          qh_option("Qrandom-outside", NULL, NULL);
    +          qh RANDOMoutside= True;
    +          break;
    +        case 's':
    +          qh_option("Qsearch-initial-simplex", NULL, NULL);
    +          qh ALLpoints= True;
    +          break;
    +        case 't':
    +          qh_option("Qtriangulate", NULL, NULL);
    +          qh TRIangulate= True;
    +          break;
    +        case 'T':
    +          qh_option("QTestPoints", NULL, NULL);
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 6039, "qhull input error: missing number of test points for option 'QTn'\n");
    +          else {
    +            qh TESTpoints= qh_strtol(s, &s);
    +            qh_option("QTestPoints", &qh TESTpoints, NULL);
    +          }
    +          break;
    +        case 'u':
    +          qh_option("QupperDelaunay", NULL, NULL);
    +          qh UPPERdelaunay= True;
    +          break;
    +        case 'v':
    +          qh_option("Qvertex-neighbors-convex", NULL, NULL);
    +          qh TESTvneighbors= True;
    +          break;
    +        case 'x':
    +          qh_option("Qxact-merge", NULL, NULL);
    +          qh MERGEexact= True;
    +          break;
    +        case 'z':
    +          qh_option("Qz-infinity-point", NULL, NULL);
    +          qh ATinfinity= True;
    +          break;
    +        case '0':
    +          qh_option("Q0-no-premerge", NULL, NULL);
    +          qh NOpremerge= True;
    +          break;
    +        case '1':
    +          if (!isdigit(*s)) {
    +            qh_option("Q1-no-angle-sort", NULL, NULL);
    +            qh ANGLEmerge= False;
    +            break;
    +          }
    +          switch (*s++) {
    +          case '0':
    +            qh_option("Q10-no-narrow", NULL, NULL);
    +            qh NOnarrow= True;
    +            break;
    +          case '1':
    +            qh_option("Q11-trinormals Qtriangulate", NULL, NULL);
    +            qh TRInormals= True;
    +            qh TRIangulate= True;
    +            break;
    +          case '2':
    +            qh_option("Q12-no-wide-dup", NULL, NULL);
    +            qh NOwide= True;
    +            break;
    +          default:
    +            s--;
    +            qh_fprintf(qh ferr, 7016, "qhull warning: unknown 'Q' qhull option 1%c, rest ignored\n", (int)s[0]);
    +            while (*++s && !isspace(*s));
    +            break;
    +          }
    +          break;
    +        case '2':
    +          qh_option("Q2-no-merge-independent", NULL, NULL);
    +          qh MERGEindependent= False;
    +          goto LABELcheckdigit;
    +          break; /* no warnings */
    +        case '3':
    +          qh_option("Q3-no-merge-vertices", NULL, NULL);
    +          qh MERGEvertices= False;
    +        LABELcheckdigit:
    +          if (isdigit(*s))
    +            qh_fprintf(qh ferr, 7017, "qhull warning: can not follow '1', '2', or '3' with a digit.  '%c' skipped.\n",
    +                     *s++);
    +          break;
    +        case '4':
    +          qh_option("Q4-avoid-old-into-new", NULL, NULL);
    +          qh AVOIDold= True;
    +          break;
    +        case '5':
    +          qh_option("Q5-no-check-outer", NULL, NULL);
    +          qh SKIPcheckmax= True;
    +          break;
    +        case '6':
    +          qh_option("Q6-no-concave-merge", NULL, NULL);
    +          qh SKIPconvex= True;
    +          break;
    +        case '7':
    +          qh_option("Q7-no-breadth-first", NULL, NULL);
    +          qh VIRTUALmemory= True;
    +          break;
    +        case '8':
    +          qh_option("Q8-no-near-inside", NULL, NULL);
    +          qh NOnearinside= True;
    +          break;
    +        case '9':
    +          qh_option("Q9-pick-furthest", NULL, NULL);
    +          qh PICKfurthest= True;
    +          break;
    +        case 'G':
    +          i= qh_strtol(s, &t);
    +          if (qh GOODpoint)
    +            qh_fprintf(qh ferr, 7018, "qhull warning: good point already defined for option 'QGn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh ferr, 7019, "qhull warning: missing good point id for option 'QGn'.  Ignored\n");
    +          else if (i < 0 || *s == '-') {
    +            qh GOODpoint= i-1;
    +            qh_option("QGood-if-dont-see-point", &i, NULL);
    +          }else {
    +            qh GOODpoint= i+1;
    +            qh_option("QGood-if-see-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'J':
    +          if (!isdigit(*s) && *s != '-')
    +            qh JOGGLEmax= 0.0;
    +          else {
    +            qh JOGGLEmax= (realT) qh_strtod(s, &s);
    +            qh_option("QJoggle", NULL, &qh JOGGLEmax);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s) && *s != '-')
    +            qh_fprintf(qh ferr, 7020, "qhull warning: missing random seed for option 'QRn'.  Ignored\n");
    +          else {
    +            qh ROTATErandom= i= qh_strtol(s, &s);
    +            if (i > 0)
    +              qh_option("QRotate-id", &i, NULL );
    +            else if (i < -1)
    +              qh_option("QRandom-seed", &i, NULL );
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (qh GOODvertex)
    +            qh_fprintf(qh ferr, 7021, "qhull warning: good vertex already defined for option 'QVn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh ferr, 7022, "qhull warning: no good point id given for option 'QVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh GOODvertex= i - 1;
    +            qh_option("QV-good-facets-not-point", &i, NULL);
    +          }else {
    +            qh_option("QV-good-facets-point", &i, NULL);
    +            qh GOODvertex= i + 1;
    +          }
    +          s= t;
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7023, "qhull warning: unknown 'Q' qhull option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'T':
    +      while (*s && !isspace(*s)) {
    +        if (isdigit(*s) || *s == '-')
    +          qh IStracing= qh_strtol(s, &s);
    +        else switch (*s++) {
    +        case 'a':
    +          qh_option("Tannotate-output", NULL, NULL);
    +          qh ANNOTATEoutput= True;
    +          break;
    +        case 'c':
    +          qh_option("Tcheck-frequently", NULL, NULL);
    +          qh CHECKfrequently= True;
    +          break;
    +        case 's':
    +          qh_option("Tstatistics", NULL, NULL);
    +          qh PRINTstatistics= True;
    +          break;
    +        case 'v':
    +          qh_option("Tverify", NULL, NULL);
    +          qh VERIFYoutput= True;
    +          break;
    +        case 'z':
    +          if (qh ferr == qh_FILEstderr) {
    +            /* The C++ interface captures the output in qh_fprint_qhull() */
    +            qh_option("Tz-stdout", NULL, NULL);
    +            qh USEstdout= True;
    +          }else if (!qh fout)
    +            qh_fprintf(qh ferr, 7024, "qhull warning: output file undefined(stdout).  Option 'Tz' ignored.\n");
    +          else {
    +            qh_option("Tz-stdout", NULL, NULL);
    +            qh USEstdout= True;
    +            qh ferr= qh fout;
    +            qhmem.ferr= qh fout;
    +          }
    +          break;
    +        case 'C':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7025, "qhull warning: missing point id for cone for trace option 'TCn'.  Ignored\n");
    +          else {
    +            i= qh_strtol(s, &s);
    +            qh_option("TCone-stop", &i, NULL);
    +            qh STOPcone= i + 1;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7026, "qhull warning: missing frequency count for trace option 'TFn'.  Ignored\n");
    +          else {
    +            qh REPORTfreq= qh_strtol(s, &s);
    +            qh_option("TFacet-log", &qh REPORTfreq, NULL);
    +            qh REPORTfreq2= qh REPORTfreq/2;  /* for tracemerging() */
    +          }
    +          break;
    +        case 'I':
    +          if (!isspace(*s))
    +            qh_fprintf(qh ferr, 7027, "qhull warning: missing space between 'TI' and filename, %s\n", s);
    +          while (isspace(*s))
    +            s++;
    +          t= qh_skipfilename(s);
    +          {
    +            char filename[qh_FILENAMElen];
    +
    +            qh_copyfilename(filename, (int)sizeof(filename), s, (int)(t-s));   /* WARN64 */
    +            s= t;
    +            if (!freopen(filename, "r", stdin)) {
    +              qh_fprintf(qh ferr, 6041, "qhull error: could not open file \"%s\".", filename);
    +              qh_errexit(qh_ERRinput, NULL, NULL);
    +            }else {
    +              qh_option("TInput-file", NULL, NULL);
    +              qh_option(filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'O':
    +            if (!isspace(*s))
    +                qh_fprintf(qh ferr, 7028, "qhull warning: missing space between 'TO' and filename, %s\n", s);
    +            while (isspace(*s))
    +                s++;
    +            t= qh_skipfilename(s);
    +            {
    +              char filename[qh_FILENAMElen];
    +
    +              qh_copyfilename(filename, (int)sizeof(filename), s, (int)(t-s));  /* WARN64 */
    +              s= t;
    +              if (!qh fout) {
    +                qh_fprintf(qh ferr, 6266, "qhull input warning: qh.fout was not set by caller.  Cannot use option 'TO' to redirect output.  Ignoring option 'TO'\n");
    +              }else if (!freopen(filename, "w", qh fout)) {
    +                qh_fprintf(qh ferr, 6044, "qhull error: could not open file \"%s\".", filename);
    +                qh_errexit(qh_ERRinput, NULL, NULL);
    +              }else {
    +                qh_option("TOutput-file", NULL, NULL);
    +              qh_option(filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'P':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7029, "qhull warning: missing point id for trace option 'TPn'.  Ignored\n");
    +          else {
    +            qh TRACEpoint= qh_strtol(s, &s);
    +            qh_option("Trace-point", &qh TRACEpoint, NULL);
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7030, "qhull warning: missing merge id for trace option 'TMn'.  Ignored\n");
    +          else {
    +            qh TRACEmerge= qh_strtol(s, &s);
    +            qh_option("Trace-merge", &qh TRACEmerge, NULL);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7031, "qhull warning: missing rerun count for trace option 'TRn'.  Ignored\n");
    +          else {
    +            qh RERUN= qh_strtol(s, &s);
    +            qh_option("TRerun", &qh RERUN, NULL);
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (s == t)
    +            qh_fprintf(qh ferr, 7032, "qhull warning: missing furthest point id for trace option 'TVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh STOPpoint= i - 1;
    +            qh_option("TV-stop-before-point", &i, NULL);
    +          }else {
    +            qh STOPpoint= i + 1;
    +            qh_option("TV-stop-after-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'W':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh ferr, 7033, "qhull warning: missing max width for trace option 'TWn'.  Ignored\n");
    +          else {
    +            qh TRACEdist= (realT) qh_strtod(s, &s);
    +            qh_option("TWide-trace", NULL, &qh TRACEdist);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh ferr, 7034, "qhull warning: unknown 'T' trace option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    default:
    +      qh_fprintf(qh ferr, 7035, "qhull warning: unknown flag %c(%x)\n", (int)s[-1],
    +               (int)s[-1]);
    +      break;
    +    }
    +    if (s-1 == prev_s && *s && !isspace(*s)) {
    +      qh_fprintf(qh ferr, 7036, "qhull warning: missing space after flag %c(%x); reserved for menu. Skipped.\n",
    +               (int)*prev_s, (int)*prev_s);
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +  }
    +  if (qh STOPcone && qh JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh ferr, 7078, "qhull warning: 'TCn' (stopCone) ignored when used with 'QJn' (joggle)\n");
    +  if (isgeom && !qh FORCEoutput && qh PRINTout[1])
    +    qh_fprintf(qh ferr, 7037, "qhull warning: additional output formats are not compatible with Geomview\n");
    +  /* set derived values in qh_initqhull_globals */
    +} /* initflags */
    +
    +
    +/*---------------------------------
    +
    +  qh_initqhull_buffers()
    +    initialize global memory buffers
    +
    +  notes:
    +    must match qh_freebuffers()
    +*/
    +void qh_initqhull_buffers(void) {
    +  int k;
    +
    +  qh TEMPsize= (qhmem.LASTsize - sizeof(setT))/SETelemsize;
    +  if (qh TEMPsize <= 0 || qh TEMPsize > qhmem.LASTsize)
    +    qh TEMPsize= 8;  /* e.g., if qh_NOmem */
    +  qh other_points= qh_setnew(qh TEMPsize);
    +  qh del_vertices= qh_setnew(qh TEMPsize);
    +  qh coplanarfacetset= qh_setnew(qh TEMPsize);
    +  qh NEARzero= (realT *)qh_memalloc(qh hull_dim * sizeof(realT));
    +  qh lower_threshold= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  qh upper_threshold= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  qh lower_bound= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  qh upper_bound= (realT *)qh_memalloc((qh input_dim+1) * sizeof(realT));
    +  for (k=qh input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh lower_threshold[k]= -REALmax;
    +    qh upper_threshold[k]= REALmax;
    +    qh lower_bound[k]= -REALmax;
    +    qh upper_bound[k]= REALmax;
    +  }
    +  qh gm_matrix= (coordT *)qh_memalloc((qh hull_dim+1) * qh hull_dim * sizeof(coordT));
    +  qh gm_row= (coordT **)qh_memalloc((qh hull_dim+1) * sizeof(coordT *));
    +} /* initqhull_buffers */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_globals( points, numpoints, dim, ismalloc )
    +    initialize globals
    +    if ismalloc
    +      points were malloc'd and qhull should free at end
    +
    +  returns:
    +    sets qh.first_point, num_points, input_dim, hull_dim and others
    +    seeds random number generator (seed=1 if tracing)
    +    modifies qh.hull_dim if ((qh.DELAUNAY and qh.PROJECTdelaunay) or qh.PROJECTinput)
    +    adjust user flags as needed
    +    also checks DIM3 dependencies and constants
    +
    +  notes:
    +    do not use qh_point() since an input transformation may move them elsewhere
    +
    +  see:
    +    qh_initqhull_start() sets default values for non-zero globals
    +
    +  design:
    +    initialize points array from input arguments
    +    test for qh.ZEROcentrum
    +      (i.e., use opposite vertex instead of cetrum for convexity testing)
    +    initialize qh.CENTERtype, qh.normal_size,
    +      qh.center_size, qh.TRACEpoint/level,
    +    initialize and test random numbers
    +    qh_initqhull_outputflags() -- adjust and test output flags
    +*/
    +void qh_initqhull_globals(coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  int seed, pointsneeded, extra= 0, i, randi, k;
    +  realT randr;
    +  realT factorial;
    +
    +  time_t timedata;
    +
    +  trace0((qh ferr, 13, "qh_initqhull_globals: for %s | %s\n", qh rbox_command,
    +      qh qhull_command));
    +  qh POINTSmalloc= ismalloc;
    +  qh first_point= points;
    +  qh num_points= numpoints;
    +  qh hull_dim= qh input_dim= dim;
    +  if (!qh NOpremerge && !qh MERGEexact && !qh PREmerge && qh JOGGLEmax > REALmax/2) {
    +    qh MERGING= True;
    +    if (qh hull_dim <= 4) {
    +      qh PREmerge= True;
    +      qh_option("_pre-merge", NULL, NULL);
    +    }else {
    +      qh MERGEexact= True;
    +      qh_option("Qxact_merge", NULL, NULL);
    +    }
    +  }else if (qh MERGEexact)
    +    qh MERGING= True;
    +  if (!qh NOpremerge && qh JOGGLEmax > REALmax/2) {
    +#ifdef qh_NOmerge
    +    qh JOGGLEmax= 0.0;
    +#endif
    +  }
    +  if (qh TRIangulate && qh JOGGLEmax < REALmax/2 && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 7038, "qhull warning: joggle('QJ') always produces simplicial output.  Triangulated output('Qt') does nothing.\n");
    +  if (qh JOGGLEmax < REALmax/2 && qh DELAUNAY && !qh SCALEinput && !qh SCALElast) {
    +    qh SCALElast= True;
    +    qh_option("Qbbound-last-qj", NULL, NULL);
    +  }
    +  if (qh MERGING && !qh POSTmerge && qh premerge_cos > REALmax/2
    +  && qh premerge_centrum == 0) {
    +    qh ZEROcentrum= True;
    +    qh ZEROall_ok= True;
    +    qh_option("_zero-centrum", NULL, NULL);
    +  }
    +  if (qh JOGGLEmax < REALmax/2 && REALepsilon > 2e-8 && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 7039, "qhull warning: real epsilon, %2.2g, is probably too large for joggle('QJn')\nRecompile with double precision reals(see user.h).\n",
    +          REALepsilon);
    +#ifdef qh_NOmerge
    +  if (qh MERGING) {
    +    qh_fprintf(qh ferr, 6045, "qhull input error: merging not installed(qh_NOmerge + 'Qx', 'Cn' or 'An')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +#endif
    +  if (qh DELAUNAY && qh KEEPcoplanar && !qh KEEPinside) {
    +    qh KEEPinside= True;
    +    qh_option("Qinterior-keep", NULL, NULL);
    +  }
    +  if (qh DELAUNAY && qh HALFspace) {
    +    qh_fprintf(qh ferr, 6046, "qhull input error: can not use Delaunay('d') or Voronoi('v') with halfspace intersection('H')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (!qh DELAUNAY && (qh UPPERdelaunay || qh ATinfinity)) {
    +    qh_fprintf(qh ferr, 6047, "qhull input error: use upper-Delaunay('Qu') or infinity-point('Qz') with Delaunay('d') or Voronoi('v')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh UPPERdelaunay && qh ATinfinity) {
    +    qh_fprintf(qh ferr, 6048, "qhull input error: can not use infinity-point('Qz') with upper-Delaunay('Qu')\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh SCALElast && !qh DELAUNAY && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 7040, "qhull input warning: option 'Qbb' (scale-last-coordinate) is normally used with 'd' or 'v'\n");
    +  qh DOcheckmax= (!qh SKIPcheckmax && qh MERGING );
    +  qh KEEPnearinside= (qh DOcheckmax && !(qh KEEPinside && qh KEEPcoplanar)
    +                          && !qh NOnearinside);
    +  if (qh MERGING)
    +    qh CENTERtype= qh_AScentrum;
    +  else if (qh VORONOI)
    +    qh CENTERtype= qh_ASvoronoi;
    +  if (qh TESTvneighbors && !qh MERGING) {
    +    qh_fprintf(qh ferr, 6049, "qhull input error: test vertex neighbors('Qv') needs a merge option\n");
    +    qh_errexit(qh_ERRinput, NULL ,NULL);
    +  }
    +  if (qh PROJECTinput || (qh DELAUNAY && qh PROJECTdelaunay)) {
    +    qh hull_dim -= qh PROJECTinput;
    +    if (qh DELAUNAY) {
    +      qh hull_dim++;
    +      if (qh ATinfinity)
    +        extra= 1;
    +    }
    +  }
    +  if (qh hull_dim <= 1) {
    +    qh_fprintf(qh ferr, 6050, "qhull error: dimension %d must be > 1\n", qh hull_dim);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  for (k=2, factorial=1.0; k < qh hull_dim; k++)
    +    factorial *= k;
    +  qh AREAfactor= 1.0 / factorial;
    +  trace2((qh ferr, 2005, "qh_initqhull_globals: initialize globals.  dim %d numpoints %d malloc? %d projected %d to hull_dim %d\n",
    +        dim, numpoints, ismalloc, qh PROJECTinput, qh hull_dim));
    +  qh normal_size= qh hull_dim * sizeof(coordT);
    +  qh center_size= qh normal_size - sizeof(coordT);
    +  pointsneeded= qh hull_dim+1;
    +  if (qh hull_dim > qh_DIMmergeVertex) {
    +    qh MERGEvertices= False;
    +    qh_option("Q3-no-merge-vertices-dim-high", NULL, NULL);
    +  }
    +  if (qh GOODpoint)
    +    pointsneeded++;
    +#ifdef qh_NOtrace
    +  if (qh IStracing) {
    +    qh_fprintf(qh ferr, 6051, "qhull input error: tracing is not installed(qh_NOtrace in user.h)");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +#endif
    +  if (qh RERUN > 1) {
    +    qh TRACElastrun= qh IStracing; /* qh_build_withrestart duplicates next conditional */
    +    if (qh IStracing != -1)
    +      qh IStracing= 0;
    +  }else if (qh TRACEpoint != qh_IDunknown || qh TRACEdist < REALmax/2 || qh TRACEmerge) {
    +    qh TRACElevel= (qh IStracing? qh IStracing : 3);
    +    qh IStracing= 0;
    +  }
    +  if (qh ROTATErandom == 0 || qh ROTATErandom == -1) {
    +    seed= (int)time(&timedata);
    +    if (qh ROTATErandom  == -1) {
    +      seed= -seed;
    +      qh_option("QRandom-seed", &seed, NULL );
    +    }else
    +      qh_option("QRotate-random", &seed, NULL);
    +    qh ROTATErandom= seed;
    +  }
    +  seed= qh ROTATErandom;
    +  if (seed == INT_MIN)    /* default value */
    +    seed= 1;
    +  else if (seed < 0)
    +    seed= -seed;
    +  qh_RANDOMseed_(seed);
    +  randr= 0.0;
    +  for (i=1000; i--; ) {
    +    randi= qh_RANDOMint;
    +    randr += randi;
    +    if (randi > qh_RANDOMmax) {
    +      qh_fprintf(qh ferr, 8036, "\
    +qhull configuration error (qh_RANDOMmax in user.h):\n\
    +   random integer %d > qh_RANDOMmax(%.8g)\n",
    +               randi, qh_RANDOMmax);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  qh_RANDOMseed_(seed);
    +  randr = randr/1000;
    +  if (randr < qh_RANDOMmax * 0.1
    +  || randr > qh_RANDOMmax * 0.9)
    +    qh_fprintf(qh ferr, 8037, "\
    +qhull configuration warning (qh_RANDOMmax in user.h):\n\
    +   average of 1000 random integers (%.2g) is much different than expected (%.2g).\n\
    +   Is qh_RANDOMmax (%.2g) wrong?\n",
    +             randr, qh_RANDOMmax * 0.5, qh_RANDOMmax);
    +  qh RANDOMa= 2.0 * qh RANDOMfactor/qh_RANDOMmax;
    +  qh RANDOMb= 1.0 - qh RANDOMfactor;
    +  if (qh_HASHfactor < 1.1) {
    +    qh_fprintf(qh ferr, 6052, "qhull internal error (qh_initqhull_globals): qh_HASHfactor %d must be at least 1.1.  Qhull uses linear hash probing\n",
    +      qh_HASHfactor);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (numpoints+extra < pointsneeded) {
    +    qh_fprintf(qh ferr, 6214, "qhull input error: not enough points(%d) to construct initial simplex (need %d)\n",
    +            numpoints, pointsneeded);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  qh_initqhull_outputflags();
    +} /* initqhull_globals */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_mem(  )
    +    initialize mem.c for qhull
    +    qh.hull_dim and qh.normal_size determine some of the allocation sizes
    +    if qh.MERGING,
    +      includes ridgeT
    +    calls qh_user_memsizes() to add up to 10 additional sizes for quick allocation
    +      (see numsizes below)
    +
    +  returns:
    +    mem.c already for qh_memalloc/qh_memfree (errors if called beforehand)
    +
    +  notes:
    +    qh_produceoutput() prints memsizes
    +
    +*/
    +void qh_initqhull_mem(void) {
    +  int numsizes;
    +  int i;
    +
    +  numsizes= 8+10;
    +  qh_meminitbuffers(qh IStracing, qh_MEMalign, numsizes,
    +                     qh_MEMbufsize,qh_MEMinitbuf);
    +  qh_memsize((int)sizeof(vertexT));
    +  if (qh MERGING) {
    +    qh_memsize((int)sizeof(ridgeT));
    +    qh_memsize((int)sizeof(mergeT));
    +  }
    +  qh_memsize((int)sizeof(facetT));
    +  i= sizeof(setT) + (qh hull_dim - 1) * SETelemsize;  /* ridge.vertices */
    +  qh_memsize(i);
    +  qh_memsize(qh normal_size);        /* normal */
    +  i += SETelemsize;                 /* facet.vertices, .ridges, .neighbors */
    +  qh_memsize(i);
    +  qh_user_memsizes();
    +  qh_memsetup();
    +} /* initqhull_mem */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_outputflags
    +    initialize flags concerned with output
    +
    +  returns:
    +    adjust user flags as needed
    +
    +  see:
    +    qh_clear_outputflags() resets the flags
    +
    +  design:
    +    test for qh.PRINTgood (i.e., only print 'good' facets)
    +    check for conflicting print output options
    +*/
    +void qh_initqhull_outputflags(void) {
    +  boolT printgeom= False, printmath= False, printcoplanar= False;
    +  int i;
    +
    +  trace3((qh ferr, 3024, "qh_initqhull_outputflags: %s\n", qh qhull_command));
    +  if (!(qh PRINTgood || qh PRINTneighbors)) {
    +    if (qh KEEParea || qh KEEPminArea < REALmax/2 || qh KEEPmerge || qh DELAUNAY
    +        || (!qh ONLYgood && (qh GOODvertex || qh GOODpoint))) {
    +      qh PRINTgood= True;
    +      qh_option("Pgood", NULL, NULL);
    +    }
    +  }
    +  if (qh PRINTtransparent) {
    +    if (qh hull_dim != 4 || !qh DELAUNAY || qh VORONOI || qh DROPdim >= 0) {
    +      qh_fprintf(qh ferr, 6215, "qhull input error: transparent Delaunay('Gt') needs 3-d Delaunay('d') w/o 'GDn'\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    qh DROPdim = 3;
    +    qh PRINTridges = True;
    +  }
    +  for (i=qh_PRINTEND; i--; ) {
    +    if (qh PRINTout[i] == qh_PRINTgeom)
    +      printgeom= True;
    +    else if (qh PRINTout[i] == qh_PRINTmathematica || qh PRINTout[i] == qh_PRINTmaple)
    +      printmath= True;
    +    else if (qh PRINTout[i] == qh_PRINTcoplanars)
    +      printcoplanar= True;
    +    else if (qh PRINTout[i] == qh_PRINTpointnearest)
    +      printcoplanar= True;
    +    else if (qh PRINTout[i] == qh_PRINTpointintersect && !qh HALFspace) {
    +      qh_fprintf(qh ferr, 6053, "qhull input error: option 'Fp' is only used for \nhalfspace intersection('Hn,n,n').\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }else if (qh PRINTout[i] == qh_PRINTtriangles && (qh HALFspace || qh VORONOI)) {
    +      qh_fprintf(qh ferr, 6054, "qhull input error: option 'Ft' is not available for Voronoi vertices or halfspace intersection\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }else if (qh PRINTout[i] == qh_PRINTcentrums && qh VORONOI) {
    +      qh_fprintf(qh ferr, 6055, "qhull input error: option 'FC' is not available for Voronoi vertices('v')\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }else if (qh PRINTout[i] == qh_PRINTvertices) {
    +      if (qh VORONOI)
    +        qh_option("Fvoronoi", NULL, NULL);
    +      else
    +        qh_option("Fvertices", NULL, NULL);
    +    }
    +  }
    +  if (printcoplanar && qh DELAUNAY && qh JOGGLEmax < REALmax/2) {
    +    if (qh PRINTprecision)
    +      qh_fprintf(qh ferr, 7041, "qhull input warning: 'QJ' (joggle) will usually prevent coincident input sites for options 'Fc' and 'FP'\n");
    +  }
    +  if (printmath && (qh hull_dim > 3 || qh VORONOI)) {
    +    qh_fprintf(qh ferr, 6056, "qhull input error: Mathematica and Maple output is only available for 2-d and 3-d convex hulls and 2-d Delaunay triangulations\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (printgeom) {
    +    if (qh hull_dim > 4) {
    +      qh_fprintf(qh ferr, 6057, "qhull input error: Geomview output is only available for 2-d, 3-d and 4-d\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh PRINTnoplanes && !(qh PRINTcoplanar + qh PRINTcentrums
    +     + qh PRINTdots + qh PRINTspheres + qh DOintersections + qh PRINTridges)) {
    +      qh_fprintf(qh ferr, 6058, "qhull input error: no output specified for Geomview\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh VORONOI && (qh hull_dim > 3 || qh DROPdim >= 0)) {
    +      qh_fprintf(qh ferr, 6059, "qhull input error: Geomview output for Voronoi diagrams only for 2-d\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    /* can not warn about furthest-site Geomview output: no lower_threshold */
    +    if (qh hull_dim == 4 && qh DROPdim == -1 &&
    +        (qh PRINTcoplanar || qh PRINTspheres || qh PRINTcentrums)) {
    +      qh_fprintf(qh ferr, 7042, "qhull input warning: coplanars, vertices, and centrums output not\n\
    +available for 4-d output(ignored).  Could use 'GDn' instead.\n");
    +      qh PRINTcoplanar= qh PRINTspheres= qh PRINTcentrums= False;
    +    }
    +  }
    +  if (!qh KEEPcoplanar && !qh KEEPinside && !qh ONLYgood) {
    +    if ((qh PRINTcoplanar && qh PRINTspheres) || printcoplanar) {
    +      if (qh QHULLfinished) {
    +        qh_fprintf(qh ferr, 7072, "qhull output warning: ignoring coplanar points, option 'Qc' was not set for the first run of qhull.\n");
    +      }else {
    +        qh KEEPcoplanar = True;
    +        qh_option("Qcoplanar", NULL, NULL);
    +      }
    +    }
    +  }
    +  qh PRINTdim= qh hull_dim;
    +  if (qh DROPdim >=0) {    /* after Geomview checks */
    +    if (qh DROPdim < qh hull_dim) {
    +      qh PRINTdim--;
    +      if (!printgeom || qh hull_dim < 3)
    +        qh_fprintf(qh ferr, 7043, "qhull input warning: drop dimension 'GD%d' is only available for 3-d/4-d Geomview\n", qh DROPdim);
    +    }else
    +      qh DROPdim= -1;
    +  }else if (qh VORONOI) {
    +    qh DROPdim= qh hull_dim-1;
    +    qh PRINTdim= qh hull_dim-1;
    +  }
    +} /* qh_initqhull_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start( infile, outfile, errfile )
    +    allocate memory if needed and call qh_initqhull_start2()
    +*/
    +void qh_initqhull_start(FILE *infile, FILE *outfile, FILE *errfile) {
    +
    +#if qh_QHpointer
    +  if (qh_qh) {
    +    qh_fprintf(errfile, 6205, "qhull error (qh_initqhull_start): qh_qh already defined.  Call qh_save_qhull() first\n");
    +    qh_exit(qh_ERRqhull);  /* no error handler */
    +  }
    +  if (!(qh_qh= (qhT *)qh_malloc(sizeof(qhT)))) {
    +    qh_fprintf(errfile, 6060, "qhull error (qh_initqhull_start): insufficient memory\n");
    +    qh_exit(qh_ERRmem);  /* no error handler */
    +  }
    +#endif
    +  qh_initstatistics();
    +  qh_initqhull_start2(infile, outfile, errfile);
    +} /* initqhull_start */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start2( infile, outfile, errfile )
    +    start initialization of qhull
    +    initialize statistics, stdio, default values for global variables
    +    assumes qh_qh is defined
    +  notes:
    +    report errors elsewhere, error handling and g_qhull_output [Qhull.cpp, QhullQh()] not in initialized
    +  see:
    +    qh_maxmin() determines the precision constants
    +    qh_freeqhull2()
    +*/
    +void qh_initqhull_start2(FILE *infile, FILE *outfile, FILE *errfile) {
    +  time_t timedata;
    +  int seed;
    +
    +  qh_CPUclock; /* start the clock(for qh_clock).  One-shot. */
    +#if qh_QHpointer
    +  memset((char *)qh_qh, 0, sizeof(qhT));   /* every field is 0, FALSE, NULL */
    +#else
    +  memset((char *)&qh_qh, 0, sizeof(qhT));
    +#endif
    +  qh ANGLEmerge= True;
    +  qh DROPdim= -1;
    +  qh ferr= errfile;
    +  qh fin= infile;
    +  qh fout= outfile;
    +  qh furthest_id= qh_IDunknown;
    +  qh JOGGLEmax= REALmax;
    +  qh KEEPminArea = REALmax;
    +  qh last_low= REALmax;
    +  qh last_high= REALmax;
    +  qh last_newhigh= REALmax;
    +  qh max_outside= 0.0;
    +  qh max_vertex= 0.0;
    +  qh MAXabs_coord= 0.0;
    +  qh MAXsumcoord= 0.0;
    +  qh MAXwidth= -REALmax;
    +  qh MERGEindependent= True;
    +  qh MINdenom_1= fmax_(1.0/REALmax, REALmin); /* used by qh_scalepoints */
    +  qh MINoutside= 0.0;
    +  qh MINvisible= REALmax;
    +  qh MAXcoplanar= REALmax;
    +  qh outside_err= REALmax;
    +  qh premerge_centrum= 0.0;
    +  qh premerge_cos= REALmax;
    +  qh PRINTprecision= True;
    +  qh PRINTradius= 0.0;
    +  qh postmerge_cos= REALmax;
    +  qh postmerge_centrum= 0.0;
    +  qh ROTATErandom= INT_MIN;
    +  qh MERGEvertices= True;
    +  qh totarea= 0.0;
    +  qh totvol= 0.0;
    +  qh TRACEdist= REALmax;
    +  qh TRACEpoint= qh_IDunknown; /* recompile or use 'TPn' */
    +  qh tracefacet_id= UINT_MAX;  /* recompile to trace a facet */
    +  qh tracevertex_id= UINT_MAX; /* recompile to trace a vertex */
    +  seed= (int)time(&timedata);
    +  qh_RANDOMseed_(seed);
    +  qh run_id= qh_RANDOMint;
    +  if(!qh run_id)
    +      qh run_id++;  /* guarantee non-zero */
    +  qh_option("run-id", &qh run_id, NULL);
    +  strcat(qh qhull, "qhull");
    +} /* initqhull_start2 */
    +
    +/*---------------------------------
    +
    +  qh_initthresholds( commandString )
    +    set thresholds for printing and scaling from commandString
    +
    +  returns:
    +    sets qh.GOODthreshold or qh.SPLITthreshold if 'Pd0D1' used
    +
    +  see:
    +    qh_initflags(), 'Qbk' 'QBk' 'Pdk' and 'PDk'
    +    qh_inthresholds()
    +
    +  design:
    +    for each 'Pdn' or 'PDn' option
    +      check syntax
    +      set qh.lower_threshold or qh.upper_threshold
    +    set qh.GOODthreshold if an unbounded threshold is used
    +    set qh.SPLITthreshold if a bounded threshold is used
    +*/
    +void qh_initthresholds(char *command) {
    +  realT value;
    +  int idx, maxdim, k;
    +  char *s= command; /* non-const due to strtol */
    +  char key;
    +
    +  maxdim= qh input_dim;
    +  if (qh DELAUNAY && (qh PROJECTdelaunay || qh PROJECTinput))
    +    maxdim++;
    +  while (*s) {
    +    if (*s == '-')
    +      s++;
    +    if (*s == 'P') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'd' || key == 'D') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh ferr, 7044, "qhull warning: no dimension given for Print option '%c' at: %s.  Ignored\n",
    +                    key, s-1);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= qh hull_dim) {
    +            qh_fprintf(qh ferr, 7045, "qhull warning: dimension %d for Print option '%c' is >= %d.  Ignored\n",
    +                idx, key, qh hull_dim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +            if (fabs((double)value) > 1.0) {
    +              qh_fprintf(qh ferr, 7046, "qhull warning: value %2.4g for Print option %c is > +1 or < -1.  Ignored\n",
    +                      value, key);
    +              continue;
    +            }
    +          }else
    +            value= 0.0;
    +          if (key == 'd')
    +            qh lower_threshold[idx]= value;
    +          else
    +            qh upper_threshold[idx]= value;
    +        }
    +      }
    +    }else if (*s == 'Q') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'b' && *s == 'B') {
    +          s++;
    +          for (k=maxdim; k--; ) {
    +            qh lower_bound[k]= -qh_DEFAULTbox;
    +            qh upper_bound[k]= qh_DEFAULTbox;
    +          }
    +        }else if (key == 'b' && *s == 'b')
    +          s++;
    +        else if (key == 'b' || key == 'B') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh ferr, 7047, "qhull warning: no dimension given for Qhull option %c.  Ignored\n",
    +                    key);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= maxdim) {
    +            qh_fprintf(qh ferr, 7048, "qhull warning: dimension %d for Qhull option %c is >= %d.  Ignored\n",
    +                idx, key, maxdim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +          }else if (key == 'b')
    +            value= -qh_DEFAULTbox;
    +          else
    +            value= qh_DEFAULTbox;
    +          if (key == 'b')
    +            qh lower_bound[idx]= value;
    +          else
    +            qh upper_bound[idx]= value;
    +        }
    +      }
    +    }else {
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +    while (isspace(*s))
    +      s++;
    +  }
    +  for (k=qh hull_dim; k--; ) {
    +    if (qh lower_threshold[k] > -REALmax/2) {
    +      qh GOODthreshold= True;
    +      if (qh upper_threshold[k] < REALmax/2) {
    +        qh SPLITthresholds= True;
    +        qh GOODthreshold= False;
    +        break;
    +      }
    +    }else if (qh upper_threshold[k] < REALmax/2)
    +      qh GOODthreshold= True;
    +  }
    +} /* initthresholds */
    +
    +/*---------------------------------
    +
    +  qh_lib_check( qhullLibraryType, qhTsize, vertexTsize, ridgeTsize, facetTsize, setTsize, qhmemTsize )
    +    Report error if library does not agree with caller
    +
    +  notes:
    +    NOerrors -- qh_lib_check can not call qh_errexit()
    +*/
    +void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize) {
    +    boolT iserror= False;
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
    +    // _CrtSetBreakAlloc(744);  /* Break at memalloc {744}, or 'watch' _crtBreakAlloc */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    if (qhullLibraryType==QHULL_NON_REENTRANT) { /* 0 */
    +        if (qh_QHpointer) {
    +            qh_fprintf_stderr(6246, "qh_lib_check: Incorrect qhull library called.  Caller uses a static qhT while library uses a dynamic qhT via qh_QHpointer.  Both caller and library are non-reentrant.\n");
    +            iserror= True;
    +        }
    +    }else if (qhullLibraryType==QHULL_QH_POINTER) { /* 1 */
    +        if (!qh_QHpointer) {
    +            qh_fprintf_stderr(6247, "qh_lib_check: Incorrect qhull library called.  Caller uses a dynamic qhT via qh_QHpointer while library uses a static qhT.  Both caller and library are non-reentrant.\n");
    +            iserror= True;
    +        }
    +    }else if (qhullLibraryType==QHULL_REENTRANT) { /* 2 */
    +        qh_fprintf_stderr(6248, "qh_lib_check: Incorrect qhull library called.  Caller uses reentrant Qhull while library is non-reentrant\n");
    +        iserror= True;
    +    }else{
    +        qh_fprintf_stderr(6262, "qh_lib_check: Expecting qhullLibraryType QHULL_NON_REENTRANT(0), QHULL_QH_POINTER(1), or QHULL_REENTRANT(2).  Got %d\n", qhullLibraryType);
    +        iserror= True;
    +    }
    +    if (qhTsize != sizeof(qhT)) {
    +        qh_fprintf_stderr(6249, "qh_lib_check: Incorrect qhull library called.  Size of qhT for caller is %d, but for library is %d.\n", qhTsize, sizeof(qhT));
    +        iserror= True;
    +    }
    +    if (vertexTsize != sizeof(vertexT)) {
    +        qh_fprintf_stderr(6250, "qh_lib_check: Incorrect qhull library called.  Size of vertexT for caller is %d, but for library is %d.\n", vertexTsize, sizeof(vertexT));
    +        iserror= True;
    +    }
    +    if (ridgeTsize != sizeof(ridgeT)) {
    +        qh_fprintf_stderr(6251, "qh_lib_check: Incorrect qhull library called.  Size of ridgeT for caller is %d, but for library is %d.\n", ridgeTsize, sizeof(ridgeT));
    +        iserror= True;
    +    }
    +    if (facetTsize != sizeof(facetT)) {
    +        qh_fprintf_stderr(6252, "qh_lib_check: Incorrect qhull library called.  Size of facetT for caller is %d, but for library is %d.\n", facetTsize, sizeof(facetT));
    +        iserror= True;
    +    }
    +    if (setTsize && setTsize != sizeof(setT)) {
    +        qh_fprintf_stderr(6253, "qh_lib_check: Incorrect qhull library called.  Size of setT for caller is %d, but for library is %d.\n", setTsize, sizeof(setT));
    +        iserror= True;
    +    }
    +    if (qhmemTsize && qhmemTsize != sizeof(qhmemT)) {
    +        qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called.  Size of qhmemT for caller is %d, but for library is %d.\n", qhmemTsize, sizeof(qhmemT));
    +        iserror= True;
    +    }
    +    if (iserror) {
    +        if(qh_QHpointer){
    +            qh_fprintf_stderr(6255, "qh_lib_check: Cannot continue.  Library '%s' uses a dynamic qhT via qh_QHpointer (e.g., qhull_p.so)\n", qh_version2);
    +        }else{
    +            qh_fprintf_stderr(6256, "qh_lib_check: Cannot continue.  Library '%s' uses a static qhT (e.g., libqhull.so)\n", qh_version2);
    +        }
    +        qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +    }
    +} /* lib_check */
    +
    +/*---------------------------------
    +
    +  qh_option( option, intVal, realVal )
    +    add an option description to qh.qhull_options
    +
    +  notes:
    +    NOerrors -- qh_option can not call qh_errexit() [qh_initqhull_start2]
    +    will be printed with statistics ('Ts') and errors
    +    strlen(option) < 40
    +*/
    +void qh_option(const char *option, int *i, realT *r) {
    +  char buf[200];
    +  int len, maxlen;
    +
    +  sprintf(buf, "  %s", option);
    +  if (i)
    +    sprintf(buf+strlen(buf), " %d", *i);
    +  if (r)
    +    sprintf(buf+strlen(buf), " %2.2g", *r);
    +  len= (int)strlen(buf);  /* WARN64 */
    +  qh qhull_optionlen += len;
    +  maxlen= sizeof(qh qhull_options) - len -1;
    +  maximize_(maxlen, 0);
    +  if (qh qhull_optionlen >= qh_OPTIONline && maxlen > 0) {
    +    qh qhull_optionlen= len;
    +    strncat(qh qhull_options, "\n", (size_t)(maxlen--));
    +  }
    +  strncat(qh qhull_options, buf, (size_t)maxlen);
    +} /* option */
    +
    +#if qh_QHpointer
    +/*---------------------------------
    +
    +  qh_restore_qhull( oldqh )
    +    restores a previously saved qhull
    +    also restores qh_qhstat and qhmem.tempstack
    +    Sets *oldqh to NULL
    +  notes:
    +    errors if current qhull hasn't been saved or freed
    +    uses qhmem for error reporting
    +
    +  NOTE 1998/5/11:
    +    Freeing memory after qh_save_qhull and qh_restore_qhull
    +    is complicated.  The procedures will be redesigned.
    +
    +  see:
    +    qh_save_qhull(), UsingLibQhull
    +*/
    +void qh_restore_qhull(qhT **oldqh) {
    +
    +  if (*oldqh && strcmp((*oldqh)->qhull, "qhull")) {
    +    qh_fprintf(qhmem.ferr, 6061, "qhull internal error (qh_restore_qhull): %p is not a qhull data structure\n",
    +                  *oldqh);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (qh_qh) {
    +    qh_fprintf(qhmem.ferr, 6062, "qhull internal error (qh_restore_qhull): did not save or free existing qhull\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (!*oldqh || !(*oldqh)->old_qhstat) {
    +    qh_fprintf(qhmem.ferr, 6063, "qhull internal error (qh_restore_qhull): did not previously save qhull %p\n",
    +                  *oldqh);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh_qh= *oldqh;
    +  *oldqh= NULL;
    +  qh_qhstat= qh old_qhstat;
    +  qhmem.tempstack= qh old_tempstack;
    +  qh old_qhstat= 0;
    +  qh old_tempstack= 0;
    +  trace1((qh ferr, 1007, "qh_restore_qhull: restored qhull from %p\n", *oldqh));
    +} /* restore_qhull */
    +
    +/*---------------------------------
    +
    +  qh_save_qhull(  )
    +    saves qhull for a later qh_restore_qhull
    +    also saves qh_qhstat and qhmem.tempstack
    +
    +  returns:
    +    qh_qh=NULL
    +
    +  notes:
    +    need to initialize qhull or call qh_restore_qhull before continuing
    +
    +  NOTE 1998/5/11:
    +    Freeing memory after qh_save_qhull and qh_restore_qhull
    +    is complicated.  The procedures will be redesigned.
    +
    +  see:
    +    qh_restore_qhull()
    +*/
    +qhT *qh_save_qhull(void) {
    +  qhT *oldqh;
    +
    +  trace1((qhmem.ferr, 1045, "qh_save_qhull: save qhull %p\n", qh_qh));
    +  if (!qh_qh) {
    +    qh_fprintf(qhmem.ferr, 6064, "qhull internal error (qh_save_qhull): qhull not initialized\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh old_qhstat= qh_qhstat;
    +  qh_qhstat= NULL;
    +  qh old_tempstack= qhmem.tempstack;
    +  qhmem.tempstack= NULL;
    +  oldqh= qh_qh;
    +  qh_qh= NULL;
    +  return oldqh;
    +} /* save_qhull */
    +
    +#endif
    +
    diff --git a/xs/src/qhull/src/libqhull/index.htm b/xs/src/qhull/src/libqhull/index.htm
    new file mode 100644
    index 0000000000..62b9d99701
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/index.htm
    @@ -0,0 +1,264 @@
    +
    +
    +
    +
    +Qhull functions, macros, and data structures
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser + +


    + + +

    Qhull functions, macros, and data structures

    +
    +

    The following sections provide an overview and index to +Qhull's functions, macros, and data structures. +Each section starts with an introduction. +See also Calling +Qhull from C programs and Calling Qhull from C++ programs.

    + +

    Qhull uses the following conventions:

    +
    + +
      +
    • in code, global variables start with "qh " +
    • in documentation, global variables start with 'qh.' +
    • constants start with an upper case word +
    • important globals include an '_' +
    • functions, macros, and constants start with "qh_"
    • +
    • data types end in "T"
    • +
    • macros with arguments end in "_"
    • +
    • iterators are macros that use local variables
    • +
    • iterators for sets start with "FOREACH"
    • +
    • iterators for lists start with "FORALL"
    • +
    • qhull options are in single quotes (e.g., 'Pdn')
    • +
    • lists are sorted alphabetically
    • +
    • preprocessor directives on left margin for older compilers
    • +
    +
    +

    +When reading the code, please note that the +global data structure, 'qh', is a macro. It +either expands to "qh_qh." or to +"qh_qh->". The later is used for +applications which run concurrent calls to qh_qhull(). +

    +When reading code with an editor, a search for +'"function' +will locate the header of qh_function. A search for '* function' +will locate the tail of qh_function. + +

    A useful starting point is libqhull.h. It defines most +of Qhull data structures and top-level functions. Search for 'PFn' to +determine the corresponding constant in Qhull. Search for 'Fp' to +determine the corresponding qh_PRINT... constant. +Search io.c to learn how the print function is implemented.

    + +

    If your web browser is configured for .c and .h files, the function, macro, and data type links +go to the corresponding source location. To configure your web browser for .c and .h files. +

      +
    • In the Download Preferences or Options panel, add file extensions 'c' and 'h' to mime type 'text/html'. +
    • Opera 12.10 +
        +
      1. In Tools > Preferences > Advanced > Downloads +
      2. Uncheck 'Hide file types opened with Opera' +
      3. Quick find 'html' +
      4. Select 'text/html' > Edit +
      5. Add File extensions 'c,h,' +
      6. Click 'OK' +
      +
    • Internet Explorer -- Mime types are not available from 'Internet Options'. Is there a registry key for these settings? +
    • Firefox -- Mime types are not available from 'Preferences'. Is there an add-on to change the file extensions for a mime type? +
    • Chrome -- Can Chrome be configured? +
    + +

    +Please report documentation and link errors +to qhull-bug@qhull.org. +

    + +

    Copyright © 1997-2015 C.B. Barber

    + +
    + +

    »Qhull files

    +
    + +

    This sections lists the .c and .h files for Qhull. Please +refer to these files for detailed information.

    +
    + +
    +
    Makefile, CMakeLists.txt
    +
    Makefile is preconfigured for gcc. CMakeLists.txt supports multiple +platforms with CMake. +Qhull includes project files for Visual Studio and Qt. +
    + +
     
    +
    libqhull.h
    +
    Include file for the Qhull library (libqhull.so, qhull.dll, libqhullstatic.a). +Data structures are documented under Poly. +Global variables are documented under Global. +Other data structures and variables are documented under +Qhull or Geom.
    + +
     
    +
    Geom, +geom.h, +geom.c, +geom2.c, +random.c, +random.h
    +
    Geometric routines. These routines implement mathematical +functions such as Gaussian elimination and geometric +routines needed for Qhull. Frequently used routines are +in geom.c while infrequent ones are in geom2.c. +
    + +
     
    +
    Global, +global.c, +libqhull.h
    +
    Global routines. Qhull uses a global data structure, qh, +to store globally defined constants, lists, sets, and +variables. +global.c initializes and frees these +structures.
    + +
     
    +
    Io, io.h, +io.c
    +
    Input and output routines. Qhull provides a wide range of +input and output options.
    + +
     
    +
    Mem, +mem.h, +mem.c
    +
    Memory routines. Qhull provides memory allocation and +deallocation. It uses quick-fit allocation.
    + +
     
    +
    Merge, +merge.h, +merge.c
    +
    Merge routines. Qhull handles precision problems by +merged facets or joggled input. These routines merge simplicial facets, +merge non-simplicial facets, merge cycles of facets, and +rename redundant vertices.
    + +
     
    +
    Poly, +poly.h, +poly.c, +poly2.c, +libqhull.h
    +
    Polyhedral routines. Qhull produces a polyhedron as a +list of facets with vertices, neighbors, ridges, and +geometric information. libqhull.h defines the main +data structures. Frequently used routines are in poly.c +while infrequent ones are in poly2.c.
    + +
     
    +
    Qhull, +libqhull.c, +libqhull.h, +qhull_a.h, +unix.c , +qconvex.c , +qdelaun.c , +qhalf.c , +qvoronoi.c
    +
    Top-level routines. The Quickhull algorithm is +implemented by libqhull.c. qhull_a.h +includes all header files.
    + +
     
    +
    Set, +qset.h, +qset.c
    +
    Set routines. Qhull implements its data structures as +sets. A set is an array of pointers that is expanded as +needed. This is a separate package that may be used in +other applications.
    + +
     
    +
    Stat, +stat.h, +stat.c
    +
    Statistical routines. Qhull maintains statistics about +its implementation.
    + +
     
    +
    User, +user.h, +user.c, +user_eg.c, +user_eg2.c, +
    +
    User-defined routines. Qhull allows the user to configure +the code with defined constants and specialized routines. +
    +
    +
    + +
    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull files
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/io.c b/xs/src/qhull/src/libqhull/io.c new file mode 100644 index 0000000000..401987ec08 --- /dev/null +++ b/xs/src/qhull/src/libqhull/io.c @@ -0,0 +1,4062 @@ +/*
      ---------------------------------
    +
    +   io.c
    +   Input/Output routines of qhull application
    +
    +   see qh-io.htm and io.h
    +
    +   see user.c for qh_errprint and qh_printfacetlist
    +
    +   unix.c calls qh_readpoints and qh_produce_output
    +
    +   unix.c and user.c are the only callers of io.c functions
    +   This allows the user to avoid loading io.o from qhull.a
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/io.c#5 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*========= -functions in alphabetical order after qh_produce_output()  =====*/
    +
    +/*---------------------------------
    +
    +  qh_produce_output()
    +  qh_produce_output2()
    +    prints out the result of qhull in desired format
    +    qh_produce_output2() does not call qh_prepare_output()
    +    if qh.GETarea
    +      computes and prints area and volume
    +    qh.PRINTout[] is an array of output formats
    +
    +  notes:
    +    prints output in qh.PRINTout order
    +*/
    +void qh_produce_output(void) {
    +    int tempsize= qh_setsize(qhmem.tempstack);
    +
    +    qh_prepare_output();
    +    qh_produce_output2();
    +    if (qh_setsize(qhmem.tempstack) != tempsize) {
    +        qh_fprintf(qh ferr, 6206, "qhull internal error (qh_produce_output): temporary sets not empty(%d)\n",
    +            qh_setsize(qhmem.tempstack));
    +        qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +} /* produce_output */
    +
    +
    +void qh_produce_output2(void) {
    +  int i, tempsize= qh_setsize(qhmem.tempstack), d_1;
    +
    +  if (qh PRINTsummary)
    +    qh_printsummary(qh ferr);
    +  else if (qh PRINTout[0] == qh_PRINTnone)
    +    qh_printsummary(qh fout);
    +  for (i=0; i < qh_PRINTEND; i++)
    +    qh_printfacets(qh fout, qh PRINTout[i], qh facet_list, NULL, !qh_ALL);
    +  qh_allstatistics();
    +  if (qh PRINTprecision && !qh MERGING && (qh JOGGLEmax > REALmax/2 || qh RERUN))
    +    qh_printstats(qh ferr, qhstat precision, NULL);
    +  if (qh VERIFYoutput && (zzval_(Zridge) > 0 || zzval_(Zridgemid) > 0))
    +    qh_printstats(qh ferr, qhstat vridges, NULL);
    +  if (qh PRINTstatistics) {
    +    qh_printstatistics(qh ferr, "");
    +    qh_memstatistics(qh ferr);
    +    d_1= sizeof(setT) + (qh hull_dim - 1) * SETelemsize;
    +    qh_fprintf(qh ferr, 8040, "\
    +    size in bytes: merge %d ridge %d vertex %d facet %d\n\
    +         normal %d ridge vertices %d facet vertices or neighbors %d\n",
    +            (int)sizeof(mergeT), (int)sizeof(ridgeT),
    +            (int)sizeof(vertexT), (int)sizeof(facetT),
    +            qh normal_size, d_1, d_1 + SETelemsize);
    +  }
    +  if (qh_setsize(qhmem.tempstack) != tempsize) {
    +    qh_fprintf(qh ferr, 6065, "qhull internal error (qh_produce_output2): temporary sets not empty(%d)\n",
    +             qh_setsize(qhmem.tempstack));
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +} /* produce_output2 */
    +
    +/*---------------------------------
    +
    +  qh_dfacet( id )
    +    print facet by id, for debugging
    +
    +*/
    +void qh_dfacet(unsigned id) {
    +  facetT *facet;
    +
    +  FORALLfacets {
    +    if (facet->id == id) {
    +      qh_printfacet(qh fout, facet);
    +      break;
    +    }
    +  }
    +} /* dfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_dvertex( id )
    +    print vertex by id, for debugging
    +*/
    +void qh_dvertex(unsigned id) {
    +  vertexT *vertex;
    +
    +  FORALLvertices {
    +    if (vertex->id == id) {
    +      qh_printvertex(qh fout, vertex);
    +      break;
    +    }
    +  }
    +} /* dvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_compare_facetarea( p1, p2 )
    +    used by qsort() to order facets by area
    +*/
    +int qh_compare_facetarea(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  if (!a->isarea)
    +    return -1;
    +  if (!b->isarea)
    +    return 1;
    +  if (a->f.area > b->f.area)
    +    return 1;
    +  else if (a->f.area == b->f.area)
    +    return 0;
    +  return -1;
    +} /* compare_facetarea */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetmerge( p1, p2 )
    +    used by qsort() to order facets by number of merges
    +*/
    +int qh_compare_facetmerge(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  return(a->nummerge - b->nummerge);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetvisit( p1, p2 )
    +    used by qsort() to order facets by visit id or id
    +*/
    +int qh_compare_facetvisit(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +  int i,j;
    +
    +  if (!(i= a->visitid))
    +    i= 0 - a->id; /* do not convert to int, sign distinguishes id from visitid */
    +  if (!(j= b->visitid))
    +    j= 0 - b->id;
    +  return(i - j);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_vertexpoint( p1, p2 )
    +    used by qsort() to order vertices by point id
    +
    +  Not used.  Not available in libqhull_r.h since qh_pointid depends on qh
    +*/
    +int qh_compare_vertexpoint(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return((qh_pointid(a->point) > qh_pointid(b->point)?1:-1));
    +} /* compare_vertexpoint */
    +
    +/*---------------------------------
    +
    +  qh_copyfilename( dest, size, source, length )
    +    copy filename identified by qh_skipfilename()
    +
    +  notes:
    +    see qh_skipfilename() for syntax
    +*/
    +void qh_copyfilename(char *filename, int size, const char* source, int length) {
    +  char c= *source;
    +
    +  if (length > size + 1) {
    +      qh_fprintf(qh ferr, 6040, "qhull error: filename is more than %d characters, %s\n",  size-1, source);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  strncpy(filename, source, length);
    +  filename[length]= '\0';
    +  if (c == '\'' || c == '"') {
    +    char *s= filename + 1;
    +    char *t= filename;
    +    while (*s) {
    +      if (*s == c) {
    +          if (s[-1] == '\\')
    +              t[-1]= c;
    +      }else
    +          *t++= *s;
    +      s++;
    +    }
    +    *t= '\0';
    +  }
    +} /* copyfilename */
    +
    +/*---------------------------------
    +
    +  qh_countfacets( facetlist, facets, printall,
    +          numfacets, numsimplicial, totneighbors, numridges, numcoplanar, numtricoplanars  )
    +    count good facets for printing and set visitid
    +    if allfacets, ignores qh_skipfacet()
    +
    +  notes:
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  returns:
    +    numfacets, numsimplicial, total neighbors, numridges, coplanars
    +    each facet with ->visitid indicating 1-relative position
    +      ->visitid==0 indicates not good
    +
    +  notes
    +    numfacets >= numsimplicial
    +    if qh.NEWfacets,
    +      does not count visible facets (matches qh_printafacet)
    +
    +  design:
    +    for all facets on facetlist and in facets set
    +      unless facet is skipped or visible (i.e., will be deleted)
    +        mark facet->visitid
    +        update counts
    +*/
    +void qh_countfacets(facetT *facetlist, setT *facets, boolT printall,
    +    int *numfacetsp, int *numsimplicialp, int *totneighborsp, int *numridgesp, int *numcoplanarsp, int *numtricoplanarsp) {
    +  facetT *facet, **facetp;
    +  int numfacets= 0, numsimplicial= 0, numridges= 0, totneighbors= 0, numcoplanars= 0, numtricoplanars= 0;
    +
    +  FORALLfacet_(facetlist) {
    +    if ((facet->visible && qh NEWfacets)
    +    || (!printall && qh_skipfacet(facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(facet->neighbors);
    +      if (facet->simplicial) {
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(facet->coplanarset);
    +    }
    +  }
    +
    +  FOREACHfacet_(facets) {
    +    if ((facet->visible && qh NEWfacets)
    +    || (!printall && qh_skipfacet(facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(facet->neighbors);
    +      if (facet->simplicial){
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(facet->coplanarset);
    +    }
    +  }
    +  qh visit_id += numfacets+1;
    +  *numfacetsp= numfacets;
    +  *numsimplicialp= numsimplicial;
    +  *totneighborsp= totneighbors;
    +  *numridgesp= numridges;
    +  *numcoplanarsp= numcoplanars;
    +  *numtricoplanarsp= numtricoplanars;
    +} /* countfacets */
    +
    +/*---------------------------------
    +
    +  qh_detvnorm( vertex, vertexA, centers, offset )
    +    compute separating plane of the Voronoi diagram for a pair of input sites
    +    centers= set of facets (i.e., Voronoi vertices)
    +      facet->visitid= 0 iff vertex-at-infinity (i.e., unbounded)
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  returns:
    +    norm
    +      a pointer into qh.gm_matrix to qh.hull_dim-1 reals
    +      copy the data before reusing qh.gm_matrix
    +    offset
    +      if 'QVn'
    +        sign adjusted so that qh.GOODvertexp is inside
    +      else
    +        sign adjusted so that vertex is inside
    +
    +    qh.gm_matrix= simplex of points from centers relative to first center
    +
    +  notes:
    +    in io.c so that code for 'v Tv' can be removed by removing io.c
    +    returns pointer into qh.gm_matrix to avoid tracking of temporary memory
    +
    +  design:
    +    determine midpoint of input sites
    +    build points as the set of Voronoi vertices
    +    select a simplex from points (if necessary)
    +      include midpoint if the Voronoi region is unbounded
    +    relocate the first vertex of the simplex to the origin
    +    compute the normalized hyperplane through the simplex
    +    orient the hyperplane toward 'QVn' or 'vertex'
    +    if 'Tv' or 'Ts'
    +      if bounded
    +        test that hyperplane is the perpendicular bisector of the input sites
    +      test that Voronoi vertices not in the simplex are still on the hyperplane
    +    free up temporary memory
    +*/
    +pointT *qh_detvnorm(vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp) {
    +  facetT *facet, **facetp;
    +  int  i, k, pointid, pointidA, point_i, point_n;
    +  setT *simplex= NULL;
    +  pointT *point, **pointp, *point0, *midpoint, *normal, *inpoint;
    +  coordT *coord, *gmcoord, *normalp;
    +  setT *points= qh_settemp(qh TEMPsize);
    +  boolT nearzero= False;
    +  boolT unbounded= False;
    +  int numcenters= 0;
    +  int dim= qh hull_dim - 1;
    +  realT dist, offset, angle, zero= 0.0;
    +
    +  midpoint= qh gm_matrix + qh hull_dim * qh hull_dim;  /* last row */
    +  for (k=0; k < dim; k++)
    +    midpoint[k]= (vertex->point[k] + vertexA->point[k])/2;
    +  FOREACHfacet_(centers) {
    +    numcenters++;
    +    if (!facet->visitid)
    +      unbounded= True;
    +    else {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(facet->vertices);
    +      qh_setappend(&points, facet->center);
    +    }
    +  }
    +  if (numcenters > dim) {
    +    simplex= qh_settemp(qh TEMPsize);
    +    qh_setappend(&simplex, vertex->point);
    +    if (unbounded)
    +      qh_setappend(&simplex, midpoint);
    +    qh_maxsimplex(dim, points, NULL, 0, &simplex);
    +    qh_setdelnth(simplex, 0);
    +  }else if (numcenters == dim) {
    +    if (unbounded)
    +      qh_setappend(&points, midpoint);
    +    simplex= points;
    +  }else {
    +    qh_fprintf(qh ferr, 6216, "qhull internal error (qh_detvnorm): too few points(%d) to compute separating plane\n", numcenters);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  i= 0;
    +  gmcoord= qh gm_matrix;
    +  point0= SETfirstt_(simplex, pointT);
    +  FOREACHpoint_(simplex) {
    +    if (qh IStracing >= 4)
    +      qh_printmatrix(qh ferr, "qh_detvnorm: Voronoi vertex or midpoint",
    +                              &point, 1, dim);
    +    if (point != point0) {
    +      qh gm_row[i++]= gmcoord;
    +      coord= point0;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *point++ - *coord++;
    +    }
    +  }
    +  qh gm_row[i]= gmcoord;  /* does not overlap midpoint, may be used later for qh_areasimplex */
    +  normal= gmcoord;
    +  qh_sethyperplane_gauss(dim, qh gm_row, point0, True,
    +                normal, &offset, &nearzero);
    +  if (qh GOODvertexp == vertexA->point)
    +    inpoint= vertexA->point;
    +  else
    +    inpoint= vertex->point;
    +  zinc_(Zdistio);
    +  dist= qh_distnorm(dim, inpoint, normal, &offset);
    +  if (dist > 0) {
    +    offset= -offset;
    +    normalp= normal;
    +    for (k=dim; k--; ) {
    +      *normalp= -(*normalp);
    +      normalp++;
    +    }
    +  }
    +  if (qh VERIFYoutput || qh PRINTstatistics) {
    +    pointid= qh_pointid(vertex->point);
    +    pointidA= qh_pointid(vertexA->point);
    +    if (!unbounded) {
    +      zinc_(Zdiststat);
    +      dist= qh_distnorm(dim, midpoint, normal, &offset);
    +      if (dist < 0)
    +        dist= -dist;
    +      zzinc_(Zridgemid);
    +      wwmax_(Wridgemidmax, dist);
    +      wwadd_(Wridgemid, dist);
    +      trace4((qh ferr, 4014, "qh_detvnorm: points %d %d midpoint dist %2.2g\n",
    +                 pointid, pointidA, dist));
    +      for (k=0; k < dim; k++)
    +        midpoint[k]= vertexA->point[k] - vertex->point[k];  /* overwrites midpoint! */
    +      qh_normalize(midpoint, dim, False);
    +      angle= qh_distnorm(dim, midpoint, normal, &zero); /* qh_detangle uses dim+1 */
    +      if (angle < 0.0)
    +        angle= angle + 1.0;
    +      else
    +        angle= angle - 1.0;
    +      if (angle < 0.0)
    +        angle -= angle;
    +      trace4((qh ferr, 4015, "qh_detvnorm: points %d %d angle %2.2g nearzero %d\n",
    +                 pointid, pointidA, angle, nearzero));
    +      if (nearzero) {
    +        zzinc_(Zridge0);
    +        wwmax_(Wridge0max, angle);
    +        wwadd_(Wridge0, angle);
    +      }else {
    +        zzinc_(Zridgeok)
    +        wwmax_(Wridgeokmax, angle);
    +        wwadd_(Wridgeok, angle);
    +      }
    +    }
    +    if (simplex != points) {
    +      FOREACHpoint_i_(points) {
    +        if (!qh_setin(simplex, point)) {
    +          facet= SETelemt_(centers, point_i, facetT);
    +          zinc_(Zdiststat);
    +          dist= qh_distnorm(dim, point, normal, &offset);
    +          if (dist < 0)
    +            dist= -dist;
    +          zzinc_(Zridge);
    +          wwmax_(Wridgemax, dist);
    +          wwadd_(Wridge, dist);
    +          trace4((qh ferr, 4016, "qh_detvnorm: points %d %d Voronoi vertex %d dist %2.2g\n",
    +                             pointid, pointidA, facet->visitid, dist));
    +        }
    +      }
    +    }
    +  }
    +  *offsetp= offset;
    +  if (simplex != points)
    +    qh_settempfree(&simplex);
    +  qh_settempfree(&points);
    +  return normal;
    +} /* detvnorm */
    +
    +/*---------------------------------
    +
    +  qh_detvridge( vertexA )
    +    determine Voronoi ridge from 'seen' neighbors of vertexA
    +    include one vertex-at-infinite if an !neighbor->visitid
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    sorted by center id
    +*/
    +setT *qh_detvridge(vertexT *vertex) {
    +  setT *centers= qh_settemp(qh TEMPsize);
    +  setT *tricenters= qh_settemp(qh TEMPsize);
    +  facetT *neighbor, **neighborp;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->seen) {
    +      if (neighbor->visitid) {
    +        if (!neighbor->tricoplanar || qh_setunique(&tricenters, neighbor->center))
    +          qh_setappend(¢ers, neighbor);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(¢ers, neighbor);
    +      }
    +    }
    +  }
    +  qsort(SETaddr_(centers, facetT), (size_t)qh_setsize(centers),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +  qh_settempfree(&tricenters);
    +  return centers;
    +} /* detvridge */
    +
    +/*---------------------------------
    +
    +  qh_detvridge3( atvertex, vertex )
    +    determine 3-d Voronoi ridge from 'seen' neighbors of atvertex and vertex
    +    include one vertex-at-infinite for !neighbor->visitid
    +    assumes all facet->seen2= True
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    listed in adjacency order (!oriented)
    +    all facet->seen2= True
    +
    +  design:
    +    mark all neighbors of atvertex
    +    for each adjacent neighbor of both atvertex and vertex
    +      if neighbor selected
    +        add neighbor to set of Voronoi vertices
    +*/
    +setT *qh_detvridge3(vertexT *atvertex, vertexT *vertex) {
    +  setT *centers= qh_settemp(qh TEMPsize);
    +  setT *tricenters= qh_settemp(qh TEMPsize);
    +  facetT *neighbor, **neighborp, *facet= NULL;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= False;
    +  FOREACHneighbor_(vertex) {
    +    if (!neighbor->seen2) {
    +      facet= neighbor;
    +      break;
    +    }
    +  }
    +  while (facet) {
    +    facet->seen2= True;
    +    if (neighbor->seen) {
    +      if (facet->visitid) {
    +        if (!facet->tricoplanar || qh_setunique(&tricenters, facet->center))
    +          qh_setappend(¢ers, facet);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(¢ers, facet);
    +      }
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen2) {
    +        if (qh_setin(vertex->neighbors, neighbor))
    +          break;
    +        else
    +          neighbor->seen2= True;
    +      }
    +    }
    +    facet= neighbor;
    +  }
    +  if (qh CHECKfrequently) {
    +    FOREACHneighbor_(vertex) {
    +      if (!neighbor->seen2) {
    +          qh_fprintf(qh ferr, 6217, "qhull internal error (qh_detvridge3): neighbors of vertex p%d are not connected at facet %d\n",
    +                 qh_pointid(vertex->point), neighbor->id);
    +        qh_errexit(qh_ERRqhull, neighbor, NULL);
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= True;
    +  qh_settempfree(&tricenters);
    +  return centers;
    +} /* detvridge3 */
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi( fp, printvridge, vertex, visitall, innerouter, inorder )
    +    if visitall,
    +      visit all Voronoi ridges for vertex (i.e., an input site)
    +    else
    +      visit all unvisited Voronoi ridges for vertex
    +      all vertex->seen= False if unvisited
    +    assumes
    +      all facet->seen= False
    +      all facet->seen2= True (for qh_detvridge3)
    +      all facet->visitid == 0 if vertex_at_infinity
    +                         == index of Voronoi vertex
    +                         >= qh.num_facets if ignored
    +    innerouter:
    +      qh_RIDGEall--  both inner (bounded) and outer(unbounded) ridges
    +      qh_RIDGEinner- only inner
    +      qh_RIDGEouter- only outer
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns:
    +    number of visited ridges (does not include previously visited ridges)
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +        fp== any pointer (assumes FILE*)
    +        vertex,vertexA= pair of input sites that define a Voronoi ridge
    +        centers= set of facets (i.e., Voronoi vertices)
    +                 ->visitid == index or 0 if vertex_at_infinity
    +                 ordered for 3-d Voronoi diagram
    +  notes:
    +    uses qh.vertex_visit
    +
    +  see:
    +    qh_eachvoronoi_all()
    +
    +  design:
    +    mark selected neighbors of atvertex
    +    for each selected neighbor (either Voronoi vertex or vertex-at-infinity)
    +      for each unvisited vertex
    +        if atvertex and vertex share more than d-1 neighbors
    +          bump totalcount
    +          if printvridge defined
    +            build the set of shared neighbors (i.e., Voronoi vertices)
    +            call printvridge
    +*/
    +int qh_eachvoronoi(FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder) {
    +  boolT unbounded;
    +  int count;
    +  facetT *neighbor, **neighborp, *neighborA, **neighborAp;
    +  setT *centers;
    +  setT *tricenters= qh_settemp(qh TEMPsize);
    +
    +  vertexT *vertex, **vertexp;
    +  boolT firstinf;
    +  unsigned int numfacets= (unsigned int)qh num_facets;
    +  int totridges= 0;
    +
    +  qh vertex_visit++;
    +  atvertex->seen= True;
    +  if (visitall) {
    +    FORALLvertices
    +      vertex->seen= False;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->visitid < numfacets)
    +      neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->seen) {
    +      FOREACHvertex_(neighbor->vertices) {
    +        if (vertex->visitid != qh vertex_visit && !vertex->seen) {
    +          vertex->visitid= qh vertex_visit;
    +          count= 0;
    +          firstinf= True;
    +          qh_settruncate(tricenters, 0);
    +          FOREACHneighborA_(vertex) {
    +            if (neighborA->seen) {
    +              if (neighborA->visitid) {
    +                if (!neighborA->tricoplanar || qh_setunique(&tricenters, neighborA->center))
    +                  count++;
    +              }else if (firstinf) {
    +                count++;
    +                firstinf= False;
    +              }
    +            }
    +          }
    +          if (count >= qh hull_dim - 1) {  /* e.g., 3 for 3-d Voronoi */
    +            if (firstinf) {
    +              if (innerouter == qh_RIDGEouter)
    +                continue;
    +              unbounded= False;
    +            }else {
    +              if (innerouter == qh_RIDGEinner)
    +                continue;
    +              unbounded= True;
    +            }
    +            totridges++;
    +            trace4((qh ferr, 4017, "qh_eachvoronoi: Voronoi ridge of %d vertices between sites %d and %d\n",
    +                  count, qh_pointid(atvertex->point), qh_pointid(vertex->point)));
    +            if (printvridge && fp) {
    +              if (inorder && qh hull_dim == 3+1) /* 3-d Voronoi diagram */
    +                centers= qh_detvridge3(atvertex, vertex);
    +              else
    +                centers= qh_detvridge(vertex);
    +              (*printvridge)(fp, atvertex, vertex, centers, unbounded);
    +              qh_settempfree(¢ers);
    +            }
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen= False;
    +  qh_settempfree(&tricenters);
    +  return totridges;
    +} /* eachvoronoi */
    +
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi_all( fp, printvridge, isUpper, innerouter, inorder )
    +    visit all Voronoi ridges
    +
    +    innerouter:
    +      see qh_eachvoronoi()
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns
    +    total number of ridges
    +
    +    if isUpper == facet->upperdelaunay  (i.e., a Vornoi vertex)
    +      facet->visitid= Voronoi vertex index(same as 'o' format)
    +    else
    +      facet->visitid= 0
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +      [see qh_eachvoronoi]
    +
    +  notes:
    +    Not used for qhull.exe
    +    same effect as qh_printvdiagram but ridges not sorted by point id
    +*/
    +int qh_eachvoronoi_all(FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int numcenters= 1;  /* vertex 0 is vertex-at-infinity */
    +  int totridges= 0;
    +
    +  qh_clearcenters(qh_ASvoronoi);
    +  qh_vertexneighbors();
    +  maximize_(qh visit_id, (unsigned) qh num_facets);
    +  FORALLfacets {
    +    facet->visitid= 0;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  FORALLfacets {
    +    if (facet->upperdelaunay == isUpper)
    +      facet->visitid= numcenters++;
    +  }
    +  FORALLvertices
    +    vertex->seen= False;
    +  FORALLvertices {
    +    if (qh GOODvertex > 0 && qh_pointid(vertex->point)+1 != qh GOODvertex)
    +      continue;
    +    totridges += qh_eachvoronoi(fp, printvridge, vertex,
    +                   !qh_ALL, innerouter, inorder);
    +  }
    +  return totridges;
    +} /* eachvoronoi_all */
    +
    +/*---------------------------------
    +
    +  qh_facet2point( facet, point0, point1, mindist )
    +    return two projected temporary vertices for a 2-d facet
    +    may be non-simplicial
    +
    +  returns:
    +    point0 and point1 oriented and projected to the facet
    +    returns mindist (maximum distance below plane)
    +*/
    +void qh_facet2point(facetT *facet, pointT **point0, pointT **point1, realT *mindist) {
    +  vertexT *vertex0, *vertex1;
    +  realT dist;
    +
    +  if (facet->toporient ^ qh_ORIENTclock) {
    +    vertex0= SETfirstt_(facet->vertices, vertexT);
    +    vertex1= SETsecondt_(facet->vertices, vertexT);
    +  }else {
    +    vertex1= SETfirstt_(facet->vertices, vertexT);
    +    vertex0= SETsecondt_(facet->vertices, vertexT);
    +  }
    +  zadd_(Zdistio, 2);
    +  qh_distplane(vertex0->point, facet, &dist);
    +  *mindist= dist;
    +  *point0= qh_projectpoint(vertex0->point, facet, dist);
    +  qh_distplane(vertex1->point, facet, &dist);
    +  minimize_(*mindist, dist);
    +  *point1= qh_projectpoint(vertex1->point, facet, dist);
    +} /* facet2point */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetvertices( facetlist, facets, allfacets )
    +    returns temporary set of vertices in a set and/or list of facets
    +    if allfacets, ignores qh_skipfacet()
    +
    +  returns:
    +    vertices with qh.vertex_visit
    +
    +  notes:
    +    optimized for allfacets of facet_list
    +
    +  design:
    +    if allfacets of facet_list
    +      create vertex set from vertex_list
    +    else
    +      for each selected facet in facets or facetlist
    +        append unvisited vertices to vertex set
    +*/
    +setT *qh_facetvertices(facetT *facetlist, setT *facets, boolT allfacets) {
    +  setT *vertices;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +
    +  qh vertex_visit++;
    +  if (facetlist == qh facet_list && allfacets && !facets) {
    +    vertices= qh_settemp(qh num_vertices);
    +    FORALLvertices {
    +      vertex->visitid= qh vertex_visit;
    +      qh_setappend(&vertices, vertex);
    +    }
    +  }else {
    +    vertices= qh_settemp(qh TEMPsize);
    +    FORALLfacet_(facetlist) {
    +      if (!allfacets && qh_skipfacet(facet))
    +        continue;
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex->visitid != qh vertex_visit) {
    +          vertex->visitid= qh vertex_visit;
    +          qh_setappend(&vertices, vertex);
    +        }
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (!allfacets && qh_skipfacet(facet))
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        vertex->visitid= qh vertex_visit;
    +        qh_setappend(&vertices, vertex);
    +      }
    +    }
    +  }
    +  return vertices;
    +} /* facetvertices */
    +
    +/*---------------------------------
    +
    +  qh_geomplanes( facet, outerplane, innerplane )
    +    return outer and inner planes for Geomview
    +    qh.PRINTradius is size of vertices and points (includes qh.JOGGLEmax)
    +
    +  notes:
    +    assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +*/
    +void qh_geomplanes(facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT radius;
    +
    +  if (qh MERGING || qh JOGGLEmax < REALmax/2) {
    +    qh_outerinner(facet, outerplane, innerplane);
    +    radius= qh PRINTradius;
    +    if (qh JOGGLEmax < REALmax/2)
    +      radius -= qh JOGGLEmax * sqrt((realT)qh hull_dim);  /* already accounted for in qh_outerinner() */
    +    *outerplane += radius;
    +    *innerplane -= radius;
    +    if (qh PRINTcoplanar || qh PRINTspheres) {
    +      *outerplane += qh MAXabs_coord * qh_GEOMepsilon;
    +      *innerplane -= qh MAXabs_coord * qh_GEOMepsilon;
    +    }
    +  }else
    +    *innerplane= *outerplane= 0;
    +} /* geomplanes */
    +
    +
    +/*---------------------------------
    +
    +  qh_markkeep( facetlist )
    +    mark good facets that meet qh.KEEParea, qh.KEEPmerge, and qh.KEEPminArea
    +    ignores visible facets (!part of convex hull)
    +
    +  returns:
    +    may clear facet->good
    +    recomputes qh.num_good
    +
    +  design:
    +    get set of good facets
    +    if qh.KEEParea
    +      sort facets by area
    +      clear facet->good for all but n largest facets
    +    if qh.KEEPmerge
    +      sort facets by merge count
    +      clear facet->good for all but n most merged facets
    +    if qh.KEEPminarea
    +      clear facet->good if area too small
    +    update qh.num_good
    +*/
    +void qh_markkeep(facetT *facetlist) {
    +  facetT *facet, **facetp;
    +  setT *facets= qh_settemp(qh num_facets);
    +  int size, count;
    +
    +  trace2((qh ferr, 2006, "qh_markkeep: only keep %d largest and/or %d most merged facets and/or min area %.2g\n",
    +          qh KEEParea, qh KEEPmerge, qh KEEPminArea));
    +  FORALLfacet_(facetlist) {
    +    if (!facet->visible && facet->good)
    +      qh_setappend(&facets, facet);
    +  }
    +  size= qh_setsize(facets);
    +  if (qh KEEParea) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetarea);
    +    if ((count= size - qh KEEParea) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh KEEPmerge) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetmerge);
    +    if ((count= size - qh KEEPmerge) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh KEEPminArea < REALmax/2) {
    +    FOREACHfacet_(facets) {
    +      if (!facet->isarea || facet->f.area < qh KEEPminArea)
    +        facet->good= False;
    +    }
    +  }
    +  qh_settempfree(&facets);
    +  count= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      count++;
    +  }
    +  qh num_good= count;
    +} /* markkeep */
    +
    +
    +/*---------------------------------
    +
    +  qh_markvoronoi( facetlist, facets, printall, isLower, numcenters )
    +    mark voronoi vertices for printing by site pairs
    +
    +  returns:
    +    temporary set of vertices indexed by pointid
    +    isLower set if printing lower hull (i.e., at least one facet is lower hull)
    +    numcenters= total number of Voronoi vertices
    +    bumps qh.printoutnum for vertex-at-infinity
    +    clears all facet->seen and sets facet->seen2
    +
    +    if selected
    +      facet->visitid= Voronoi vertex id
    +    else if upper hull (or 'Qu' and lower hull)
    +      facet->visitid= 0
    +    else
    +      facet->visitid >= qh num_facets
    +
    +  notes:
    +    ignores qh.ATinfinity, if defined
    +*/
    +setT *qh_markvoronoi(facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp) {
    +  int numcenters=0;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  boolT isLower= False;
    +
    +  qh printoutnum++;
    +  qh_clearcenters(qh_ASvoronoi);  /* in case, qh_printvdiagram2 called by user */
    +  qh_vertexneighbors();
    +  vertices= qh_pointvertex();
    +  if (qh ATinfinity)
    +    SETelem_(vertices, qh num_points-1)= NULL;
    +  qh visit_id++;
    +  maximize_(qh visit_id, (unsigned) qh num_facets);
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->normal && (facet->upperdelaunay == isLower))
    +      facet->visitid= 0;  /* facetlist or facets may overwrite */
    +    else
    +      facet->visitid= qh visit_id;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  numcenters++;  /* qh_INFINITE */
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(facet))
    +      facet->visitid= numcenters++;
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(facet))
    +      facet->visitid= numcenters++;
    +  }
    +  *isLowerp= isLower;
    +  *numcentersp= numcenters;
    +  trace2((qh ferr, 2007, "qh_markvoronoi: isLower %d numcenters %d\n", isLower, numcenters));
    +  return vertices;
    +} /* markvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_order_vertexneighbors( vertex )
    +    order facet neighbors of a 2-d or 3-d vertex by adjacency
    +
    +  notes:
    +    does not orient the neighbors
    +
    +  design:
    +    initialize a new neighbor set with the first facet in vertex->neighbors
    +    while vertex->neighbors non-empty
    +      select next neighbor in the previous facet's neighbor set
    +    set vertex->neighbors to the new neighbor set
    +*/
    +void qh_order_vertexneighbors(vertexT *vertex) {
    +  setT *newset;
    +  facetT *facet, *neighbor, **neighborp;
    +
    +  trace4((qh ferr, 4018, "qh_order_vertexneighbors: order neighbors of v%d for 3-d\n", vertex->id));
    +  newset= qh_settemp(qh_setsize(vertex->neighbors));
    +  facet= (facetT*)qh_setdellast(vertex->neighbors);
    +  qh_setappend(&newset, facet);
    +  while (qh_setsize(vertex->neighbors)) {
    +    FOREACHneighbor_(vertex) {
    +      if (qh_setin(facet->neighbors, neighbor)) {
    +        qh_setdel(vertex->neighbors, neighbor);
    +        qh_setappend(&newset, neighbor);
    +        facet= neighbor;
    +        break;
    +      }
    +    }
    +    if (!neighbor) {
    +      qh_fprintf(qh ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n",
    +        vertex->id, facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  qh_setfree(&vertex->neighbors);
    +  qh_settemppop();
    +  vertex->neighbors= newset;
    +} /* order_vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_prepare_output( )
    +    prepare for qh_produce_output2() according to
    +      qh.KEEPminArea, KEEParea, KEEPmerge, GOODvertex, GOODthreshold, GOODpoint, ONLYgood, SPLITthresholds
    +    does not reset facet->good
    +
    +  notes
    +    except for PRINTstatistics, no-op if previously called with same options
    +*/
    +void qh_prepare_output(void) {
    +  if (qh VORONOI) {
    +    qh_clearcenters(qh_ASvoronoi);  /* must be before qh_triangulate */
    +    qh_vertexneighbors();
    +  }
    +  if (qh TRIangulate && !qh hasTriangulation) {
    +    qh_triangulate();
    +    if (qh VERIFYoutput && !qh CHECKfrequently)
    +      qh_checkpolygon(qh facet_list);
    +  }
    +  qh_findgood_all(qh facet_list);
    +  if (qh GETarea)
    +    qh_getarea(qh facet_list);
    +  if (qh KEEParea || qh KEEPmerge || qh KEEPminArea < REALmax/2)
    +    qh_markkeep(qh facet_list);
    +  if (qh PRINTstatistics)
    +    qh_collectstatistics();
    +}
    +
    +/*---------------------------------
    +
    +  qh_printafacet( fp, format, facet, printall )
    +    print facet to fp in given output format (see qh.PRINTout)
    +
    +  returns:
    +    nop if !printall and qh_skipfacet()
    +    nop if visible facet and NEWfacets and format != PRINTfacets
    +    must match qh_countfacets
    +
    +  notes
    +    preserves qh.visit_id
    +    facet->normal may be null if PREmerge/MERGEexact and STOPcone before merge
    +
    +  see
    +    qh_printbegin() and qh_printend()
    +
    +  design:
    +    test for printing facet
    +    call appropriate routine for format
    +    or output results directly
    +*/
    +void qh_printafacet(FILE *fp, qh_PRINT format, facetT *facet, boolT printall) {
    +  realT color[4], offset, dist, outerplane, innerplane;
    +  boolT zerodiv;
    +  coordT *point, *normp, *coordp, **pointp, *feasiblep;
    +  int k;
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  if (!printall && qh_skipfacet(facet))
    +    return;
    +  if (facet->visible && qh NEWfacets && format != qh_PRINTfacets)
    +    return;
    +  qh printoutnum++;
    +  switch (format) {
    +  case qh_PRINTarea:
    +    if (facet->isarea) {
    +      qh_fprintf(fp, 9009, qh_REAL_1, facet->f.area);
    +      qh_fprintf(fp, 9010, "\n");
    +    }else
    +      qh_fprintf(fp, 9011, "0\n");
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(fp, 9012, "%d", qh_setsize(facet->coplanarset));
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_fprintf(fp, 9013, " %d", qh_pointid(point));
    +    qh_fprintf(fp, 9014, "\n");
    +    break;
    +  case qh_PRINTcentrums:
    +    qh_printcenter(fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTfacets:
    +    qh_printfacet(fp, facet);
    +    break;
    +  case qh_PRINTfacets_xridge:
    +    qh_printfacetheader(fp, facet);
    +    break;
    +  case qh_PRINTgeom:  /* either 2 , 3, or 4-d by qh_printbegin */
    +    if (!facet->normal)
    +      break;
    +    for (k=qh hull_dim; k--; ) {
    +      color[k]= (facet->normal[k]+1.0)/2.0;
    +      maximize_(color[k], -1.0);
    +      minimize_(color[k], +1.0);
    +    }
    +    qh_projectdim3(color, color);
    +    if (qh PRINTdim != qh hull_dim)
    +      qh_normalize2(color, 3, True, NULL, NULL);
    +    if (qh hull_dim <= 2)
    +      qh_printfacet2geom(fp, facet, color);
    +    else if (qh hull_dim == 3) {
    +      if (facet->simplicial)
    +        qh_printfacet3geom_simplicial(fp, facet, color);
    +      else
    +        qh_printfacet3geom_nonsimplicial(fp, facet, color);
    +    }else {
    +      if (facet->simplicial)
    +        qh_printfacet4geom_simplicial(fp, facet, color);
    +      else
    +        qh_printfacet4geom_nonsimplicial(fp, facet, color);
    +    }
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(fp, 9015, "%d\n", facet->id);
    +    break;
    +  case qh_PRINTincidences:
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh hull_dim == 3 && format != qh_PRINTtriangles)
    +      qh_printfacet3vertex(fp, facet, format);
    +    else if (facet->simplicial || qh hull_dim == 2 || format == qh_PRINToff)
    +      qh_printfacetNvertex_simplicial(fp, facet, format);
    +    else
    +      qh_printfacetNvertex_nonsimplicial(fp, facet, qh printoutvar++, format);
    +    break;
    +  case qh_PRINTinner:
    +    qh_outerinner(facet, NULL, &innerplane);
    +    offset= facet->offset - innerplane;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTmerges:
    +    qh_fprintf(fp, 9016, "%d\n", facet->nummerge);
    +    break;
    +  case qh_PRINTnormals:
    +    offset= facet->offset;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTouter:
    +    qh_outerinner(facet, &outerplane, NULL);
    +    offset= facet->offset - outerplane;
    +  LABELprintnorm:
    +    if (!facet->normal) {
    +      qh_fprintf(fp, 9017, "no normal for facet f%d\n", facet->id);
    +      break;
    +    }
    +    if (qh CDDoutput) {
    +      qh_fprintf(fp, 9018, qh_REAL_1, -offset);
    +      for (k=0; k < qh hull_dim; k++)
    +        qh_fprintf(fp, 9019, qh_REAL_1, -facet->normal[k]);
    +    }else {
    +      for (k=0; k < qh hull_dim; k++)
    +        qh_fprintf(fp, 9020, qh_REAL_1, facet->normal[k]);
    +      qh_fprintf(fp, 9021, qh_REAL_1, offset);
    +    }
    +    qh_fprintf(fp, 9022, "\n");
    +    break;
    +  case qh_PRINTmathematica:  /* either 2 or 3-d by qh_printbegin */
    +  case qh_PRINTmaple:
    +    if (qh hull_dim == 2)
    +      qh_printfacet2math(fp, facet, format, qh printoutvar++);
    +    else
    +      qh_printfacet3math(fp, facet, format, qh printoutvar++);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(fp, 9023, "%d", qh_setsize(facet->neighbors));
    +    FOREACHneighbor_(facet)
    +      qh_fprintf(fp, 9024, " %d",
    +               neighbor->visitid ? neighbor->visitid - 1: 0 - neighbor->id);
    +    qh_fprintf(fp, 9025, "\n");
    +    break;
    +  case qh_PRINTpointintersect:
    +    if (!qh feasible_point) {
    +      qh_fprintf(qh ferr, 6067, "qhull input error (qh_printafacet): option 'Fp' needs qh feasible_point\n");
    +      qh_errexit( qh_ERRinput, NULL, NULL);
    +    }
    +    if (facet->offset > 0)
    +      goto LABELprintinfinite;
    +    point= coordp= (coordT*)qh_memalloc(qh normal_size);
    +    normp= facet->normal;
    +    feasiblep= qh feasible_point;
    +    if (facet->offset < -qh MINdenom) {
    +      for (k=qh hull_dim; k--; )
    +        *(coordp++)= (*(normp++) / - facet->offset) + *(feasiblep++);
    +    }else {
    +      for (k=qh hull_dim; k--; ) {
    +        *(coordp++)= qh_divzero(*(normp++), facet->offset, qh MINdenom_1,
    +                                 &zerodiv) + *(feasiblep++);
    +        if (zerodiv) {
    +          qh_memfree(point, qh normal_size);
    +          goto LABELprintinfinite;
    +        }
    +      }
    +    }
    +    qh_printpoint(fp, NULL, point);
    +    qh_memfree(point, qh normal_size);
    +    break;
    +  LABELprintinfinite:
    +    for (k=qh hull_dim; k--; )
    +      qh_fprintf(fp, 9026, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(fp, 9027, "\n");
    +    break;
    +  case qh_PRINTpointnearest:
    +    FOREACHpoint_(facet->coplanarset) {
    +      int id, id2;
    +      vertex= qh_nearvertex(facet, point, &dist);
    +      id= qh_pointid(vertex->point);
    +      id2= qh_pointid(point);
    +      qh_fprintf(fp, 9028, "%d %d %d " qh_REAL_1 "\n", id, id2, facet->id, dist);
    +    }
    +    break;
    +  case qh_PRINTpoints:  /* VORONOI only by qh_printbegin */
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9029, "1 ");
    +    qh_printcenter(fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(fp, 9030, "%d", qh_setsize(facet->vertices));
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9031, " %d", qh_pointid(vertex->point));
    +    qh_fprintf(fp, 9032, "\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printafacet */
    +
    +/*---------------------------------
    +
    +  qh_printbegin(  )
    +    prints header for all output formats
    +
    +  returns:
    +    checks for valid format
    +
    +  notes:
    +    uses qh.visit_id for 3/4off
    +    changes qh.interior_point if printing centrums
    +    qh_countfacets clears facet->visitid for non-good facets
    +
    +  see
    +    qh_printend() and qh_printafacet()
    +
    +  design:
    +    count facets and related statistics
    +    print header for format
    +*/
    +void qh_printbegin(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  int i, num;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +  pointT *point, **pointp, *pointtemp;
    +
    +  qh printoutnum= 0;
    +  qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +  switch (format) {
    +  case qh_PRINTnone:
    +    break;
    +  case qh_PRINTarea:
    +    qh_fprintf(fp, 9033, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(fp, 9034, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcentrums:
    +    if (qh CENTERtype == qh_ASnone)
    +      qh_clearcenters(qh_AScentrum);
    +    qh_fprintf(fp, 9035, "%d\n%d\n", qh hull_dim, numfacets);
    +    break;
    +  case qh_PRINTfacets:
    +  case qh_PRINTfacets_xridge:
    +    if (facetlist)
    +      qh_printvertexlist(fp, "Vertices and facets:\n", facetlist, facets, printall);
    +    break;
    +  case qh_PRINTgeom:
    +    if (qh hull_dim > 4)  /* qh_initqhull_globals also checks */
    +      goto LABELnoformat;
    +    if (qh VORONOI && qh hull_dim > 3)  /* PRINTdim == DROPdim == hull_dim-1 */
    +      goto LABELnoformat;
    +    if (qh hull_dim == 2 && (qh PRINTridges || qh DOintersections))
    +      qh_fprintf(qh ferr, 7049, "qhull warning: output for ridges and intersections not implemented in 2-d\n");
    +    if (qh hull_dim == 4 && (qh PRINTinner || qh PRINTouter ||
    +                             (qh PRINTdim == 4 && qh PRINTcentrums)))
    +      qh_fprintf(qh ferr, 7050, "qhull warning: output for outer/inner planes and centrums not implemented in 4-d\n");
    +    if (qh PRINTdim == 4 && (qh PRINTspheres))
    +      qh_fprintf(qh ferr, 7051, "qhull warning: output for vertices not implemented in 4-d\n");
    +    if (qh PRINTdim == 4 && qh DOintersections && qh PRINTnoplanes)
    +      qh_fprintf(qh ferr, 7052, "qhull warning: 'Gnh' generates no output in 4-d\n");
    +    if (qh PRINTdim == 2) {
    +      qh_fprintf(fp, 9036, "{appearance {linewidth 3} LIST # %s | %s\n",
    +              qh rbox_command, qh qhull_command);
    +    }else if (qh PRINTdim == 3) {
    +      qh_fprintf(fp, 9037, "{appearance {+edge -evert linewidth 2} LIST # %s | %s\n",
    +              qh rbox_command, qh qhull_command);
    +    }else if (qh PRINTdim == 4) {
    +      qh visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)    /* get number of ridges to be printed */
    +        qh_printend4geom(NULL, facet, &num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(NULL, facet, &num, printall);
    +      qh ridgeoutnum= num;
    +      qh printoutvar= 0;  /* counts number of ridges in output */
    +      qh_fprintf(fp, 9038, "LIST # %s | %s\n", qh rbox_command, qh qhull_command);
    +    }
    +
    +    if (qh PRINTdots) {
    +      qh printoutnum++;
    +      num= qh num_points + qh_setsize(qh other_points);
    +      if (qh DELAUNAY && qh ATinfinity)
    +        num--;
    +      if (qh PRINTdim == 4)
    +        qh_fprintf(fp, 9039, "4VECT %d %d 1\n", num, num);
    +      else
    +        qh_fprintf(fp, 9040, "VECT %d %d 1\n", num, num);
    +
    +      for (i=num; i--; ) {
    +        if (i % 20 == 0)
    +          qh_fprintf(fp, 9041, "\n");
    +        qh_fprintf(fp, 9042, "1 ");
    +      }
    +      qh_fprintf(fp, 9043, "# 1 point per line\n1 ");
    +      for (i=num-1; i--; ) { /* num at least 3 for D2 */
    +        if (i % 20 == 0)
    +          qh_fprintf(fp, 9044, "\n");
    +        qh_fprintf(fp, 9045, "0 ");
    +      }
    +      qh_fprintf(fp, 9046, "# 1 color for all\n");
    +      FORALLpoints {
    +        if (!qh DELAUNAY || !qh ATinfinity || qh_pointid(point) != qh num_points-1) {
    +          if (qh PRINTdim == 4)
    +            qh_printpoint(fp, NULL, point);
    +            else
    +              qh_printpoint3(fp, point);
    +        }
    +      }
    +      FOREACHpoint_(qh other_points) {
    +        if (qh PRINTdim == 4)
    +          qh_printpoint(fp, NULL, point);
    +        else
    +          qh_printpoint3(fp, point);
    +      }
    +      qh_fprintf(fp, 9047, "0 1 1 1  # color of points\n");
    +    }
    +
    +    if (qh PRINTdim == 4  && !qh PRINTnoplanes)
    +      /* 4dview loads up multiple 4OFF objects slowly */
    +      qh_fprintf(fp, 9048, "4OFF %d %d 1\n", 3*qh ridgeoutnum, qh ridgeoutnum);
    +    qh PRINTcradius= 2 * qh DISTround;  /* include test DISTround */
    +    if (qh PREmerge) {
    +      maximize_(qh PRINTcradius, qh premerge_centrum + qh DISTround);
    +    }else if (qh POSTmerge)
    +      maximize_(qh PRINTcradius, qh postmerge_centrum + qh DISTround);
    +    qh PRINTradius= qh PRINTcradius;
    +    if (qh PRINTspheres + qh PRINTcoplanar)
    +      maximize_(qh PRINTradius, qh MAXabs_coord * qh_MINradius);
    +    if (qh premerge_cos < REALmax/2) {
    +      maximize_(qh PRINTradius, (1- qh premerge_cos) * qh MAXabs_coord);
    +    }else if (!qh PREmerge && qh POSTmerge && qh postmerge_cos < REALmax/2) {
    +      maximize_(qh PRINTradius, (1- qh postmerge_cos) * qh MAXabs_coord);
    +    }
    +    maximize_(qh PRINTradius, qh MINvisible);
    +    if (qh JOGGLEmax < REALmax/2)
    +      qh PRINTradius += qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +    if (qh PRINTdim != 4 &&
    +        (qh PRINTcoplanar || qh PRINTspheres || qh PRINTcentrums)) {
    +      vertices= qh_facetvertices(facetlist, facets, printall);
    +      if (qh PRINTspheres && qh PRINTdim <= 3)
    +        qh_printspheres(fp, vertices, qh PRINTradius);
    +      if (qh PRINTcoplanar || qh PRINTcentrums) {
    +        qh firstcentrum= True;
    +        if (qh PRINTcoplanar&& !qh PRINTspheres) {
    +          FOREACHvertex_(vertices)
    +            qh_printpointvect2(fp, vertex->point, NULL, qh interior_point, qh PRINTradius);
    +        }
    +        FORALLfacet_(facetlist) {
    +          if (!printall && qh_skipfacet(facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh PRINTcentrums && qh PRINTdim <= 3)
    +            qh_printcentrum(fp, facet, qh PRINTcradius);
    +          if (!qh PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +        }
    +        FOREACHfacet_(facets) {
    +          if (!printall && qh_skipfacet(facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh PRINTcentrums && qh PRINTdim <= 3)
    +            qh_printcentrum(fp, facet, qh PRINTcradius);
    +          if (!qh PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(fp, point, facet->normal, NULL, qh PRINTradius);
    +        }
    +      }
    +      qh_settempfree(&vertices);
    +    }
    +    qh visit_id++; /* for printing hyperplane intersections */
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(fp, 9049, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTincidences:
    +    if (qh VORONOI && qh PRINTprecision)
    +      qh_fprintf(qh ferr, 7053, "qhull warning: writing Delaunay.  Use 'p' or 'o' for Voronoi centers\n");
    +    qh printoutvar= qh vertex_id;  /* centrum id for non-simplicial facets */
    +    if (qh hull_dim <= 3)
    +      qh_fprintf(fp, 9050, "%d\n", numfacets);
    +    else
    +      qh_fprintf(fp, 9051, "%d\n", numsimplicial+numridges);
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9052, "%s | %s\nbegin\n    %d %d real\n", qh rbox_command,
    +            qh qhull_command, numfacets, qh hull_dim+1);
    +    else
    +      qh_fprintf(fp, 9053, "%d\n%d\n", qh hull_dim+1, numfacets);
    +    break;
    +  case qh_PRINTmathematica:
    +  case qh_PRINTmaple:
    +    if (qh hull_dim > 3)  /* qh_initbuffers also checks */
    +      goto LABELnoformat;
    +    if (qh VORONOI)
    +      qh_fprintf(qh ferr, 7054, "qhull warning: output is the Delaunay triangulation\n");
    +    if (format == qh_PRINTmaple) {
    +      if (qh hull_dim == 2)
    +        qh_fprintf(fp, 9054, "PLOT(CURVES(\n");
    +      else
    +        qh_fprintf(fp, 9055, "PLOT3D(POLYGONS(\n");
    +    }else
    +      qh_fprintf(fp, 9056, "{\n");
    +    qh printoutvar= 0;   /* counts number of facets for notfirst */
    +    break;
    +  case qh_PRINTmerges:
    +    qh_fprintf(fp, 9057, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTpointintersect:
    +    qh_fprintf(fp, 9058, "%d\n%d\n", qh hull_dim, numfacets);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(fp, 9059, "%d\n", numfacets);
    +    break;
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh VORONOI)
    +      goto LABELnoformat;
    +    num = qh hull_dim;
    +    if (format == qh_PRINToff || qh hull_dim == 2)
    +      qh_fprintf(fp, 9060, "%d\n%d %d %d\n", num,
    +        qh num_points+qh_setsize(qh other_points), numfacets, totneighbors/2);
    +    else { /* qh_PRINTtriangles */
    +      qh printoutvar= qh num_points+qh_setsize(qh other_points); /* first centrum */
    +      if (qh DELAUNAY)
    +        num--;  /* drop last dimension */
    +      qh_fprintf(fp, 9061, "%d\n%d %d %d\n", num, qh printoutvar
    +        + numfacets - numsimplicial, numsimplicial + numridges, totneighbors/2);
    +    }
    +    FORALLpoints
    +      qh_printpointid(qh fout, NULL, num, point, qh_IDunknown);
    +    FOREACHpoint_(qh other_points)
    +      qh_printpointid(qh fout, NULL, num, point, qh_IDunknown);
    +    if (format == qh_PRINTtriangles && qh hull_dim > 2) {
    +      FORALLfacets {
    +        if (!facet->simplicial && facet->visitid)
    +          qh_printcenter(qh fout, format, NULL, facet);
    +      }
    +    }
    +    break;
    +  case qh_PRINTpointnearest:
    +    qh_fprintf(fp, 9062, "%d\n", numcoplanars);
    +    break;
    +  case qh_PRINTpoints:
    +    if (!qh VORONOI)
    +      goto LABELnoformat;
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9063, "%s | %s\nbegin\n%d %d real\n", qh rbox_command,
    +           qh qhull_command, numfacets, qh hull_dim);
    +    else
    +      qh_fprintf(fp, 9064, "%d\n%d\n", qh hull_dim-1, numfacets);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(fp, 9065, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTsummary:
    +  default:
    +  LABELnoformat:
    +    qh_fprintf(qh ferr, 6068, "qhull internal error (qh_printbegin): can not use this format for dimension %d\n",
    +         qh hull_dim);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +} /* printbegin */
    +
    +/*---------------------------------
    +
    +  qh_printcenter( fp, string, facet )
    +    print facet->center as centrum or Voronoi center
    +    string may be NULL.  Don't include '%' codes.
    +    nop if qh CENTERtype neither CENTERvoronoi nor CENTERcentrum
    +    if upper envelope of Delaunay triangulation and point at-infinity
    +      prints qh_INFINITE instead;
    +
    +  notes:
    +    defines facet->center if needed
    +    if format=PRINTgeom, adds a 0 if would otherwise be 2-d
    +    Same as QhullFacet::printCenter
    +*/
    +void qh_printcenter(FILE *fp, qh_PRINT format, const char *string, facetT *facet) {
    +  int k, num;
    +
    +  if (qh CENTERtype != qh_ASvoronoi && qh CENTERtype != qh_AScentrum)
    +    return;
    +  if (string)
    +    qh_fprintf(fp, 9066, string);
    +  if (qh CENTERtype == qh_ASvoronoi) {
    +    num= qh hull_dim-1;
    +    if (!facet->normal || !facet->upperdelaunay || !qh ATinfinity) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(facet->vertices);
    +      for (k=0; k < num; k++)
    +        qh_fprintf(fp, 9067, qh_REAL_1, facet->center[k]);
    +    }else {
    +      for (k=0; k < num; k++)
    +        qh_fprintf(fp, 9068, qh_REAL_1, qh_INFINITE);
    +    }
    +  }else /* qh.CENTERtype == qh_AScentrum */ {
    +    num= qh hull_dim;
    +    if (format == qh_PRINTtriangles && qh DELAUNAY)
    +      num--;
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(facet);
    +    for (k=0; k < num; k++)
    +      qh_fprintf(fp, 9069, qh_REAL_1, facet->center[k]);
    +  }
    +  if (format == qh_PRINTgeom && num == 2)
    +    qh_fprintf(fp, 9070, " 0\n");
    +  else
    +    qh_fprintf(fp, 9071, "\n");
    +} /* printcenter */
    +
    +/*---------------------------------
    +
    +  qh_printcentrum( fp, facet, radius )
    +    print centrum for a facet in OOGL format
    +    radius defines size of centrum
    +    2-d or 3-d only
    +
    +  returns:
    +    defines facet->center if needed
    +*/
    +void qh_printcentrum(FILE *fp, facetT *facet, realT radius) {
    +  pointT *centrum, *projpt;
    +  boolT tempcentrum= False;
    +  realT xaxis[4], yaxis[4], normal[4], dist;
    +  realT green[3]={0, 1, 0};
    +  vertexT *apex;
    +  int k;
    +
    +  if (qh CENTERtype == qh_AScentrum) {
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(facet);
    +    centrum= facet->center;
    +  }else {
    +    centrum= qh_getcentrum(facet);
    +    tempcentrum= True;
    +  }
    +  qh_fprintf(fp, 9072, "{appearance {-normal -edge normscale 0} ");
    +  if (qh firstcentrum) {
    +    qh firstcentrum= False;
    +    qh_fprintf(fp, 9073, "{INST geom { define centrum CQUAD  # f%d\n\
    +-0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3  0.3 0.0001     0 0 1 1\n\
    +-0.3  0.3 0.0001     0 0 1 1 } transform { \n", facet->id);
    +  }else
    +    qh_fprintf(fp, 9074, "{INST geom { : centrum } transform { # f%d\n", facet->id);
    +  apex= SETfirstt_(facet->vertices, vertexT);
    +  qh_distplane(apex->point, facet, &dist);
    +  projpt= qh_projectpoint(apex->point, facet, dist);
    +  for (k=qh hull_dim; k--; ) {
    +    xaxis[k]= projpt[k] - centrum[k];
    +    normal[k]= facet->normal[k];
    +  }
    +  if (qh hull_dim == 2) {
    +    xaxis[2]= 0;
    +    normal[2]= 0;
    +  }else if (qh hull_dim == 4) {
    +    qh_projectdim3(xaxis, xaxis);
    +    qh_projectdim3(normal, normal);
    +    qh_normalize2(normal, qh PRINTdim, True, NULL, NULL);
    +  }
    +  qh_crossproduct(3, xaxis, normal, yaxis);
    +  qh_fprintf(fp, 9075, "%8.4g %8.4g %8.4g 0\n", xaxis[0], xaxis[1], xaxis[2]);
    +  qh_fprintf(fp, 9076, "%8.4g %8.4g %8.4g 0\n", yaxis[0], yaxis[1], yaxis[2]);
    +  qh_fprintf(fp, 9077, "%8.4g %8.4g %8.4g 0\n", normal[0], normal[1], normal[2]);
    +  qh_printpoint3(fp, centrum);
    +  qh_fprintf(fp, 9078, "1 }}}\n");
    +  qh_memfree(projpt, qh normal_size);
    +  qh_printpointvect(fp, centrum, facet->normal, NULL, radius, green);
    +  if (tempcentrum)
    +    qh_memfree(centrum, qh normal_size);
    +} /* printcentrum */
    +
    +/*---------------------------------
    +
    +  qh_printend( fp, format )
    +    prints trailer for all output formats
    +
    +  see:
    +    qh_printbegin() and qh_printafacet()
    +
    +*/
    +void qh_printend(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int num;
    +  facetT *facet, **facetp;
    +
    +  if (!qh printoutnum)
    +    qh_fprintf(qh ferr, 7055, "qhull warning: no facets printed\n");
    +  switch (format) {
    +  case qh_PRINTgeom:
    +    if (qh hull_dim == 4 && qh DROPdim < 0  && !qh PRINTnoplanes) {
    +      qh visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)
    +        qh_printend4geom(fp, facet,&num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(fp, facet, &num, printall);
    +      if (num != qh ridgeoutnum || qh printoutvar != qh ridgeoutnum) {
    +        qh_fprintf(qh ferr, 6069, "qhull internal error (qh_printend): number of ridges %d != number printed %d and at end %d\n", qh ridgeoutnum, qh printoutvar, num);
    +        qh_errexit(qh_ERRqhull, NULL, NULL);
    +      }
    +    }else
    +      qh_fprintf(fp, 9079, "}\n");
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9080, "end\n");
    +    break;
    +  case qh_PRINTmaple:
    +    qh_fprintf(fp, 9081, "));\n");
    +    break;
    +  case qh_PRINTmathematica:
    +    qh_fprintf(fp, 9082, "}\n");
    +    break;
    +  case qh_PRINTpoints:
    +    if (qh CDDoutput)
    +      qh_fprintf(fp, 9083, "end\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printend */
    +
    +/*---------------------------------
    +
    +  qh_printend4geom( fp, facet, numridges, printall )
    +    helper function for qh_printbegin/printend
    +
    +  returns:
    +    number of printed ridges
    +
    +  notes:
    +    just counts printed ridges if fp=NULL
    +    uses facet->visitid
    +    must agree with qh_printfacet4geom...
    +
    +  design:
    +    computes color for facet from its normal
    +    prints each ridge of facet
    +*/
    +void qh_printend4geom(FILE *fp, facetT *facet, int *nump, boolT printall) {
    +  realT color[3];
    +  int i, num= *nump;
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (!printall && qh_skipfacet(facet))
    +    return;
    +  if (qh PRINTnoplanes || (facet->visible && qh NEWfacets))
    +    return;
    +  if (!facet->normal)
    +    return;
    +  if (fp) {
    +    for (i=0; i < 3; i++) {
    +      color[i]= (facet->normal[i]+1.0)/2.0;
    +      maximize_(color[i], -1.0);
    +      minimize_(color[i], +1.0);
    +    }
    +  }
    +  facet->visitid= qh visit_id;
    +  if (facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        if (fp)
    +          qh_fprintf(fp, 9084, "3 %d %d %d %8.4g %8.4g %8.4g 1 # f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }else {
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh visit_id) {
    +        if (fp)
    +          qh_fprintf(fp, 9085, "3 %d %d %d %8.4g %8.4g %8.4g 1 #r%d f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 ridge->id, facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }
    +  *nump= num;
    +} /* printend4geom */
    +
    +/*---------------------------------
    +
    +  qh_printextremes( fp, facetlist, facets, printall )
    +    print extreme points for convex hulls or halfspace intersections
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    sorted by id
    +    same order as qh_printpoints_out if no coplanar/interior points
    +*/
    +void qh_printextremes(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices, *points;
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +  int numpoints=0, point_i, point_n;
    +  int allpoints= qh num_points + qh_setsize(qh other_points);
    +
    +  points= qh_settemp(allpoints);
    +  qh_setzero(points, 0, allpoints);
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(vertex->point);
    +    if (id >= 0) {
    +      SETelem_(points, id)= vertex->point;
    +      numpoints++;
    +    }
    +  }
    +  qh_settempfree(&vertices);
    +  qh_fprintf(fp, 9086, "%d\n", numpoints);
    +  FOREACHpoint_i_(points) {
    +    if (point)
    +      qh_fprintf(fp, 9087, "%d\n", point_i);
    +  }
    +  qh_settempfree(&points);
    +} /* printextremes */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_2d( fp, facetlist, facets, printall )
    +    prints point ids for facets in qh_ORIENTclock order
    +
    +  notes:
    +    #points, followed by ids, one per line
    +    if facetlist/facets are disjoint than the output includes skips
    +    errors if facets form a loop
    +    does not print coplanar points
    +*/
    +void qh_printextremes_2d(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numridges, totneighbors, numcoplanars, numsimplicial, numtricoplanars;
    +  setT *vertices;
    +  facetT *facet, *startfacet, *nextfacet;
    +  vertexT *vertexA, *vertexB;
    +
    +  qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars); /* marks qh visit_id */
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  qh_fprintf(fp, 9088, "%d\n", qh_setsize(vertices));
    +  qh_settempfree(&vertices);
    +  if (!numfacets)
    +    return;
    +  facet= startfacet= facetlist ? facetlist : SETfirstt_(facets, facetT);
    +  qh vertex_visit++;
    +  qh visit_id++;
    +  do {
    +    if (facet->toporient ^ qh_ORIENTclock) {
    +      vertexA= SETfirstt_(facet->vertices, vertexT);
    +      vertexB= SETsecondt_(facet->vertices, vertexT);
    +      nextfacet= SETfirstt_(facet->neighbors, facetT);
    +    }else {
    +      vertexA= SETsecondt_(facet->vertices, vertexT);
    +      vertexB= SETfirstt_(facet->vertices, vertexT);
    +      nextfacet= SETsecondt_(facet->neighbors, facetT);
    +    }
    +    if (facet->visitid == qh visit_id) {
    +      qh_fprintf(qh ferr, 6218, "Qhull internal error (qh_printextremes_2d): loop in facet list.  facet %d nextfacet %d\n",
    +                 facet->id, nextfacet->id);
    +      qh_errexit2(qh_ERRqhull, facet, nextfacet);
    +    }
    +    if (facet->visitid) {
    +      if (vertexA->visitid != qh vertex_visit) {
    +        vertexA->visitid= qh vertex_visit;
    +        qh_fprintf(fp, 9089, "%d\n", qh_pointid(vertexA->point));
    +      }
    +      if (vertexB->visitid != qh vertex_visit) {
    +        vertexB->visitid= qh vertex_visit;
    +        qh_fprintf(fp, 9090, "%d\n", qh_pointid(vertexB->point));
    +      }
    +    }
    +    facet->visitid= qh visit_id;
    +    facet= nextfacet;
    +  }while (facet && facet != startfacet);
    +} /* printextremes_2d */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_d( fp, facetlist, facets, printall )
    +    print extreme points of input sites for Delaunay triangulations
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    unordered
    +*/
    +void qh_printextremes_d(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  vertexT *vertex, **vertexp;
    +  boolT upperseen, lowerseen;
    +  facetT *neighbor, **neighborp;
    +  int numpoints=0;
    +
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  qh_vertexneighbors();
    +  FOREACHvertex_(vertices) {
    +    upperseen= lowerseen= False;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay)
    +        upperseen= True;
    +      else
    +        lowerseen= True;
    +    }
    +    if (upperseen && lowerseen) {
    +      vertex->seen= True;
    +      numpoints++;
    +    }else
    +      vertex->seen= False;
    +  }
    +  qh_fprintf(fp, 9091, "%d\n", numpoints);
    +  FOREACHvertex_(vertices) {
    +    if (vertex->seen)
    +      qh_fprintf(fp, 9092, "%d\n", qh_pointid(vertex->point));
    +  }
    +  qh_settempfree(&vertices);
    +} /* printextremes_d */
    +
    +/*---------------------------------
    +
    +  qh_printfacet( fp, facet )
    +    prints all fields of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +*/
    +void qh_printfacet(FILE *fp, facetT *facet) {
    +
    +  qh_printfacetheader(fp, facet);
    +  if (facet->ridges)
    +    qh_printfacetridges(fp, facet);
    +} /* printfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom( fp, facet, color )
    +    print facet as part of a 2-d VECT for Geomview
    +
    +    notes:
    +      assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +      mindist is calculated within io.c.  maxoutside is calculated elsewhere
    +      so a DISTround error may have occurred.
    +*/
    +void qh_printfacet2geom(FILE *fp, facetT *facet, realT color[3]) {
    +  pointT *point0, *point1;
    +  realT mindist, innerplane, outerplane;
    +  int k;
    +
    +  qh_facet2point(facet, &point0, &point1, &mindist);
    +  qh_geomplanes(facet, &outerplane, &innerplane);
    +  if (qh PRINTouter || (!qh PRINTnoplanes && !qh PRINTinner))
    +    qh_printfacet2geom_points(fp, point0, point1, facet, outerplane, color);
    +  if (qh PRINTinner || (!qh PRINTnoplanes && !qh PRINTouter &&
    +                outerplane - innerplane > 2 * qh MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet2geom_points(fp, point0, point1, facet, innerplane, color);
    +  }
    +  qh_memfree(point1, qh normal_size);
    +  qh_memfree(point0, qh normal_size);
    +} /* printfacet2geom */
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom_points( fp, point1, point2, facet, offset, color )
    +    prints a 2-d facet as a VECT with 2 points at some offset.
    +    The points are on the facet's plane.
    +*/
    +void qh_printfacet2geom_points(FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]) {
    +  pointT *p1= point1, *p2= point2;
    +
    +  qh_fprintf(fp, 9093, "VECT 1 2 1 2 1 # f%d\n", facet->id);
    +  if (offset != 0.0) {
    +    p1= qh_projectpoint(p1, facet, -offset);
    +    p2= qh_projectpoint(p2, facet, -offset);
    +  }
    +  qh_fprintf(fp, 9094, "%8.4g %8.4g %8.4g\n%8.4g %8.4g %8.4g\n",
    +           p1[0], p1[1], 0.0, p2[0], p2[1], 0.0);
    +  if (offset != 0.0) {
    +    qh_memfree(p1, qh normal_size);
    +    qh_memfree(p2, qh normal_size);
    +  }
    +  qh_fprintf(fp, 9095, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printfacet2geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2math( fp, facet, format, notfirst )
    +    print 2-d Maple or Mathematica output for a facet
    +    may be non-simplicial
    +
    +  notes:
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet3math
    +*/
    +void qh_printfacet2math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  pointT *point0, *point1;
    +  realT mindist;
    +  const char *pointfmt;
    +
    +  qh_facet2point(facet, &point0, &point1, &mindist);
    +  if (notfirst)
    +    qh_fprintf(fp, 9096, ",");
    +  if (format == qh_PRINTmaple)
    +    pointfmt= "[[%16.8f, %16.8f], [%16.8f, %16.8f]]\n";
    +  else
    +    pointfmt= "Line[{{%16.8f, %16.8f}, {%16.8f, %16.8f}}]\n";
    +  qh_fprintf(fp, 9097, pointfmt, point0[0], point0[1], point1[0], point1[1]);
    +  qh_memfree(point1, qh normal_size);
    +  qh_memfree(point0, qh normal_size);
    +} /* printfacet2math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_nonsimplicial( fp, facet, color )
    +    print Geomview OFF for a 3-d nonsimplicial facet.
    +    if DOintersections, prints ridges to unvisited neighbors(qh visit_id)
    +
    +  notes
    +    uses facet->visitid for intersections and ridges
    +*/
    +void qh_printfacet3geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  ridgeT *ridge, **ridgep;
    +  setT *projectedpoints, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  pointT *projpt, *point, **pointp;
    +  facetT *neighbor;
    +  realT dist, outerplane, innerplane;
    +  int cntvertices, k;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +
    +  qh_geomplanes(facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(facet); /* oriented */
    +  cntvertices= qh_setsize(vertices);
    +  projectedpoints= qh_settemp(cntvertices);
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(vertex->point, facet, &dist);
    +    projpt= qh_projectpoint(vertex->point, facet, dist);
    +    qh_setappend(&projectedpoints, projpt);
    +  }
    +  if (qh PRINTouter || (!qh PRINTnoplanes && !qh PRINTinner))
    +    qh_printfacet3geom_points(fp, projectedpoints, facet, outerplane, color);
    +  if (qh PRINTinner || (!qh PRINTnoplanes && !qh PRINTouter &&
    +                outerplane - innerplane > 2 * qh MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(fp, projectedpoints, facet, innerplane, color);
    +  }
    +  FOREACHpoint_(projectedpoints)
    +    qh_memfree(point, qh normal_size);
    +  qh_settempfree(&projectedpoints);
    +  qh_settempfree(&vertices);
    +  if ((qh DOintersections || qh PRINTridges)
    +  && (!facet->visible || !qh NEWfacets)) {
    +    facet->visitid= qh visit_id;
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh visit_id) {
    +        if (qh DOintersections)
    +          qh_printhyperplaneintersection(fp, facet, neighbor, ridge->vertices, black);
    +        if (qh PRINTridges) {
    +          vertexA= SETfirstt_(ridge->vertices, vertexT);
    +          vertexB= SETsecondt_(ridge->vertices, vertexT);
    +          qh_printline3geom(fp, vertexA->point, vertexB->point, green);
    +        }
    +      }
    +    }
    +  }
    +} /* printfacet3geom_nonsimplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_points( fp, points, facet, offset )
    +    prints a 3-d facet as OFF Geomview object.
    +    offset is relative to the facet's hyperplane
    +    Facet is determined as a list of points
    +*/
    +void qh_printfacet3geom_points(FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]) {
    +  int k, n= qh_setsize(points), i;
    +  pointT *point, **pointp;
    +  setT *printpoints;
    +
    +  qh_fprintf(fp, 9098, "{ OFF %d 1 1 # f%d\n", n, facet->id);
    +  if (offset != 0.0) {
    +    printpoints= qh_settemp(n);
    +    FOREACHpoint_(points)
    +      qh_setappend(&printpoints, qh_projectpoint(point, facet, -offset));
    +  }else
    +    printpoints= points;
    +  FOREACHpoint_(printpoints) {
    +    for (k=0; k < qh hull_dim; k++) {
    +      if (k == qh DROPdim)
    +        qh_fprintf(fp, 9099, "0 ");
    +      else
    +        qh_fprintf(fp, 9100, "%8.4g ", point[k]);
    +    }
    +    if (printpoints != points)
    +      qh_memfree(point, qh normal_size);
    +    qh_fprintf(fp, 9101, "\n");
    +  }
    +  if (printpoints != points)
    +    qh_settempfree(&printpoints);
    +  qh_fprintf(fp, 9102, "%d ", n);
    +  for (i=0; i < n; i++)
    +    qh_fprintf(fp, 9103, "%d ", i);
    +  qh_fprintf(fp, 9104, "%8.4g %8.4g %8.4g 1.0 }\n", color[0], color[1], color[2]);
    +} /* printfacet3geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_simplicial(  )
    +    print Geomview OFF for a 3-d simplicial facet.
    +
    +  notes:
    +    may flip color
    +    uses facet->visitid for intersections and ridges
    +
    +    assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +    innerplane may be off by qh DISTround.  Maxoutside is calculated elsewhere
    +    so a DISTround error may have occurred.
    +*/
    +void qh_printfacet3geom_simplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  setT *points, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  facetT *neighbor, **neighborp;
    +  realT outerplane, innerplane;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +  int k;
    +
    +  qh_geomplanes(facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(facet);
    +  points= qh_settemp(qh TEMPsize);
    +  FOREACHvertex_(vertices)
    +    qh_setappend(&points, vertex->point);
    +  if (qh PRINTouter || (!qh PRINTnoplanes && !qh PRINTinner))
    +    qh_printfacet3geom_points(fp, points, facet, outerplane, color);
    +  if (qh PRINTinner || (!qh PRINTnoplanes && !qh PRINTouter &&
    +              outerplane - innerplane > 2 * qh MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(fp, points, facet, innerplane, color);
    +  }
    +  qh_settempfree(&points);
    +  qh_settempfree(&vertices);
    +  if ((qh DOintersections || qh PRINTridges)
    +  && (!facet->visible || !qh NEWfacets)) {
    +    facet->visitid= qh visit_id;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        vertices= qh_setnew_delnthsorted(facet->vertices, qh hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +        if (qh DOintersections)
    +           qh_printhyperplaneintersection(fp, facet, neighbor, vertices, black);
    +        if (qh PRINTridges) {
    +          vertexA= SETfirstt_(vertices, vertexT);
    +          vertexB= SETsecondt_(vertices, vertexT);
    +          qh_printline3geom(fp, vertexA->point, vertexB->point, green);
    +        }
    +        qh_setfree(&vertices);
    +      }
    +    }
    +  }
    +} /* printfacet3geom_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3math( fp, facet, notfirst )
    +    print 3-d Maple or Mathematica output for a facet
    +
    +  notes:
    +    may be non-simplicial
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet2math
    +*/
    +void qh_printfacet3math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  vertexT *vertex, **vertexp;
    +  setT *points, *vertices;
    +  pointT *point, **pointp;
    +  boolT firstpoint= True;
    +  realT dist;
    +  const char *pointfmt, *endfmt;
    +
    +  if (notfirst)
    +    qh_fprintf(fp, 9105, ",\n");
    +  vertices= qh_facet3vertex(facet);
    +  points= qh_settemp(qh_setsize(vertices));
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(vertex->point, facet, &dist);
    +    point= qh_projectpoint(vertex->point, facet, dist);
    +    qh_setappend(&points, point);
    +  }
    +  if (format == qh_PRINTmaple) {
    +    qh_fprintf(fp, 9106, "[");
    +    pointfmt= "[%16.8f, %16.8f, %16.8f]";
    +    endfmt= "]";
    +  }else {
    +    qh_fprintf(fp, 9107, "Polygon[{");
    +    pointfmt= "{%16.8f, %16.8f, %16.8f}";
    +    endfmt= "}]";
    +  }
    +  FOREACHpoint_(points) {
    +    if (firstpoint)
    +      firstpoint= False;
    +    else
    +      qh_fprintf(fp, 9108, ",\n");
    +    qh_fprintf(fp, 9109, pointfmt, point[0], point[1], point[2]);
    +  }
    +  FOREACHpoint_(points)
    +    qh_memfree(point, qh normal_size);
    +  qh_settempfree(&points);
    +  qh_settempfree(&vertices);
    +  qh_fprintf(fp, 9110, "%s", endfmt);
    +} /* printfacet3math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3vertex( fp, facet, format )
    +    print vertices in a 3-d facet as point ids
    +
    +  notes:
    +    prints number of vertices first if format == qh_PRINToff
    +    the facet may be non-simplicial
    +*/
    +void qh_printfacet3vertex(FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facet3vertex(facet);
    +  if (format == qh_PRINToff)
    +    qh_fprintf(fp, 9111, "%d ", qh_setsize(vertices));
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(fp, 9112, "%d ", qh_pointid(vertex->point));
    +  qh_fprintf(fp, 9113, "\n");
    +  qh_settempfree(&vertices);
    +} /* printfacet3vertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_nonsimplicial(  )
    +    print Geomview 4OFF file for a 4d nonsimplicial facet
    +    prints all ridges to unvisited neighbors (qh.visit_id)
    +    if qh.DROPdim
    +      prints in OFF format
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  facetT *neighbor;
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  pointT *point;
    +  int k;
    +  realT dist;
    +
    +  facet->visitid= qh visit_id;
    +  if (qh PRINTnoplanes || (facet->visible && qh NEWfacets))
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh visit_id)
    +      continue;
    +    if (qh PRINTtransparent && !neighbor->good)
    +      continue;
    +    if (qh DOintersections)
    +      qh_printhyperplaneintersection(fp, facet, neighbor, ridge->vertices, color);
    +    else {
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9114, "OFF 3 1 1 # f%d\n", facet->id);
    +      else {
    +        qh printoutvar++;
    +        qh_fprintf(fp, 9115, "# r%d between f%d f%d\n", ridge->id, facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(ridge->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(vertex->point,facet, &dist);
    +        point=qh_projectpoint(vertex->point,facet, dist);
    +        for (k=0; k < qh hull_dim; k++) {
    +          if (k != qh DROPdim)
    +            qh_fprintf(fp, 9116, "%8.4g ", point[k]);
    +        }
    +        qh_fprintf(fp, 9117, "\n");
    +        qh_memfree(point, qh normal_size);
    +      }
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9118, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +  }
    +} /* printfacet4geom_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_simplicial( fp, facet, color )
    +    print Geomview 4OFF file for a 4d simplicial facet
    +    prints triangles for unvisited neighbors (qh.visit_id)
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_simplicial(FILE *fp, facetT *facet, realT color[3]) {
    +  setT *vertices;
    +  facetT *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int k;
    +
    +  facet->visitid= qh visit_id;
    +  if (qh PRINTnoplanes || (facet->visible && qh NEWfacets))
    +    return;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid == qh visit_id)
    +      continue;
    +    if (qh PRINTtransparent && !neighbor->good)
    +      continue;
    +    vertices= qh_setnew_delnthsorted(facet->vertices, qh hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +    if (qh DOintersections)
    +      qh_printhyperplaneintersection(fp, facet, neighbor, vertices, color);
    +    else {
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9119, "OFF 3 1 1 # ridge between f%d f%d\n",
    +                facet->id, neighbor->id);
    +      else {
    +        qh printoutvar++;
    +        qh_fprintf(fp, 9120, "# ridge between f%d f%d\n", facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(vertices) {
    +        for (k=0; k < qh hull_dim; k++) {
    +          if (k != qh DROPdim)
    +            qh_fprintf(fp, 9121, "%8.4g ", vertex->point[k]);
    +        }
    +        qh_fprintf(fp, 9122, "\n");
    +      }
    +      if (qh DROPdim >= 0)
    +        qh_fprintf(fp, 9123, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +    qh_setfree(&vertices);
    +  }
    +} /* printfacet4geom_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_nonsimplicial( fp, facet, id, format )
    +    print vertices for an N-d non-simplicial facet
    +    triangulates each ridge to the id
    +*/
    +void qh_printfacetNvertex_nonsimplicial(FILE *fp, facetT *facet, int id, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->visible && qh NEWfacets)
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    if (format == qh_PRINTtriangles)
    +      qh_fprintf(fp, 9124, "%d ", qh hull_dim);
    +    qh_fprintf(fp, 9125, "%d ", id);
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      FOREACHvertex_(ridge->vertices)
    +        qh_fprintf(fp, 9126, "%d ", qh_pointid(vertex->point));
    +    }else {
    +      FOREACHvertexreverse12_(ridge->vertices)
    +        qh_fprintf(fp, 9127, "%d ", qh_pointid(vertex->point));
    +    }
    +    qh_fprintf(fp, 9128, "\n");
    +  }
    +} /* printfacetNvertex_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_simplicial( fp, facet, format )
    +    print vertices for an N-d simplicial facet
    +    prints vertices for non-simplicial facets
    +      2-d facets (orientation preserved by qh_mergefacet2d)
    +      PRINToff ('o') for 4-d and higher
    +*/
    +void qh_printfacetNvertex_simplicial(FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +
    +  if (format == qh_PRINToff || format == qh_PRINTtriangles)
    +    qh_fprintf(fp, 9129, "%d ", qh_setsize(facet->vertices));
    +  if ((facet->toporient ^ qh_ORIENTclock)
    +  || (qh hull_dim > 2 && !facet->simplicial)) {
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9130, "%d ", qh_pointid(vertex->point));
    +  }else {
    +    FOREACHvertexreverse12_(facet->vertices)
    +      qh_fprintf(fp, 9131, "%d ", qh_pointid(vertex->point));
    +  }
    +  qh_fprintf(fp, 9132, "\n");
    +} /* printfacetNvertex_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetheader( fp, facet )
    +    prints header fields of a facet to fp
    +
    +  notes:
    +    for 'f' output and debugging
    +    Same as QhullFacet::printHeader()
    +*/
    +void qh_printfacetheader(FILE *fp, facetT *facet) {
    +  pointT *point, **pointp, *furthest;
    +  facetT *neighbor, **neighborp;
    +  realT dist;
    +
    +  if (facet == qh_MERGEridge) {
    +    qh_fprintf(fp, 9133, " MERGEridge\n");
    +    return;
    +  }else if (facet == qh_DUPLICATEridge) {
    +    qh_fprintf(fp, 9134, " DUPLICATEridge\n");
    +    return;
    +  }else if (!facet) {
    +    qh_fprintf(fp, 9135, " NULLfacet\n");
    +    return;
    +  }
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  qh_fprintf(fp, 9136, "- f%d\n", facet->id);
    +  qh_fprintf(fp, 9137, "    - flags:");
    +  if (facet->toporient)
    +    qh_fprintf(fp, 9138, " top");
    +  else
    +    qh_fprintf(fp, 9139, " bottom");
    +  if (facet->simplicial)
    +    qh_fprintf(fp, 9140, " simplicial");
    +  if (facet->tricoplanar)
    +    qh_fprintf(fp, 9141, " tricoplanar");
    +  if (facet->upperdelaunay)
    +    qh_fprintf(fp, 9142, " upperDelaunay");
    +  if (facet->visible)
    +    qh_fprintf(fp, 9143, " visible");
    +  if (facet->newfacet)
    +    qh_fprintf(fp, 9144, " new");
    +  if (facet->tested)
    +    qh_fprintf(fp, 9145, " tested");
    +  if (!facet->good)
    +    qh_fprintf(fp, 9146, " notG");
    +  if (facet->seen)
    +    qh_fprintf(fp, 9147, " seen");
    +  if (facet->coplanar)
    +    qh_fprintf(fp, 9148, " coplanar");
    +  if (facet->mergehorizon)
    +    qh_fprintf(fp, 9149, " mergehorizon");
    +  if (facet->keepcentrum)
    +    qh_fprintf(fp, 9150, " keepcentrum");
    +  if (facet->dupridge)
    +    qh_fprintf(fp, 9151, " dupridge");
    +  if (facet->mergeridge && !facet->mergeridge2)
    +    qh_fprintf(fp, 9152, " mergeridge1");
    +  if (facet->mergeridge2)
    +    qh_fprintf(fp, 9153, " mergeridge2");
    +  if (facet->newmerge)
    +    qh_fprintf(fp, 9154, " newmerge");
    +  if (facet->flipped)
    +    qh_fprintf(fp, 9155, " flipped");
    +  if (facet->notfurthest)
    +    qh_fprintf(fp, 9156, " notfurthest");
    +  if (facet->degenerate)
    +    qh_fprintf(fp, 9157, " degenerate");
    +  if (facet->redundant)
    +    qh_fprintf(fp, 9158, " redundant");
    +  qh_fprintf(fp, 9159, "\n");
    +  if (facet->isarea)
    +    qh_fprintf(fp, 9160, "    - area: %2.2g\n", facet->f.area);
    +  else if (qh NEWfacets && facet->visible && facet->f.replace)
    +    qh_fprintf(fp, 9161, "    - replacement: f%d\n", facet->f.replace->id);
    +  else if (facet->newfacet) {
    +    if (facet->f.samecycle && facet->f.samecycle != facet)
    +      qh_fprintf(fp, 9162, "    - shares same visible/horizon as f%d\n", facet->f.samecycle->id);
    +  }else if (facet->tricoplanar /* !isarea */) {
    +    if (facet->f.triowner)
    +      qh_fprintf(fp, 9163, "    - owner of normal & centrum is facet f%d\n", facet->f.triowner->id);
    +  }else if (facet->f.newcycle)
    +    qh_fprintf(fp, 9164, "    - was horizon to f%d\n", facet->f.newcycle->id);
    +  if (facet->nummerge)
    +    qh_fprintf(fp, 9165, "    - merges: %d\n", facet->nummerge);
    +  qh_printpointid(fp, "    - normal: ", qh hull_dim, facet->normal, qh_IDunknown);
    +  qh_fprintf(fp, 9166, "    - offset: %10.7g\n", facet->offset);
    +  if (qh CENTERtype == qh_ASvoronoi || facet->center)
    +    qh_printcenter(fp, qh_PRINTfacets, "    - center: ", facet);
    +#if qh_MAXoutside
    +  if (facet->maxoutside > qh DISTround)
    +    qh_fprintf(fp, 9167, "    - maxoutside: %10.7g\n", facet->maxoutside);
    +#endif
    +  if (!SETempty_(facet->outsideset)) {
    +    furthest= (pointT*)qh_setlast(facet->outsideset);
    +    if (qh_setsize(facet->outsideset) < 6) {
    +      qh_fprintf(fp, 9168, "    - outside set(furthest p%d):\n", qh_pointid(furthest));
    +      FOREACHpoint_(facet->outsideset)
    +        qh_printpoint(fp, "     ", point);
    +    }else if (qh_setsize(facet->outsideset) < 21) {
    +      qh_printpoints(fp, "    - outside set:", facet->outsideset);
    +    }else {
    +      qh_fprintf(fp, 9169, "    - outside set:  %d points.", qh_setsize(facet->outsideset));
    +      qh_printpoint(fp, "  Furthest", furthest);
    +    }
    +#if !qh_COMPUTEfurthest
    +    qh_fprintf(fp, 9170, "    - furthest distance= %2.2g\n", facet->furthestdist);
    +#endif
    +  }
    +  if (!SETempty_(facet->coplanarset)) {
    +    furthest= (pointT*)qh_setlast(facet->coplanarset);
    +    if (qh_setsize(facet->coplanarset) < 6) {
    +      qh_fprintf(fp, 9171, "    - coplanar set(furthest p%d):\n", qh_pointid(furthest));
    +      FOREACHpoint_(facet->coplanarset)
    +        qh_printpoint(fp, "     ", point);
    +    }else if (qh_setsize(facet->coplanarset) < 21) {
    +      qh_printpoints(fp, "    - coplanar set:", facet->coplanarset);
    +    }else {
    +      qh_fprintf(fp, 9172, "    - coplanar set:  %d points.", qh_setsize(facet->coplanarset));
    +      qh_printpoint(fp, "  Furthest", furthest);
    +    }
    +    zinc_(Zdistio);
    +    qh_distplane(furthest, facet, &dist);
    +    qh_fprintf(fp, 9173, "      furthest distance= %2.2g\n", dist);
    +  }
    +  qh_printvertices(fp, "    - vertices:", facet->vertices);
    +  qh_fprintf(fp, 9174, "    - neighboring facets:");
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      qh_fprintf(fp, 9175, " MERGE");
    +    else if (neighbor == qh_DUPLICATEridge)
    +      qh_fprintf(fp, 9176, " DUP");
    +    else
    +      qh_fprintf(fp, 9177, " f%d", neighbor->id);
    +  }
    +  qh_fprintf(fp, 9178, "\n");
    +  qh RANDOMdist= qh old_randomdist;
    +} /* printfacetheader */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetridges( fp, facet )
    +    prints ridges of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +    assumes the ridges exist
    +    for 'f' output
    +    same as QhullFacet::printRidges
    +*/
    +void qh_printfacetridges(FILE *fp, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int numridges= 0;
    +
    +
    +  if (facet->visible && qh NEWfacets) {
    +    qh_fprintf(fp, 9179, "    - ridges(ids may be garbage):");
    +    FOREACHridge_(facet->ridges)
    +      qh_fprintf(fp, 9180, " r%d", ridge->id);
    +    qh_fprintf(fp, 9181, "\n");
    +  }else {
    +    qh_fprintf(fp, 9182, "    - ridges:\n");
    +    FOREACHridge_(facet->ridges)
    +      ridge->seen= False;
    +    if (qh hull_dim == 3) {
    +      ridge= SETfirstt_(facet->ridges, ridgeT);
    +      while (ridge && !ridge->seen) {
    +        ridge->seen= True;
    +        qh_printridge(fp, ridge);
    +        numridges++;
    +        ridge= qh_nextridge3d(ridge, facet, NULL);
    +        }
    +    }else {
    +      FOREACHneighbor_(facet) {
    +        FOREACHridge_(facet->ridges) {
    +          if (otherfacet_(ridge,facet) == neighbor) {
    +            ridge->seen= True;
    +            qh_printridge(fp, ridge);
    +            numridges++;
    +          }
    +        }
    +      }
    +    }
    +    if (numridges != qh_setsize(facet->ridges)) {
    +      qh_fprintf(fp, 9183, "     - all ridges:");
    +      FOREACHridge_(facet->ridges)
    +        qh_fprintf(fp, 9184, " r%d", ridge->id);
    +        qh_fprintf(fp, 9185, "\n");
    +    }
    +    FOREACHridge_(facet->ridges) {
    +      if (!ridge->seen)
    +        qh_printridge(fp, ridge);
    +    }
    +  }
    +} /* printfacetridges */
    +
    +/*---------------------------------
    +
    +  qh_printfacets( fp, format, facetlist, facets, printall )
    +    prints facetlist and/or facet set in output format
    +
    +  notes:
    +    also used for specialized formats ('FO' and summary)
    +    turns off 'Rn' option since want actual numbers
    +*/
    +void qh_printfacets(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  coordT *center;
    +  realT outerplane, innerplane;
    +
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  if (qh CDDoutput && (format == qh_PRINTcentrums || format == qh_PRINTpointintersect || format == qh_PRINToff))
    +    qh_fprintf(qh ferr, 7056, "qhull warning: CDD format is not available for centrums, halfspace\nintersections, and OFF file format.\n");
    +  if (format == qh_PRINTnone)
    +    ; /* print nothing */
    +  else if (format == qh_PRINTaverage) {
    +    vertices= qh_facetvertices(facetlist, facets, printall);
    +    center= qh_getcenter(vertices);
    +    qh_fprintf(fp, 9186, "%d 1\n", qh hull_dim);
    +    qh_printpointid(fp, NULL, qh hull_dim, center, qh_IDunknown);
    +    qh_memfree(center, qh normal_size);
    +    qh_settempfree(&vertices);
    +  }else if (format == qh_PRINTextremes) {
    +    if (qh DELAUNAY)
    +      qh_printextremes_d(fp, facetlist, facets, printall);
    +    else if (qh hull_dim == 2)
    +      qh_printextremes_2d(fp, facetlist, facets, printall);
    +    else
    +      qh_printextremes(fp, facetlist, facets, printall);
    +  }else if (format == qh_PRINToptions)
    +    qh_fprintf(fp, 9187, "Options selected for Qhull %s:\n%s\n", qh_version, qh qhull_options);
    +  else if (format == qh_PRINTpoints && !qh VORONOI)
    +    qh_printpoints_out(fp, facetlist, facets, printall);
    +  else if (format == qh_PRINTqhull)
    +    qh_fprintf(fp, 9188, "%s | %s\n", qh rbox_command, qh qhull_command);
    +  else if (format == qh_PRINTsize) {
    +    qh_fprintf(fp, 9189, "0\n2 ");
    +    qh_fprintf(fp, 9190, qh_REAL_1, qh totarea);
    +    qh_fprintf(fp, 9191, qh_REAL_1, qh totvol);
    +    qh_fprintf(fp, 9192, "\n");
    +  }else if (format == qh_PRINTsummary) {
    +    qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +    vertices= qh_facetvertices(facetlist, facets, printall);
    +    qh_fprintf(fp, 9193, "10 %d %d %d %d %d %d %d %d %d %d\n2 ", qh hull_dim,
    +                qh num_points + qh_setsize(qh other_points),
    +                qh num_vertices, qh num_facets - qh num_visible,
    +                qh_setsize(vertices), numfacets, numcoplanars,
    +                numfacets - numsimplicial, zzval_(Zdelvertextot),
    +                numtricoplanars);
    +    qh_settempfree(&vertices);
    +    qh_outerinner(NULL, &outerplane, &innerplane);
    +    qh_fprintf(fp, 9194, qh_REAL_2n, outerplane, innerplane);
    +  }else if (format == qh_PRINTvneighbors)
    +    qh_printvneighbors(fp, facetlist, facets, printall);
    +  else if (qh VORONOI && format == qh_PRINToff)
    +    qh_printvoronoi(fp, format, facetlist, facets, printall);
    +  else if (qh VORONOI && format == qh_PRINTgeom) {
    +    qh_printbegin(fp, format, facetlist, facets, printall);
    +    qh_printvoronoi(fp, format, facetlist, facets, printall);
    +    qh_printend(fp, format, facetlist, facets, printall);
    +  }else if (qh VORONOI
    +  && (format == qh_PRINTvertices || format == qh_PRINTinner || format == qh_PRINTouter))
    +    qh_printvdiagram(fp, format, facetlist, facets, printall);
    +  else {
    +    qh_printbegin(fp, format, facetlist, facets, printall);
    +    FORALLfacet_(facetlist)
    +      qh_printafacet(fp, format, facet, printall);
    +    FOREACHfacet_(facets)
    +      qh_printafacet(fp, format, facet, printall);
    +    qh_printend(fp, format, facetlist, facets, printall);
    +  }
    +  qh RANDOMdist= qh old_randomdist;
    +} /* printfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhyperplaneintersection( fp, facet1, facet2, vertices, color )
    +    print Geomview OFF or 4OFF for the intersection of two hyperplanes in 3-d or 4-d
    +*/
    +void qh_printhyperplaneintersection(FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]) {
    +  realT costheta, denominator, dist1, dist2, s, t, mindenom, p[4];
    +  vertexT *vertex, **vertexp;
    +  int i, k;
    +  boolT nearzero1, nearzero2;
    +
    +  costheta= qh_getangle(facet1->normal, facet2->normal);
    +  denominator= 1 - costheta * costheta;
    +  i= qh_setsize(vertices);
    +  if (qh hull_dim == 3)
    +    qh_fprintf(fp, 9195, "VECT 1 %d 1 %d 1 ", i, i);
    +  else if (qh hull_dim == 4 && qh DROPdim >= 0)
    +    qh_fprintf(fp, 9196, "OFF 3 1 1 ");
    +  else
    +    qh printoutvar++;
    +  qh_fprintf(fp, 9197, "# intersect f%d f%d\n", facet1->id, facet2->id);
    +  mindenom= 1 / (10.0 * qh MAXabs_coord);
    +  FOREACHvertex_(vertices) {
    +    zadd_(Zdistio, 2);
    +    qh_distplane(vertex->point, facet1, &dist1);
    +    qh_distplane(vertex->point, facet2, &dist2);
    +    s= qh_divzero(-dist1 + costheta * dist2, denominator,mindenom,&nearzero1);
    +    t= qh_divzero(-dist2 + costheta * dist1, denominator,mindenom,&nearzero2);
    +    if (nearzero1 || nearzero2)
    +      s= t= 0.0;
    +    for (k=qh hull_dim; k--; )
    +      p[k]= vertex->point[k] + facet1->normal[k] * s + facet2->normal[k] * t;
    +    if (qh PRINTdim <= 3) {
    +      qh_projectdim3(p, p);
    +      qh_fprintf(fp, 9198, "%8.4g %8.4g %8.4g # ", p[0], p[1], p[2]);
    +    }else
    +      qh_fprintf(fp, 9199, "%8.4g %8.4g %8.4g %8.4g # ", p[0], p[1], p[2], p[3]);
    +    if (nearzero1+nearzero2)
    +      qh_fprintf(fp, 9200, "p%d(coplanar facets)\n", qh_pointid(vertex->point));
    +    else
    +      qh_fprintf(fp, 9201, "projected p%d\n", qh_pointid(vertex->point));
    +  }
    +  if (qh hull_dim == 3)
    +    qh_fprintf(fp, 9202, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +  else if (qh hull_dim == 4 && qh DROPdim >= 0)
    +    qh_fprintf(fp, 9203, "3 0 1 2 %8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printhyperplaneintersection */
    +
    +/*---------------------------------
    +
    +  qh_printline3geom( fp, pointA, pointB, color )
    +    prints a line as a VECT
    +    prints 0's for qh.DROPdim
    +
    +  notes:
    +    if pointA == pointB,
    +      it's a 1 point VECT
    +*/
    +void qh_printline3geom(FILE *fp, pointT *pointA, pointT *pointB, realT color[3]) {
    +  int k;
    +  realT pA[4], pB[4];
    +
    +  qh_projectdim3(pointA, pA);
    +  qh_projectdim3(pointB, pB);
    +  if ((fabs(pA[0] - pB[0]) > 1e-3) ||
    +      (fabs(pA[1] - pB[1]) > 1e-3) ||
    +      (fabs(pA[2] - pB[2]) > 1e-3)) {
    +    qh_fprintf(fp, 9204, "VECT 1 2 1 2 1\n");
    +    for (k=0; k < 3; k++)
    +       qh_fprintf(fp, 9205, "%8.4g ", pB[k]);
    +    qh_fprintf(fp, 9206, " # p%d\n", qh_pointid(pointB));
    +  }else
    +    qh_fprintf(fp, 9207, "VECT 1 1 1 1 1\n");
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(fp, 9208, "%8.4g ", pA[k]);
    +  qh_fprintf(fp, 9209, " # p%d\n", qh_pointid(pointA));
    +  qh_fprintf(fp, 9210, "%8.4g %8.4g %8.4g 1\n", color[0], color[1], color[2]);
    +}
    +
    +/*---------------------------------
    +
    +  qh_printneighborhood( fp, format, facetA, facetB, printall )
    +    print neighborhood of one or two facets
    +
    +  notes:
    +    calls qh_findgood_all()
    +    bumps qh.visit_id
    +*/
    +void qh_printneighborhood(FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall) {
    +  facetT *neighbor, **neighborp, *facet;
    +  setT *facets;
    +
    +  if (format == qh_PRINTnone)
    +    return;
    +  qh_findgood_all(qh facet_list);
    +  if (facetA == facetB)
    +    facetB= NULL;
    +  facets= qh_settemp(2*(qh_setsize(facetA->neighbors)+1));
    +  qh visit_id++;
    +  for (facet= facetA; facet; facet= ((facet == facetA) ? facetB : NULL)) {
    +    if (facet->visitid != qh visit_id) {
    +      facet->visitid= qh visit_id;
    +      qh_setappend(&facets, facet);
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh visit_id)
    +        continue;
    +      neighbor->visitid= qh visit_id;
    +      if (printall || !qh_skipfacet(neighbor))
    +        qh_setappend(&facets, neighbor);
    +    }
    +  }
    +  qh_printfacets(fp, format, NULL, facets, printall);
    +  qh_settempfree(&facets);
    +} /* printneighborhood */
    +
    +/*---------------------------------
    +
    +  qh_printpoint( fp, string, point )
    +  qh_printpointid( fp, string, dim, point, id )
    +    prints the coordinates of a point
    +
    +  returns:
    +    if string is defined
    +      prints 'string p%d'.  Skips p%d if id=qh_IDunknown(-1) or qh_IDnone(-3)
    +
    +  notes:
    +    nop if point is NULL
    +    Same as QhullPoint's printPoint
    +*/
    +void qh_printpoint(FILE *fp, const char *string, pointT *point) {
    +  int id= qh_pointid( point);
    +
    +  qh_printpointid( fp, string, qh hull_dim, point, id);
    +} /* printpoint */
    +
    +void qh_printpointid(FILE *fp, const char *string, int dim, pointT *point, int id) {
    +  int k;
    +  realT r; /*bug fix*/
    +
    +  if (!point)
    +    return;
    +  if (string) {
    +    qh_fprintf(fp, 9211, "%s", string);
    +    if (id != qh_IDunknown && id != qh_IDnone)
    +      qh_fprintf(fp, 9212, " p%d: ", id);
    +  }
    +  for (k=dim; k--; ) {
    +    r= *point++;
    +    if (string)
    +      qh_fprintf(fp, 9213, " %8.4g", r);
    +    else
    +      qh_fprintf(fp, 9214, qh_REAL_1, r);
    +  }
    +  qh_fprintf(fp, 9215, "\n");
    +} /* printpointid */
    +
    +/*---------------------------------
    +
    +  qh_printpoint3( fp, point )
    +    prints 2-d, 3-d, or 4-d point as Geomview 3-d coordinates
    +*/
    +void qh_printpoint3(FILE *fp, pointT *point) {
    +  int k;
    +  realT p[4];
    +
    +  qh_projectdim3(point, p);
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(fp, 9216, "%8.4g ", p[k]);
    +  qh_fprintf(fp, 9217, " # p%d\n", qh_pointid(point));
    +} /* printpoint3 */
    +
    +/*----------------------------------------
    +-printpoints- print pointids for a set of points starting at index
    +   see geom.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printpoints_out( fp, facetlist, facets, printall )
    +    prints vertices, coplanar/inside points, for facets by their point coordinates
    +    allows qh.CDDoutput
    +
    +  notes:
    +    same format as qhull input
    +    if no coplanar/interior points,
    +      same order as qh_printextremes
    +*/
    +void qh_printpoints_out(FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int allpoints= qh num_points + qh_setsize(qh other_points);
    +  int numpoints=0, point_i, point_n;
    +  setT *vertices, *points;
    +  facetT *facet, **facetp;
    +  pointT *point, **pointp;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +
    +  points= qh_settemp(allpoints);
    +  qh_setzero(points, 0, allpoints);
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(vertex->point);
    +    if (id >= 0)
    +      SETelem_(points, id)= vertex->point;
    +  }
    +  if (qh KEEPinside || qh KEEPcoplanar || qh KEEPnearinside) {
    +    FORALLfacet_(facetlist) {
    +      if (!printall && qh_skipfacet(facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +    FOREACHfacet_(facets) {
    +      if (!printall && qh_skipfacet(facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +  }
    +  qh_settempfree(&vertices);
    +  FOREACHpoint_i_(points) {
    +    if (point)
    +      numpoints++;
    +  }
    +  if (qh CDDoutput)
    +    qh_fprintf(fp, 9218, "%s | %s\nbegin\n%d %d real\n", qh rbox_command,
    +             qh qhull_command, numpoints, qh hull_dim + 1);
    +  else
    +    qh_fprintf(fp, 9219, "%d\n%d\n", qh hull_dim, numpoints);
    +  FOREACHpoint_i_(points) {
    +    if (point) {
    +      if (qh CDDoutput)
    +        qh_fprintf(fp, 9220, "1 ");
    +      qh_printpoint(fp, NULL, point);
    +    }
    +  }
    +  if (qh CDDoutput)
    +    qh_fprintf(fp, 9221, "end\n");
    +  qh_settempfree(&points);
    +} /* printpoints_out */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpointvect( fp, point, normal, center, radius, color )
    +    prints a 2-d, 3-d, or 4-d point as 3-d VECT's relative to normal or to center point
    +*/
    +void qh_printpointvect(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]) {
    +  realT diff[4], pointA[4];
    +  int k;
    +
    +  for (k=qh hull_dim; k--; ) {
    +    if (center)
    +      diff[k]= point[k]-center[k];
    +    else if (normal)
    +      diff[k]= normal[k];
    +    else
    +      diff[k]= 0;
    +  }
    +  if (center)
    +    qh_normalize2(diff, qh hull_dim, True, NULL, NULL);
    +  for (k=qh hull_dim; k--; )
    +    pointA[k]= point[k]+diff[k] * radius;
    +  qh_printline3geom(fp, point, pointA, color);
    +} /* printpointvect */
    +
    +/*---------------------------------
    +
    +  qh_printpointvect2( fp, point, normal, center, radius )
    +    prints a 2-d, 3-d, or 4-d point as 2 3-d VECT's for an imprecise point
    +*/
    +void qh_printpointvect2(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius) {
    +  realT red[3]={1, 0, 0}, yellow[3]={1, 1, 0};
    +
    +  qh_printpointvect(fp, point, normal, center, radius, red);
    +  qh_printpointvect(fp, point, normal, center, -radius, yellow);
    +} /* printpointvect2 */
    +
    +/*---------------------------------
    +
    +  qh_printridge( fp, ridge )
    +    prints the information in a ridge
    +
    +  notes:
    +    for qh_printfacetridges()
    +    same as operator<< [QhullRidge.cpp]
    +*/
    +void qh_printridge(FILE *fp, ridgeT *ridge) {
    +
    +  qh_fprintf(fp, 9222, "     - r%d", ridge->id);
    +  if (ridge->tested)
    +    qh_fprintf(fp, 9223, " tested");
    +  if (ridge->nonconvex)
    +    qh_fprintf(fp, 9224, " nonconvex");
    +  qh_fprintf(fp, 9225, "\n");
    +  qh_printvertices(fp, "           vertices:", ridge->vertices);
    +  if (ridge->top && ridge->bottom)
    +    qh_fprintf(fp, 9226, "           between f%d and f%d\n",
    +            ridge->top->id, ridge->bottom->id);
    +} /* printridge */
    +
    +/*---------------------------------
    +
    +  qh_printspheres( fp, vertices, radius )
    +    prints 3-d vertices as OFF spheres
    +
    +  notes:
    +    inflated octahedron from Stuart Levy earth/mksphere2
    +*/
    +void qh_printspheres(FILE *fp, setT *vertices, realT radius) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh printoutnum++;
    +  qh_fprintf(fp, 9227, "{appearance {-edge -normal normscale 0} {\n\
    +INST geom {define vsphere OFF\n\
    +18 32 48\n\
    +\n\
    +0 0 1\n\
    +1 0 0\n\
    +0 1 0\n\
    +-1 0 0\n\
    +0 -1 0\n\
    +0 0 -1\n\
    +0.707107 0 0.707107\n\
    +0 -0.707107 0.707107\n\
    +0.707107 -0.707107 0\n\
    +-0.707107 0 0.707107\n\
    +-0.707107 -0.707107 0\n\
    +0 0.707107 0.707107\n\
    +-0.707107 0.707107 0\n\
    +0.707107 0.707107 0\n\
    +0.707107 0 -0.707107\n\
    +0 0.707107 -0.707107\n\
    +-0.707107 0 -0.707107\n\
    +0 -0.707107 -0.707107\n\
    +\n\
    +3 0 6 11\n\
    +3 0 7 6 \n\
    +3 0 9 7 \n\
    +3 0 11 9\n\
    +3 1 6 8 \n\
    +3 1 8 14\n\
    +3 1 13 6\n\
    +3 1 14 13\n\
    +3 2 11 13\n\
    +3 2 12 11\n\
    +3 2 13 15\n\
    +3 2 15 12\n\
    +3 3 9 12\n\
    +3 3 10 9\n\
    +3 3 12 16\n\
    +3 3 16 10\n\
    +3 4 7 10\n\
    +3 4 8 7\n\
    +3 4 10 17\n\
    +3 4 17 8\n\
    +3 5 14 17\n\
    +3 5 15 14\n\
    +3 5 16 15\n\
    +3 5 17 16\n\
    +3 6 13 11\n\
    +3 7 8 6\n\
    +3 9 10 7\n\
    +3 11 12 9\n\
    +3 14 8 17\n\
    +3 15 13 14\n\
    +3 16 12 15\n\
    +3 17 10 16\n} transforms { TLIST\n");
    +  FOREACHvertex_(vertices) {
    +    qh_fprintf(fp, 9228, "%8.4g 0 0 0 # v%d\n 0 %8.4g 0 0\n0 0 %8.4g 0\n",
    +      radius, vertex->id, radius, radius);
    +    qh_printpoint3(fp, vertex->point);
    +    qh_fprintf(fp, 9229, "1\n");
    +  }
    +  qh_fprintf(fp, 9230, "}}}\n");
    +} /* printspheres */
    +
    +
    +/*----------------------------------------------
    +-printsummary-
    +                see libqhull.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram( fp, format, facetlist, facets, printall )
    +    print voronoi diagram
    +      # of pairs of input sites
    +      #indices site1 site2 vertex1 ...
    +
    +    sites indexed by input point id
    +      point 0 is the first input point
    +    vertices indexed by 'o' and 'p' order
    +      vertex 0 is the 'vertex-at-infinity'
    +      vertex 1 is the first Voronoi vertex
    +
    +  see:
    +    qh_printvoronoi()
    +    qh_eachvoronoi_all()
    +
    +  notes:
    +    if all facets are upperdelaunay,
    +      prints upper hull (furthest-site Voronoi diagram)
    +*/
    +void qh_printvdiagram(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  int totcount, numcenters;
    +  boolT isLower;
    +  qh_RIDGE innerouter= qh_RIDGEall;
    +  printvridgeT printvridge= NULL;
    +
    +  if (format == qh_PRINTvertices) {
    +    innerouter= qh_RIDGEall;
    +    printvridge= qh_printvridge;
    +  }else if (format == qh_PRINTinner) {
    +    innerouter= qh_RIDGEinner;
    +    printvridge= qh_printvnorm;
    +  }else if (format == qh_PRINTouter) {
    +    innerouter= qh_RIDGEouter;
    +    printvridge= qh_printvnorm;
    +  }else {
    +    qh_fprintf(qh ferr, 6219, "Qhull internal error (qh_printvdiagram): unknown print format %d.\n", format);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  vertices= qh_markvoronoi(facetlist, facets, printall, &isLower, &numcenters);
    +  totcount= qh_printvdiagram2(NULL, NULL, vertices, innerouter, False);
    +  qh_fprintf(fp, 9231, "%d\n", totcount);
    +  totcount= qh_printvdiagram2(fp, printvridge, vertices, innerouter, True /* inorder*/);
    +  qh_settempfree(&vertices);
    +#if 0  /* for testing qh_eachvoronoi_all */
    +  qh_fprintf(fp, 9232, "\n");
    +  totcount= qh_eachvoronoi_all(fp, printvridge, qh UPPERdelaunay, innerouter, True /* inorder*/);
    +  qh_fprintf(fp, 9233, "%d\n", totcount);
    +#endif
    +} /* printvdiagram */
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram2( fp, printvridge, vertices, innerouter, inorder )
    +    visit all pairs of input sites (vertices) for selected Voronoi vertices
    +    vertices may include NULLs
    +
    +  innerouter:
    +    qh_RIDGEall   print inner ridges(bounded) and outer ridges(unbounded)
    +    qh_RIDGEinner print only inner ridges
    +    qh_RIDGEouter print only outer ridges
    +
    +  inorder:
    +    print 3-d Voronoi vertices in order
    +
    +  assumes:
    +    qh_markvoronoi marked facet->visitid for Voronoi vertices
    +    all facet->seen= False
    +    all facet->seen2= True
    +
    +  returns:
    +    total number of Voronoi ridges
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers) for each ridge
    +      [see qh_eachvoronoi()]
    +
    +  see:
    +    qh_eachvoronoi_all()
    +*/
    +int qh_printvdiagram2(FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder) {
    +  int totcount= 0;
    +  int vertex_i, vertex_n;
    +  vertexT *vertex;
    +
    +  FORALLvertices
    +    vertex->seen= False;
    +  FOREACHvertex_i_(vertices) {
    +    if (vertex) {
    +      if (qh GOODvertex > 0 && qh_pointid(vertex->point)+1 != qh GOODvertex)
    +        continue;
    +      totcount += qh_eachvoronoi(fp, printvridge, vertex, !qh_ALL, innerouter, inorder);
    +    }
    +  }
    +  return totcount;
    +} /* printvdiagram2 */
    +
    +/*---------------------------------
    +
    +  qh_printvertex( fp, vertex )
    +    prints the information in a vertex
    +    Duplicated as operator<< [QhullVertex.cpp]
    +*/
    +void qh_printvertex(FILE *fp, vertexT *vertex) {
    +  pointT *point;
    +  int k, count= 0;
    +  facetT *neighbor, **neighborp;
    +  realT r; /*bug fix*/
    +
    +  if (!vertex) {
    +    qh_fprintf(fp, 9234, "  NULLvertex\n");
    +    return;
    +  }
    +  qh_fprintf(fp, 9235, "- p%d(v%d):", qh_pointid(vertex->point), vertex->id);
    +  point= vertex->point;
    +  if (point) {
    +    for (k=qh hull_dim; k--; ) {
    +      r= *point++;
    +      qh_fprintf(fp, 9236, " %5.2g", r);
    +    }
    +  }
    +  if (vertex->deleted)
    +    qh_fprintf(fp, 9237, " deleted");
    +  if (vertex->delridge)
    +    qh_fprintf(fp, 9238, " ridgedeleted");
    +  qh_fprintf(fp, 9239, "\n");
    +  if (vertex->neighbors) {
    +    qh_fprintf(fp, 9240, "  neighbors:");
    +    FOREACHneighbor_(vertex) {
    +      if (++count % 100 == 0)
    +        qh_fprintf(fp, 9241, "\n     ");
    +      qh_fprintf(fp, 9242, " f%d", neighbor->id);
    +    }
    +    qh_fprintf(fp, 9243, "\n");
    +  }
    +} /* printvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertexlist( fp, string, facetlist, facets, printall )
    +    prints vertices used by a facetlist or facet set
    +    tests qh_skipfacet() if !printall
    +*/
    +void qh_printvertexlist(FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  qh_fprintf(fp, 9244, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_printvertex(fp, vertex);
    +  qh_settempfree(&vertices);
    +} /* printvertexlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertices( fp, string, vertices )
    +    prints vertices in a set
    +    duplicated as printVertexSet [QhullVertex.cpp]
    +*/
    +void qh_printvertices(FILE *fp, const char* string, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh_fprintf(fp, 9245, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(fp, 9246, " p%d(v%d)", qh_pointid(vertex->point), vertex->id);
    +  qh_fprintf(fp, 9247, "\n");
    +} /* printvertices */
    +
    +/*---------------------------------
    +
    +  qh_printvneighbors( fp, facetlist, facets, printall )
    +    print vertex neighbors of vertices in facetlist and facets ('FN')
    +
    +  notes:
    +    qh_countfacets clears facet->visitid for non-printed facets
    +
    +  design:
    +    collect facet count and related statistics
    +    if necessary, build neighbor sets for each vertex
    +    collect vertices in facetlist and facets
    +    build a point array for point->vertex and point->coplanar facet
    +    for each point
    +      list vertex neighbors or coplanar facet
    +*/
    +void qh_printvneighbors(FILE *fp, facetT* facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numneighbors, numcoplanars, numtricoplanars;
    +  setT *vertices, *vertex_points, *coplanar_points;
    +  int numpoints= qh num_points + qh_setsize(qh other_points);
    +  vertexT *vertex, **vertexp;
    +  int vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  pointT *point, **pointp;
    +
    +  qh_countfacets(facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);  /* sets facet->visitid */
    +  qh_fprintf(fp, 9248, "%d\n", numpoints);
    +  qh_vertexneighbors();
    +  vertices= qh_facetvertices(facetlist, facets, printall);
    +  vertex_points= qh_settemp(numpoints);
    +  coplanar_points= qh_settemp(numpoints);
    +  qh_setzero(vertex_points, 0, numpoints);
    +  qh_setzero(coplanar_points, 0, numpoints);
    +  FOREACHvertex_(vertices)
    +    qh_point_add(vertex_points, vertex->point, vertex);
    +  FORALLfacet_(facetlist) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(coplanar_points, point, facet);
    +  }
    +  FOREACHfacet_(facets) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(coplanar_points, point, facet);
    +  }
    +  FOREACHvertex_i_(vertex_points) {
    +    if (vertex) {
    +      numneighbors= qh_setsize(vertex->neighbors);
    +      qh_fprintf(fp, 9249, "%d", numneighbors);
    +      if (qh hull_dim == 3)
    +        qh_order_vertexneighbors(vertex);
    +      else if (qh hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors,
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex)
    +        qh_fprintf(fp, 9250, " %d",
    +                 neighbor->visitid ? neighbor->visitid - 1 : 0 - neighbor->id);
    +      qh_fprintf(fp, 9251, "\n");
    +    }else if ((facet= SETelemt_(coplanar_points, vertex_i, facetT)))
    +      qh_fprintf(fp, 9252, "1 %d\n",
    +                  facet->visitid ? facet->visitid - 1 : 0 - facet->id);
    +    else
    +      qh_fprintf(fp, 9253, "0\n");
    +  }
    +  qh_settempfree(&coplanar_points);
    +  qh_settempfree(&vertex_points);
    +  qh_settempfree(&vertices);
    +} /* printvneighbors */
    +
    +/*---------------------------------
    +
    +  qh_printvoronoi( fp, format, facetlist, facets, printall )
    +    print voronoi diagram in 'o' or 'G' format
    +    for 'o' format
    +      prints voronoi centers for each facet and for infinity
    +      for each vertex, lists ids of printed facets or infinity
    +      assumes facetlist and facets are disjoint
    +    for 'G' format
    +      prints an OFF object
    +      adds a 0 coordinate to center
    +      prints infinity but does not list in vertices
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    if 'o',
    +      prints a line for each point except "at-infinity"
    +    if all facets are upperdelaunay,
    +      reverses lower and upper hull
    +*/
    +void qh_printvoronoi(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int k, numcenters, numvertices= 0, numneighbors, numinf, vid=1, vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  setT *vertices;
    +  vertexT *vertex;
    +  boolT isLower;
    +  unsigned int numfacets= (unsigned int) qh num_facets;
    +
    +  vertices= qh_markvoronoi(facetlist, facets, printall, &isLower, &numcenters);
    +  FOREACHvertex_i_(vertices) {
    +    if (vertex) {
    +      numvertices++;
    +      numneighbors = numinf = 0;
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +      if (numinf && !numneighbors) {
    +        SETelem_(vertices, vertex_i)= NULL;
    +        numvertices--;
    +      }
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(fp, 9254, "{appearance {+edge -face} OFF %d %d 1 # Voronoi centers and cells\n",
    +                numcenters, numvertices);
    +  else
    +    qh_fprintf(fp, 9255, "%d\n%d %d 1\n", qh hull_dim-1, numcenters, qh_setsize(vertices));
    +  if (format == qh_PRINTgeom) {
    +    for (k=qh hull_dim-1; k--; )
    +      qh_fprintf(fp, 9256, qh_REAL_1, 0.0);
    +    qh_fprintf(fp, 9257, " 0 # infinity not used\n");
    +  }else {
    +    for (k=qh hull_dim-1; k--; )
    +      qh_fprintf(fp, 9258, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(fp, 9259, "\n");
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(fp, 9260, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(fp, 9261, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHvertex_i_(vertices) {
    +    numneighbors= 0;
    +    numinf=0;
    +    if (vertex) {
    +      if (qh hull_dim == 3)
    +        qh_order_vertexneighbors(vertex);
    +      else if (qh hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT),
    +             (size_t)qh_setsize(vertex->neighbors),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +    }
    +    if (format == qh_PRINTgeom) {
    +      if (vertex) {
    +        qh_fprintf(fp, 9262, "%d", numneighbors);
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid && neighbor->visitid < numfacets)
    +            qh_fprintf(fp, 9263, " %d", neighbor->visitid);
    +        }
    +        qh_fprintf(fp, 9264, " # p%d(v%d)\n", vertex_i, vertex->id);
    +      }else
    +        qh_fprintf(fp, 9265, " # p%d is coplanar or isolated\n", vertex_i);
    +    }else {
    +      if (numinf)
    +        numneighbors++;
    +      qh_fprintf(fp, 9266, "%d", numneighbors);
    +      if (vertex) {
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid == 0) {
    +            if (numinf) {
    +              numinf= 0;
    +              qh_fprintf(fp, 9267, " %d", neighbor->visitid);
    +            }
    +          }else if (neighbor->visitid < numfacets)
    +            qh_fprintf(fp, 9268, " %d", neighbor->visitid);
    +        }
    +      }
    +      qh_fprintf(fp, 9269, "\n");
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(fp, 9270, "}\n");
    +  qh_settempfree(&vertices);
    +} /* printvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_printvnorm( fp, vertex, vertexA, centers, unbounded )
    +    print one separating plane of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  note:
    +    parameter unbounded is UNUSED by this callback
    +
    +  see:
    +    qh_printvdiagram()
    +    qh_eachvoronoi()
    +*/
    +void qh_printvnorm(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  pointT *normal;
    +  realT offset;
    +  int k;
    +  QHULL_UNUSED(unbounded);
    +
    +  normal= qh_detvnorm(vertex, vertexA, centers, &offset);
    +  qh_fprintf(fp, 9271, "%d %d %d ",
    +      2+qh hull_dim, qh_pointid(vertex->point), qh_pointid(vertexA->point));
    +  for (k=0; k< qh hull_dim-1; k++)
    +    qh_fprintf(fp, 9272, qh_REAL_1, normal[k]);
    +  qh_fprintf(fp, 9273, qh_REAL_1, offset);
    +  qh_fprintf(fp, 9274, "\n");
    +} /* printvnorm */
    +
    +/*---------------------------------
    +
    +  qh_printvridge( fp, vertex, vertexA, centers, unbounded )
    +    print one ridge of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    the user may use a different function
    +    parameter unbounded is UNUSED
    +*/
    +void qh_printvridge(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  facetT *facet, **facetp;
    +  QHULL_UNUSED(unbounded);
    +
    +  qh_fprintf(fp, 9275, "%d %d %d", qh_setsize(centers)+2,
    +       qh_pointid(vertex->point), qh_pointid(vertexA->point));
    +  FOREACHfacet_(centers)
    +    qh_fprintf(fp, 9276, " %d", facet->visitid);
    +  qh_fprintf(fp, 9277, "\n");
    +} /* printvridge */
    +
    +/*---------------------------------
    +
    +  qh_projectdim3( source, destination )
    +    project 2-d 3-d or 4-d point to a 3-d point
    +    uses qh.DROPdim and qh.hull_dim
    +    source and destination may be the same
    +
    +  notes:
    +    allocate 4 elements to destination just in case
    +*/
    +void qh_projectdim3(pointT *source, pointT *destination) {
    +  int i,k;
    +
    +  for (k=0, i=0; k < qh hull_dim; k++) {
    +    if (qh hull_dim == 4) {
    +      if (k != qh DROPdim)
    +        destination[i++]= source[k];
    +    }else if (k == qh DROPdim)
    +      destination[i++]= 0;
    +    else
    +      destination[i++]= source[k];
    +  }
    +  while (i < 3)
    +    destination[i++]= 0.0;
    +} /* projectdim3 */
    +
    +/*---------------------------------
    +
    +  qh_readfeasible( dim, curline )
    +    read feasible point from current line and qh.fin
    +
    +  returns:
    +    number of lines read from qh.fin
    +    sets qh.feasible_point with malloc'd coordinates
    +
    +  notes:
    +    checks for qh.HALFspace
    +    assumes dim > 1
    +
    +  see:
    +    qh_setfeasible
    +*/
    +int qh_readfeasible(int dim, const char *curline) {
    +  boolT isfirst= True;
    +  int linecount= 0, tokcount= 0;
    +  const char *s;
    +  char *t, firstline[qh_MAXfirst+1];
    +  coordT *coords, value;
    +
    +  if (!qh HALFspace) {
    +    qh_fprintf(qh ferr, 6070, "qhull input error: feasible point(dim 1 coords) is only valid for halfspace intersection\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh feasible_string)
    +    qh_fprintf(qh ferr, 7057, "qhull input warning: feasible point(dim 1 coords) overrides 'Hn,n,n' feasible point for halfspace intersection\n");
    +  if (!(qh feasible_point= (coordT*)qh_malloc(dim* sizeof(coordT)))) {
    +    qh_fprintf(qh ferr, 6071, "qhull error: insufficient memory for feasible point\n");
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh feasible_point;
    +  while ((s= (isfirst ?  curline : fgets(firstline, qh_MAXfirst, qh fin)))) {
    +    if (isfirst)
    +      isfirst= False;
    +    else
    +      linecount++;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t)
    +        break;
    +      s= t;
    +      *(coords++)= value;
    +      if (++tokcount == dim) {
    +        while (isspace(*s))
    +          s++;
    +        qh_strtod(s, &t);
    +        if (s != t) {
    +          qh_fprintf(qh ferr, 6072, "qhull input error: coordinates for feasible point do not finish out the line: %s\n",
    +               s);
    +          qh_errexit(qh_ERRinput, NULL, NULL);
    +        }
    +        return linecount;
    +      }
    +    }
    +  }
    +  qh_fprintf(qh ferr, 6073, "qhull input error: only %d coordinates.  Could not read %d-d feasible point.\n",
    +           tokcount, dim);
    +  qh_errexit(qh_ERRinput, NULL, NULL);
    +  return 0;
    +} /* readfeasible */
    +
    +/*---------------------------------
    +
    +  qh_readpoints( numpoints, dimension, ismalloc )
    +    read points from qh.fin into qh.first_point, qh.num_points
    +    qh.fin is lines of coordinates, one per vertex, first line number of points
    +    if 'rbox D4',
    +      gives message
    +    if qh.ATinfinity,
    +      adds point-at-infinity for Delaunay triangulations
    +
    +  returns:
    +    number of points, array of point coordinates, dimension, ismalloc True
    +    if qh.DELAUNAY & !qh.PROJECTinput, projects points to paraboloid
    +        and clears qh.PROJECTdelaunay
    +    if qh.HALFspace, reads optional feasible point, reads halfspaces,
    +        converts to dual.
    +
    +  for feasible point in "cdd format" in 3-d:
    +    3 1
    +    coordinates
    +    comments
    +    begin
    +    n 4 real/integer
    +    ...
    +    end
    +
    +  notes:
    +    dimension will change in qh_initqhull_globals if qh.PROJECTinput
    +    uses malloc() since qh_mem not initialized
    +    FIXUP QH11012: qh_readpoints needs rewriting, too long
    +*/
    +coordT *qh_readpoints(int *numpoints, int *dimension, boolT *ismalloc) {
    +  coordT *points, *coords, *infinity= NULL;
    +  realT paraboloid, maxboloid= -REALmax, value;
    +  realT *coordp= NULL, *offsetp= NULL, *normalp= NULL;
    +  char *s= 0, *t, firstline[qh_MAXfirst+1];
    +  int diminput=0, numinput=0, dimfeasible= 0, newnum, k, tempi;
    +  int firsttext=0, firstshort=0, firstlong=0, firstpoint=0;
    +  int tokcount= 0, linecount=0, maxcount, coordcount=0;
    +  boolT islong, isfirst= True, wasbegin= False;
    +  boolT isdelaunay= qh DELAUNAY && !qh PROJECTinput;
    +
    +  if (qh CDDinput) {
    +    while ((s= fgets(firstline, qh_MAXfirst, qh fin))) {
    +      linecount++;
    +      if (qh HALFspace && linecount == 1 && isdigit(*s)) {
    +        dimfeasible= qh_strtol(s, &s);
    +        while (isspace(*s))
    +          s++;
    +        if (qh_strtol(s, &s) == 1)
    +          linecount += qh_readfeasible(dimfeasible, s);
    +        else
    +          dimfeasible= 0;
    +      }else if (!memcmp(firstline, "begin", (size_t)5) || !memcmp(firstline, "BEGIN", (size_t)5))
    +        break;
    +      else if (!*qh rbox_command)
    +        strncat(qh rbox_command, s, sizeof(qh rbox_command)-1);
    +    }
    +    if (!s) {
    +      qh_fprintf(qh ferr, 6074, "qhull input error: missing \"begin\" for cdd-formated input\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  while (!numinput && (s= fgets(firstline, qh_MAXfirst, qh fin))) {
    +    linecount++;
    +    if (!memcmp(s, "begin", (size_t)5) || !memcmp(s, "BEGIN", (size_t)5))
    +      wasbegin= True;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      if (!*s)
    +        break;
    +      if (!isdigit(*s)) {
    +        if (!*qh rbox_command) {
    +          strncat(qh rbox_command, s, sizeof(qh rbox_command)-1);
    +          firsttext= linecount;
    +        }
    +        break;
    +      }
    +      if (!diminput)
    +        diminput= qh_strtol(s, &s);
    +      else {
    +        numinput= qh_strtol(s, &s);
    +        if (numinput == 1 && diminput >= 2 && qh HALFspace && !qh CDDinput) {
    +          linecount += qh_readfeasible(diminput, s); /* checks if ok */
    +          dimfeasible= diminput;
    +          diminput= numinput= 0;
    +        }else
    +          break;
    +      }
    +    }
    +  }
    +  if (!s) {
    +    qh_fprintf(qh ferr, 6075, "qhull input error: short input file.  Did not find dimension and number of points\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (diminput > numinput) {
    +    tempi= diminput;    /* exchange dim and n, e.g., for cdd input format */
    +    diminput= numinput;
    +    numinput= tempi;
    +  }
    +  if (diminput < 2) {
    +    qh_fprintf(qh ferr, 6220,"qhull input error: dimension %d(first number) should be at least 2\n",
    +            diminput);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (isdelaunay) {
    +    qh PROJECTdelaunay= False;
    +    if (qh CDDinput)
    +      *dimension= diminput;
    +    else
    +      *dimension= diminput+1;
    +    *numpoints= numinput;
    +    if (qh ATinfinity)
    +      (*numpoints)++;
    +  }else if (qh HALFspace) {
    +    *dimension= diminput - 1;
    +    *numpoints= numinput;
    +    if (diminput < 3) {
    +      qh_fprintf(qh ferr, 6221,"qhull input error: dimension %d(first number, includes offset) should be at least 3 for halfspaces\n",
    +            diminput);
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (dimfeasible) {
    +      if (dimfeasible != *dimension) {
    +        qh_fprintf(qh ferr, 6222,"qhull input error: dimension %d of feasible point is not one less than dimension %d for halfspaces\n",
    +          dimfeasible, diminput);
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +    }else
    +      qh_setfeasible(*dimension);
    +  }else {
    +    if (qh CDDinput)
    +      *dimension= diminput-1;
    +    else
    +      *dimension= diminput;
    +    *numpoints= numinput;
    +  }
    +  qh normal_size= *dimension * sizeof(coordT); /* for tracing with qh_printpoint */
    +  if (qh HALFspace) {
    +    qh half_space= coordp= (coordT*)qh_malloc(qh normal_size + sizeof(coordT));
    +    if (qh CDDinput) {
    +      offsetp= qh half_space;
    +      normalp= offsetp + 1;
    +    }else {
    +      normalp= qh half_space;
    +      offsetp= normalp + *dimension;
    +    }
    +  }
    +  qh maxline= diminput * (qh_REALdigits + 5);
    +  maximize_(qh maxline, 500);
    +  qh line= (char*)qh_malloc((qh maxline+1) * sizeof(char));
    +  *ismalloc= True;  /* use malloc since memory not setup */
    +  coords= points= qh temp_malloc=  /* numinput and diminput >=2 by QH6220 */
    +        (coordT*)qh_malloc((*numpoints)*(*dimension)*sizeof(coordT));
    +  if (!coords || !qh line || (qh HALFspace && !qh half_space)) {
    +    qh_fprintf(qh ferr, 6076, "qhull error: insufficient memory to read %d points\n",
    +            numinput);
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  if (isdelaunay && qh ATinfinity) {
    +    infinity= points + numinput * (*dimension);
    +    for (k= (*dimension) - 1; k--; )
    +      infinity[k]= 0.0;
    +  }
    +  maxcount= numinput * diminput;
    +  paraboloid= 0.0;
    +  while ((s= (isfirst ?  s : fgets(qh line, qh maxline, qh fin)))) {
    +    if (!isfirst) {
    +      linecount++;
    +      if (*s == 'e' || *s == 'E') {
    +        if (!memcmp(s, "end", (size_t)3) || !memcmp(s, "END", (size_t)3)) {
    +          if (qh CDDinput )
    +            break;
    +          else if (wasbegin)
    +            qh_fprintf(qh ferr, 7058, "qhull input warning: the input appears to be in cdd format.  If so, use 'Fd'\n");
    +        }
    +      }
    +    }
    +    islong= False;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t) {
    +        if (!*qh rbox_command)
    +         strncat(qh rbox_command, s, sizeof(qh rbox_command)-1);
    +        if (*s && !firsttext)
    +          firsttext= linecount;
    +        if (!islong && !firstshort && coordcount)
    +          firstshort= linecount;
    +        break;
    +      }
    +      if (!firstpoint)
    +        firstpoint= linecount;
    +      s= t;
    +      if (++tokcount > maxcount)
    +        continue;
    +      if (qh HALFspace) {
    +        if (qh CDDinput)
    +          *(coordp++)= -value; /* both coefficients and offset */
    +        else
    +          *(coordp++)= value;
    +      }else {
    +        *(coords++)= value;
    +        if (qh CDDinput && !coordcount) {
    +          if (value != 1.0) {
    +            qh_fprintf(qh ferr, 6077, "qhull input error: for cdd format, point at line %d does not start with '1'\n",
    +                   linecount);
    +            qh_errexit(qh_ERRinput, NULL, NULL);
    +          }
    +          coords--;
    +        }else if (isdelaunay) {
    +          paraboloid += value * value;
    +          if (qh ATinfinity) {
    +            if (qh CDDinput)
    +              infinity[coordcount-1] += value;
    +            else
    +              infinity[coordcount] += value;
    +          }
    +        }
    +      }
    +      if (++coordcount == diminput) {
    +        coordcount= 0;
    +        if (isdelaunay) {
    +          *(coords++)= paraboloid;
    +          maximize_(maxboloid, paraboloid);
    +          paraboloid= 0.0;
    +        }else if (qh HALFspace) {
    +          if (!qh_sethalfspace(*dimension, coords, &coords, normalp, offsetp, qh feasible_point)) {
    +            qh_fprintf(qh ferr, 8048, "The halfspace was on line %d\n", linecount);
    +            if (wasbegin)
    +              qh_fprintf(qh ferr, 8049, "The input appears to be in cdd format.  If so, you should use option 'Fd'\n");
    +            qh_errexit(qh_ERRinput, NULL, NULL);
    +          }
    +          coordp= qh half_space;
    +        }
    +        while (isspace(*s))
    +          s++;
    +        if (*s) {
    +          islong= True;
    +          if (!firstlong)
    +            firstlong= linecount;
    +        }
    +      }
    +    }
    +    if (!islong && !firstshort && coordcount)
    +      firstshort= linecount;
    +    if (!isfirst && s - qh line >= qh maxline) {
    +      qh_fprintf(qh ferr, 6078, "qhull input error: line %d contained more than %d characters\n",
    +              linecount, (int) (s - qh line));   /* WARN64 */
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    isfirst= False;
    +  }
    +  if (tokcount != maxcount) {
    +    newnum= fmin_(numinput, tokcount/diminput);
    +    qh_fprintf(qh ferr, 7073,"\
    +qhull warning: instead of %d %d-dimensional points, input contains\n\
    +%d points and %d extra coordinates.  Line %d is the first\npoint",
    +       numinput, diminput, tokcount/diminput, tokcount % diminput, firstpoint);
    +    if (firsttext)
    +      qh_fprintf(qh ferr, 8051, ", line %d is the first comment", firsttext);
    +    if (firstshort)
    +      qh_fprintf(qh ferr, 8052, ", line %d is the first short\nline", firstshort);
    +    if (firstlong)
    +      qh_fprintf(qh ferr, 8053, ", line %d is the first long line", firstlong);
    +    qh_fprintf(qh ferr, 8054, ".  Continue with %d points.\n", newnum);
    +    numinput= newnum;
    +    if (isdelaunay && qh ATinfinity) {
    +      for (k= tokcount % diminput; k--; )
    +        infinity[k] -= *(--coords);
    +      *numpoints= newnum+1;
    +    }else {
    +      coords -= tokcount % diminput;
    +      *numpoints= newnum;
    +    }
    +  }
    +  if (isdelaunay && qh ATinfinity) {
    +    for (k= (*dimension) -1; k--; )
    +      infinity[k] /= numinput;
    +    if (coords == infinity)
    +      coords += (*dimension) -1;
    +    else {
    +      for (k=0; k < (*dimension) -1; k++)
    +        *(coords++)= infinity[k];
    +    }
    +    *(coords++)= maxboloid * 1.1;
    +  }
    +  if (qh rbox_command[0]) {
    +    qh rbox_command[strlen(qh rbox_command)-1]= '\0';
    +    if (!strcmp(qh rbox_command, "./rbox D4"))
    +      qh_fprintf(qh ferr, 8055, "\n\
    +This is the qhull test case.  If any errors or core dumps occur,\n\
    +recompile qhull with 'make new'.  If errors still occur, there is\n\
    +an incompatibility.  You should try a different compiler.  You can also\n\
    +change the choices in user.h.  If you discover the source of the problem,\n\
    +please send mail to qhull_bug@qhull.org.\n\
    +\n\
    +Type 'qhull' for a short list of options.\n");
    +  }
    +  qh_free(qh line);
    +  qh line= NULL;
    +  if (qh half_space) {
    +    qh_free(qh half_space);
    +    qh half_space= NULL;
    +  }
    +  qh temp_malloc= NULL;
    +  trace1((qh ferr, 1008,"qh_readpoints: read in %d %d-dimensional points\n",
    +          numinput, diminput));
    +  return(points);
    +} /* readpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfeasible( dim )
    +    set qh.feasible_point from qh.feasible_string in "n,n,n" or "n n n" format
    +
    +  notes:
    +    "n,n,n" already checked by qh_initflags()
    +    see qh_readfeasible()
    +    called only once from qh_new_qhull, otherwise leaks memory
    +*/
    +void qh_setfeasible(int dim) {
    +  int tokcount= 0;
    +  char *s;
    +  coordT *coords, value;
    +
    +  if (!(s= qh feasible_string)) {
    +    qh_fprintf(qh ferr, 6223, "\
    +qhull input error: halfspace intersection needs a feasible point.\n\
    +Either prepend the input with 1 point or use 'Hn,n,n'.  See manual.\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (!(qh feasible_point= (pointT*)qh_malloc(dim * sizeof(coordT)))) {
    +    qh_fprintf(qh ferr, 6079, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +    qh_errexit(qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh feasible_point;
    +  while (*s) {
    +    value= qh_strtod(s, &s);
    +    if (++tokcount > dim) {
    +      qh_fprintf(qh ferr, 7059, "qhull input warning: more coordinates for 'H%s' than dimension %d\n",
    +          qh feasible_string, dim);
    +      break;
    +    }
    +    *(coords++)= value;
    +    if (*s)
    +      s++;
    +  }
    +  while (++tokcount <= dim)
    +    *(coords++)= 0.0;
    +} /* setfeasible */
    +
    +/*---------------------------------
    +
    +  qh_skipfacet( facet )
    +    returns 'True' if this facet is not to be printed
    +
    +  notes:
    +    based on the user provided slice thresholds and 'good' specifications
    +*/
    +boolT qh_skipfacet(facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +
    +  if (qh PRINTneighbors) {
    +    if (facet->good)
    +      return !qh PRINTgood;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->good)
    +        return False;
    +    }
    +    return True;
    +  }else if (qh PRINTgood)
    +    return !facet->good;
    +  else if (!facet->normal)
    +    return True;
    +  return(!qh_inthresholds(facet->normal, NULL));
    +} /* skipfacet */
    +
    +/*---------------------------------
    +
    +  qh_skipfilename( string )
    +    returns pointer to character after filename
    +
    +  notes:
    +    skips leading spaces
    +    ends with spacing or eol
    +    if starts with ' or " ends with the same, skipping \' or \"
    +    For qhull, qh_argv_to_command() only uses double quotes
    +*/
    +char *qh_skipfilename(char *filename) {
    +  char *s= filename;  /* non-const due to return */
    +  char c;
    +
    +  while (*s && isspace(*s))
    +    s++;
    +  c= *s++;
    +  if (c == '\0') {
    +    qh_fprintf(qh ferr, 6204, "qhull input error: filename expected, none found.\n");
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  if (c == '\'' || c == '"') {
    +    while (*s !=c || s[-1] == '\\') {
    +      if (!*s) {
    +        qh_fprintf(qh ferr, 6203, "qhull input error: missing quote after filename -- %s\n", filename);
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +      s++;
    +    }
    +    s++;
    +  }
    +  else while (*s && !isspace(*s))
    +      s++;
    +  return s;
    +} /* skipfilename */
    +
    diff --git a/xs/src/qhull/src/libqhull/io.h b/xs/src/qhull/src/libqhull/io.h
    new file mode 100644
    index 0000000000..eca0369d30
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/io.h
    @@ -0,0 +1,159 @@
    +/*
      ---------------------------------
    +
    +   io.h
    +   declarations of Input/Output functions
    +
    +   see README, libqhull.h and io.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/io.h#1 $$Change: 1981 $
    +   $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFio
    +#define qhDEFio 1
    +
    +#include "libqhull.h"
    +
    +/*============ constants and flags ==================*/
    +
    +/*----------------------------------
    +
    +  qh_MAXfirst
    +    maximum length of first two lines of stdin
    +*/
    +#define qh_MAXfirst  200
    +
    +/*----------------------------------
    +
    +  qh_MINradius
    +    min radius for Gp and Gv, fraction of maxcoord
    +*/
    +#define qh_MINradius 0.02
    +
    +/*----------------------------------
    +
    +  qh_GEOMepsilon
    +    adjust outer planes for 'lines closer' and geomview roundoff.
    +    This prevents bleed through.
    +*/
    +#define qh_GEOMepsilon 2e-3
    +
    +/*----------------------------------
    +
    +  qh_WHITESPACE
    +    possible values of white space
    +*/
    +#define qh_WHITESPACE " \n\t\v\r\f"
    +
    +
    +/*----------------------------------
    +
    +  qh_RIDGE
    +    to select which ridges to print in qh_eachvoronoi
    +*/
    +typedef enum
    +{
    +    qh_RIDGEall = 0, qh_RIDGEinner, qh_RIDGEouter
    +}
    +qh_RIDGE;
    +
    +/*----------------------------------
    +
    +  printvridgeT
    +    prints results of qh_printvdiagram
    +
    +  see:
    +    qh_printvridge for an example
    +*/
    +typedef void (*printvridgeT)(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +
    +/*============== -prototypes in alphabetical order =========*/
    +
    +void    qh_dfacet(unsigned id);
    +void    qh_dvertex(unsigned id);
    +int     qh_compare_facetarea(const void *p1, const void *p2);
    +int     qh_compare_facetmerge(const void *p1, const void *p2);
    +int     qh_compare_facetvisit(const void *p1, const void *p2);
    +int     qh_compare_vertexpoint(const void *p1, const void *p2); /* not used, not in libqhull_r.h */
    +void    qh_copyfilename(char *filename, int size, const char* source, int length);
    +void    qh_countfacets(facetT *facetlist, setT *facets, boolT printall,
    +              int *numfacetsp, int *numsimplicialp, int *totneighborsp,
    +              int *numridgesp, int *numcoplanarsp, int *numnumtricoplanarsp);
    +pointT *qh_detvnorm(vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp);
    +setT   *qh_detvridge(vertexT *vertex);
    +setT   *qh_detvridge3(vertexT *atvertex, vertexT *vertex);
    +int     qh_eachvoronoi(FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder);
    +int     qh_eachvoronoi_all(FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder);
    +void    qh_facet2point(facetT *facet, pointT **point0, pointT **point1, realT *mindist);
    +setT   *qh_facetvertices(facetT *facetlist, setT *facets, boolT allfacets);
    +void    qh_geomplanes(facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_markkeep(facetT *facetlist);
    +setT   *qh_markvoronoi(facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp);
    +void    qh_order_vertexneighbors(vertexT *vertex);
    +void    qh_prepare_output(void);
    +void    qh_printafacet(FILE *fp, qh_PRINT format, facetT *facet, boolT printall);
    +void    qh_printbegin(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printcenter(FILE *fp, qh_PRINT format, const char *string, facetT *facet);
    +void    qh_printcentrum(FILE *fp, facetT *facet, realT radius);
    +void    qh_printend(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printend4geom(FILE *fp, facetT *facet, int *num, boolT printall);
    +void    qh_printextremes(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_2d(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_d(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printfacet(FILE *fp, facetT *facet);
    +void    qh_printfacet2math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet2geom(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet2geom_points(FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3math(FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet3geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3geom_points(FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3geom_simplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3vertex(FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacet4geom_nonsimplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet4geom_simplicial(FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacetNvertex_nonsimplicial(FILE *fp, facetT *facet, int id, qh_PRINT format);
    +void    qh_printfacetNvertex_simplicial(FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacetheader(FILE *fp, facetT *facet);
    +void    qh_printfacetridges(FILE *fp, facetT *facet);
    +void    qh_printfacets(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhyperplaneintersection(FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]);
    +void    qh_printneighborhood(FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_printline3geom(FILE *fp, pointT *pointA, pointT *pointB, realT color[3]);
    +void    qh_printpoint(FILE *fp, const char *string, pointT *point);
    +void    qh_printpointid(FILE *fp, const char *string, int dim, pointT *point, int id);
    +void    qh_printpoint3(FILE *fp, pointT *point);
    +void    qh_printpoints_out(FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printpointvect(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]);
    +void    qh_printpointvect2(FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius);
    +void    qh_printridge(FILE *fp, ridgeT *ridge);
    +void    qh_printspheres(FILE *fp, setT *vertices, realT radius);
    +void    qh_printvdiagram(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +int     qh_printvdiagram2(FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder);
    +void    qh_printvertex(FILE *fp, vertexT *vertex);
    +void    qh_printvertexlist(FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall);
    +void    qh_printvertices(FILE *fp, const char* string, setT *vertices);
    +void    qh_printvneighbors(FILE *fp, facetT* facetlist, setT *facets, boolT printall);
    +void    qh_printvoronoi(FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printvnorm(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_printvridge(FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_produce_output(void);
    +void    qh_produce_output2(void);
    +void    qh_projectdim3(pointT *source, pointT *destination);
    +int     qh_readfeasible(int dim, const char *curline);
    +coordT *qh_readpoints(int *numpoints, int *dimension, boolT *ismalloc);
    +void    qh_setfeasible(int dim);
    +boolT   qh_skipfacet(facetT *facet);
    +char   *qh_skipfilename(char *filename);
    +
    +#endif /* qhDEFio */
    diff --git a/xs/src/qhull/src/libqhull/libqhull.c b/xs/src/qhull/src/libqhull/libqhull.c
    new file mode 100644
    index 0000000000..7696a8a9fe
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/libqhull.c
    @@ -0,0 +1,1403 @@
    +/*
      ---------------------------------
    +
    +   libqhull.c
    +   Quickhull algorithm for convex hulls
    +
    +   qhull() and top-level routines
    +
    +   see qh-qhull.htm, libqhull.h, unix.c
    +
    +   see qhull_a.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/libqhull.c#3 $$Change: 2047 $
    +   $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*============= functions in alphabetic order after qhull() =======*/
    +
    +/*---------------------------------
    +
    +  qh_qhull()
    +    compute DIM3 convex hull of qh.num_points starting at qh.first_point
    +    qh contains all global options and variables
    +
    +  returns:
    +    returns polyhedron
    +      qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices,
    +
    +    returns global variables
    +      qh.hulltime, qh.max_outside, qh.interior_point, qh.max_vertex, qh.min_vertex
    +
    +    returns precision constants
    +      qh.ANGLEround, centrum_radius, cos_max, DISTround, MAXabs_coord, ONEmerge
    +
    +  notes:
    +    unless needed for output
    +      qh.max_vertex and qh.min_vertex are max/min due to merges
    +
    +  see:
    +    to add individual points to either qh.num_points
    +      use qh_addpoint()
    +
    +    if qh.GETarea
    +      qh_produceoutput() returns qh.totarea and qh.totvol via qh_getarea()
    +
    +  design:
    +    record starting time
    +    initialize hull and partition points
    +    build convex hull
    +    unless early termination
    +      update facet->maxoutside for vertices, coplanar, and near-inside points
    +    error if temporary sets exist
    +    record end time
    +*/
    +
    +void qh_qhull(void) {
    +  int numoutside;
    +
    +  qh hulltime= qh_CPUclock;
    +  if (qh RERUN || qh JOGGLEmax < REALmax/2)
    +    qh_build_withrestart();
    +  else {
    +    qh_initbuild();
    +    qh_buildhull();
    +  }
    +  if (!qh STOPpoint && !qh STOPcone) {
    +    if (qh ZEROall_ok && !qh TESTvneighbors && qh MERGEexact)
    +      qh_checkzero( qh_ALL);
    +    if (qh ZEROall_ok && !qh TESTvneighbors && !qh WAScoplanar) {
    +      trace2((qh ferr, 2055, "qh_qhull: all facets are clearly convex and no coplanar points.  Post-merging and check of maxout not needed.\n"));
    +      qh DOcheckmax= False;
    +    }else {
    +      if (qh MERGEexact || (qh hull_dim > qh_DIMreduceBuild && qh PREmerge))
    +        qh_postmerge("First post-merge", qh premerge_centrum, qh premerge_cos,
    +             (qh POSTmerge ? False : qh TESTvneighbors));
    +      else if (!qh POSTmerge && qh TESTvneighbors)
    +        qh_postmerge("For testing vertex neighbors", qh premerge_centrum,
    +             qh premerge_cos, True);
    +      if (qh POSTmerge)
    +        qh_postmerge("For post-merging", qh postmerge_centrum,
    +             qh postmerge_cos, qh TESTvneighbors);
    +      if (qh visible_list == qh facet_list) { /* i.e., merging done */
    +        qh findbestnew= True;
    +        qh_partitionvisible(/*qh.visible_list*/ !qh_ALL, &numoutside);
    +        qh findbestnew= False;
    +        qh_deletevisible(/*qh.visible_list*/);
    +        qh_resetlists(False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +      }
    +    }
    +    if (qh DOcheckmax){
    +      if (qh REPORTfreq) {
    +        qh_buildtracing(NULL, NULL);
    +        qh_fprintf(qh ferr, 8115, "\nTesting all coplanar points.\n");
    +      }
    +      qh_check_maxout();
    +    }
    +    if (qh KEEPnearinside && !qh maxoutdone)
    +      qh_nearcoplanar();
    +  }
    +  if (qh_setsize(qhmem.tempstack) != 0) {
    +    qh_fprintf(qh ferr, 6164, "qhull internal error (qh_qhull): temporary sets not empty(%d)\n",
    +             qh_setsize(qhmem.tempstack));
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh hulltime= qh_CPUclock - qh hulltime;
    +  qh QHULLfinished= True;
    +  trace1((qh ferr, 1036, "Qhull: algorithm completed\n"));
    +} /* qhull */
    +
    +/*---------------------------------
    +
    +  qh_addpoint( furthest, facet, checkdist )
    +    add point (usually furthest point) above facet to hull
    +    if checkdist,
    +      check that point is above facet.
    +      if point is not outside of the hull, uses qh_partitioncoplanar()
    +      assumes that facet is defined by qh_findbestfacet()
    +    else if facet specified,
    +      assumes that point is above facet (major damage if below)
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns False if user requested an early termination
    +     qh.visible_list, newfacet_list, delvertex_list, NEWfacets may be defined
    +    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
    +    clear qh.maxoutdone (will need to call qh_check_maxout() for facet->maxoutside)
    +    if unknown point, adds a pointer to qh.other_points
    +      do not deallocate the point's coordinates
    +
    +  notes:
    +    assumes point is near its best facet and not at a local minimum of a lens
    +      distributions.  Use qh_findbestfacet to avoid this case.
    +    uses qh.visible_list, qh.newfacet_list, qh.delvertex_list, qh.NEWfacets
    +
    +  see also:
    +    qh_triangulate() -- triangulate non-simplicial facets
    +
    +  design:
    +    add point to other_points if needed
    +    if checkdist
    +      if point not above facet
    +        partition coplanar point
    +        exit
    +    exit if pre STOPpoint requested
    +    find horizon and visible facets for point
    +    make new facets for point to horizon
    +    make hyperplanes for point
    +    compute balance statistics
    +    match neighboring new facets
    +    update vertex neighbors and delete interior vertices
    +    exit if STOPcone requested
    +    merge non-convex new facets
    +    if merge found, many merges, or 'Qf'
    +       use qh_findbestnew() instead of qh_findbest()
    +    partition outside points from visible facets
    +    delete visible facets
    +    check polyhedron if requested
    +    exit if post STOPpoint requested
    +    reset working lists of facets and vertices
    +*/
    +boolT qh_addpoint(pointT *furthest, facetT *facet, boolT checkdist) {
    +  int goodvisible, goodhorizon;
    +  vertexT *vertex;
    +  facetT *newfacet;
    +  realT dist, newbalance, pbalance;
    +  boolT isoutside= False;
    +  int numpart, numpoints, numnew, firstnew;
    +
    +  qh maxoutdone= False;
    +  if (qh_pointid(furthest) == qh_IDunknown)
    +    qh_setappend(&qh other_points, furthest);
    +  if (!facet) {
    +    qh_fprintf(qh ferr, 6213, "qhull internal error (qh_addpoint): NULL facet.  Need to call qh_findbestfacet first\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (checkdist) {
    +    facet= qh_findbest(furthest, facet, !qh_ALL, !qh_ISnewfacets, !qh_NOupper,
    +                        &dist, &isoutside, &numpart);
    +    zzadd_(Zpartition, numpart);
    +    if (!isoutside) {
    +      zinc_(Znotmax);  /* last point of outsideset is no longer furthest. */
    +      facet->notfurthest= True;
    +      qh_partitioncoplanar(furthest, facet, &dist);
    +      return True;
    +    }
    +  }
    +  qh_buildtracing(furthest, facet);
    +  if (qh STOPpoint < 0 && qh furthest_id == -qh STOPpoint-1) {
    +    facet->notfurthest= True;
    +    return False;
    +  }
    +  qh_findhorizon(furthest, facet, &goodvisible, &goodhorizon);
    +  if (qh ONLYgood && !(goodvisible+goodhorizon) && !qh GOODclosest) {
    +    zinc_(Znotgood);
    +    facet->notfurthest= True;
    +    /* last point of outsideset is no longer furthest.  This is ok
    +       since all points of the outside are likely to be bad */
    +    qh_resetlists(False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    return True;
    +  }
    +  zzinc_(Zprocessed);
    +  firstnew= qh facet_id;
    +  vertex= qh_makenewfacets(furthest /*visible_list, attaches if !ONLYgood */);
    +  qh_makenewplanes(/* newfacet_list */);
    +  numnew= qh facet_id - firstnew;
    +  newbalance= numnew - (realT) (qh num_facets-qh num_visible)
    +                         * qh hull_dim/qh num_vertices;
    +  wadd_(Wnewbalance, newbalance);
    +  wadd_(Wnewbalance2, newbalance * newbalance);
    +  if (qh ONLYgood
    +  && !qh_findgood(qh newfacet_list, goodhorizon) && !qh GOODclosest) {
    +    FORALLnew_facets
    +      qh_delfacet(newfacet);
    +    qh_delvertex(vertex);
    +    qh_resetlists(True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    zinc_(Znotgoodnew);
    +    facet->notfurthest= True;
    +    return True;
    +  }
    +  if (qh ONLYgood)
    +    qh_attachnewfacets(/*visible_list*/);
    +  qh_matchnewfacets();
    +  qh_updatevertices();
    +  if (qh STOPcone && qh furthest_id == qh STOPcone-1) {
    +    facet->notfurthest= True;
    +    return False;  /* visible_list etc. still defined */
    +  }
    +  qh findbestnew= False;
    +  if (qh PREmerge || qh MERGEexact) {
    +    qh_premerge(vertex, qh premerge_centrum, qh premerge_cos);
    +    if (qh_USEfindbestnew)
    +      qh findbestnew= True;
    +    else {
    +      FORALLnew_facets {
    +        if (!newfacet->simplicial) {
    +          qh findbestnew= True;  /* use qh_findbestnew instead of qh_findbest*/
    +          break;
    +        }
    +      }
    +    }
    +  }else if (qh BESToutside)
    +    qh findbestnew= True;
    +  qh_partitionvisible(/*qh.visible_list*/ !qh_ALL, &numpoints);
    +  qh findbestnew= False;
    +  qh findbest_notsharp= False;
    +  zinc_(Zpbalance);
    +  pbalance= numpoints - (realT) qh hull_dim /* assumes all points extreme */
    +                * (qh num_points - qh num_vertices)/qh num_vertices;
    +  wadd_(Wpbalance, pbalance);
    +  wadd_(Wpbalance2, pbalance * pbalance);
    +  qh_deletevisible(/*qh.visible_list*/);
    +  zmax_(Zmaxvertex, qh num_vertices);
    +  qh NEWfacets= False;
    +  if (qh IStracing >= 4) {
    +    if (qh num_facets < 2000)
    +      qh_printlists();
    +    qh_printfacetlist(qh newfacet_list, NULL, True);
    +    qh_checkpolygon(qh facet_list);
    +  }else if (qh CHECKfrequently) {
    +    if (qh num_facets < 50)
    +      qh_checkpolygon(qh facet_list);
    +    else
    +      qh_checkpolygon(qh newfacet_list);
    +  }
    +  if (qh STOPpoint > 0 && qh furthest_id == qh STOPpoint-1)
    +    return False;
    +  qh_resetlists(True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  /* qh_triangulate(); to test qh.TRInormals */
    +  trace2((qh ferr, 2056, "qh_addpoint: added p%d new facets %d new balance %2.2g point balance %2.2g\n",
    +    qh_pointid(furthest), numnew, newbalance, pbalance));
    +  return True;
    +} /* addpoint */
    +
    +/*---------------------------------
    +
    +  qh_build_withrestart()
    +    allow restarts due to qh.JOGGLEmax while calling qh_buildhull()
    +       qh_errexit always undoes qh_build_withrestart()
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +        it may be moved by qh_joggleinput()
    +*/
    +void qh_build_withrestart(void) {
    +  int restart;
    +
    +  qh ALLOWrestart= True;
    +  while (True) {
    +    restart= setjmp(qh restartexit); /* simple statement for CRAY J916 */
    +    if (restart) {       /* only from qh_precision() */
    +      zzinc_(Zretry);
    +      wmax_(Wretrymax, qh JOGGLEmax);
    +      /* QH7078 warns about using 'TCn' with 'QJn' */
    +      qh STOPcone= qh_IDunknown; /* if break from joggle, prevents normal output */
    +    }
    +    if (!qh RERUN && qh JOGGLEmax < REALmax/2) {
    +      if (qh build_cnt > qh_JOGGLEmaxretry) {
    +        qh_fprintf(qh ferr, 6229, "qhull precision error: %d attempts to construct a convex hull\n\
    +        with joggled input.  Increase joggle above 'QJ%2.2g'\n\
    +        or modify qh_JOGGLE... parameters in user.h\n",
    +           qh build_cnt, qh JOGGLEmax);
    +        qh_errexit(qh_ERRqhull, NULL, NULL);
    +      }
    +      if (qh build_cnt && !restart)
    +        break;
    +    }else if (qh build_cnt && qh build_cnt >= qh RERUN)
    +      break;
    +    qh STOPcone= 0;
    +    qh_freebuild(True);  /* first call is a nop */
    +    qh build_cnt++;
    +    if (!qh qhull_optionsiz)
    +      qh qhull_optionsiz= (int)strlen(qh qhull_options);   /* WARN64 */
    +    else {
    +      qh qhull_options [qh qhull_optionsiz]= '\0';
    +      qh qhull_optionlen= qh_OPTIONline;  /* starts a new line */
    +    }
    +    qh_option("_run", &qh build_cnt, NULL);
    +    if (qh build_cnt == qh RERUN) {
    +      qh IStracing= qh TRACElastrun;  /* duplicated from qh_initqhull_globals */
    +      if (qh TRACEpoint != qh_IDunknown || qh TRACEdist < REALmax/2 || qh TRACEmerge) {
    +        qh TRACElevel= (qh IStracing? qh IStracing : 3);
    +        qh IStracing= 0;
    +      }
    +      qhmem.IStracing= qh IStracing;
    +    }
    +    if (qh JOGGLEmax < REALmax/2)
    +      qh_joggleinput();
    +    qh_initbuild();
    +    qh_buildhull();
    +    if (qh JOGGLEmax < REALmax/2 && !qh MERGING)
    +      qh_checkconvex(qh facet_list, qh_ALGORITHMfault);
    +  }
    +  qh ALLOWrestart= False;
    +} /* qh_build_withrestart */
    +
    +/*---------------------------------
    +
    +  qh_buildhull()
    +    construct a convex hull by adding outside points one at a time
    +
    +  returns:
    +
    +  notes:
    +    may be called multiple times
    +    checks facet and vertex lists for incorrect flags
    +    to recover from STOPcone, call qh_deletevisible and qh_resetlists
    +
    +  design:
    +    check visible facet and newfacet flags
    +    check newlist vertex flags and qh.STOPcone/STOPpoint
    +    for each facet with a furthest outside point
    +      add point to facet
    +      exit if qh.STOPcone or qh.STOPpoint requested
    +    if qh.NARROWhull for initial simplex
    +      partition remaining outside points to coplanar sets
    +*/
    +void qh_buildhull(void) {
    +  facetT *facet;
    +  pointT *furthest;
    +  vertexT *vertex;
    +  int id;
    +
    +  trace1((qh ferr, 1037, "qh_buildhull: start build hull\n"));
    +  FORALLfacets {
    +    if (facet->visible || facet->newfacet) {
    +      qh_fprintf(qh ferr, 6165, "qhull internal error (qh_buildhull): visible or new facet f%d in facet list\n",
    +                   facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->newlist) {
    +      qh_fprintf(qh ferr, 6166, "qhull internal error (qh_buildhull): new vertex f%d in vertex list\n",
    +                   vertex->id);
    +      qh_errprint("ERRONEOUS", NULL, NULL, NULL, vertex);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +    id= qh_pointid(vertex->point);
    +    if ((qh STOPpoint>0 && id == qh STOPpoint-1) ||
    +        (qh STOPpoint<0 && id == -qh STOPpoint-1) ||
    +        (qh STOPcone>0 && id == qh STOPcone-1)) {
    +      trace1((qh ferr, 1038,"qh_buildhull: stop point or cone P%d in initial hull\n", id));
    +      return;
    +    }
    +  }
    +  qh facet_next= qh facet_list;      /* advance facet when processed */
    +  while ((furthest= qh_nextfurthest(&facet))) {
    +    qh num_outside--;  /* if ONLYmax, furthest may not be outside */
    +    if (!qh_addpoint(furthest, facet, qh ONLYmax))
    +      break;
    +  }
    +  if (qh NARROWhull) /* move points from outsideset to coplanarset */
    +    qh_outcoplanar( /* facet_list */ );
    +  if (qh num_outside && !furthest) {
    +    qh_fprintf(qh ferr, 6167, "qhull internal error (qh_buildhull): %d outside points were never processed.\n", qh num_outside);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  trace1((qh ferr, 1039, "qh_buildhull: completed the hull construction\n"));
    +} /* buildhull */
    +
    +
    +/*---------------------------------
    +
    +  qh_buildtracing( furthest, facet )
    +    trace an iteration of qh_buildhull() for furthest point and facet
    +    if !furthest, prints progress message
    +
    +  returns:
    +    tracks progress with qh.lastreport
    +    updates qh.furthest_id (-3 if furthest is NULL)
    +    also resets visit_id, vertext_visit on wrap around
    +
    +  see:
    +    qh_tracemerging()
    +
    +  design:
    +    if !furthest
    +      print progress message
    +      exit
    +    if 'TFn' iteration
    +      print progress message
    +    else if tracing
    +      trace furthest point and facet
    +    reset qh.visit_id and qh.vertex_visit if overflow may occur
    +    set qh.furthest_id for tracing
    +*/
    +void qh_buildtracing(pointT *furthest, facetT *facet) {
    +  realT dist= 0;
    +  float cpu;
    +  int total, furthestid;
    +  time_t timedata;
    +  struct tm *tp;
    +  vertexT *vertex;
    +
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  if (!furthest) {
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    qh_fprintf(qh ferr, 8118, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  Last point was p%d\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh facet_id -1,
    +      total, qh num_facets, qh num_vertices, qh furthest_id);
    +    return;
    +  }
    +  furthestid= qh_pointid(furthest);
    +  if (qh TRACEpoint == furthestid) {
    +    qh IStracing= qh TRACElevel;
    +    qhmem.IStracing= qh TRACElevel;
    +  }else if (qh TRACEpoint != qh_IDunknown && qh TRACEdist < REALmax/2) {
    +    qh IStracing= 0;
    +    qhmem.IStracing= 0;
    +  }
    +  if (qh REPORTfreq && (qh facet_id-1 > qh lastreport+qh REPORTfreq)) {
    +    qh lastreport= qh facet_id-1;
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    zinc_(Zdistio);
    +    qh_distplane(furthest, facet, &dist);
    +    qh_fprintf(qh ferr, 8119, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  There are %d\n\
    + outside points.  Next is point p%d(v%d), %2.2g above f%d.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh facet_id -1,
    +      total, qh num_facets, qh num_vertices, qh num_outside+1,
    +      furthestid, qh vertex_id, dist, getid_(facet));
    +  }else if (qh IStracing >=1) {
    +    cpu= (float)qh_CPUclock - (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    qh_distplane(furthest, facet, &dist);
    +    qh_fprintf(qh ferr, 8120, "qh_addpoint: add p%d(v%d) to hull of %d facets(%2.2g above f%d) and %d outside at %4.4g CPU secs.  Previous was p%d.\n",
    +      furthestid, qh vertex_id, qh num_facets, dist,
    +      getid_(facet), qh num_outside+1, cpu, qh furthest_id);
    +  }
    +  zmax_(Zvisit2max, (int)qh visit_id/2);
    +  if (qh visit_id > (unsigned) INT_MAX) { /* 31 bits */
    +    zinc_(Zvisit);
    +    qh visit_id= 0;
    +    FORALLfacets
    +      facet->visitid= 0;
    +  }
    +  zmax_(Zvvisit2max, (int)qh vertex_visit/2);
    +  if (qh vertex_visit > (unsigned) INT_MAX) { /* 31 bits */
    +    zinc_(Zvvisit);
    +    qh vertex_visit= 0;
    +    FORALLvertices
    +      vertex->visitid= 0;
    +  }
    +  qh furthest_id= furthestid;
    +  qh RANDOMdist= qh old_randomdist;
    +} /* buildtracing */
    +
    +/*---------------------------------
    +
    +  qh_errexit2( exitcode, facet, otherfacet )
    +    return exitcode to system after an error
    +    report two facets
    +
    +  returns:
    +    assumes exitcode non-zero
    +
    +  see:
    +    normally use qh_errexit() in user.c(reports a facet and a ridge)
    +*/
    +void qh_errexit2(int exitcode, facetT *facet, facetT *otherfacet) {
    +
    +  qh_errprint("ERRONEOUS", facet, otherfacet, NULL, NULL);
    +  qh_errexit(exitcode, NULL, NULL);
    +} /* errexit2 */
    +
    +
    +/*---------------------------------
    +
    +  qh_findhorizon( point, facet, goodvisible, goodhorizon )
    +    given a visible facet, find the point's horizon and visible facets
    +    for all facets, !facet-visible
    +
    +  returns:
    +    returns qh.visible_list/num_visible with all visible facets
    +      marks visible facets with ->visible
    +    updates count of good visible and good horizon facets
    +    updates qh.max_outside, qh.max_vertex, facet->maxoutside
    +
    +  see:
    +    similar to qh_delpoint()
    +
    +  design:
    +    move facet to qh.visible_list at end of qh.facet_list
    +    for all visible facets
    +     for each unvisited neighbor of a visible facet
    +       compute distance of point to neighbor
    +       if point above neighbor
    +         move neighbor to end of qh.visible_list
    +       else if point is coplanar with neighbor
    +         update qh.max_outside, qh.max_vertex, neighbor->maxoutside
    +         mark neighbor coplanar (will create a samecycle later)
    +         update horizon statistics
    +*/
    +void qh_findhorizon(pointT *point, facetT *facet, int *goodvisible, int *goodhorizon) {
    +  facetT *neighbor, **neighborp, *visible;
    +  int numhorizon= 0, coplanar= 0;
    +  realT dist;
    +
    +  trace1((qh ferr, 1040,"qh_findhorizon: find horizon for point p%d facet f%d\n",qh_pointid(point),facet->id));
    +  *goodvisible= *goodhorizon= 0;
    +  zinc_(Ztotvisible);
    +  qh_removefacet(facet);  /* visible_list at end of qh facet_list */
    +  qh_appendfacet(facet);
    +  qh num_visible= 1;
    +  if (facet->good)
    +    (*goodvisible)++;
    +  qh visible_list= facet;
    +  facet->visible= True;
    +  facet->f.replace= NULL;
    +  if (qh IStracing >=4)
    +    qh_errprint("visible", facet, NULL, NULL, NULL);
    +  qh visit_id++;
    +  FORALLvisible_facets {
    +    if (visible->tricoplanar && !qh TRInormals) {
    +      qh_fprintf(qh ferr, 6230, "Qhull internal error (qh_findhorizon): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh_ERRqhull, visible, NULL);
    +    }
    +    visible->visitid= qh visit_id;
    +    FOREACHneighbor_(visible) {
    +      if (neighbor->visitid == qh visit_id)
    +        continue;
    +      neighbor->visitid= qh visit_id;
    +      zzinc_(Znumvisibility);
    +      qh_distplane(point, neighbor, &dist);
    +      if (dist > qh MINvisible) {
    +        zinc_(Ztotvisible);
    +        qh_removefacet(neighbor);  /* append to end of qh visible_list */
    +        qh_appendfacet(neighbor);
    +        neighbor->visible= True;
    +        neighbor->f.replace= NULL;
    +        qh num_visible++;
    +        if (neighbor->good)
    +          (*goodvisible)++;
    +        if (qh IStracing >=4)
    +          qh_errprint("visible", neighbor, NULL, NULL, NULL);
    +      }else {
    +        if (dist > - qh MAXcoplanar) {
    +          neighbor->coplanar= True;
    +          zzinc_(Zcoplanarhorizon);
    +          qh_precision("coplanar horizon");
    +          coplanar++;
    +          if (qh MERGING) {
    +            if (dist > 0) {
    +              maximize_(qh max_outside, dist);
    +              maximize_(qh max_vertex, dist);
    +#if qh_MAXoutside
    +              maximize_(neighbor->maxoutside, dist);
    +#endif
    +            }else
    +              minimize_(qh min_vertex, dist);  /* due to merge later */
    +          }
    +          trace2((qh ferr, 2057, "qh_findhorizon: point p%d is coplanar to horizon f%d, dist=%2.7g < qh MINvisible(%2.7g)\n",
    +              qh_pointid(point), neighbor->id, dist, qh MINvisible));
    +        }else
    +          neighbor->coplanar= False;
    +        zinc_(Ztothorizon);
    +        numhorizon++;
    +        if (neighbor->good)
    +          (*goodhorizon)++;
    +        if (qh IStracing >=4)
    +          qh_errprint("horizon", neighbor, NULL, NULL, NULL);
    +      }
    +    }
    +  }
    +  if (!numhorizon) {
    +    qh_precision("empty horizon");
    +    qh_fprintf(qh ferr, 6168, "qhull precision error (qh_findhorizon): empty horizon\n\
    +QhullPoint p%d was above all facets.\n", qh_pointid(point));
    +    qh_printfacetlist(qh facet_list, NULL, True);
    +    qh_errexit(qh_ERRprec, NULL, NULL);
    +  }
    +  trace1((qh ferr, 1041, "qh_findhorizon: %d horizon facets(good %d), %d visible(good %d), %d coplanar\n",
    +       numhorizon, *goodhorizon, qh num_visible, *goodvisible, coplanar));
    +  if (qh IStracing >= 4 && qh num_facets < 50)
    +    qh_printlists();
    +} /* findhorizon */
    +
    +/*---------------------------------
    +
    +  qh_nextfurthest( visible )
    +    returns next furthest point and visible facet for qh_addpoint()
    +    starts search at qh.facet_next
    +
    +  returns:
    +    removes furthest point from outside set
    +    NULL if none available
    +    advances qh.facet_next over facets with empty outside sets
    +
    +  design:
    +    for each facet from qh.facet_next
    +      if empty outside set
    +        advance qh.facet_next
    +      else if qh.NARROWhull
    +        determine furthest outside point
    +        if furthest point is not outside
    +          advance qh.facet_next(point will be coplanar)
    +    remove furthest point from outside set
    +*/
    +pointT *qh_nextfurthest(facetT **visible) {
    +  facetT *facet;
    +  int size, idx;
    +  realT randr, dist;
    +  pointT *furthest;
    +
    +  while ((facet= qh facet_next) != qh facet_tail) {
    +    if (!facet->outsideset) {
    +      qh facet_next= facet->next;
    +      continue;
    +    }
    +    SETreturnsize_(facet->outsideset, size);
    +    if (!size) {
    +      qh_setfree(&facet->outsideset);
    +      qh facet_next= facet->next;
    +      continue;
    +    }
    +    if (qh NARROWhull) {
    +      if (facet->notfurthest)
    +        qh_furthestout(facet);
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +#if qh_COMPUTEfurthest
    +      qh_distplane(furthest, facet, &dist);
    +      zinc_(Zcomputefurthest);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist < qh MINoutside) { /* remainder of outside set is coplanar for qh_outcoplanar */
    +        qh facet_next= facet->next;
    +        continue;
    +      }
    +    }
    +    if (!qh RANDOMoutside && !qh VIRTUALmemory) {
    +      if (qh PICKfurthest) {
    +        qh_furthestnext(/* qh.facet_list */);
    +        facet= qh facet_next;
    +      }
    +      *visible= facet;
    +      return((pointT*)qh_setdellast(facet->outsideset));
    +    }
    +    if (qh RANDOMoutside) {
    +      int outcoplanar = 0;
    +      if (qh NARROWhull) {
    +        FORALLfacets {
    +          if (facet == qh facet_next)
    +            break;
    +          if (facet->outsideset)
    +            outcoplanar += qh_setsize( facet->outsideset);
    +        }
    +      }
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor((qh num_outside - outcoplanar) * randr);
    +      FORALLfacet_(qh facet_next) {
    +        if (facet->outsideset) {
    +          SETreturnsize_(facet->outsideset, size);
    +          if (!size)
    +            qh_setfree(&facet->outsideset);
    +          else if (size > idx) {
    +            *visible= facet;
    +            return((pointT*)qh_setdelnth(facet->outsideset, idx));
    +          }else
    +            idx -= size;
    +        }
    +      }
    +      qh_fprintf(qh ferr, 6169, "qhull internal error (qh_nextfurthest): num_outside %d is too low\nby at least %d, or a random real %g >= 1.0\n",
    +              qh num_outside, idx+1, randr);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }else { /* VIRTUALmemory */
    +      facet= qh facet_tail->previous;
    +      if (!(furthest= (pointT*)qh_setdellast(facet->outsideset))) {
    +        if (facet->outsideset)
    +          qh_setfree(&facet->outsideset);
    +        qh_removefacet(facet);
    +        qh_prependfacet(facet, &qh facet_list);
    +        continue;
    +      }
    +      *visible= facet;
    +      return furthest;
    +    }
    +  }
    +  return NULL;
    +} /* nextfurthest */
    +
    +/*---------------------------------
    +
    +  qh_partitionall( vertices, points, numpoints )
    +    partitions all points in points/numpoints to the outsidesets of facets
    +    vertices= vertices in qh.facet_list(!partitioned)
    +
    +  returns:
    +    builds facet->outsideset
    +    does not partition qh.GOODpoint
    +    if qh.ONLYgood && !qh.MERGING,
    +      does not partition qh.GOODvertex
    +
    +  notes:
    +    faster if qh.facet_list sorted by anticipated size of outside set
    +
    +  design:
    +    initialize pointset with all points
    +    remove vertices from pointset
    +    remove qh.GOODpointp from pointset (unless it's qh.STOPcone or qh.STOPpoint)
    +    for all facets
    +      for all remaining points in pointset
    +        compute distance from point to facet
    +        if point is outside facet
    +          remove point from pointset (by not reappending)
    +          update bestpoint
    +          append point or old bestpoint to facet's outside set
    +      append bestpoint to facet's outside set (furthest)
    +    for all points remaining in pointset
    +      partition point into facets' outside sets and coplanar sets
    +*/
    +void qh_partitionall(setT *vertices, pointT *points, int numpoints){
    +  setT *pointset;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp, *bestpoint;
    +  int size, point_i, point_n, point_end, remaining, i, id;
    +  facetT *facet;
    +  realT bestdist= -REALmax, dist, distoutside;
    +
    +  trace1((qh ferr, 1042, "qh_partitionall: partition all points into outside sets\n"));
    +  pointset= qh_settemp(numpoints);
    +  qh num_outside= 0;
    +  pointp= SETaddr_(pointset, pointT);
    +  for (i=numpoints, point= points; i--; point += qh hull_dim)
    +    *(pointp++)= point;
    +  qh_settruncate(pointset, numpoints);
    +  FOREACHvertex_(vertices) {
    +    if ((id= qh_pointid(vertex->point)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  id= qh_pointid(qh GOODpointp);
    +  if (id >=0 && qh STOPcone-1 != id && -qh STOPpoint-1 != id)
    +    SETelem_(pointset, id)= NULL;
    +  if (qh GOODvertexp && qh ONLYgood && !qh MERGING) { /* matches qhull()*/
    +    if ((id= qh_pointid(qh GOODvertexp)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  if (!qh BESToutside) {  /* matches conditional for qh_partitionpoint below */
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +    zval_(Ztotpartition)= qh num_points - qh hull_dim - 1; /*misses GOOD... */
    +    remaining= qh num_facets;
    +    point_end= numpoints;
    +    FORALLfacets {
    +      size= point_end/(remaining--) + 100;
    +      facet->outsideset= qh_setnew(size);
    +      bestpoint= NULL;
    +      point_end= 0;
    +      FOREACHpoint_i_(pointset) {
    +        if (point) {
    +          zzinc_(Zpartitionall);
    +          qh_distplane(point, facet, &dist);
    +          if (dist < distoutside)
    +            SETelem_(pointset, point_end++)= point;
    +          else {
    +            qh num_outside++;
    +            if (!bestpoint) {
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else if (dist > bestdist) {
    +              qh_setappend(&facet->outsideset, bestpoint);
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else
    +              qh_setappend(&facet->outsideset, point);
    +          }
    +        }
    +      }
    +      if (bestpoint) {
    +        qh_setappend(&facet->outsideset, bestpoint);
    +#if !qh_COMPUTEfurthest
    +        facet->furthestdist= bestdist;
    +#endif
    +      }else
    +        qh_setfree(&facet->outsideset);
    +      qh_settruncate(pointset, point_end);
    +    }
    +  }
    +  /* if !qh BESToutside, pointset contains points not assigned to outsideset */
    +  if (qh BESToutside || qh MERGING || qh KEEPcoplanar || qh KEEPinside) {
    +    qh findbestnew= True;
    +    FOREACHpoint_i_(pointset) {
    +      if (point)
    +        qh_partitionpoint(point, qh facet_list);
    +    }
    +    qh findbestnew= False;
    +  }
    +  zzadd_(Zpartitionall, zzval_(Zpartition));
    +  zzval_(Zpartition)= 0;
    +  qh_settempfree(&pointset);
    +  if (qh IStracing >= 4)
    +    qh_printfacetlist(qh facet_list, NULL, True);
    +} /* partitionall */
    +
    +
    +/*---------------------------------
    +
    +  qh_partitioncoplanar( point, facet, dist )
    +    partition coplanar point to a facet
    +    dist is distance from point to facet
    +    if dist NULL,
    +      searches for bestfacet and does nothing if inside
    +    if qh.findbestnew set,
    +      searches new facets instead of using qh_findbest()
    +
    +  returns:
    +    qh.max_ouside updated
    +    if qh.KEEPcoplanar or qh.KEEPinside
    +      point assigned to best coplanarset
    +
    +  notes:
    +    facet->maxoutside is updated at end by qh_check_maxout
    +
    +  design:
    +    if dist undefined
    +      find best facet for point
    +      if point sufficiently below facet (depends on qh.NEARinside and qh.KEEPinside)
    +        exit
    +    if keeping coplanar/nearinside/inside points
    +      if point is above furthest coplanar point
    +        append point to coplanar set (it is the new furthest)
    +        update qh.max_outside
    +      else
    +        append point one before end of coplanar set
    +    else if point is clearly outside of qh.max_outside and bestfacet->coplanarset
    +    and bestfacet is more than perpendicular to facet
    +      repartition the point using qh_findbest() -- it may be put on an outsideset
    +    else
    +      update qh.max_outside
    +*/
    +void qh_partitioncoplanar(pointT *point, facetT *facet, realT *dist) {
    +  facetT *bestfacet;
    +  pointT *oldfurthest;
    +  realT bestdist, dist2= 0, angle;
    +  int numpart= 0, oldfindbest;
    +  boolT isoutside;
    +
    +  qh WAScoplanar= True;
    +  if (!dist) {
    +    if (qh findbestnew)
    +      bestfacet= qh_findbestnew(point, facet, &bestdist, qh_ALL, &isoutside, &numpart);
    +    else
    +      bestfacet= qh_findbest(point, facet, qh_ALL, !qh_ISnewfacets, qh DELAUNAY,
    +                          &bestdist, &isoutside, &numpart);
    +    zinc_(Ztotpartcoplanar);
    +    zzadd_(Zpartcoplanar, numpart);
    +    if (!qh DELAUNAY && !qh KEEPinside) { /*  for 'd', bestdist skips upperDelaunay facets */
    +      if (qh KEEPnearinside) {
    +        if (bestdist < -qh NEARinside) {
    +          zinc_(Zcoplanarinside);
    +          trace4((qh ferr, 4062, "qh_partitioncoplanar: point p%d is more than near-inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(point), bestfacet->id, bestdist, qh findbestnew));
    +          return;
    +        }
    +      }else if (bestdist < -qh MAXcoplanar) {
    +          trace4((qh ferr, 4063, "qh_partitioncoplanar: point p%d is inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(point), bestfacet->id, bestdist, qh findbestnew));
    +        zinc_(Zcoplanarinside);
    +        return;
    +      }
    +    }
    +  }else {
    +    bestfacet= facet;
    +    bestdist= *dist;
    +  }
    +  if (bestdist > qh max_outside) {
    +    if (!dist && facet != bestfacet) {
    +      zinc_(Zpartangle);
    +      angle= qh_getangle(facet->normal, bestfacet->normal);
    +      if (angle < 0) {
    +        /* typically due to deleted vertex and coplanar facets, e.g.,
    +             RBOX 1000 s Z1 G1e-13 t1001185205 | QHULL Tv */
    +        zinc_(Zpartflip);
    +        trace2((qh ferr, 2058, "qh_partitioncoplanar: repartition point p%d from f%d.  It is above flipped facet f%d dist %2.2g\n",
    +                qh_pointid(point), facet->id, bestfacet->id, bestdist));
    +        oldfindbest= qh findbestnew;
    +        qh findbestnew= False;
    +        qh_partitionpoint(point, bestfacet);
    +        qh findbestnew= oldfindbest;
    +        return;
    +      }
    +    }
    +    qh max_outside= bestdist;
    +    if (bestdist > qh TRACEdist) {
    +      qh_fprintf(qh ferr, 8122, "qh_partitioncoplanar: ====== p%d from f%d increases max_outside to %2.2g of f%d last p%d\n",
    +                     qh_pointid(point), facet->id, bestdist, bestfacet->id, qh furthest_id);
    +      qh_errprint("DISTANT", facet, bestfacet, NULL, NULL);
    +    }
    +  }
    +  if (qh KEEPcoplanar + qh KEEPinside + qh KEEPnearinside) {
    +    oldfurthest= (pointT*)qh_setlast(bestfacet->coplanarset);
    +    if (oldfurthest) {
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(oldfurthest, bestfacet, &dist2);
    +    }
    +    if (!oldfurthest || dist2 < bestdist)
    +      qh_setappend(&bestfacet->coplanarset, point);
    +    else
    +      qh_setappend2ndlast(&bestfacet->coplanarset, point);
    +  }
    +  trace4((qh ferr, 4064, "qh_partitioncoplanar: point p%d is coplanar with facet f%d(or inside) dist %2.2g\n",
    +          qh_pointid(point), bestfacet->id, bestdist));
    +} /* partitioncoplanar */
    +
    +/*---------------------------------
    +
    +  qh_partitionpoint( point, facet )
    +    assigns point to an outside set, coplanar set, or inside set (i.e., dropt)
    +    if qh.findbestnew
    +      uses qh_findbestnew() to search all new facets
    +    else
    +      uses qh_findbest()
    +
    +  notes:
    +    after qh_distplane(), this and qh_findbest() are most expensive in 3-d
    +
    +  design:
    +    find best facet for point
    +      (either exhaustive search of new facets or directed search from facet)
    +    if qh.NARROWhull
    +      retain coplanar and nearinside points as outside points
    +    if point is outside bestfacet
    +      if point above furthest point for bestfacet
    +        append point to outside set (it becomes the new furthest)
    +        if outside set was empty
    +          move bestfacet to end of qh.facet_list (i.e., after qh.facet_next)
    +        update bestfacet->furthestdist
    +      else
    +        append point one before end of outside set
    +    else if point is coplanar to bestfacet
    +      if keeping coplanar points or need to update qh.max_outside
    +        partition coplanar point into bestfacet
    +    else if near-inside point
    +      partition as coplanar point into bestfacet
    +    else is an inside point
    +      if keeping inside points
    +        partition as coplanar point into bestfacet
    +*/
    +void qh_partitionpoint(pointT *point, facetT *facet) {
    +  realT bestdist;
    +  boolT isoutside;
    +  facetT *bestfacet;
    +  int numpart;
    +#if qh_COMPUTEfurthest
    +  realT dist;
    +#endif
    +
    +  if (qh findbestnew)
    +    bestfacet= qh_findbestnew(point, facet, &bestdist, qh BESToutside, &isoutside, &numpart);
    +  else
    +    bestfacet= qh_findbest(point, facet, qh BESToutside, qh_ISnewfacets, !qh_NOupper,
    +                          &bestdist, &isoutside, &numpart);
    +  zinc_(Ztotpartition);
    +  zzadd_(Zpartition, numpart);
    +  if (qh NARROWhull) {
    +    if (qh DELAUNAY && !isoutside && bestdist >= -qh MAXcoplanar)
    +      qh_precision("nearly incident point(narrow hull)");
    +    if (qh KEEPnearinside) {
    +      if (bestdist >= -qh NEARinside)
    +        isoutside= True;
    +    }else if (bestdist >= -qh MAXcoplanar)
    +      isoutside= True;
    +  }
    +
    +  if (isoutside) {
    +    if (!bestfacet->outsideset
    +    || !qh_setlast(bestfacet->outsideset)) {
    +      qh_setappend(&(bestfacet->outsideset), point);
    +      if (!bestfacet->newfacet) {
    +        qh_removefacet(bestfacet);  /* make sure it's after qh facet_next */
    +        qh_appendfacet(bestfacet);
    +      }
    +#if !qh_COMPUTEfurthest
    +      bestfacet->furthestdist= bestdist;
    +#endif
    +    }else {
    +#if qh_COMPUTEfurthest
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(oldfurthest, bestfacet, &dist);
    +      if (dist < bestdist)
    +        qh_setappend(&(bestfacet->outsideset), point);
    +      else
    +        qh_setappend2ndlast(&(bestfacet->outsideset), point);
    +#else
    +      if (bestfacet->furthestdist < bestdist) {
    +        qh_setappend(&(bestfacet->outsideset), point);
    +        bestfacet->furthestdist= bestdist;
    +      }else
    +        qh_setappend2ndlast(&(bestfacet->outsideset), point);
    +#endif
    +    }
    +    qh num_outside++;
    +    trace4((qh ferr, 4065, "qh_partitionpoint: point p%d is outside facet f%d new? %d (or narrowhull)\n",
    +          qh_pointid(point), bestfacet->id, bestfacet->newfacet));
    +  }else if (qh DELAUNAY || bestdist >= -qh MAXcoplanar) { /* for 'd', bestdist skips upperDelaunay facets */
    +    zzinc_(Zcoplanarpart);
    +    if (qh DELAUNAY)
    +      qh_precision("nearly incident point");
    +    if ((qh KEEPcoplanar + qh KEEPnearinside) || bestdist > qh max_outside)
    +      qh_partitioncoplanar(point, bestfacet, &bestdist);
    +    else {
    +      trace4((qh ferr, 4066, "qh_partitionpoint: point p%d is coplanar to facet f%d (dropped)\n",
    +          qh_pointid(point), bestfacet->id));
    +    }
    +  }else if (qh KEEPnearinside && bestdist > -qh NEARinside) {
    +    zinc_(Zpartnear);
    +    qh_partitioncoplanar(point, bestfacet, &bestdist);
    +  }else {
    +    zinc_(Zpartinside);
    +    trace4((qh ferr, 4067, "qh_partitionpoint: point p%d is inside all facets, closest to f%d dist %2.2g\n",
    +          qh_pointid(point), bestfacet->id, bestdist));
    +    if (qh KEEPinside)
    +      qh_partitioncoplanar(point, bestfacet, &bestdist);
    +  }
    +} /* partitionpoint */
    +
    +/*---------------------------------
    +
    +  qh_partitionvisible( allpoints, numoutside )
    +    partitions points in visible facets to qh.newfacet_list
    +    qh.visible_list= visible facets
    +    for visible facets
    +      1st neighbor (if any) points to a horizon facet or a new facet
    +    if allpoints(!used),
    +      repartitions coplanar points
    +
    +  returns:
    +    updates outside sets and coplanar sets of qh.newfacet_list
    +    updates qh.num_outside (count of outside points)
    +
    +  notes:
    +    qh.findbest_notsharp should be clear (extra work if set)
    +
    +  design:
    +    for all visible facets with outside set or coplanar set
    +      select a newfacet for visible facet
    +      if outside set
    +        partition outside set into new facets
    +      if coplanar set and keeping coplanar/near-inside/inside points
    +        if allpoints
    +          partition coplanar set into new facets, may be assigned outside
    +        else
    +          partition coplanar set into coplanar sets of new facets
    +    for each deleted vertex
    +      if allpoints
    +        partition vertex into new facets, may be assigned outside
    +      else
    +        partition vertex into coplanar sets of new facets
    +*/
    +void qh_partitionvisible(/*qh.visible_list*/ boolT allpoints, int *numoutside) {
    +  facetT *visible, *newfacet;
    +  pointT *point, **pointp;
    +  int coplanar=0, size;
    +  unsigned count;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh ONLYmax)
    +    maximize_(qh MINoutside, qh max_vertex);
    +  *numoutside= 0;
    +  FORALLvisible_facets {
    +    if (!visible->outsideset && !visible->coplanarset)
    +      continue;
    +    newfacet= visible->f.replace;
    +    count= 0;
    +    while (newfacet && newfacet->visible) {
    +      newfacet= newfacet->f.replace;
    +      if (count++ > qh facet_id)
    +        qh_infiniteloop(visible);
    +    }
    +    if (!newfacet)
    +      newfacet= qh newfacet_list;
    +    if (newfacet == qh facet_tail) {
    +      qh_fprintf(qh ferr, 6170, "qhull precision error (qh_partitionvisible): all new facets deleted as\n        degenerate facets. Can not continue.\n");
    +      qh_errexit(qh_ERRprec, NULL, NULL);
    +    }
    +    if (visible->outsideset) {
    +      size= qh_setsize(visible->outsideset);
    +      *numoutside += size;
    +      qh num_outside -= size;
    +      FOREACHpoint_(visible->outsideset)
    +        qh_partitionpoint(point, newfacet);
    +    }
    +    if (visible->coplanarset && (qh KEEPcoplanar + qh KEEPinside + qh KEEPnearinside)) {
    +      size= qh_setsize(visible->coplanarset);
    +      coplanar += size;
    +      FOREACHpoint_(visible->coplanarset) {
    +        if (allpoints) /* not used */
    +          qh_partitionpoint(point, newfacet);
    +        else
    +          qh_partitioncoplanar(point, newfacet, NULL);
    +      }
    +    }
    +  }
    +  FOREACHvertex_(qh del_vertices) {
    +    if (vertex->point) {
    +      if (allpoints) /* not used */
    +        qh_partitionpoint(vertex->point, qh newfacet_list);
    +      else
    +        qh_partitioncoplanar(vertex->point, qh newfacet_list, NULL);
    +    }
    +  }
    +  trace1((qh ferr, 1043,"qh_partitionvisible: partitioned %d points from outsidesets and %d points from coplanarsets\n", *numoutside, coplanar));
    +} /* partitionvisible */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_precision( reason )
    +    restart on precision errors if not merging and if 'QJn'
    +*/
    +void qh_precision(const char *reason) {
    +
    +  if (qh ALLOWrestart && !qh PREmerge && !qh MERGEexact) {
    +    if (qh JOGGLEmax < REALmax/2) {
    +      trace0((qh ferr, 26, "qh_precision: qhull restart because of %s\n", reason));
    +      /* May be called repeatedly if qh->ALLOWrestart */
    +      longjmp(qh restartexit, qh_ERRprec);
    +    }
    +  }
    +} /* qh_precision */
    +
    +/*---------------------------------
    +
    +  qh_printsummary( fp )
    +    prints summary to fp
    +
    +  notes:
    +    not in io.c so that user_eg.c can prevent io.c from loading
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  design:
    +    determine number of points, vertices, and coplanar points
    +    print summary
    +*/
    +void qh_printsummary(FILE *fp) {
    +  realT ratio, outerplane, innerplane;
    +  float cpu;
    +  int size, id, nummerged, numvertices, numcoplanars= 0, nonsimplicial=0;
    +  int goodused;
    +  facetT *facet;
    +  const char *s;
    +  int numdel= zzval_(Zdelvertextot);
    +  int numtricoplanars= 0;
    +
    +  size= qh num_points + qh_setsize(qh other_points);
    +  numvertices= qh num_vertices - qh_setsize(qh del_vertices);
    +  id= qh_pointid(qh GOODpointp);
    +  FORALLfacets {
    +    if (facet->coplanarset)
    +      numcoplanars += qh_setsize( facet->coplanarset);
    +    if (facet->good) {
    +      if (facet->simplicial) {
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else if (qh_setsize(facet->vertices) != qh hull_dim)
    +        nonsimplicial++;
    +    }
    +  }
    +  if (id >=0 && qh STOPcone-1 != id && -qh STOPpoint-1 != id)
    +    size--;
    +  if (qh STOPcone || qh STOPpoint)
    +      qh_fprintf(fp, 9288, "\nAt a premature exit due to 'TVn', 'TCn', 'TRn', or precision error with 'QJn'.");
    +  if (qh UPPERdelaunay)
    +    goodused= qh GOODvertex + qh GOODpoint + qh SPLITthresholds;
    +  else if (qh DELAUNAY)
    +    goodused= qh GOODvertex + qh GOODpoint + qh GOODthreshold;
    +  else
    +    goodused= qh num_good;
    +  nummerged= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  if (qh VORONOI) {
    +    if (qh UPPERdelaunay)
    +      qh_fprintf(fp, 9289, "\n\
    +Furthest-site Voronoi vertices by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    else
    +      qh_fprintf(fp, 9290, "\n\
    +Voronoi diagram by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9291, "  Number of Voronoi regions%s: %d\n",
    +              qh ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(fp, 9292, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(fp, 9293, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(fp, 9294, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(fp, 9295, "  Number of%s Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9296, "  Number of%s non-simplicial Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh DELAUNAY) {
    +    if (qh UPPERdelaunay)
    +      qh_fprintf(fp, 9297, "\n\
    +Furthest-site Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    else
    +      qh_fprintf(fp, 9298, "\n\
    +Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9299, "  Number of input sites%s: %d\n",
    +              qh ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(fp, 9300, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(fp, 9301, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(fp, 9302, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(fp, 9303, "  Number of%s Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9304, "  Number of%s non-simplicial Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh HALFspace) {
    +    qh_fprintf(fp, 9305, "\n\
    +Halfspace intersection by the convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9306, "  Number of halfspaces: %d\n", size);
    +    qh_fprintf(fp, 9307, "  Number of non-redundant halfspaces: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh KEEPinside && qh KEEPcoplanar)
    +        s= "similar and redundant";
    +      else if (qh KEEPinside)
    +        s= "redundant";
    +      else
    +        s= "similar";
    +      qh_fprintf(fp, 9308, "  Number of %s halfspaces: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(fp, 9309, "  Number of intersection points: %d\n", qh num_facets - qh num_visible);
    +    if (goodused)
    +      qh_fprintf(fp, 9310, "  Number of 'good' intersection points: %d\n", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9311, "  Number of%s non-simplicial intersection points: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else {
    +    qh_fprintf(fp, 9312, "\n\
    +Convex hull of %d points in %d-d:\n\n", size, qh hull_dim);
    +    qh_fprintf(fp, 9313, "  Number of vertices: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh KEEPinside && qh KEEPcoplanar)
    +        s= "coplanar and interior";
    +      else if (qh KEEPinside)
    +        s= "interior";
    +      else
    +        s= "coplanar";
    +      qh_fprintf(fp, 9314, "  Number of %s points: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(fp, 9315, "  Number of facets: %d\n", qh num_facets - qh num_visible);
    +    if (goodused)
    +      qh_fprintf(fp, 9316, "  Number of 'good' facets: %d\n", qh num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(fp, 9317, "  Number of%s non-simplicial facets: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }
    +  if (numtricoplanars)
    +      qh_fprintf(fp, 9318, "  Number of triangulated facets: %d\n", numtricoplanars);
    +  qh_fprintf(fp, 9319, "\nStatistics for: %s | %s",
    +                      qh rbox_command, qh qhull_command);
    +  if (qh ROTATErandom != INT_MIN)
    +    qh_fprintf(fp, 9320, " QR%d\n\n", qh ROTATErandom);
    +  else
    +    qh_fprintf(fp, 9321, "\n\n");
    +  qh_fprintf(fp, 9322, "  Number of points processed: %d\n", zzval_(Zprocessed));
    +  qh_fprintf(fp, 9323, "  Number of hyperplanes created: %d\n", zzval_(Zsetplane));
    +  if (qh DELAUNAY)
    +    qh_fprintf(fp, 9324, "  Number of facets in hull: %d\n", qh num_facets - qh num_visible);
    +  qh_fprintf(fp, 9325, "  Number of distance tests for qhull: %d\n", zzval_(Zpartition)+
    +      zzval_(Zpartitionall)+zzval_(Znumvisibility)+zzval_(Zpartcoplanar));
    +#if 0  /* NOTE: must print before printstatistics() */
    +  {realT stddev, ave;
    +  qh_fprintf(fp, 9326, "  average new facet balance: %2.2g\n",
    +          wval_(Wnewbalance)/zval_(Zprocessed));
    +  stddev= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(fp, 9327, "  new facet standard deviation: %2.2g\n", stddev);
    +  qh_fprintf(fp, 9328, "  average partition balance: %2.2g\n",
    +          wval_(Wpbalance)/zval_(Zpbalance));
    +  stddev= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  qh_fprintf(fp, 9329, "  partition standard deviation: %2.2g\n", stddev);
    +  }
    +#endif
    +  if (nummerged) {
    +    qh_fprintf(fp, 9330,"  Number of distance tests for merging: %d\n",zzval_(Zbestdist)+
    +          zzval_(Zcentrumtests)+zzval_(Zdistconvex)+zzval_(Zdistcheck)+
    +          zzval_(Zdistzero));
    +    qh_fprintf(fp, 9331,"  Number of distance tests for checking: %d\n",zzval_(Zcheckpart));
    +    qh_fprintf(fp, 9332,"  Number of merged facets: %d\n", nummerged);
    +  }
    +  if (!qh RANDOMoutside && qh QHULLfinished) {
    +    cpu= (float)qh hulltime;
    +    cpu /= (float)qh_SECticks;
    +    wval_(Wcpu)= cpu;
    +    qh_fprintf(fp, 9333, "  CPU seconds to compute hull (after input): %2.4g\n", cpu);
    +  }
    +  if (qh RERUN) {
    +    if (!qh PREmerge && !qh MERGEexact)
    +      qh_fprintf(fp, 9334, "  Percentage of runs with precision errors: %4.1f\n",
    +           zzval_(Zretry)*100.0/qh build_cnt);  /* careful of order */
    +  }else if (qh JOGGLEmax < REALmax/2) {
    +    if (zzval_(Zretry))
    +      qh_fprintf(fp, 9335, "  After %d retries, input joggled by: %2.2g\n",
    +         zzval_(Zretry), qh JOGGLEmax);
    +    else
    +      qh_fprintf(fp, 9336, "  Input joggled by: %2.2g\n", qh JOGGLEmax);
    +  }
    +  if (qh totarea != 0.0)
    +    qh_fprintf(fp, 9337, "  %s facet area:   %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh totarea);
    +  if (qh totvol != 0.0)
    +    qh_fprintf(fp, 9338, "  %s volume:       %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh totvol);
    +  if (qh MERGING) {
    +    qh_outerinner(NULL, &outerplane, &innerplane);
    +    if (outerplane > 2 * qh DISTround) {
    +      qh_fprintf(fp, 9339, "  Maximum distance of %spoint above facet: %2.2g",
    +            (qh QHULLfinished ? "" : "merged "), outerplane);
    +      ratio= outerplane/(qh ONEmerge + qh DISTround);
    +      /* don't report ratio if MINoutside is large */
    +      if (ratio > 0.05 && 2* qh ONEmerge > qh MINoutside && qh JOGGLEmax > REALmax/2)
    +        qh_fprintf(fp, 9340, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(fp, 9341, "\n");
    +    }
    +    if (innerplane < -2 * qh DISTround) {
    +      qh_fprintf(fp, 9342, "  Maximum distance of %svertex below facet: %2.2g",
    +            (qh QHULLfinished ? "" : "merged "), innerplane);
    +      ratio= -innerplane/(qh ONEmerge+qh DISTround);
    +      if (ratio > 0.05 && qh JOGGLEmax > REALmax/2)
    +        qh_fprintf(fp, 9343, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(fp, 9344, "\n");
    +    }
    +  }
    +  qh_fprintf(fp, 9345, "\n");
    +} /* printsummary */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/libqhull.h b/xs/src/qhull/src/libqhull/libqhull.h
    new file mode 100644
    index 0000000000..677085808d
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/libqhull.h
    @@ -0,0 +1,1140 @@
    +/*
      ---------------------------------
    +
    +   libqhull.h
    +   user-level header file for using qhull.a library
    +
    +   see qh-qhull.htm, qhull_a.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/libqhull.h#7 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +
    +   NOTE: access to qh_qh is via the 'qh' macro.  This allows
    +   qh_qh to be either a pointer or a structure.  An example
    +   of using qh is "qh.DROPdim" which accesses the DROPdim
    +   field of qh_qh.  Similarly, access to qh_qhstat is via
    +   the 'qhstat' macro.
    +
    +   includes function prototypes for libqhull.c, geom.c, global.c, io.c, user.c
    +
    +   use mem.h for mem.c
    +   use qset.h for qset.c
    +
    +   see unix.c for an example of using libqhull.h
    +
    +   recompile qhull if you change this file
    +*/
    +
    +#ifndef qhDEFlibqhull
    +#define qhDEFlibqhull 1
    +
    +/*=========================== -included files ==============*/
    +
    +/* user_r.h first for QHULL_CRTDBG */
    +#include "user.h"      /* user definable constants (e.g., qh_QHpointer) */
    +
    +#include "mem.h"   /* Needed qhT in libqhull_r.h.  Here for compatibility */
    +#include "qset.h"   /* Needed for QHULL_LIB_CHECK */
    +/* include stat_r.h after defining boolT.  Needed for qhT in libqhull_r.h.  Here for compatibility and statT */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __MWERKS__ && __POWERPC__
    +#include  
    +#include  
    +#include        
    +#endif
    +
    +#ifndef __STDC__
    +#ifndef __cplusplus
    +#if     !_MSC_VER
    +#error  Neither __STDC__ nor __cplusplus is defined.  Please use strict ANSI C or C++ to compile
    +#error  Qhull.  You may need to turn off compiler extensions in your project configuration.  If
    +#error  your compiler is a standard C compiler, you can delete this warning from libqhull.h
    +#endif
    +#endif
    +#endif
    +
    +/*============ constants and basic types ====================*/
    +
    +extern const char qh_version[]; /* defined in global.c */
    +extern const char qh_version2[]; /* defined in global.c */
    +
    +/*----------------------------------
    +
    +  coordT
    +    coordinates and coefficients are stored as realT (i.e., double)
    +
    +  notes:
    +    Qhull works well if realT is 'float'.  If so joggle (QJ) is not effective.
    +
    +    Could use 'float' for data and 'double' for calculations (realT vs. coordT)
    +      This requires many type casts, and adjusted error bounds.
    +      Also C compilers may do expressions in double anyway.
    +*/
    +#define coordT realT
    +
    +/*----------------------------------
    +
    +  pointT
    +    a point is an array of coordinates, usually qh.hull_dim
    +    qh_pointid returns
    +      qh_IDnone if point==0 or qh is undefined
    +      qh_IDinterior for qh.interior_point
    +      qh_IDunknown if point is neither in qh.first_point... nor qh.other_points
    +
    +  notes:
    +    qh.STOPcone and qh.STOPpoint assume that qh_IDunknown==-1 (other negative numbers indicate points)
    +    qh_IDunknown is also returned by getid_() for unknown facet, ridge, or vertex
    +*/
    +#define pointT coordT
    +typedef enum
    +{
    +    qh_IDnone = -3, qh_IDinterior = -2, qh_IDunknown = -1
    +}
    +qh_pointT;
    +
    +/*----------------------------------
    +
    +  flagT
    +    Boolean flag as a bit
    +*/
    +#define flagT unsigned int
    +
    +/*----------------------------------
    +
    +  boolT
    +    boolean value, either True or False
    +
    +  notes:
    +    needed for portability
    +    Use qh_False/qh_True as synonyms
    +*/
    +#define boolT unsigned int
    +#ifdef False
    +#undef False
    +#endif
    +#ifdef True
    +#undef True
    +#endif
    +#define False 0
    +#define True 1
    +#define qh_False 0
    +#define qh_True 1
    +
    +#include "stat.h"  /* after define of boolT */
    +
    +/*----------------------------------
    +
    +  qh_CENTER
    +    to distinguish facet->center
    +*/
    +typedef enum
    +{
    +    qh_ASnone = 0,   /* If not MERGING and not VORONOI */
    +    qh_ASvoronoi,    /* Set by qh_clearcenters on qh_prepare_output, or if not MERGING and VORONOI */
    +    qh_AScentrum     /* If MERGING (assumed during merging) */
    +}
    +qh_CENTER;
    +
    +/*----------------------------------
    +
    +  qh_PRINT
    +    output formats for printing (qh.PRINTout).
    +    'Fa' 'FV' 'Fc' 'FC'
    +
    +
    +   notes:
    +   some of these names are similar to qhT names.  The similar names are only
    +   used in switch statements in qh_printbegin() etc.
    +*/
    +typedef enum {qh_PRINTnone= 0,
    +  qh_PRINTarea, qh_PRINTaverage,           /* 'Fa' 'FV' 'Fc' 'FC' */
    +  qh_PRINTcoplanars, qh_PRINTcentrums,
    +  qh_PRINTfacets, qh_PRINTfacets_xridge,   /* 'f' 'FF' 'G' 'FI' 'Fi' 'Fn' */
    +  qh_PRINTgeom, qh_PRINTids, qh_PRINTinner, qh_PRINTneighbors,
    +  qh_PRINTnormals, qh_PRINTouter, qh_PRINTmaple, /* 'n' 'Fo' 'i' 'm' 'Fm' 'FM', 'o' */
    +  qh_PRINTincidences, qh_PRINTmathematica, qh_PRINTmerges, qh_PRINToff,
    +  qh_PRINToptions, qh_PRINTpointintersect, /* 'FO' 'Fp' 'FP' 'p' 'FQ' 'FS' */
    +  qh_PRINTpointnearest, qh_PRINTpoints, qh_PRINTqhull, qh_PRINTsize,
    +  qh_PRINTsummary, qh_PRINTtriangles,      /* 'Fs' 'Ft' 'Fv' 'FN' 'Fx' */
    +  qh_PRINTvertices, qh_PRINTvneighbors, qh_PRINTextremes,
    +  qh_PRINTEND} qh_PRINT;
    +
    +/*----------------------------------
    +
    +  qh_ALL
    +    argument flag for selecting everything
    +*/
    +#define qh_ALL      True
    +#define qh_NOupper  True     /* argument for qh_findbest */
    +#define qh_IScheckmax  True     /* argument for qh_findbesthorizon */
    +#define qh_ISnewfacets  True     /* argument for qh_findbest */
    +#define qh_RESETvisible  True     /* argument for qh_resetlists */
    +
    +/*----------------------------------
    +
    +  qh_ERR
    +    Qhull exit codes, for indicating errors
    +    See: MSG_ERROR and MSG_WARNING [user.h]
    +*/
    +#define qh_ERRnone  0    /* no error occurred during qhull */
    +#define qh_ERRinput 1    /* input inconsistency */
    +#define qh_ERRsingular 2 /* singular input data */
    +#define qh_ERRprec  3    /* precision error */
    +#define qh_ERRmem   4    /* insufficient memory, matches mem.h */
    +#define qh_ERRqhull 5    /* internal error detected, matches mem.h */
    +
    +/*----------------------------------
    +
    +qh_FILEstderr
    +Fake stderr to distinguish error output from normal output
    +For C++ interface.  Must redefine qh_fprintf_qhull
    +*/
    +#define qh_FILEstderr ((FILE*)1)
    +
    +/* ============ -structures- ====================
    +   each of the following structures is defined by a typedef
    +   all realT and coordT fields occur at the beginning of a structure
    +        (otherwise space may be wasted due to alignment)
    +   define all flags together and pack into 32-bit number
    +   DEFsetT is likewise defined in
    +   mem.h and qset.h
    +*/
    +
    +typedef struct vertexT vertexT;
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset.h */
    +#endif
    +
    +/*----------------------------------
    +
    +  facetT
    +    defines a facet
    +
    +  notes:
    +   qhull() generates the hull as a list of facets.
    +
    +  topological information:
    +    f.previous,next     doubly-linked list of facets
    +    f.vertices          set of vertices
    +    f.ridges            set of ridges
    +    f.neighbors         set of neighbors
    +    f.toporient         True if facet has top-orientation (else bottom)
    +
    +  geometric information:
    +    f.offset,normal     hyperplane equation
    +    f.maxoutside        offset to outer plane -- all points inside
    +    f.center            centrum for testing convexity
    +    f.simplicial        True if facet is simplicial
    +    f.flipped           True if facet does not include qh.interior_point
    +
    +  for constructing hull:
    +    f.visible           True if facet on list of visible facets (will be deleted)
    +    f.newfacet          True if facet on list of newly created facets
    +    f.coplanarset       set of points coplanar with this facet
    +                        (includes near-inside points for later testing)
    +    f.outsideset        set of points outside of this facet
    +    f.furthestdist      distance to furthest point of outside set
    +    f.visitid           marks visited facets during a loop
    +    f.replace           replacement facet for to-be-deleted, visible facets
    +    f.samecycle,newcycle cycle of facets for merging into horizon facet
    +
    +  see below for other flags and fields
    +*/
    +struct facetT {
    +#if !qh_COMPUTEfurthest
    +  coordT   furthestdist;/* distance to furthest point of outsideset */
    +#endif
    +#if qh_MAXoutside
    +  coordT   maxoutside;  /* max computed distance of point to facet
    +                        Before QHULLfinished this is an approximation
    +                        since maxdist not always set for mergefacet
    +                        Actual outer plane is +DISTround and
    +                        computed outer plane is +2*DISTround */
    +#endif
    +  coordT   offset;      /* exact offset of hyperplane from origin */
    +  coordT  *normal;      /* normal of hyperplane, hull_dim coefficients */
    +                        /*   if tricoplanar, shared with a neighbor */
    +  union {               /* in order of testing */
    +   realT   area;        /* area of facet, only in io.c if  ->isarea */
    +   facetT *replace;     /*  replacement facet if ->visible and NEWfacets
    +                             is NULL only if qh_mergedegen_redundant or interior */
    +   facetT *samecycle;   /*  cycle of facets from the same visible/horizon intersection,
    +                             if ->newfacet */
    +   facetT *newcycle;    /*  in horizon facet, current samecycle of new facets */
    +   facetT *trivisible;  /* visible facet for ->tricoplanar facets during qh_triangulate() */
    +   facetT *triowner;    /* owner facet for ->tricoplanar, !isarea facets w/ ->keepcentrum */
    +  }f;
    +  coordT  *center;      /* set according to qh.CENTERtype */
    +                        /*   qh_ASnone:    no center (not MERGING) */
    +                        /*   qh_AScentrum: centrum for testing convexity (qh_getcentrum) */
    +                        /*                 assumed qh_AScentrum while merging */
    +                        /*   qh_ASvoronoi: Voronoi center (qh_facetcenter) */
    +                        /* after constructing the hull, it may be changed (qh_clearcenter) */
    +                        /* if tricoplanar and !keepcentrum, shared with a neighbor */
    +  facetT  *previous;    /* previous facet in the facet_list */
    +  facetT  *next;        /* next facet in the facet_list */
    +  setT    *vertices;    /* vertices for this facet, inverse sorted by ID
    +                           if simplicial, 1st vertex was apex/furthest */
    +  setT    *ridges;      /* explicit ridges for nonsimplicial facets.
    +                           for simplicial facets, neighbors define the ridges */
    +  setT    *neighbors;   /* neighbors of the facet.  If simplicial, the kth
    +                           neighbor is opposite the kth vertex, and the first
    +                           neighbor is the horizon facet for the first vertex*/
    +  setT    *outsideset;  /* set of points outside this facet
    +                           if non-empty, last point is furthest
    +                           if NARROWhull, includes coplanars for partitioning*/
    +  setT    *coplanarset; /* set of points coplanar with this facet
    +                           > qh.min_vertex and <= facet->max_outside
    +                           a point is assigned to the furthest facet
    +                           if non-empty, last point is furthest away */
    +  unsigned visitid;     /* visit_id, for visiting all neighbors,
    +                           all uses are independent */
    +  unsigned id;          /* unique identifier from qh.facet_id */
    +  unsigned nummerge:9;  /* number of merges */
    +#define qh_MAXnummerge 511 /*     2^9-1, 32 flags total, see "flags:" in io.c */
    +  flagT    tricoplanar:1; /* True if TRIangulate and simplicial and coplanar with a neighbor */
    +                          /*   all tricoplanars share the same apex */
    +                          /*   all tricoplanars share the same ->center, ->normal, ->offset, ->maxoutside */
    +                          /*     ->keepcentrum is true for the owner.  It has the ->coplanareset */
    +                          /*   if ->degenerate, does not span facet (one logical ridge) */
    +                          /*   during qh_triangulate, f.trivisible points to original facet */
    +  flagT    newfacet:1;  /* True if facet on qh.newfacet_list (new or merged) */
    +  flagT    visible:1;   /* True if visible facet (will be deleted) */
    +  flagT    toporient:1; /* True if created with top orientation
    +                           after merging, use ridge orientation */
    +  flagT    simplicial:1;/* True if simplicial facet, ->ridges may be implicit */
    +  flagT    seen:1;      /* used to perform operations only once, like visitid */
    +  flagT    seen2:1;     /* used to perform operations only once, like visitid */
    +  flagT    flipped:1;   /* True if facet is flipped */
    +  flagT    upperdelaunay:1; /* True if facet is upper envelope of Delaunay triangulation */
    +  flagT    notfurthest:1; /* True if last point of outsideset is not furthest*/
    +
    +/*-------- flags primarily for output ---------*/
    +  flagT    good:1;      /* True if a facet marked good for output */
    +  flagT    isarea:1;    /* True if facet->f.area is defined */
    +
    +/*-------- flags for merging ------------------*/
    +  flagT    dupridge:1;  /* True if duplicate ridge in facet */
    +  flagT    mergeridge:1; /* True if facet or neighbor contains a qh_MERGEridge
    +                            ->normal defined (also defined for mergeridge2) */
    +  flagT    mergeridge2:1; /* True if neighbor contains a qh_MERGEridge (mark_dupridges */
    +  flagT    coplanar:1;  /* True if horizon facet is coplanar at last use */
    +  flagT     mergehorizon:1; /* True if will merge into horizon (->coplanar) */
    +  flagT     cycledone:1;/* True if mergecycle_all already done */
    +  flagT    tested:1;    /* True if facet convexity has been tested (false after merge */
    +  flagT    keepcentrum:1; /* True if keep old centrum after a merge, or marks owner for ->tricoplanar */
    +  flagT    newmerge:1;  /* True if facet is newly merged for reducevertices */
    +  flagT    degenerate:1; /* True if facet is degenerate (degen_mergeset or ->tricoplanar) */
    +  flagT    redundant:1;  /* True if facet is redundant (degen_mergeset) */
    +};
    +
    +
    +/*----------------------------------
    +
    +  ridgeT
    +    defines a ridge
    +
    +  notes:
    +  a ridge is hull_dim-1 simplex between two neighboring facets.  If the
    +  facets are non-simplicial, there may be more than one ridge between
    +  two facets.  E.G. a 4-d hypercube has two triangles between each pair
    +  of neighboring facets.
    +
    +  topological information:
    +    vertices            a set of vertices
    +    top,bottom          neighboring facets with orientation
    +
    +  geometric information:
    +    tested              True if ridge is clearly convex
    +    nonconvex           True if ridge is non-convex
    +*/
    +struct ridgeT {
    +  setT    *vertices;    /* vertices belonging to this ridge, inverse sorted by ID
    +                           NULL if a degen ridge (matchsame) */
    +  facetT  *top;         /* top facet this ridge is part of */
    +  facetT  *bottom;      /* bottom facet this ridge is part of */
    +  unsigned id;          /* unique identifier.  Same size as vertex_id and ridge_id */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    tested:1;    /* True when ridge is tested for convexity */
    +  flagT    nonconvex:1; /* True if getmergeset detected a non-convex neighbor
    +                           only one ridge between neighbors may have nonconvex */
    +};
    +
    +/*----------------------------------
    +
    +  vertexT
    +     defines a vertex
    +
    +  topological information:
    +    next,previous       doubly-linked list of all vertices
    +    neighbors           set of adjacent facets (only if qh.VERTEXneighbors)
    +
    +  geometric information:
    +    point               array of DIM3 coordinates
    +*/
    +struct vertexT {
    +  vertexT *next;        /* next vertex in vertex_list */
    +  vertexT *previous;    /* previous vertex in vertex_list */
    +  pointT  *point;       /* hull_dim coordinates (coordT) */
    +  setT    *neighbors;   /* neighboring facets of vertex, qh_vertexneighbors()
    +                           inits in io.c or after first merge */
    +  unsigned id;          /* unique identifier.  Same size as qh.vertex_id and qh.ridge_id */
    +  unsigned visitid;     /* for use with qh.vertex_visit, size must match */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    seen2:1;     /* another seen flag */
    +  flagT    delridge:1;  /* vertex was part of a deleted ridge */
    +  flagT    deleted:1;   /* true if vertex on qh.del_vertices */
    +  flagT    newlist:1;   /* true if vertex on qh.newvertex_list */
    +};
    +
    +/*======= -global variables -qh ============================*/
    +
    +/*----------------------------------
    +
    +  qh
    +   all global variables for qhull are in qh, qhmem, and qhstat
    +
    +  notes:
    +   qhmem is defined in mem.h, qhstat is defined in stat.h, qhrbox is defined in rboxpoints.h
    +   Access to qh_qh is via the "qh" macro.  See qh_QHpointer in user.h
    +
    +   All global variables for qhull are in qh, qhmem, and qhstat
    +   qh must be unique for each instance of qhull
    +   qhstat may be shared between qhull instances.
    +   qhmem may be shared across multiple instances of Qhull.
    +   Rbox uses global variables rbox_inuse and rbox, but does not persist data across calls.
    +
    +   Qhull is not multi-threaded.  Global state could be stored in thread-local storage.
    +
    +   QHULL_LIB_CHECK checks that a program and the corresponding
    +   qhull library were built with the same type of header files.
    +*/
    +
    +typedef struct qhT qhT;
    +
    +#define QHULL_NON_REENTRANT 0
    +#define QHULL_QH_POINTER 1
    +#define QHULL_REENTRANT 2
    +
    +#if qh_QHpointer_dllimport
    +#define qh qh_qh->
    +__declspec(dllimport) extern qhT *qh_qh;     /* allocated in global.c */
    +#define QHULL_LIB_TYPE QHULL_QH_POINTER
    +
    +#elif qh_QHpointer
    +#define qh qh_qh->
    +extern qhT *qh_qh;     /* allocated in global.c */
    +#define QHULL_LIB_TYPE QHULL_QH_POINTER
    +
    +#elif qh_dllimport
    +#define qh qh_qh.
    +__declspec(dllimport) extern qhT qh_qh;      /* allocated in global.c */
    +#define QHULL_LIB_TYPE QHULL_NON_REENTRANT
    +
    +#else
    +#define qh qh_qh.
    +extern qhT qh_qh;
    +#define QHULL_LIB_TYPE QHULL_NON_REENTRANT
    +#endif
    +
    +#define QHULL_LIB_CHECK qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), sizeof(setT), sizeof(qhmemT));
    +#define QHULL_LIB_CHECK_RBOX qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), 0, 0);
    +
    +struct qhT {
    +
    +/*----------------------------------
    +
    +  qh constants
    +    configuration flags and constants for Qhull
    +
    +  notes:
    +    The user configures Qhull by defining flags.  They are
    +    copied into qh by qh_setflags().  qh-quick.htm#options defines the flags.
    +*/
    +  boolT ALLpoints;        /* true 'Qs' if search all points for initial simplex */
    +  boolT ANGLEmerge;       /* true 'Qa' if sort potential merges by angle */
    +  boolT APPROXhull;       /* true 'Wn' if MINoutside set */
    +  realT   MINoutside;     /*   'Wn' min. distance for an outside point */
    +  boolT ANNOTATEoutput;   /* true 'Ta' if annotate output with message codes */
    +  boolT ATinfinity;       /* true 'Qz' if point num_points-1 is "at-infinity"
    +                             for improving precision in Delaunay triangulations */
    +  boolT AVOIDold;         /* true 'Q4' if avoid old->new merges */
    +  boolT BESToutside;      /* true 'Qf' if partition points into best outsideset */
    +  boolT CDDinput;         /* true 'Pc' if input uses CDD format (1.0/offset first) */
    +  boolT CDDoutput;        /* true 'PC' if print normals in CDD format (offset first) */
    +  boolT CHECKfrequently;  /* true 'Tc' if checking frequently */
    +  realT premerge_cos;     /*   'A-n'   cos_max when pre merging */
    +  realT postmerge_cos;    /*   'An'    cos_max when post merging */
    +  boolT DELAUNAY;         /* true 'd' if computing DELAUNAY triangulation */
    +  boolT DOintersections;  /* true 'Gh' if print hyperplane intersections */
    +  int   DROPdim;          /* drops dim 'GDn' for 4-d -> 3-d output */
    +  boolT FORCEoutput;      /* true 'Po' if forcing output despite degeneracies */
    +  int   GOODpoint;        /* 1+n for 'QGn', good facet if visible/not(-) from point n*/
    +  pointT *GOODpointp;     /*   the actual point */
    +  boolT GOODthreshold;    /* true if qh.lower_threshold/upper_threshold defined
    +                             false if qh.SPLITthreshold */
    +  int   GOODvertex;       /* 1+n, good facet if vertex for point n */
    +  pointT *GOODvertexp;     /*   the actual point */
    +  boolT HALFspace;        /* true 'Hn,n,n' if halfspace intersection */
    +  boolT ISqhullQh;        /* Set by Qhull.cpp on initialization */
    +  int   IStracing;        /* trace execution, 0=none, 1=least, 4=most, -1=events */
    +  int   KEEParea;         /* 'PAn' number of largest facets to keep */
    +  boolT KEEPcoplanar;     /* true 'Qc' if keeping nearest facet for coplanar points */
    +  boolT KEEPinside;       /* true 'Qi' if keeping nearest facet for inside points
    +                              set automatically if 'd Qc' */
    +  int   KEEPmerge;        /* 'PMn' number of facets to keep with most merges */
    +  realT KEEPminArea;      /* 'PFn' minimum facet area to keep */
    +  realT MAXcoplanar;      /* 'Un' max distance below a facet to be coplanar*/
    +  boolT MERGEexact;       /* true 'Qx' if exact merges (coplanar, degen, dupridge, flipped) */
    +  boolT MERGEindependent; /* true 'Q2' if merging independent sets */
    +  boolT MERGING;          /* true if exact-, pre- or post-merging, with angle and centrum tests */
    +  realT   premerge_centrum;  /*   'C-n' centrum_radius when pre merging.  Default is round-off */
    +  realT   postmerge_centrum; /*   'Cn' centrum_radius when post merging.  Default is round-off */
    +  boolT MERGEvertices;    /* true 'Q3' if merging redundant vertices */
    +  realT MINvisible;       /* 'Vn' min. distance for a facet to be visible */
    +  boolT NOnarrow;         /* true 'Q10' if no special processing for narrow distributions */
    +  boolT NOnearinside;     /* true 'Q8' if ignore near-inside points when partitioning */
    +  boolT NOpremerge;       /* true 'Q0' if no defaults for C-0 or Qx */
    +  boolT NOwide;           /* true 'Q12' if no error on wide merge due to duplicate ridge */
    +  boolT ONLYgood;         /* true 'Qg' if process points with good visible or horizon facets */
    +  boolT ONLYmax;          /* true 'Qm' if only process points that increase max_outside */
    +  boolT PICKfurthest;     /* true 'Q9' if process furthest of furthest points*/
    +  boolT POSTmerge;        /* true if merging after buildhull (Cn or An) */
    +  boolT PREmerge;         /* true if merging during buildhull (C-n or A-n) */
    +                        /* NOTE: some of these names are similar to qh_PRINT names */
    +  boolT PRINTcentrums;    /* true 'Gc' if printing centrums */
    +  boolT PRINTcoplanar;    /* true 'Gp' if printing coplanar points */
    +  int   PRINTdim;         /* print dimension for Geomview output */
    +  boolT PRINTdots;        /* true 'Ga' if printing all points as dots */
    +  boolT PRINTgood;        /* true 'Pg' if printing good facets */
    +  boolT PRINTinner;       /* true 'Gi' if printing inner planes */
    +  boolT PRINTneighbors;   /* true 'PG' if printing neighbors of good facets */
    +  boolT PRINTnoplanes;    /* true 'Gn' if printing no planes */
    +  boolT PRINToptions1st;  /* true 'FO' if printing options to stderr */
    +  boolT PRINTouter;       /* true 'Go' if printing outer planes */
    +  boolT PRINTprecision;   /* false 'Pp' if not reporting precision problems */
    +  qh_PRINT PRINTout[qh_PRINTEND]; /* list of output formats to print */
    +  boolT PRINTridges;      /* true 'Gr' if print ridges */
    +  boolT PRINTspheres;     /* true 'Gv' if print vertices as spheres */
    +  boolT PRINTstatistics;  /* true 'Ts' if printing statistics to stderr */
    +  boolT PRINTsummary;     /* true 's' if printing summary to stderr */
    +  boolT PRINTtransparent; /* true 'Gt' if print transparent outer ridges */
    +  boolT PROJECTdelaunay;  /* true if DELAUNAY, no readpoints() and
    +                             need projectinput() for Delaunay in qh_init_B */
    +  int   PROJECTinput;     /* number of projected dimensions 'bn:0Bn:0' */
    +  boolT QUICKhelp;        /* true if quick help message for degen input */
    +  boolT RANDOMdist;       /* true if randomly change distplane and setfacetplane */
    +  realT RANDOMfactor;     /*    maximum random perturbation */
    +  realT RANDOMa;          /*    qh_randomfactor is randr * RANDOMa + RANDOMb */
    +  realT RANDOMb;
    +  boolT RANDOMoutside;    /* true if select a random outside point */
    +  int   REPORTfreq;       /* buildtracing reports every n facets */
    +  int   REPORTfreq2;      /* tracemerging reports every REPORTfreq/2 facets */
    +  int   RERUN;            /* 'TRn' rerun qhull n times (qh.build_cnt) */
    +  int   ROTATErandom;     /* 'QRn' seed, 0 time, >= rotate input */
    +  boolT SCALEinput;       /* true 'Qbk' if scaling input */
    +  boolT SCALElast;        /* true 'Qbb' if scale last coord to max prev coord */
    +  boolT SETroundoff;      /* true 'E' if qh.DISTround is predefined */
    +  boolT SKIPcheckmax;     /* true 'Q5' if skip qh_check_maxout */
    +  boolT SKIPconvex;       /* true 'Q6' if skip convexity testing during pre-merge */
    +  boolT SPLITthresholds;  /* true if upper_/lower_threshold defines a region
    +                               used only for printing (!for qh.ONLYgood) */
    +  int   STOPcone;         /* 'TCn' 1+n for stopping after cone for point n */
    +                          /*       also used by qh_build_withresart for err exit*/
    +  int   STOPpoint;        /* 'TVn' 'TV-n' 1+n for stopping after/before(-)
    +                                        adding point n */
    +  int   TESTpoints;       /* 'QTn' num of test points after qh.num_points.  Test points always coplanar. */
    +  boolT TESTvneighbors;   /*  true 'Qv' if test vertex neighbors at end */
    +  int   TRACElevel;       /* 'Tn' conditional IStracing level */
    +  int   TRACElastrun;     /*  qh.TRACElevel applies to last qh.RERUN */
    +  int   TRACEpoint;       /* 'TPn' start tracing when point n is a vertex */
    +  realT TRACEdist;        /* 'TWn' start tracing when merge distance too big */
    +  int   TRACEmerge;       /* 'TMn' start tracing before this merge */
    +  boolT TRIangulate;      /* true 'Qt' if triangulate non-simplicial facets */
    +  boolT TRInormals;       /* true 'Q11' if triangulate duplicates ->normal and ->center (sets Qt) */
    +  boolT UPPERdelaunay;    /* true 'Qu' if computing furthest-site Delaunay */
    +  boolT USEstdout;        /* true 'Tz' if using stdout instead of stderr */
    +  boolT VERIFYoutput;     /* true 'Tv' if verify output at end of qhull */
    +  boolT VIRTUALmemory;    /* true 'Q7' if depth-first processing in buildhull */
    +  boolT VORONOI;          /* true 'v' if computing Voronoi diagram */
    +
    +  /*--------input constants ---------*/
    +  realT AREAfactor;       /* 1/(hull_dim-1)! for converting det's to area */
    +  boolT DOcheckmax;       /* true if calling qh_check_maxout (qh_initqhull_globals) */
    +  char  *feasible_string;  /* feasible point 'Hn,n,n' for halfspace intersection */
    +  coordT *feasible_point;  /*    as coordinates, both malloc'd */
    +  boolT GETarea;          /* true 'Fa', 'FA', 'FS', 'PAn', 'PFn' if compute facet area/Voronoi volume in io.c */
    +  boolT KEEPnearinside;   /* true if near-inside points in coplanarset */
    +  int   hull_dim;         /* dimension of hull, set by initbuffers */
    +  int   input_dim;        /* dimension of input, set by initbuffers */
    +  int   num_points;       /* number of input points */
    +  pointT *first_point;    /* array of input points, see POINTSmalloc */
    +  boolT POINTSmalloc;     /*   true if qh.first_point/num_points allocated */
    +  pointT *input_points;   /* copy of original qh.first_point for input points for qh_joggleinput */
    +  boolT input_malloc;     /* true if qh.input_points malloc'd */
    +  char  qhull_command[256];/* command line that invoked this program */
    +  int   qhull_commandsiz2; /*    size of qhull_command at qh_clear_outputflags */
    +  char  rbox_command[256]; /* command line that produced the input points */
    +  char  qhull_options[512];/* descriptive list of options */
    +  int   qhull_optionlen;  /*    length of last line */
    +  int   qhull_optionsiz;  /*    size of qhull_options at qh_build_withrestart */
    +  int   qhull_optionsiz2; /*    size of qhull_options at qh_clear_outputflags */
    +  int   run_id;           /* non-zero, random identifier for this instance of qhull */
    +  boolT VERTEXneighbors;  /* true if maintaining vertex neighbors */
    +  boolT ZEROcentrum;      /* true if 'C-0' or 'C-0 Qx'.  sets ZEROall_ok */
    +  realT *upper_threshold; /* don't print if facet->normal[k]>=upper_threshold[k]
    +                             must set either GOODthreshold or SPLITthreshold
    +                             if Delaunay, default is 0.0 for upper envelope */
    +  realT *lower_threshold; /* don't print if facet->normal[k] <=lower_threshold[k] */
    +  realT *upper_bound;     /* scale point[k] to new upper bound */
    +  realT *lower_bound;     /* scale point[k] to new lower bound
    +                             project if both upper_ and lower_bound == 0 */
    +
    +/*----------------------------------
    +
    +  qh precision constants
    +    precision constants for Qhull
    +
    +  notes:
    +    qh_detroundoff() computes the maximum roundoff error for distance
    +    and other computations.  It also sets default values for the
    +    qh constants above.
    +*/
    +  realT ANGLEround;       /* max round off error for angles */
    +  realT centrum_radius;   /* max centrum radius for convexity (roundoff added) */
    +  realT cos_max;          /* max cosine for convexity (roundoff added) */
    +  realT DISTround;        /* max round off error for distances, 'E' overrides qh_distround() */
    +  realT MAXabs_coord;     /* max absolute coordinate */
    +  realT MAXlastcoord;     /* max last coordinate for qh_scalelast */
    +  realT MAXsumcoord;      /* max sum of coordinates */
    +  realT MAXwidth;         /* max rectilinear width of point coordinates */
    +  realT MINdenom_1;       /* min. abs. value for 1/x */
    +  realT MINdenom;         /*    use divzero if denominator < MINdenom */
    +  realT MINdenom_1_2;     /* min. abs. val for 1/x that allows normalization */
    +  realT MINdenom_2;       /*    use divzero if denominator < MINdenom_2 */
    +  realT MINlastcoord;     /* min. last coordinate for qh_scalelast */
    +  boolT NARROWhull;       /* set in qh_initialhull if angle < qh_MAXnarrow */
    +  realT *NEARzero;        /* hull_dim array for near zero in gausselim */
    +  realT NEARinside;       /* keep points for qh_check_maxout if close to facet */
    +  realT ONEmerge;         /* max distance for merging simplicial facets */
    +  realT outside_err;      /* application's epsilon for coplanar points
    +                             qh_check_bestdist() qh_check_points() reports error if point outside */
    +  realT WIDEfacet;        /* size of wide facet for skipping ridge in
    +                             area computation and locking centrum */
    +
    +/*----------------------------------
    +
    +  qh internal constants
    +    internal constants for Qhull
    +*/
    +  char qhull[sizeof("qhull")]; /* "qhull" for checking ownership while debugging */
    +  jmp_buf errexit;        /* exit label for qh_errexit, defined by setjmp() and NOerrexit */
    +  char jmpXtra[40];       /* extra bytes in case jmp_buf is defined wrong by compiler */
    +  jmp_buf restartexit;    /* restart label for qh_errexit, defined by setjmp() and ALLOWrestart */
    +  char jmpXtra2[40];      /* extra bytes in case jmp_buf is defined wrong by compiler*/
    +  FILE *fin;              /* pointer to input file, init by qh_initqhull_start2 */
    +  FILE *fout;             /* pointer to output file */
    +  FILE *ferr;             /* pointer to error file */
    +  pointT *interior_point; /* center point of the initial simplex*/
    +  int normal_size;     /* size in bytes for facet normals and point coords*/
    +  int center_size;     /* size in bytes for Voronoi centers */
    +  int   TEMPsize;         /* size for small, temporary sets (in quick mem) */
    +
    +/*----------------------------------
    +
    +  qh facet and vertex lists
    +    defines lists of facets, new facets, visible facets, vertices, and
    +    new vertices.  Includes counts, next ids, and trace ids.
    +  see:
    +    qh_resetlists()
    +*/
    +  facetT *facet_list;     /* first facet */
    +  facetT  *facet_tail;     /* end of facet_list (dummy facet) */
    +  facetT *facet_next;     /* next facet for buildhull()
    +                             previous facets do not have outside sets
    +                             NARROWhull: previous facets may have coplanar outside sets for qh_outcoplanar */
    +  facetT *newfacet_list;  /* list of new facets to end of facet_list */
    +  facetT *visible_list;   /* list of visible facets preceding newfacet_list,
    +                             facet->visible set */
    +  int       num_visible;  /* current number of visible facets */
    +  unsigned tracefacet_id;  /* set at init, then can print whenever */
    +  facetT *tracefacet;     /*   set in newfacet/mergefacet, undone in delfacet*/
    +  unsigned tracevertex_id;  /* set at buildtracing, can print whenever */
    +  vertexT *tracevertex;     /*   set in newvertex, undone in delvertex*/
    +  vertexT *vertex_list;     /* list of all vertices, to vertex_tail */
    +  vertexT  *vertex_tail;    /*      end of vertex_list (dummy vertex) */
    +  vertexT *newvertex_list; /* list of vertices in newfacet_list, to vertex_tail
    +                             all vertices have 'newlist' set */
    +  int   num_facets;       /* number of facets in facet_list
    +                             includes visible faces (num_visible) */
    +  int   num_vertices;     /* number of vertices in facet_list */
    +  int   num_outside;      /* number of points in outsidesets (for tracing and RANDOMoutside)
    +                               includes coplanar outsideset points for NARROWhull/qh_outcoplanar() */
    +  int   num_good;         /* number of good facets (after findgood_all) */
    +  unsigned facet_id;      /* ID of next, new facet from newfacet() */
    +  unsigned ridge_id;      /* ID of next, new ridge from newridge() */
    +  unsigned vertex_id;     /* ID of next, new vertex from newvertex() */
    +
    +/*----------------------------------
    +
    +  qh global variables
    +    defines minimum and maximum distances, next visit ids, several flags,
    +    and other global variables.
    +    initialize in qh_initbuild or qh_maxmin if used in qh_buildhull
    +*/
    +  unsigned long hulltime; /* ignore time to set up input and randomize */
    +                          /*   use unsigned to avoid wrap-around errors */
    +  boolT ALLOWrestart;     /* true if qh_precision can use qh.restartexit */
    +  int   build_cnt;        /* number of calls to qh_initbuild */
    +  qh_CENTER CENTERtype;   /* current type of facet->center, qh_CENTER */
    +  int   furthest_id;      /* pointid of furthest point, for tracing */
    +  facetT *GOODclosest;    /* closest facet to GOODthreshold in qh_findgood */
    +  boolT hasAreaVolume;    /* true if totarea, totvol was defined by qh_getarea */
    +  boolT hasTriangulation; /* true if triangulation created by qh_triangulate */
    +  realT JOGGLEmax;        /* set 'QJn' if randomly joggle input */
    +  boolT maxoutdone;       /* set qh_check_maxout(), cleared by qh_addpoint() */
    +  realT max_outside;      /* maximum distance from a point to a facet,
    +                               before roundoff, not simplicial vertices
    +                               actual outer plane is +DISTround and
    +                               computed outer plane is +2*DISTround */
    +  realT max_vertex;       /* maximum distance (>0) from vertex to a facet,
    +                               before roundoff, due to a merge */
    +  realT min_vertex;       /* minimum distance (<0) from vertex to a facet,
    +                               before roundoff, due to a merge
    +                               if qh.JOGGLEmax, qh_makenewplanes sets it
    +                               recomputed if qh.DOcheckmax, default -qh.DISTround */
    +  boolT NEWfacets;        /* true while visible facets invalid due to new or merge
    +                              from makecone/attachnewfacets to deletevisible */
    +  boolT findbestnew;      /* true if partitioning calls qh_findbestnew */
    +  boolT findbest_notsharp; /* true if new facets are at least 90 degrees */
    +  boolT NOerrexit;        /* true if qh.errexit is not available, cleared after setjmp */
    +  realT PRINTcradius;     /* radius for printing centrums */
    +  realT PRINTradius;      /* radius for printing vertex spheres and points */
    +  boolT POSTmerging;      /* true when post merging */
    +  int   printoutvar;      /* temporary variable for qh_printbegin, etc. */
    +  int   printoutnum;      /* number of facets printed */
    +  boolT QHULLfinished;    /* True after qhull() is finished */
    +  realT totarea;          /* 'FA': total facet area computed by qh_getarea, hasAreaVolume */
    +  realT totvol;           /* 'FA': total volume computed by qh_getarea, hasAreaVolume */
    +  unsigned int visit_id;  /* unique ID for searching neighborhoods, */
    +  unsigned int vertex_visit; /* unique ID for searching vertices, reset with qh_buildtracing */
    +  boolT ZEROall_ok;       /* True if qh_checkzero always succeeds */
    +  boolT WAScoplanar;      /* True if qh_partitioncoplanar (qh_check_maxout) */
    +
    +/*----------------------------------
    +
    +  qh global sets
    +    defines sets for merging, initial simplex, hashing, extra input points,
    +    and deleted vertices
    +*/
    +  setT *facet_mergeset;   /* temporary set of merges to be done */
    +  setT *degen_mergeset;   /* temporary set of degenerate and redundant merges */
    +  setT *hash_table;       /* hash table for matching ridges in qh_matchfacets
    +                             size is setsize() */
    +  setT *other_points;     /* additional points */
    +  setT *del_vertices;     /* vertices to partition and delete with visible
    +                             facets.  Have deleted set for checkfacet */
    +
    +/*----------------------------------
    +
    +  qh global buffers
    +    defines buffers for maxtrix operations, input, and error messages
    +*/
    +  coordT *gm_matrix;      /* (dim+1)Xdim matrix for geom.c */
    +  coordT **gm_row;        /* array of gm_matrix rows */
    +  char* line;             /* malloc'd input line of maxline+1 chars */
    +  int maxline;
    +  coordT *half_space;     /* malloc'd input array for halfspace (qh normal_size+coordT) */
    +  coordT *temp_malloc;    /* malloc'd input array for points */
    +
    +/*----------------------------------
    +
    +  qh static variables
    +    defines static variables for individual functions
    +
    +  notes:
    +    do not use 'static' within a function.  Multiple instances of qhull
    +    may exist.
    +
    +    do not assume zero initialization, 'QPn' may cause a restart
    +*/
    +  boolT ERREXITcalled;    /* true during qh_errexit (prevents duplicate calls */
    +  boolT firstcentrum;     /* for qh_printcentrum */
    +  boolT old_randomdist;   /* save RANDOMdist flag during io, tracing, or statistics */
    +  setT *coplanarfacetset;  /* set of coplanar facets for searching qh_findbesthorizon() */
    +  realT last_low;         /* qh_scalelast parameters for qh_setdelaunay */
    +  realT last_high;
    +  realT last_newhigh;
    +  unsigned lastreport;    /* for qh_buildtracing */
    +  int mergereport;        /* for qh_tracemerging */
    +  qhstatT *old_qhstat;    /* for saving qh_qhstat in save_qhull() and UsingLibQhull.  Free with qh_free() */
    +  setT *old_tempstack;    /* for saving qhmem.tempstack in save_qhull */
    +  int   ridgeoutnum;      /* number of ridges for 4OFF output (qh_printbegin,etc) */
    +};
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  otherfacet_(ridge, facet)
    +    return neighboring facet for a ridge in facet
    +*/
    +#define otherfacet_(ridge, facet) \
    +                        (((ridge)->top == (facet)) ? (ridge)->bottom : (ridge)->top)
    +
    +/*----------------------------------
    +
    +  getid_(p)
    +    return int ID for facet, ridge, or vertex
    +    return qh_IDunknown(-1) if NULL
    +*/
    +#define getid_(p)       ((p) ? (int)((p)->id) : qh_IDunknown)
    +
    +/*============== FORALL macros ===================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacets { ... }
    +    assign 'facet' to each facet in qh.facet_list
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +
    +  see:
    +    FORALLfacet_( facetlist )
    +*/
    +#define FORALLfacets for (facet=qh facet_list;facet && facet->next;facet=facet->next)
    +
    +/*----------------------------------
    +
    +  FORALLpoints { ... }
    +    assign 'point' to each point in qh.first_point, qh.num_points
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoints FORALLpoint_(qh first_point, qh num_points)
    +
    +/*----------------------------------
    +
    +  FORALLpoint_( points, num) { ... }
    +    assign 'point' to each point in points array of num points
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoint_(points, num) for (point= (points), \
    +      pointtemp= (points)+qh hull_dim*(num); point < pointtemp; point += qh hull_dim)
    +
    +/*----------------------------------
    +
    +  FORALLvertices { ... }
    +    assign 'vertex' to each vertex in qh.vertex_list
    +
    +  declare:
    +    vertexT *vertex;
    +
    +  notes:
    +    assumes qh.vertex_list terminated with a sentinel
    +*/
    +#define FORALLvertices for (vertex=qh vertex_list;vertex && vertex->next;vertex= vertex->next)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_( facets ) { ... }
    +    assign 'facet' to each facet in facets
    +
    +  declare:
    +    facetT *facet, **facetp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHfacet_(facets)    FOREACHsetelement_(facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_( facet ) { ... }
    +    assign 'neighbor' to each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_( vertex ) { ... }
    +    assign 'neighbor' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor, **neighborp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighbor_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_( points ) { ... }
    +    assign 'point' to each point in points set
    +
    +  declare:
    +    pointT *point, **pointp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHpoint_(points)    FOREACHsetelement_(pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_( ridges ) { ... }
    +    assign 'ridge' to each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge, **ridgep;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHridge_(ridges)    FOREACHsetelement_(ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex, **vertexp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHvertex_(vertices) FOREACHsetelement_(vertexT, vertices,vertex)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_i_( facets ) { ... }
    +    assign 'facet' and 'facet_i' for each facet in facets set
    +
    +  declare:
    +    facetT *facet;
    +    int     facet_n, facet_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHfacet_i_(facets)    FOREACHsetelement_i_(facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_i_( facet ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_i_( vertex ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor;
    +    int     neighbor_n, neighbor_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHneighbor_i_(facet)  FOREACHsetelement_i_(facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_i_( points ) { ... }
    +    assign 'point' and 'point_i' for each point in points set
    +
    +  declare:
    +    pointT *point;
    +    int     point_n, point_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHpoint_i_(points)    FOREACHsetelement_i_(pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_i_( ridges ) { ... }
    +    assign 'ridge' and 'ridge_i' for each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge;
    +    int     ridge_n, ridge_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHridge_i_(ridges)    FOREACHsetelement_i_(ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_i_( vertices ) { ... }
    +    assign 'vertex' and 'vertex_i' for each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex;
    +    int     vertex_n, vertex_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHvertex_i_(vertices) FOREACHsetelement_i_(vertexT, vertices,vertex)
    +
    +/********* -libqhull.c prototypes (duplicated from qhull_a.h) **********************/
    +
    +void    qh_qhull(void);
    +boolT   qh_addpoint(pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_printsummary(FILE *fp);
    +
    +/********* -user.c prototypes (alphabetical) **********************/
    +
    +void    qh_errexit(int exitcode, facetT *facet, ridgeT *ridge);
    +void    qh_errprint(const char* string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex);
    +int     qh_new_qhull(int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile);
    +void    qh_printfacetlist(facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhelp_degenerate(FILE *fp);
    +void    qh_printhelp_narrowhull(FILE *fp, realT minangle);
    +void    qh_printhelp_singular(FILE *fp);
    +void    qh_user_memsizes(void);
    +
    +/********* -usermem.c prototypes (alphabetical) **********************/
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/********* -userprintf.c and userprintf_rbox.c prototypes **********************/
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... );
    +
    +/***** -geom.c/geom2.c/random.c prototypes (duplicated from geom.h, random.h) ****************/
    +
    +facetT *qh_findbest(pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT newfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbestnew(pointT *point, facetT *startfacet,
    +                     realT *dist, boolT bestoutside, boolT *isoutside, int *numpart);
    +boolT   qh_gram_schmidt(int dim, realT **rows);
    +void    qh_outerinner(facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_printsummary(FILE *fp);
    +void    qh_projectinput(void);
    +void    qh_randommatrix(realT *buffer, int dim, realT **row);
    +void    qh_rotateinput(realT **rows);
    +void    qh_scaleinput(void);
    +void    qh_setdelaunay(int dim, int count, pointT *points);
    +coordT  *qh_sethalfspace_all(int dim, int count, coordT *halfspaces, pointT *feasible);
    +
    +/***** -global.c prototypes (alphabetical) ***********************/
    +
    +unsigned long qh_clock(void);
    +void    qh_checkflags(char *command, char *hiddenflags);
    +void    qh_clear_outputflags(void);
    +void    qh_freebuffers(void);
    +void    qh_freeqhull(boolT allmem);
    +void    qh_freeqhull2(boolT allmem);
    +void    qh_init_A(FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]);
    +void    qh_init_B(coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_init_qhull_command(int argc, char *argv[]);
    +void    qh_initbuffers(coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initflags(char *command);
    +void    qh_initqhull_buffers(void);
    +void    qh_initqhull_globals(coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initqhull_mem(void);
    +void    qh_initqhull_outputflags(void);
    +void    qh_initqhull_start(FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initqhull_start2(FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initthresholds(char *command);
    +void    qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize);
    +void    qh_option(const char *option, int *i, realT *r);
    +#if qh_QHpointer
    +void    qh_restore_qhull(qhT **oldqh);
    +qhT    *qh_save_qhull(void);
    +#endif
    +
    +/***** -io.c prototypes (duplicated from io.h) ***********************/
    +
    +void    qh_dfacet(unsigned id);
    +void    qh_dvertex(unsigned id);
    +void    qh_printneighborhood(FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_produce_output(void);
    +coordT *qh_readpoints(int *numpoints, int *dimension, boolT *ismalloc);
    +
    +
    +/********* -mem.c prototypes (duplicated from mem.h) **********************/
    +
    +void qh_meminit(FILE *ferr);
    +void qh_memfreeshort(int *curlong, int *totlong);
    +
    +/********* -poly.c/poly2.c prototypes (duplicated from poly.h) **********************/
    +
    +void    qh_check_output(void);
    +void    qh_check_points(void);
    +setT   *qh_facetvertices(facetT *facetlist, setT *facets, boolT allfacets);
    +facetT *qh_findbestfacet(pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +vertexT *qh_nearvertex(facetT *facet, pointT *point, realT *bestdistp);
    +pointT *qh_point(int id);
    +setT   *qh_pointfacet(void /*qh.facet_list*/);
    +int     qh_pointid(pointT *point);
    +setT   *qh_pointvertex(void /*qh.facet_list*/);
    +void    qh_setvoronoi_all(void);
    +void    qh_triangulate(void /*qh.facet_list*/);
    +
    +/********* -rboxlib.c prototypes **********************/
    +int     qh_rboxpoints(FILE* fout, FILE* ferr, char* rbox_command);
    +void    qh_errexit_rbox(int exitcode);
    +
    +/********* -stat.c prototypes (duplicated from stat.h) **********************/
    +
    +void    qh_collectstatistics(void);
    +void    qh_printallstatistics(FILE *fp, const char *string);
    +
    +#endif /* qhDEFlibqhull */
    diff --git a/xs/src/qhull/src/libqhull/libqhull.pro b/xs/src/qhull/src/libqhull/libqhull.pro
    new file mode 100644
    index 0000000000..18005da59d
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/libqhull.pro
    @@ -0,0 +1,67 @@
    +# -------------------------------------------------
    +# libqhull.pro -- Qt project for Qhull shared library
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +DLLDESTDIR = ../../bin
    +TEMPLATE = lib
    +CONFIG += shared warn_on
    +CONFIG -= qt
    +
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhull_d
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhull
    +    OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEF_FILE += ../../src/libqhull/qhull-exports.def
    +
    +# Order object files by frequency of execution.  Small files at end.
    +
    +# libqhull/libqhull.pro and ../qhull-libqhull-src.pri have the same SOURCES and HEADERS
    +SOURCES += ../libqhull/global.c
    +SOURCES += ../libqhull/stat.c
    +SOURCES += ../libqhull/geom2.c
    +SOURCES += ../libqhull/poly2.c
    +SOURCES += ../libqhull/merge.c
    +SOURCES += ../libqhull/libqhull.c
    +SOURCES += ../libqhull/geom.c
    +SOURCES += ../libqhull/poly.c
    +SOURCES += ../libqhull/qset.c
    +SOURCES += ../libqhull/mem.c
    +SOURCES += ../libqhull/random.c
    +SOURCES += ../libqhull/usermem.c
    +SOURCES += ../libqhull/userprintf.c
    +SOURCES += ../libqhull/io.c
    +SOURCES += ../libqhull/user.c
    +SOURCES += ../libqhull/rboxlib.c
    +SOURCES += ../libqhull/userprintf_rbox.c
    +
    +HEADERS += ../libqhull/geom.h
    +HEADERS += ../libqhull/io.h
    +HEADERS += ../libqhull/libqhull.h
    +HEADERS += ../libqhull/mem.h
    +HEADERS += ../libqhull/merge.h
    +HEADERS += ../libqhull/poly.h
    +HEADERS += ../libqhull/random.h
    +HEADERS += ../libqhull/qhull_a.h
    +HEADERS += ../libqhull/qset.h
    +HEADERS += ../libqhull/stat.h
    +HEADERS += ../libqhull/user.h
    +
    +OTHER_FILES += Mborland
    +OTHER_FILES += qh-geom.htm
    +OTHER_FILES += qh-globa.htm
    +OTHER_FILES += qh-io.htm
    +OTHER_FILES += qh-mem.htm
    +OTHER_FILES += qh-merge.htm
    +OTHER_FILES += qh-poly.htm
    +OTHER_FILES += qh-qhull.htm
    +OTHER_FILES += qh-set.htm
    +OTHER_FILES += qh-stat.htm
    +OTHER_FILES += qh-user.htm
    diff --git a/xs/src/qhull/src/libqhull/mem.c b/xs/src/qhull/src/libqhull/mem.c
    new file mode 100644
    index 0000000000..db72bb4e19
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/mem.c
    @@ -0,0 +1,576 @@
    +/*
      ---------------------------------
    +
    +  mem.c
    +    memory management routines for qhull
    +
    +  This is a standalone program.
    +
    +  To initialize memory:
    +
    +    qh_meminit(stderr);
    +    qh_meminitbuffers(qh IStracing, qh_MEMalign, 7, qh_MEMbufsize,qh_MEMinitbuf);
    +    qh_memsize((int)sizeof(facetT));
    +    qh_memsize((int)sizeof(facetT));
    +    ...
    +    qh_memsetup();
    +
    +  To free up all memory buffers:
    +    qh_memfreeshort(&curlong, &totlong);
    +
    +  if qh_NOmem,
    +    malloc/free is used instead of mem.c
    +
    +  notes:
    +    uses Quickfit algorithm (freelists for commonly allocated sizes)
    +    assumes small sizes for freelists (it discards the tail of memory buffers)
    +
    +  see:
    +    qh-mem.htm and mem.h
    +    global.c (qh_initbuffers) for an example of using mem.c
    +
    +  Copyright (c) 1993-2015 The Geometry Center.
    +  $Id: //main/2015/qhull/src/libqhull/mem.c#7 $$Change: 2065 $
    +  $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +*/
    +
    +#include "user.h"  /* for QHULL_CRTDBG */
    +#include "mem.h"
    +#include 
    +#include 
    +#include 
    +
    +#ifndef qhDEFlibqhull
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4127)  /* conditional expression is constant */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#endif
    +void    qh_errexit(int exitcode, facetT *, ridgeT *);
    +void    qh_exit(int exitcode);
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +#endif
    +
    +/*============ -global data structure ==============
    +    see mem.h for definition
    +*/
    +
    +qhmemT qhmem= {0,0,0,0,0,0,0,0,0,0,0,
    +               0,0,0,0,0,0,0,0,0,0,0,
    +               0,0,0,0,0,0,0};     /* remove "= {0}" if this causes a compiler error */
    +
    +#ifndef qh_NOmem
    +
    +/*============= internal functions ==============*/
    +
    +static int qh_intcompare(const void *i, const void *j);
    +
    +/*========== functions in alphabetical order ======== */
    +
    +/*---------------------------------
    +
    +  qh_intcompare( i, j )
    +    used by qsort and bsearch to compare two integers
    +*/
    +static int qh_intcompare(const void *i, const void *j) {
    +  return(*((const int *)i) - *((const int *)j));
    +} /* intcompare */
    +
    +
    +/*----------------------------------
    +
    +  qh_memalloc( insize )
    +    returns object of insize bytes
    +    qhmem is the global memory structure
    +
    +  returns:
    +    pointer to allocated memory
    +    errors if insufficient memory
    +
    +  notes:
    +    use explicit type conversion to avoid type warnings on some compilers
    +    actual object may be larger than insize
    +    use qh_memalloc_() for inline code for quick allocations
    +    logs allocations if 'T5'
    +    caller is responsible for freeing the memory.
    +    short memory is freed on shutdown by qh_memfreeshort unless qh_NOmem
    +
    +  design:
    +    if size < qhmem.LASTsize
    +      if qhmem.freelists[size] non-empty
    +        return first object on freelist
    +      else
    +        round up request to size of qhmem.freelists[size]
    +        allocate new allocation buffer if necessary
    +        allocate object from allocation buffer
    +    else
    +      allocate object with qh_malloc() in user.c
    +*/
    +void *qh_memalloc(int insize) {
    +  void **freelistp, *newbuffer;
    +  int idx, size, n;
    +  int outsize, bufsize;
    +  void *object;
    +
    +  if (insize<0) {
    +      qh_fprintf(qhmem.ferr, 6235, "qhull error (qh_memalloc): negative request size (%d).  Did int overflow due to high-D?\n", insize); /* WARN64 */
    +      qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (insize>=0 && insize <= qhmem.LASTsize) {
    +    idx= qhmem.indextable[insize];
    +    outsize= qhmem.sizetable[idx];
    +    qhmem.totshort += outsize;
    +    freelistp= qhmem.freelists+idx;
    +    if ((object= *freelistp)) {
    +      qhmem.cntquick++;
    +      qhmem.totfree -= outsize;
    +      *freelistp= *((void **)*freelistp);  /* replace freelist with next object */
    +#ifdef qh_TRACEshort
    +      n= qhmem.cntshort+qhmem.cntquick+qhmem.freeshort;
    +      if (qhmem.IStracing >= 5)
    +          qh_fprintf(qhmem.ferr, 8141, "qh_mem %p n %8d alloc quick: %d bytes (tot %d cnt %d)\n", object, n, outsize, qhmem.totshort, qhmem.cntshort+qhmem.cntquick-qhmem.freeshort);
    +#endif
    +      return(object);
    +    }else {
    +      qhmem.cntshort++;
    +      if (outsize > qhmem.freesize) {
    +        qhmem.totdropped += qhmem.freesize;
    +        if (!qhmem.curbuffer)
    +          bufsize= qhmem.BUFinit;
    +        else
    +          bufsize= qhmem.BUFsize;
    +        if (!(newbuffer= qh_malloc((size_t)bufsize))) {
    +          qh_fprintf(qhmem.ferr, 6080, "qhull error (qh_memalloc): insufficient memory to allocate short memory buffer (%d bytes)\n", bufsize);
    +          qh_errexit(qhmem_ERRmem, NULL, NULL);
    +        }
    +        *((void **)newbuffer)= qhmem.curbuffer;  /* prepend newbuffer to curbuffer
    +                                                    list.  newbuffer!=0 by QH6080 */
    +        qhmem.curbuffer= newbuffer;
    +        size= (sizeof(void **) + qhmem.ALIGNmask) & ~qhmem.ALIGNmask;
    +        qhmem.freemem= (void *)((char *)newbuffer+size);
    +        qhmem.freesize= bufsize - size;
    +        qhmem.totbuffer += bufsize - size; /* easier to check */
    +        /* Periodically test totbuffer.  It matches at beginning and exit of every call */
    +        n = qhmem.totshort + qhmem.totfree + qhmem.totdropped + qhmem.freesize - outsize;
    +        if (qhmem.totbuffer != n) {
    +            qh_fprintf(qhmem.ferr, 6212, "qh_memalloc internal error: short totbuffer %d != totshort+totfree... %d\n", qhmem.totbuffer, n);
    +            qh_errexit(qhmem_ERRmem, NULL, NULL);
    +        }
    +      }
    +      object= qhmem.freemem;
    +      qhmem.freemem= (void *)((char *)qhmem.freemem + outsize);
    +      qhmem.freesize -= outsize;
    +      qhmem.totunused += outsize - insize;
    +#ifdef qh_TRACEshort
    +      n= qhmem.cntshort+qhmem.cntquick+qhmem.freeshort;
    +      if (qhmem.IStracing >= 5)
    +          qh_fprintf(qhmem.ferr, 8140, "qh_mem %p n %8d alloc short: %d bytes (tot %d cnt %d)\n", object, n, outsize, qhmem.totshort, qhmem.cntshort+qhmem.cntquick-qhmem.freeshort);
    +#endif
    +      return object;
    +    }
    +  }else {                     /* long allocation */
    +    if (!qhmem.indextable) {
    +      qh_fprintf(qhmem.ferr, 6081, "qhull internal error (qh_memalloc): qhmem has not been initialized.\n");
    +      qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +    }
    +    outsize= insize;
    +    qhmem.cntlong++;
    +    qhmem.totlong += outsize;
    +    if (qhmem.maxlong < qhmem.totlong)
    +      qhmem.maxlong= qhmem.totlong;
    +    if (!(object= qh_malloc((size_t)outsize))) {
    +      qh_fprintf(qhmem.ferr, 6082, "qhull error (qh_memalloc): insufficient memory to allocate %d bytes\n", outsize);
    +      qh_errexit(qhmem_ERRmem, NULL, NULL);
    +    }
    +    if (qhmem.IStracing >= 5)
    +      qh_fprintf(qhmem.ferr, 8057, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, outsize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +  }
    +  return(object);
    +} /* memalloc */
    +
    +
    +/*----------------------------------
    +
    +  qh_memcheck( )
    +*/
    +void qh_memcheck(void) {
    +  int i, count, totfree= 0;
    +  void *object;
    +
    +  if (qhmem.ferr == 0 || qhmem.IStracing < 0 || qhmem.IStracing > 10 || (((qhmem.ALIGNmask+1) & qhmem.ALIGNmask) != 0)) {
    +    qh_fprintf_stderr(6244, "qh_memcheck error: either qhmem is overwritten or qhmem is not initialized.  Call qh_meminit() or qh_new_qhull() before calling qh_mem routines.  ferr 0x%x IsTracing %d ALIGNmask 0x%x", qhmem.ferr, qhmem.IStracing, qhmem.ALIGNmask);
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (qhmem.IStracing != 0)
    +    qh_fprintf(qhmem.ferr, 8143, "qh_memcheck: check size of freelists on qhmem\nqh_memcheck: A segmentation fault indicates an overwrite of qhmem\n");
    +  for (i=0; i < qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    totfree += qhmem.sizetable[i] * count;
    +  }
    +  if (totfree != qhmem.totfree) {
    +    qh_fprintf(qhmem.ferr, 6211, "Qhull internal error (qh_memcheck): totfree %d not equal to freelist total %d\n", qhmem.totfree, totfree);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qhmem.IStracing != 0)
    +    qh_fprintf(qhmem.ferr, 8144, "qh_memcheck: total size of freelists totfree is the same as qhmem.totfree\n", totfree);
    +} /* memcheck */
    +
    +/*----------------------------------
    +
    +  qh_memfree( object, insize )
    +    free up an object of size bytes
    +    size is insize from qh_memalloc
    +
    +  notes:
    +    object may be NULL
    +    type checking warns if using (void **)object
    +    use qh_memfree_() for quick free's of small objects
    +
    +  design:
    +    if size <= qhmem.LASTsize
    +      append object to corresponding freelist
    +    else
    +      call qh_free(object)
    +*/
    +void qh_memfree(void *object, int insize) {
    +  void **freelistp;
    +  int idx, outsize;
    +
    +  if (!object)
    +    return;
    +  if (insize <= qhmem.LASTsize) {
    +    qhmem.freeshort++;
    +    idx= qhmem.indextable[insize];
    +    outsize= qhmem.sizetable[idx];
    +    qhmem.totfree += outsize;
    +    qhmem.totshort -= outsize;
    +    freelistp= qhmem.freelists + idx;
    +    *((void **)object)= *freelistp;
    +    *freelistp= object;
    +#ifdef qh_TRACEshort
    +    idx= qhmem.cntshort+qhmem.cntquick+qhmem.freeshort;
    +    if (qhmem.IStracing >= 5)
    +        qh_fprintf(qhmem.ferr, 8142, "qh_mem %p n %8d free short: %d bytes (tot %d cnt %d)\n", object, idx, outsize, qhmem.totshort, qhmem.cntshort+qhmem.cntquick-qhmem.freeshort);
    +#endif
    +  }else {
    +    qhmem.freelong++;
    +    qhmem.totlong -= insize;
    +    if (qhmem.IStracing >= 5)
    +      qh_fprintf(qhmem.ferr, 8058, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, insize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +    qh_free(object);
    +  }
    +} /* memfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_memfreeshort( curlong, totlong )
    +    frees up all short and qhmem memory allocations
    +
    +  returns:
    +    number and size of current long allocations
    +
    +  see:
    +    qh_freeqhull(allMem)
    +    qh_memtotal(curlong, totlong, curshort, totshort, maxlong, totbuffer);
    +*/
    +void qh_memfreeshort(int *curlong, int *totlong) {
    +  void *buffer, *nextbuffer;
    +  FILE *ferr;
    +
    +  *curlong= qhmem.cntlong - qhmem.freelong;
    +  *totlong= qhmem.totlong;
    +  for (buffer= qhmem.curbuffer; buffer; buffer= nextbuffer) {
    +    nextbuffer= *((void **) buffer);
    +    qh_free(buffer);
    +  }
    +  qhmem.curbuffer= NULL;
    +  if (qhmem.LASTsize) {
    +    qh_free(qhmem.indextable);
    +    qh_free(qhmem.freelists);
    +    qh_free(qhmem.sizetable);
    +  }
    +  ferr= qhmem.ferr;
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +  qhmem.ferr= ferr;
    +} /* memfreeshort */
    +
    +
    +/*----------------------------------
    +
    +  qh_meminit( ferr )
    +    initialize qhmem and test sizeof( void*)
    +    Does not throw errors.  qh_exit on failure
    +*/
    +void qh_meminit(FILE *ferr) {
    +
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +    qhmem.ferr= ferr;
    +  else
    +    qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qhmem.ferr, 6083, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (sizeof(void*) > sizeof(ptr_intT)) {
    +      qh_fprintf(qhmem.ferr, 6084, "qhull internal error (qh_meminit): sizeof(void*) %d > sizeof(ptr_intT) %d. Change ptr_intT in mem.h to 'long long'\n", (int)sizeof(void*), (int)sizeof(ptr_intT));
    +      qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qh_memcheck();
    +} /* meminit */
    +
    +/*---------------------------------
    +
    +  qh_meminitbuffers( tracelevel, alignment, numsizes, bufsize, bufinit )
    +    initialize qhmem
    +    if tracelevel >= 5, trace memory allocations
    +    alignment= desired address alignment for memory allocations
    +    numsizes= number of freelists
    +    bufsize=  size of additional memory buffers for short allocations
    +    bufinit=  size of initial memory buffer for short allocations
    +*/
    +void qh_meminitbuffers(int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qhmem.IStracing= tracelevel;
    +  qhmem.NUMsizes= numsizes;
    +  qhmem.BUFsize= bufsize;
    +  qhmem.BUFinit= bufinit;
    +  qhmem.ALIGNmask= alignment-1;
    +  if (qhmem.ALIGNmask & ~qhmem.ALIGNmask) {
    +    qh_fprintf(qhmem.ferr, 6085, "qhull internal error (qh_meminit): memory alignment %d is not a power of 2\n", alignment);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qhmem.sizetable= (int *) calloc((size_t)numsizes, sizeof(int));
    +  qhmem.freelists= (void **) calloc((size_t)numsizes, sizeof(void *));
    +  if (!qhmem.sizetable || !qhmem.freelists) {
    +    qh_fprintf(qhmem.ferr, 6086, "qhull error (qh_meminit): insufficient memory\n");
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (qhmem.IStracing >= 1)
    +    qh_fprintf(qhmem.ferr, 8059, "qh_meminitbuffers: memory initialized with alignment %d\n", alignment);
    +} /* meminitbuffers */
    +
    +/*---------------------------------
    +
    +  qh_memsetup()
    +    set up memory after running memsize()
    +*/
    +void qh_memsetup(void) {
    +  int k,i;
    +
    +  qsort(qhmem.sizetable, (size_t)qhmem.TABLEsize, sizeof(int), qh_intcompare);
    +  qhmem.LASTsize= qhmem.sizetable[qhmem.TABLEsize-1];
    +  if (qhmem.LASTsize >= qhmem.BUFsize || qhmem.LASTsize >= qhmem.BUFinit) {
    +    qh_fprintf(qhmem.ferr, 6087, "qhull error (qh_memsetup): largest mem size %d is >= buffer size %d or initial buffer size %d\n",
    +            qhmem.LASTsize, qhmem.BUFsize, qhmem.BUFinit);
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (!(qhmem.indextable= (int *)qh_malloc((qhmem.LASTsize+1) * sizeof(int)))) {
    +    qh_fprintf(qhmem.ferr, 6088, "qhull error (qh_memsetup): insufficient memory\n");
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  for (k=qhmem.LASTsize+1; k--; )
    +    qhmem.indextable[k]= k;
    +  i= 0;
    +  for (k=0; k <= qhmem.LASTsize; k++) {
    +    if (qhmem.indextable[k] <= qhmem.sizetable[i])
    +      qhmem.indextable[k]= i;
    +    else
    +      qhmem.indextable[k]= ++i;
    +  }
    +} /* memsetup */
    +
    +/*---------------------------------
    +
    +  qh_memsize( size )
    +    define a free list for this size
    +*/
    +void qh_memsize(int size) {
    +  int k;
    +
    +  if (qhmem.LASTsize) {
    +    qh_fprintf(qhmem.ferr, 6089, "qhull error (qh_memsize): called after qhmem_setup\n");
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  size= (size + qhmem.ALIGNmask) & ~qhmem.ALIGNmask;
    +  for (k=qhmem.TABLEsize; k--; ) {
    +    if (qhmem.sizetable[k] == size)
    +      return;
    +  }
    +  if (qhmem.TABLEsize < qhmem.NUMsizes)
    +    qhmem.sizetable[qhmem.TABLEsize++]= size;
    +  else
    +    qh_fprintf(qhmem.ferr, 7060, "qhull warning (memsize): free list table has room for only %d sizes\n", qhmem.NUMsizes);
    +} /* memsize */
    +
    +
    +/*---------------------------------
    +
    +  qh_memstatistics( fp )
    +    print out memory statistics
    +
    +    Verifies that qhmem.totfree == sum of freelists
    +*/
    +void qh_memstatistics(FILE *fp) {
    +  int i;
    +  int count;
    +  void *object;
    +
    +  qh_memcheck();
    +  qh_fprintf(fp, 9278, "\nmemory statistics:\n\
    +%7d quick allocations\n\
    +%7d short allocations\n\
    +%7d long allocations\n\
    +%7d short frees\n\
    +%7d long frees\n\
    +%7d bytes of short memory in use\n\
    +%7d bytes of short memory in freelists\n\
    +%7d bytes of dropped short memory\n\
    +%7d bytes of unused short memory (estimated)\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n\
    +%7d bytes of short memory buffers (minus links)\n\
    +%7d bytes per short memory buffer (initially %d bytes)\n",
    +           qhmem.cntquick, qhmem.cntshort, qhmem.cntlong,
    +           qhmem.freeshort, qhmem.freelong,
    +           qhmem.totshort, qhmem.totfree,
    +           qhmem.totdropped + qhmem.freesize, qhmem.totunused,
    +           qhmem.maxlong, qhmem.totlong, qhmem.cntlong - qhmem.freelong,
    +           qhmem.totbuffer, qhmem.BUFsize, qhmem.BUFinit);
    +  if (qhmem.cntlarger) {
    +    qh_fprintf(fp, 9279, "%7d calls to qh_setlarger\n%7.2g     average copy size\n",
    +           qhmem.cntlarger, ((float)qhmem.totlarger)/(float)qhmem.cntlarger);
    +    qh_fprintf(fp, 9280, "  freelists(bytes->count):");
    +  }
    +  for (i=0; i < qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    qh_fprintf(fp, 9281, " %d->%d", qhmem.sizetable[i], count);
    +  }
    +  qh_fprintf(fp, 9282, "\n\n");
    +} /* memstatistics */
    +
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    uses qh_malloc() and qh_free() instead
    +*/
    +#else /* qh_NOmem */
    +
    +void *qh_memalloc(int insize) {
    +  void *object;
    +
    +  if (!(object= qh_malloc((size_t)insize))) {
    +    qh_fprintf(qhmem.ferr, 6090, "qhull error (qh_memalloc): insufficient memory\n");
    +    qh_errexit(qhmem_ERRmem, NULL, NULL);
    +  }
    +  qhmem.cntlong++;
    +  qhmem.totlong += insize;
    +  if (qhmem.maxlong < qhmem.totlong)
    +      qhmem.maxlong= qhmem.totlong;
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8060, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, insize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +  return object;
    +}
    +
    +void qh_memfree(void *object, int insize) {
    +
    +  if (!object)
    +    return;
    +  qh_free(object);
    +  qhmem.freelong++;
    +  qhmem.totlong -= insize;
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8061, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qhmem.cntlong+qhmem.freelong, insize, qhmem.totlong, qhmem.cntlong-qhmem.freelong);
    +}
    +
    +void qh_memfreeshort(int *curlong, int *totlong) {
    +  *totlong= qhmem.totlong;
    +  *curlong= qhmem.cntlong - qhmem.freelong;
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +}
    +
    +void qh_meminit(FILE *ferr) {
    +
    +  memset((char *)&qhmem, 0, sizeof(qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +      qhmem.ferr= ferr;
    +  else
    +      qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qhmem.ferr, 6091, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +}
    +
    +void qh_meminitbuffers(int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qhmem.IStracing= tracelevel;
    +}
    +
    +void qh_memsetup(void) {
    +
    +}
    +
    +void qh_memsize(int size) {
    +
    +}
    +
    +void qh_memstatistics(FILE *fp) {
    +
    +  qh_fprintf(fp, 9409, "\nmemory statistics:\n\
    +%7d long allocations\n\
    +%7d long frees\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n",
    +           qhmem.cntlong,
    +           qhmem.freelong,
    +           qhmem.maxlong, qhmem.totlong, qhmem.cntlong - qhmem.freelong);
    +}
    +
    +#endif /* qh_NOmem */
    +
    +/*---------------------------------
    +
    +  qh_memtotal( totlong, curlong, totshort, curshort, maxlong, totbuffer )
    +    Return the total, allocated long and short memory
    +
    +  returns:
    +    Returns the total current bytes of long and short allocations
    +    Returns the current count of long and short allocations
    +    Returns the maximum long memory and total short buffer (minus one link per buffer)
    +    Does not error (UsingLibQhull.cpp)
    +*/
    +void qh_memtotal(int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer) {
    +    *totlong= qhmem.totlong;
    +    *curlong= qhmem.cntlong - qhmem.freelong;
    +    *totshort= qhmem.totshort;
    +    *curshort= qhmem.cntshort + qhmem.cntquick - qhmem.freeshort;
    +    *maxlong= qhmem.maxlong;
    +    *totbuffer= qhmem.totbuffer;
    +} /* memtotlong */
    +
    diff --git a/xs/src/qhull/src/libqhull/mem.h b/xs/src/qhull/src/libqhull/mem.h
    new file mode 100644
    index 0000000000..453f319df3
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/mem.h
    @@ -0,0 +1,222 @@
    +/*
      ---------------------------------
    +
    +   mem.h
    +     prototypes for memory management functions
    +
    +   see qh-mem.htm, mem.c and qset.h
    +
    +   for error handling, writes message and calls
    +     qh_errexit(qhmem_ERRmem, NULL, NULL) if insufficient memory
    +       and
    +     qh_errexit(qhmem_ERRqhull, NULL, NULL) otherwise
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/mem.h#2 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmem
    +#define qhDEFmem 1
    +
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    mem.c implements Quickfit memory allocation for about 20% time
    +    savings.  If it fails on your machine, try to locate the
    +    problem, and send the answer to qhull@qhull.org.  If this can
    +    not be done, define qh_NOmem to use malloc/free instead.
    +
    +   #define qh_NOmem
    +*/
    +
    +/*---------------------------------
    +
    +qh_TRACEshort
    +Trace short and quick memory allocations at T5
    +
    +*/
    +#define qh_TRACEshort
    +
    +/*-------------------------------------------
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.  If gcc is available,
    +    use __alignof__(double) or fmax_(__alignof__(float), __alignof__(void *)).
    +
    +   see qh_MEMalign in user.h for qhull's alignment
    +*/
    +
    +#define qhmem_ERRmem 4    /* matches qh_ERRmem in libqhull.h */
    +#define qhmem_ERRqhull 5  /* matches qh_ERRqhull in libqhull.h */
    +
    +/*----------------------------------
    +
    +  ptr_intT
    +    for casting a void * to an integer-type that holds a pointer
    +    Used for integer expressions (e.g., computing qh_gethash() in poly.c)
    +
    +  notes:
    +    WARN64 -- these notes indicate 64-bit issues
    +    On 64-bit machines, a pointer may be larger than an 'int'.
    +    qh_meminit()/mem.c checks that 'ptr_intT' holds a 'void*'
    +    ptr_intT is typically a signed value, but not necessarily so
    +    size_t is typically unsigned, but should match the parameter type
    +    Qhull uses int instead of size_t except for system calls such as malloc, qsort, qh_malloc, etc.
    +    This matches Qt convention and is easier to work with.
    +*/
    +#if (defined(__MINGW64__)) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#elif (_MSC_VER) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#else
    +typedef long ptr_intT;
    +#endif
    +
    +/*----------------------------------
    +
    +  qhmemT
    +    global memory structure for mem.c
    +
    + notes:
    +   users should ignore qhmem except for writing extensions
    +   qhmem is allocated in mem.c
    +
    +   qhmem could be swapable like qh and qhstat, but then
    +   multiple qh's and qhmem's would need to keep in synch.
    +   A swapable qhmem would also waste memory buffers.  As long
    +   as memory operations are atomic, there is no problem with
    +   multiple qh structures being active at the same time.
    +   If you need separate address spaces, you can swap the
    +   contents of qhmem.
    +*/
    +typedef struct qhmemT qhmemT;
    +extern qhmemT qhmem;
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset.h */
    +#endif
    +
    +/* Update qhmem in mem.c if add or remove fields */
    +struct qhmemT {               /* global memory management variables */
    +  int      BUFsize;           /* size of memory allocation buffer */
    +  int      BUFinit;           /* initial size of memory allocation buffer */
    +  int      TABLEsize;         /* actual number of sizes in free list table */
    +  int      NUMsizes;          /* maximum number of sizes in free list table */
    +  int      LASTsize;          /* last size in free list table */
    +  int      ALIGNmask;         /* worst-case alignment, must be 2^n-1 */
    +  void   **freelists;          /* free list table, linked by offset 0 */
    +  int     *sizetable;         /* size of each freelist */
    +  int     *indextable;        /* size->index table */
    +  void    *curbuffer;         /* current buffer, linked by offset 0 */
    +  void    *freemem;           /*   free memory in curbuffer */
    +  int      freesize;          /*   size of freemem in bytes */
    +  setT    *tempstack;         /* stack of temporary memory, managed by users */
    +  FILE    *ferr;              /* file for reporting errors when 'qh' may be undefined */
    +  int      IStracing;         /* =5 if tracing memory allocations */
    +  int      cntquick;          /* count of quick allocations */
    +                              /* Note: removing statistics doesn't effect speed */
    +  int      cntshort;          /* count of short allocations */
    +  int      cntlong;           /* count of long allocations */
    +  int      freeshort;         /* count of short memfrees */
    +  int      freelong;          /* count of long memfrees */
    +  int      totbuffer;         /* total short memory buffers minus buffer links */
    +  int      totdropped;        /* total dropped memory at end of short memory buffers (e.g., freesize) */
    +  int      totfree;           /* total size of free, short memory on freelists */
    +  int      totlong;           /* total size of long memory in use */
    +  int      maxlong;           /*   maximum totlong */
    +  int      totshort;          /* total size of short memory in use */
    +  int      totunused;         /* total unused short memory (estimated, short size - request size of first allocations) */
    +  int      cntlarger;         /* count of setlarger's */
    +  int      totlarger;         /* total copied by setlarger */
    +};
    +
    +
    +/*==================== -macros ====================*/
    +
    +/*----------------------------------
    +
    +  qh_memalloc_(insize, freelistp, object, type)
    +    returns object of size bytes
    +        assumes size<=qhmem.LASTsize and void **freelistp is a temp
    +*/
    +
    +#if defined qh_NOmem
    +#define qh_memalloc_(insize, freelistp, object, type) {\
    +  object= (type*)qh_memalloc(insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memalloc_(insize, freelistp, object, type) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    object= (type*)qh_memalloc(insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memalloc_(insize, freelistp, object, type) {\
    +  freelistp= qhmem.freelists + qhmem.indextable[insize];\
    +  if ((object= (type*)*freelistp)) {\
    +    qhmem.totshort += qhmem.sizetable[qhmem.indextable[insize]]; \
    +    qhmem.totfree -= qhmem.sizetable[qhmem.indextable[insize]]; \
    +    qhmem.cntquick++;  \
    +    *freelistp= *((void **)*freelistp);\
    +  }else object= (type*)qh_memalloc(insize);}
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_memfree_(object, insize, freelistp)
    +    free up an object
    +
    +  notes:
    +    object may be NULL
    +    assumes size<=qhmem.LASTsize and void **freelistp is a temp
    +*/
    +#if defined qh_NOmem
    +#define qh_memfree_(object, insize, freelistp) {\
    +  qh_memfree(object, insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memfree_(object, insize, freelistp) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    qh_memfree(object, insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memfree_(object, insize, freelistp) {\
    +  if (object) { \
    +    qhmem.freeshort++;\
    +    freelistp= qhmem.freelists + qhmem.indextable[insize];\
    +    qhmem.totshort -= qhmem.sizetable[qhmem.indextable[insize]]; \
    +    qhmem.totfree += qhmem.sizetable[qhmem.indextable[insize]]; \
    +    *((void **)object)= *freelistp;\
    +    *freelistp= object;}}
    +#endif
    +
    +/*=============== prototypes in alphabetical order ============*/
    +
    +void *qh_memalloc(int insize);
    +void qh_memcheck(void);
    +void qh_memfree(void *object, int insize);
    +void qh_memfreeshort(int *curlong, int *totlong);
    +void qh_meminit(FILE *ferr);
    +void qh_meminitbuffers(int tracelevel, int alignment, int numsizes,
    +                        int bufsize, int bufinit);
    +void qh_memsetup(void);
    +void qh_memsize(int size);
    +void qh_memstatistics(FILE *fp);
    +void qh_memtotal(int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer);
    +
    +#endif /* qhDEFmem */
    diff --git a/xs/src/qhull/src/libqhull/merge.c b/xs/src/qhull/src/libqhull/merge.c
    new file mode 100644
    index 0000000000..22104dc031
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/merge.c
    @@ -0,0 +1,3628 @@
    +/*
      ---------------------------------
    +
    +   merge.c
    +   merges non-convex facets
    +
    +   see qh-merge.htm and merge.h
    +
    +   other modules call qh_premerge() and qh_postmerge()
    +
    +   the user may call qh_postmerge() to perform additional merges.
    +
    +   To remove deleted facets and vertices (qhull() in libqhull.c):
    +     qh_partitionvisible(!qh_ALL, &numoutside);  // visible_list, newfacet_list
    +     qh_deletevisible();         // qh.visible_list
    +     qh_resetlists(False, qh_RESETvisible);       // qh.visible_list newvertex_list newfacet_list
    +
    +   assumes qh.CENTERtype= centrum
    +
    +   merges occur in qh_mergefacet and in qh_mergecycle
    +   vertex->neighbors not set until the first merge occurs
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull/merge.c#4 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +#ifndef qh_NOmerge
    +
    +/*===== functions(alphabetical after premerge and postmerge) ======*/
    +
    +/*---------------------------------
    +
    +  qh_premerge( apex, maxcentrum )
    +    pre-merge nonconvex facets in qh.newfacet_list for apex
    +    maxcentrum defines coplanar and concave (qh_test_appendmerge)
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible set
    +
    +  notes:
    +    uses globals, qh.MERGEexact, qh.PREmerge
    +
    +  design:
    +    mark duplicate ridges in qh.newfacet_list
    +    merge facet cycles in qh.newfacet_list
    +    merge duplicate ridges and concave facets in qh.newfacet_list
    +    check merged facet cycles for degenerate and redundant facets
    +    merge degenerate and redundant facets
    +    collect coplanar and concave facets
    +    merge concave, coplanar, degenerate, and redundant facets
    +*/
    +void qh_premerge(vertexT *apex, realT maxcentrum, realT maxangle) {
    +  boolT othermerge= False;
    +  facetT *newfacet;
    +
    +  if (qh ZEROcentrum && qh_checkzero(!qh_ALL))
    +    return;
    +  trace2((qh ferr, 2008, "qh_premerge: premerge centrum %2.2g angle %2.2g for apex v%d facetlist f%d\n",
    +            maxcentrum, maxangle, apex->id, getid_(qh newfacet_list)));
    +  if (qh IStracing >= 4 && qh num_facets < 50)
    +    qh_printlists();
    +  qh centrum_radius= maxcentrum;
    +  qh cos_max= maxangle;
    +  qh degen_mergeset= qh_settemp(qh TEMPsize);
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  if (qh hull_dim >=3) {
    +    qh_mark_dupridges(qh newfacet_list); /* facet_mergeset */
    +    qh_mergecycle_all(qh newfacet_list, &othermerge);
    +    qh_forcedmerges(&othermerge /* qh.facet_mergeset */);
    +    FORALLnew_facets {  /* test samecycle merges */
    +      if (!newfacet->simplicial && !newfacet->mergeridge)
    +        qh_degen_redundant_neighbors(newfacet, NULL);
    +    }
    +    if (qh_merge_degenredundant())
    +      othermerge= True;
    +  }else /* qh.hull_dim == 2 */
    +    qh_mergecycle_all(qh newfacet_list, &othermerge);
    +  qh_flippedmerges(qh newfacet_list, &othermerge);
    +  if (!qh MERGEexact || zzval_(Ztotmerge)) {
    +    zinc_(Zpremergetot);
    +    qh POSTmerging= False;
    +    qh_getmergeset_initial(qh newfacet_list);
    +    qh_all_merges(othermerge, False);
    +  }
    +  qh_settempfree(&qh facet_mergeset);
    +  qh_settempfree(&qh degen_mergeset);
    +} /* premerge */
    +
    +/*---------------------------------
    +
    +  qh_postmerge( reason, maxcentrum, maxangle, vneighbors )
    +    post-merge nonconvex facets as defined by maxcentrum and maxangle
    +    'reason' is for reporting progress
    +    if vneighbors,
    +      calls qh_test_vneighbors at end of qh_all_merge
    +    if firstmerge,
    +      calls qh_reducevertices before qh_getmergeset
    +
    +  returns:
    +    if first call (qh.visible_list != qh.facet_list),
    +      builds qh.facet_newlist, qh.newvertex_list
    +    deleted facets added to qh.visible_list with facet->visible
    +    qh.visible_list == qh.facet_list
    +
    +  notes:
    +
    +
    +  design:
    +    if first call
    +      set qh.visible_list and qh.newfacet_list to qh.facet_list
    +      add all facets to qh.newfacet_list
    +      mark non-simplicial facets, facet->newmerge
    +      set qh.newvertext_list to qh.vertex_list
    +      add all vertices to qh.newvertex_list
    +      if a pre-merge occured
    +        set vertex->delridge {will retest the ridge}
    +        if qh.MERGEexact
    +          call qh_reducevertices()
    +      if no pre-merging
    +        merge flipped facets
    +    determine non-convex facets
    +    merge all non-convex facets
    +*/
    +void qh_postmerge(const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +  facetT *newfacet;
    +  boolT othermerges= False;
    +  vertexT *vertex;
    +
    +  if (qh REPORTfreq || qh IStracing) {
    +    qh_buildtracing(NULL, NULL);
    +    qh_printsummary(qh ferr);
    +    if (qh PRINTstatistics)
    +      qh_printallstatistics(qh ferr, "reason");
    +    qh_fprintf(qh ferr, 8062, "\n%s with 'C%.2g' and 'A%.2g'\n",
    +        reason, maxcentrum, maxangle);
    +  }
    +  trace2((qh ferr, 2009, "qh_postmerge: postmerge.  test vneighbors? %d\n",
    +            vneighbors));
    +  qh centrum_radius= maxcentrum;
    +  qh cos_max= maxangle;
    +  qh POSTmerging= True;
    +  qh degen_mergeset= qh_settemp(qh TEMPsize);
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  if (qh visible_list != qh facet_list) {  /* first call */
    +    qh NEWfacets= True;
    +    qh visible_list= qh newfacet_list= qh facet_list;
    +    FORALLnew_facets {
    +      newfacet->newfacet= True;
    +       if (!newfacet->simplicial)
    +        newfacet->newmerge= True;
    +     zinc_(Zpostfacets);
    +    }
    +    qh newvertex_list= qh vertex_list;
    +    FORALLvertices
    +      vertex->newlist= True;
    +    if (qh VERTEXneighbors) { /* a merge has occurred */
    +      FORALLvertices
    +        vertex->delridge= True; /* test for redundant, needed? */
    +      if (qh MERGEexact) {
    +        if (qh hull_dim <= qh_DIMreduceBuild)
    +          qh_reducevertices(); /* was skipped during pre-merging */
    +      }
    +    }
    +    if (!qh PREmerge && !qh MERGEexact)
    +      qh_flippedmerges(qh newfacet_list, &othermerges);
    +  }
    +  qh_getmergeset_initial(qh newfacet_list);
    +  qh_all_merges(False, vneighbors);
    +  qh_settempfree(&qh facet_mergeset);
    +  qh_settempfree(&qh degen_mergeset);
    +} /* post_merge */
    +
    +/*---------------------------------
    +
    +  qh_all_merges( othermerge, vneighbors )
    +    merge all non-convex facets
    +
    +    set othermerge if already merged facets (for qh_reducevertices)
    +    if vneighbors
    +      tests vertex neighbors for convexity at end
    +    qh.facet_mergeset lists the non-convex ridges in qh_newfacet_list
    +    qh.degen_mergeset is defined
    +    if qh.MERGEexact && !qh.POSTmerging,
    +      does not merge coplanar facets
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible
    +    deleted vertices added qh.delvertex_list with vertex->delvertex
    +
    +  notes:
    +    unless !qh.MERGEindependent,
    +      merges facets in independent sets
    +    uses qh.newfacet_list as argument since merges call qh_removefacet()
    +
    +  design:
    +    while merges occur
    +      for each merge in qh.facet_mergeset
    +        unless one of the facets was already merged in this pass
    +          merge the facets
    +        test merged facets for additional merges
    +        add merges to qh.facet_mergeset
    +      if vertices record neighboring facets
    +        rename redundant vertices
    +          update qh.facet_mergeset
    +    if vneighbors ??
    +      tests vertex neighbors for convexity at end
    +*/
    +void qh_all_merges(boolT othermerge, boolT vneighbors) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge;
    +  boolT wasmerge= True, isreduce;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +  vertexT *vertex;
    +  mergeType mergetype;
    +  int numcoplanar=0, numconcave=0, numdegenredun= 0, numnewmerges= 0;
    +
    +  trace2((qh ferr, 2010, "qh_all_merges: starting to merge facets beginning from f%d\n",
    +            getid_(qh newfacet_list)));
    +  while (True) {
    +    wasmerge= False;
    +    while (qh_setsize(qh facet_mergeset)) {
    +      while ((merge= (mergeT*)qh_setdellast(qh facet_mergeset))) {
    +        facet1= merge->facet1;
    +        facet2= merge->facet2;
    +        mergetype= merge->type;
    +        qh_memfree_(merge, (int)sizeof(mergeT), freelistp);
    +        if (facet1->visible || facet2->visible) /*deleted facet*/
    +          continue;
    +        if ((facet1->newfacet && !facet1->tested)
    +                || (facet2->newfacet && !facet2->tested)) {
    +          if (qh MERGEindependent && mergetype <= MRGanglecoplanar)
    +            continue;      /* perform independent sets of merges */
    +        }
    +        qh_merge_nonconvex(facet1, facet2, mergetype);
    +        numdegenredun += qh_merge_degenredundant();
    +        numnewmerges++;
    +        wasmerge= True;
    +        if (mergetype == MRGconcave)
    +          numconcave++;
    +        else /* MRGcoplanar or MRGanglecoplanar */
    +          numcoplanar++;
    +      } /* while setdellast */
    +      if (qh POSTmerging && qh hull_dim <= qh_DIMreduceBuild
    +      && numnewmerges > qh_MAXnewmerges) {
    +        numnewmerges= 0;
    +        qh_reducevertices();  /* otherwise large post merges too slow */
    +      }
    +      qh_getmergeset(qh newfacet_list); /* facet_mergeset */
    +    } /* while mergeset */
    +    if (qh VERTEXneighbors) {
    +      isreduce= False;
    +      if (qh hull_dim >=4 && qh POSTmerging) {
    +        FORALLvertices
    +          vertex->delridge= True;
    +        isreduce= True;
    +      }
    +      if ((wasmerge || othermerge) && (!qh MERGEexact || qh POSTmerging)
    +          && qh hull_dim <= qh_DIMreduceBuild) {
    +        othermerge= False;
    +        isreduce= True;
    +      }
    +      if (isreduce) {
    +        if (qh_reducevertices()) {
    +          qh_getmergeset(qh newfacet_list); /* facet_mergeset */
    +          continue;
    +        }
    +      }
    +    }
    +    if (vneighbors && qh_test_vneighbors(/* qh.newfacet_list */))
    +      continue;
    +    break;
    +  } /* while (True) */
    +  if (qh CHECKfrequently && !qh MERGEexact) {
    +    qh old_randomdist= qh RANDOMdist;
    +    qh RANDOMdist= False;
    +    qh_checkconvex(qh newfacet_list, qh_ALGORITHMfault);
    +    /* qh_checkconnect(); [this is slow and it changes the facet order] */
    +    qh RANDOMdist= qh old_randomdist;
    +  }
    +  trace1((qh ferr, 1009, "qh_all_merges: merged %d coplanar facets %d concave facets and %d degen or redundant facets.\n",
    +    numcoplanar, numconcave, numdegenredun));
    +  if (qh IStracing >= 4 && qh num_facets < 50)
    +    qh_printlists();
    +} /* all_merges */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendmergeset( facet, neighbor, mergetype, angle )
    +    appends an entry to qh.facet_mergeset or qh.degen_mergeset
    +
    +    angle ignored if NULL or !qh.ANGLEmerge
    +
    +  returns:
    +    merge appended to facet_mergeset or degen_mergeset
    +      sets ->degenerate or ->redundant if degen_mergeset
    +
    +  see:
    +    qh_test_appendmerge()
    +
    +  design:
    +    allocate merge entry
    +    if regular merge
    +      append to qh.facet_mergeset
    +    else if degenerate merge and qh.facet_mergeset is all degenerate
    +      append to qh.degen_mergeset
    +    else if degenerate merge
    +      prepend to qh.degen_mergeset
    +    else if redundant merge
    +      append to qh.degen_mergeset
    +*/
    +void qh_appendmergeset(facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle) {
    +  mergeT *merge, *lastmerge;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (facet->redundant)
    +    return;
    +  if (facet->degenerate && mergetype == MRGdegen)
    +    return;
    +  qh_memalloc_((int)sizeof(mergeT), freelistp, merge, mergeT);
    +  merge->facet1= facet;
    +  merge->facet2= neighbor;
    +  merge->type= mergetype;
    +  if (angle && qh ANGLEmerge)
    +    merge->angle= *angle;
    +  if (mergetype < MRGdegen)
    +    qh_setappend(&(qh facet_mergeset), merge);
    +  else if (mergetype == MRGdegen) {
    +    facet->degenerate= True;
    +    if (!(lastmerge= (mergeT*)qh_setlast(qh degen_mergeset))
    +    || lastmerge->type == MRGdegen)
    +      qh_setappend(&(qh degen_mergeset), merge);
    +    else
    +      qh_setaddnth(&(qh degen_mergeset), 0, merge);
    +  }else if (mergetype == MRGredundant) {
    +    facet->redundant= True;
    +    qh_setappend(&(qh degen_mergeset), merge);
    +  }else /* mergetype == MRGmirror */ {
    +    if (facet->redundant || neighbor->redundant) {
    +      qh_fprintf(qh ferr, 6092, "qhull error (qh_appendmergeset): facet f%d or f%d is already a mirrored facet\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh_ERRqhull, facet, neighbor);
    +    }
    +    if (!qh_setequal(facet->vertices, neighbor->vertices)) {
    +      qh_fprintf(qh ferr, 6093, "qhull error (qh_appendmergeset): mirrored facets f%d and f%d do not have the same vertices\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh_ERRqhull, facet, neighbor);
    +    }
    +    facet->redundant= True;
    +    neighbor->redundant= True;
    +    qh_setappend(&(qh degen_mergeset), merge);
    +  }
    +} /* appendmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_basevertices( samecycle )
    +    return temporary set of base vertices for samecycle
    +    samecycle is first facet in the cycle
    +    assumes apex is SETfirst_( samecycle->vertices )
    +
    +  returns:
    +    vertices(settemp)
    +    all ->seen are cleared
    +
    +  notes:
    +    uses qh_vertex_visit;
    +
    +  design:
    +    for each facet in samecycle
    +      for each unseen vertex in facet->vertices
    +        append to result
    +*/
    +setT *qh_basevertices(facetT *samecycle) {
    +  facetT *same;
    +  vertexT *apex, *vertex, **vertexp;
    +  setT *vertices= qh_settemp(qh TEMPsize);
    +
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  apex->visitid= ++qh vertex_visit;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->mergeridge)
    +      continue;
    +    FOREACHvertex_(same->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        qh_setappend(&vertices, vertex);
    +        vertex->visitid= qh vertex_visit;
    +        vertex->seen= False;
    +      }
    +    }
    +  }
    +  trace4((qh ferr, 4019, "qh_basevertices: found %d vertices\n",
    +         qh_setsize(vertices)));
    +  return vertices;
    +} /* basevertices */
    +
    +/*---------------------------------
    +
    +  qh_checkconnect()
    +    check that new facets are connected
    +    new facets are on qh.newfacet_list
    +
    +  notes:
    +    this is slow and it changes the order of the facets
    +    uses qh.visit_id
    +
    +  design:
    +    move first new facet to end of qh.facet_list
    +    for all newly appended facets
    +      append unvisited neighbors to end of qh.facet_list
    +    for all new facets
    +      report error if unvisited
    +*/
    +void qh_checkconnect(void /* qh.newfacet_list */) {
    +  facetT *facet, *newfacet, *errfacet= NULL, *neighbor, **neighborp;
    +
    +  facet= qh newfacet_list;
    +  qh_removefacet(facet);
    +  qh_appendfacet(facet);
    +  facet->visitid= ++qh visit_id;
    +  FORALLfacet_(facet) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        qh_removefacet(neighbor);
    +        qh_appendfacet(neighbor);
    +        neighbor->visitid= qh visit_id;
    +      }
    +    }
    +  }
    +  FORALLnew_facets {
    +    if (newfacet->visitid == qh visit_id)
    +      break;
    +    qh_fprintf(qh ferr, 6094, "qhull error: f%d is not attached to the new facets\n",
    +         newfacet->id);
    +    errfacet= newfacet;
    +  }
    +  if (errfacet)
    +    qh_errexit(qh_ERRqhull, errfacet, NULL);
    +} /* checkconnect */
    +
    +/*---------------------------------
    +
    +  qh_checkzero( testall )
    +    check that facets are clearly convex for qh.DISTround with qh.MERGEexact
    +
    +    if testall,
    +      test all facets for qh.MERGEexact post-merging
    +    else
    +      test qh.newfacet_list
    +
    +    if qh.MERGEexact,
    +      allows coplanar ridges
    +      skips convexity test while qh.ZEROall_ok
    +
    +  returns:
    +    True if all facets !flipped, !dupridge, normal
    +         if all horizon facets are simplicial
    +         if all vertices are clearly below neighbor
    +         if all opposite vertices of horizon are below
    +    clears qh.ZEROall_ok if any problems or coplanar facets
    +
    +  notes:
    +    uses qh.vertex_visit
    +    horizon facets may define multiple new facets
    +
    +  design:
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      check for flagged faults (flipped, etc.)
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      for each neighbor of facet
    +        skip horizon facets for qh.newfacet_list
    +        test the opposite vertex
    +      if qh.newfacet_list
    +        test the other vertices in the facet's horizon facet
    +*/
    +boolT qh_checkzero(boolT testall) {
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *horizon, *facetlist;
    +  int neighbor_i;
    +  vertexT *vertex, **vertexp;
    +  realT dist;
    +
    +  if (testall)
    +    facetlist= qh facet_list;
    +  else {
    +    facetlist= qh newfacet_list;
    +    FORALLfacet_(facetlist) {
    +      horizon= SETfirstt_(facet->neighbors, facetT);
    +      if (!horizon->simplicial)
    +        goto LABELproblem;
    +      if (facet->flipped || facet->dupridge || !facet->normal)
    +        goto LABELproblem;
    +    }
    +    if (qh MERGEexact && qh ZEROall_ok) {
    +      trace2((qh ferr, 2011, "qh_checkzero: skip convexity check until first pre-merge\n"));
    +      return True;
    +    }
    +  }
    +  FORALLfacet_(facetlist) {
    +    qh vertex_visit++;
    +    neighbor_i= 0;
    +    horizon= NULL;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor_i && !testall) {
    +        horizon= neighbor;
    +        neighbor_i++;
    +        continue; /* horizon facet tested in qh_findhorizon */
    +      }
    +      vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +      vertex->visitid= qh vertex_visit;
    +      zzinc_(Zdistzero);
    +      qh_distplane(vertex->point, neighbor, &dist);
    +      if (dist >= -qh DISTround) {
    +        qh ZEROall_ok= False;
    +        if (!qh MERGEexact || testall || dist > qh DISTround)
    +          goto LABELnonconvex;
    +      }
    +    }
    +    if (!testall && horizon) {
    +      FOREACHvertex_(horizon->vertices) {
    +        if (vertex->visitid != qh vertex_visit) {
    +          zzinc_(Zdistzero);
    +          qh_distplane(vertex->point, facet, &dist);
    +          if (dist >= -qh DISTround) {
    +            qh ZEROall_ok= False;
    +            if (!qh MERGEexact || dist > qh DISTround)
    +              goto LABELnonconvex;
    +          }
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh ferr, 2012, "qh_checkzero: testall %d, facets are %s\n", testall,
    +        (qh MERGEexact && !testall) ?
    +           "not concave, flipped, or duplicate ridged" : "clearly convex"));
    +  return True;
    +
    + LABELproblem:
    +  qh ZEROall_ok= False;
    +  trace2((qh ferr, 2013, "qh_checkzero: facet f%d needs pre-merging\n",
    +       facet->id));
    +  return False;
    +
    + LABELnonconvex:
    +  trace2((qh ferr, 2014, "qh_checkzero: facet f%d and f%d are not clearly convex.  v%d dist %.2g\n",
    +         facet->id, neighbor->id, vertex->id, dist));
    +  return False;
    +} /* checkzero */
    +
    +/*---------------------------------
    +
    +  qh_compareangle( angle1, angle2 )
    +    used by qsort() to order merges by angle
    +*/
    +int qh_compareangle(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return((a->angle > b->angle) ? 1 : -1);
    +} /* compareangle */
    +
    +/*---------------------------------
    +
    +  qh_comparemerge( merge1, merge2 )
    +    used by qsort() to order merges
    +*/
    +int qh_comparemerge(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return(a->type - b->type);
    +} /* comparemerge */
    +
    +/*---------------------------------
    +
    +  qh_comparevisit( vertex1, vertex2 )
    +    used by qsort() to order vertices by their visitid
    +*/
    +int qh_comparevisit(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return(a->visitid - b->visitid);
    +} /* comparevisit */
    +
    +/*---------------------------------
    +
    +  qh_copynonconvex( atridge )
    +    set non-convex flag on other ridges (if any) between same neighbors
    +
    +  notes:
    +    may be faster if use smaller ridge set
    +
    +  design:
    +    for each ridge of atridge's top facet
    +      if ridge shares the same neighbor
    +        set nonconvex flag
    +*/
    +void qh_copynonconvex(ridgeT *atridge) {
    +  facetT *facet, *otherfacet;
    +  ridgeT *ridge, **ridgep;
    +
    +  facet= atridge->top;
    +  otherfacet= atridge->bottom;
    +  FOREACHridge_(facet->ridges) {
    +    if (otherfacet == otherfacet_(ridge, facet) && ridge != atridge) {
    +      ridge->nonconvex= True;
    +      trace4((qh ferr, 4020, "qh_copynonconvex: moved nonconvex flag from r%d to r%d\n",
    +              atridge->id, ridge->id));
    +      break;
    +    }
    +  }
    +} /* copynonconvex */
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_facet( facet )
    +    check facet for degen. or redundancy
    +
    +  notes:
    +    bumps vertex_visit
    +    called if a facet was redundant but no longer is (qh_merge_degenredundant)
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +
    +  see:
    +    qh_degen_redundant_neighbors()
    +
    +  design:
    +    test for redundant neighbor
    +    test for degenerate facet
    +*/
    +void qh_degen_redundant_facet(facetT *facet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh ferr, 4021, "qh_degen_redundant_facet: test facet f%d for degen/redundant\n",
    +          facet->id));
    +  FOREACHneighbor_(facet) {
    +    qh vertex_visit++;
    +    FOREACHvertex_(neighbor->vertices)
    +      vertex->visitid= qh vertex_visit;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(facet, neighbor, MRGredundant, NULL);
    +      trace2((qh ferr, 2015, "qh_degen_redundant_facet: f%d is contained in f%d.  merge\n", facet->id, neighbor->id));
    +      return;
    +    }
    +  }
    +  if (qh_setsize(facet->neighbors) < qh hull_dim) {
    +    qh_appendmergeset(facet, facet, MRGdegen, NULL);
    +    trace2((qh ferr, 2016, "qh_degen_redundant_neighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* degen_redundant_facet */
    +
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_neighbors( facet, delfacet,  )
    +    append degenerate and redundant neighbors to facet_mergeset
    +    if delfacet,
    +      only checks neighbors of both delfacet and facet
    +    also checks current facet for degeneracy
    +
    +  notes:
    +    bumps vertex_visit
    +    called for each qh_mergefacet() and qh_mergecycle()
    +    merge and statistics occur in merge_nonconvex
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +      it appends redundant facets after degenerate ones
    +
    +    a degenerate facet has fewer than hull_dim neighbors
    +    a redundant facet's vertices is a subset of its neighbor's vertices
    +    tests for redundant merges first (appendmergeset is nop for others)
    +    in a merge, only needs to test neighbors of merged facet
    +
    +  see:
    +    qh_merge_degenredundant() and qh_degen_redundant_facet()
    +
    +  design:
    +    test for degenerate facet
    +    test for redundant neighbor
    +    test for degenerate neighbor
    +*/
    +void qh_degen_redundant_neighbors(facetT *facet, facetT *delfacet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +  int size;
    +
    +  trace4((qh ferr, 4022, "qh_degen_redundant_neighbors: test neighbors of f%d with delfacet f%d\n",
    +          facet->id, getid_(delfacet)));
    +  if ((size= qh_setsize(facet->neighbors)) < qh hull_dim) {
    +    qh_appendmergeset(facet, facet, MRGdegen, NULL);
    +    trace2((qh ferr, 2017, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.\n", facet->id, size));
    +  }
    +  if (!delfacet)
    +    delfacet= facet;
    +  qh vertex_visit++;
    +  FOREACHvertex_(facet->vertices)
    +    vertex->visitid= qh vertex_visit;
    +  FOREACHneighbor_(delfacet) {
    +    /* uses early out instead of checking vertex count */
    +    if (neighbor == facet)
    +      continue;
    +    FOREACHvertex_(neighbor->vertices) {
    +      if (vertex->visitid != qh vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(neighbor, facet, MRGredundant, NULL);
    +      trace2((qh ferr, 2018, "qh_degen_redundant_neighbors: f%d is contained in f%d.  merge\n", neighbor->id, facet->id));
    +    }
    +  }
    +  FOREACHneighbor_(delfacet) {   /* redundant merges occur first */
    +    if (neighbor == facet)
    +      continue;
    +    if ((size= qh_setsize(neighbor->neighbors)) < qh hull_dim) {
    +      qh_appendmergeset(neighbor, neighbor, MRGdegen, NULL);
    +      trace2((qh ferr, 2019, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.  Neighbor of f%d.\n", neighbor->id, size, facet->id));
    +    }
    +  }
    +} /* degen_redundant_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_find_newvertex( oldvertex, vertices, ridges )
    +    locate new vertex for renaming old vertex
    +    vertices is a set of possible new vertices
    +      vertices sorted by number of deleted ridges
    +
    +  returns:
    +    newvertex or NULL
    +      each ridge includes both vertex and oldvertex
    +    vertices sorted by number of deleted ridges
    +
    +  notes:
    +    modifies vertex->visitid
    +    new vertex is in one of the ridges
    +    renaming will not cause a duplicate ridge
    +    renaming will minimize the number of deleted ridges
    +    newvertex may not be adjacent in the dual (though unlikely)
    +
    +  design:
    +    for each vertex in vertices
    +      set vertex->visitid to number of references in ridges
    +    remove unvisited vertices
    +    set qh.vertex_visit above all possible values
    +    sort vertices by number of references in ridges
    +    add each ridge to qh.hash_table
    +    for each vertex in vertices
    +      look for a vertex that would not cause a duplicate ridge after a rename
    +*/
    +vertexT *qh_find_newvertex(vertexT *oldvertex, setT *vertices, setT *ridges) {
    +  vertexT *vertex, **vertexp;
    +  setT *newridges;
    +  ridgeT *ridge, **ridgep;
    +  int size, hashsize;
    +  int hash;
    +
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 4) {
    +    qh_fprintf(qh ferr, 8063, "qh_find_newvertex: find new vertex for v%d from ",
    +             oldvertex->id);
    +    FOREACHvertex_(vertices)
    +      qh_fprintf(qh ferr, 8064, "v%d ", vertex->id);
    +    FOREACHridge_(ridges)
    +      qh_fprintf(qh ferr, 8065, "r%d ", ridge->id);
    +    qh_fprintf(qh ferr, 8066, "\n");
    +  }
    +#endif
    +  FOREACHvertex_(vertices)
    +    vertex->visitid= 0;
    +  FOREACHridge_(ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->visitid++;
    +  }
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->visitid) {
    +      qh_setdelnth(vertices, SETindex_(vertices,vertex));
    +      vertexp--; /* repeat since deleted this vertex */
    +    }
    +  }
    +  qh vertex_visit += (unsigned int)qh_setsize(ridges);
    +  if (!qh_setsize(vertices)) {
    +    trace4((qh ferr, 4023, "qh_find_newvertex: vertices not in ridges for v%d\n",
    +            oldvertex->id));
    +    return NULL;
    +  }
    +  qsort(SETaddr_(vertices, vertexT), (size_t)qh_setsize(vertices),
    +                sizeof(vertexT *), qh_comparevisit);
    +  /* can now use qh vertex_visit */
    +  if (qh PRINTstatistics) {
    +    size= qh_setsize(vertices);
    +    zinc_(Zintersect);
    +    zadd_(Zintersecttot, size);
    +    zmax_(Zintersectmax, size);
    +  }
    +  hashsize= qh_newhashtable(qh_setsize(ridges));
    +  FOREACHridge_(ridges)
    +    qh_hashridge(qh hash_table, hashsize, ridge, oldvertex);
    +  FOREACHvertex_(vertices) {
    +    newridges= qh_vertexridges(vertex);
    +    FOREACHridge_(newridges) {
    +      if (qh_hashridge_find(qh hash_table, hashsize, ridge, vertex, oldvertex, &hash)) {
    +        zinc_(Zdupridge);
    +        break;
    +      }
    +    }
    +    qh_settempfree(&newridges);
    +    if (!ridge)
    +      break;  /* found a rename */
    +  }
    +  if (vertex) {
    +    /* counted in qh_renamevertex */
    +    trace2((qh ferr, 2020, "qh_find_newvertex: found v%d for old v%d from %d vertices and %d ridges.\n",
    +      vertex->id, oldvertex->id, qh_setsize(vertices), qh_setsize(ridges)));
    +  }else {
    +    zinc_(Zfindfail);
    +    trace0((qh ferr, 14, "qh_find_newvertex: no vertex for renaming v%d(all duplicated ridges) during p%d\n",
    +      oldvertex->id, qh furthest_id));
    +  }
    +  qh_setfree(&qh hash_table);
    +  return vertex;
    +} /* find_newvertex */
    +
    +/*---------------------------------
    +
    +  qh_findbest_test( testcentrum, facet, neighbor, bestfacet, dist, mindist, maxdist )
    +    test neighbor of facet for qh_findbestneighbor()
    +    if testcentrum,
    +      tests centrum (assumes it is defined)
    +    else
    +      tests vertices
    +
    +  returns:
    +    if a better facet (i.e., vertices/centrum of facet closer to neighbor)
    +      updates bestfacet, dist, mindist, and maxdist
    +*/
    +void qh_findbest_test(boolT testcentrum, facetT *facet, facetT *neighbor,
    +      facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  realT dist, mindist, maxdist;
    +
    +  if (testcentrum) {
    +    zzinc_(Zbestdist);
    +    qh_distplane(facet->center, neighbor, &dist);
    +    dist *= qh hull_dim; /* estimate furthest vertex */
    +    if (dist < 0) {
    +      maxdist= 0;
    +      mindist= dist;
    +      dist= -dist;
    +    }else {
    +      mindist= 0;
    +      maxdist= dist;
    +    }
    +  }else
    +    dist= qh_getdistance(facet, neighbor, &mindist, &maxdist);
    +  if (dist < *distp) {
    +    *bestfacet= neighbor;
    +    *mindistp= mindist;
    +    *maxdistp= maxdist;
    +    *distp= dist;
    +  }
    +} /* findbest_test */
    +
    +/*---------------------------------
    +
    +  qh_findbestneighbor( facet, dist, mindist, maxdist )
    +    finds best neighbor (least dist) of a facet for merging
    +
    +  returns:
    +    returns min and max distances and their max absolute value
    +
    +  notes:
    +    error if qh_ASvoronoi
    +    avoids merging old into new
    +    assumes ridge->nonconvex only set on one ridge between a pair of facets
    +    could use an early out predicate but not worth it
    +
    +  design:
    +    if a large facet
    +      will test centrum
    +    else
    +      will test vertices
    +    if a large facet
    +      test nonconvex neighbors for best merge
    +    else
    +      test all neighbors for the best merge
    +    if testing centrum
    +      get distance information
    +*/
    +facetT *qh_findbestneighbor(facetT *facet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  ridgeT *ridge, **ridgep;
    +  boolT nonconvex= True, testcentrum= False;
    +  int size= qh_setsize(facet->vertices);
    +
    +  if(qh CENTERtype==qh_ASvoronoi){
    +    qh_fprintf(qh ferr, 6272, "qhull error: cannot call qh_findbestneighor for f%d while qh.CENTERtype is qh_ASvoronoi\n", facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  *distp= REALmax;
    +  if (size > qh_BESTcentrum2 * qh hull_dim + qh_BESTcentrum) {
    +    testcentrum= True;
    +    zinc_(Zbestcentrum);
    +    if (!facet->center)
    +       facet->center= qh_getcentrum(facet);
    +  }
    +  if (size > qh hull_dim + qh_BESTnonconvex) {
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->nonconvex) {
    +        neighbor= otherfacet_(ridge, facet);
    +        qh_findbest_test(testcentrum, facet, neighbor,
    +                          &bestfacet, distp, mindistp, maxdistp);
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    nonconvex= False;
    +    FOREACHneighbor_(facet)
    +      qh_findbest_test(testcentrum, facet, neighbor,
    +                        &bestfacet, distp, mindistp, maxdistp);
    +  }
    +  if (!bestfacet) {
    +    qh_fprintf(qh ferr, 6095, "qhull internal error (qh_findbestneighbor): no neighbors for f%d\n", facet->id);
    +
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  if (testcentrum)
    +    qh_getdistance(facet, bestfacet, mindistp, maxdistp);
    +  trace3((qh ferr, 3002, "qh_findbestneighbor: f%d is best neighbor for f%d testcentrum? %d nonconvex? %d dist %2.2g min %2.2g max %2.2g\n",
    +     bestfacet->id, facet->id, testcentrum, nonconvex, *distp, *mindistp, *maxdistp));
    +  return(bestfacet);
    +} /* findbestneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_flippedmerges( facetlist, wasmerge )
    +    merge flipped facets into best neighbor
    +    assumes qh.facet_mergeset at top of temporary stack
    +
    +  returns:
    +    no flipped facets on facetlist
    +    sets wasmerge if merge occurred
    +    degen/redundant merges passed through
    +
    +  notes:
    +    othermerges not needed since qh.facet_mergeset is empty before & after
    +      keep it in case of change
    +
    +  design:
    +    append flipped facets to qh.facetmergeset
    +    for each flipped merge
    +      find best neighbor
    +      merge facet into neighbor
    +      merge degenerate and redundant facets
    +    remove flipped merges from qh.facet_mergeset
    +*/
    +void qh_flippedmerges(facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *neighbor, *facet1;
    +  realT dist, mindist, maxdist;
    +  mergeT *merge, **mergep;
    +  setT *othermerges;
    +  int nummerge=0;
    +
    +  trace4((qh ferr, 4024, "qh_flippedmerges: begin\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped && !facet->visible)
    +      qh_appendmergeset(facet, facet, MRGflip, NULL);
    +  }
    +  othermerges= qh_settemppop(); /* was facet_mergeset */
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  qh_settemppush(othermerges);
    +  FOREACHmerge_(othermerges) {
    +    facet1= merge->facet1;
    +    if (merge->type != MRGflip || facet1->visible)
    +      continue;
    +    if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +      qhmem.IStracing= qh IStracing= qh TRACElevel;
    +    neighbor= qh_findbestneighbor(facet1, &dist, &mindist, &maxdist);
    +    trace0((qh ferr, 15, "qh_flippedmerges: merge flipped f%d into f%d dist %2.2g during p%d\n",
    +      facet1->id, neighbor->id, dist, qh furthest_id));
    +    qh_mergefacet(facet1, neighbor, &mindist, &maxdist, !qh_MERGEapex);
    +    nummerge++;
    +    if (qh PRINTstatistics) {
    +      zinc_(Zflipped);
    +      wadd_(Wflippedtot, dist);
    +      wmax_(Wflippedmax, dist);
    +    }
    +    qh_merge_degenredundant();
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->facet1->visible || merge->facet2->visible)
    +      qh_memfree(merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(&qh facet_mergeset, merge);
    +  }
    +  qh_settempfree(&othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh ferr, 1010, "qh_flippedmerges: merged %d flipped facets into a good neighbor\n", nummerge));
    +} /* flippedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_forcedmerges( wasmerge )
    +    merge duplicated ridges
    +
    +  returns:
    +    removes all duplicate ridges on facet_mergeset
    +    wasmerge set if merge
    +    qh.facet_mergeset may include non-forced merges(none for now)
    +    qh.degen_mergeset includes degen/redun merges
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +     could rename vertices that pinch the horizon
    +    assumes qh_merge_degenredundant() has not be called
    +    othermerges isn't needed since facet_mergeset is empty afterwards
    +      keep it in case of change
    +
    +  design:
    +    for each duplicate ridge
    +      find current facets by chasing f.replace links
    +      check for wide merge due to duplicate ridge
    +      determine best direction for facet
    +      merge one facet into the other
    +      remove duplicate ridges from qh.facet_mergeset
    +*/
    +void qh_forcedmerges(boolT *wasmerge) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge, **mergep;
    +  realT dist1, dist2, mindist1, mindist2, maxdist1, maxdist2;
    +  setT *othermerges;
    +  int nummerge=0, numflip=0;
    +
    +  if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +    qhmem.IStracing= qh IStracing= qh TRACElevel;
    +  trace4((qh ferr, 4025, "qh_forcedmerges: begin\n"));
    +  othermerges= qh_settemppop(); /* was facet_mergeset */
    +  qh facet_mergeset= qh_settemp(qh TEMPsize);
    +  qh_settemppush(othermerges);
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type != MRGridge)
    +        continue;
    +    if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +        qhmem.IStracing= qh IStracing= qh TRACElevel;
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    while (facet1->visible)      /* must exist, no qh_merge_degenredunant */
    +      facet1= facet1->f.replace; /* previously merged facet */
    +    while (facet2->visible)
    +      facet2= facet2->f.replace; /* previously merged facet */
    +    if (facet1 == facet2)
    +      continue;
    +    if (!qh_setin(facet2->neighbors, facet1)) {
    +      qh_fprintf(qh ferr, 6096, "qhull internal error (qh_forcedmerges): f%d and f%d had a duplicate ridge but as f%d and f%d they are no longer neighbors\n",
    +               merge->facet1->id, merge->facet2->id, facet1->id, facet2->id);
    +      qh_errexit2(qh_ERRqhull, facet1, facet2);
    +    }
    +    dist1= qh_getdistance(facet1, facet2, &mindist1, &maxdist1);
    +    dist2= qh_getdistance(facet2, facet1, &mindist2, &maxdist2);
    +    qh_check_dupridge(facet1, dist1, facet2, dist2);
    +    if (dist1 < dist2)
    +      qh_mergefacet(facet1, facet2, &mindist1, &maxdist1, !qh_MERGEapex);
    +    else {
    +      qh_mergefacet(facet2, facet1, &mindist2, &maxdist2, !qh_MERGEapex);
    +      dist1= dist2;
    +      facet1= facet2;
    +    }
    +    if (facet1->flipped) {
    +      zinc_(Zmergeflipdup);
    +      numflip++;
    +    }else
    +      nummerge++;
    +    if (qh PRINTstatistics) {
    +      zinc_(Zduplicate);
    +      wadd_(Wduplicatetot, dist1);
    +      wmax_(Wduplicatemax, dist1);
    +    }
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type == MRGridge)
    +      qh_memfree(merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(&qh facet_mergeset, merge);
    +  }
    +  qh_settempfree(&othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh ferr, 1011, "qh_forcedmerges: merged %d facets and %d flipped facets across duplicated ridges\n",
    +                nummerge, numflip));
    +} /* forcedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset( facetlist )
    +    determines nonconvex facets on facetlist
    +    tests !tested ridges and nonconvex ridges of !tested facets
    +
    +  returns:
    +    returns sorted qh.facet_mergeset of facet-neighbor pairs to be merged
    +    all ridges tested
    +
    +  notes:
    +    assumes no nonconvex ridges with both facets tested
    +    uses facet->tested/ridge->tested to prevent duplicate tests
    +    can not limit tests to modified ridges since the centrum changed
    +    uses qh.visit_id
    +
    +  see:
    +    qh_getmergeset_initial()
    +
    +  design:
    +    for each facet on facetlist
    +      for each ridge of facet
    +        if untested ridge
    +          test ridge for convexity
    +          if non-convex
    +            append ridge to qh.facet_mergeset
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset(facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  nummerges= qh_setsize(qh facet_mergeset);
    +  trace4((qh ferr, 4026, "qh_getmergeset: started.\n"));
    +  qh visit_id++;
    +  FORALLfacet_(facetlist) {
    +    if (facet->tested)
    +      continue;
    +    facet->visitid= qh visit_id;
    +    facet->tested= True;  /* must be non-simplicial due to merge */
    +    FOREACHneighbor_(facet)
    +      neighbor->seen= False;
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->tested && !ridge->nonconvex)
    +        continue;
    +      /* if tested & nonconvex, need to append merge */
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->seen) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +      }else if (neighbor->visitid != qh visit_id) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +        neighbor->seen= True;      /* only one ridge is marked nonconvex */
    +        if (qh_test_appendmerge(facet, neighbor))
    +          ridge->nonconvex= True;
    +      }
    +    }
    +  }
    +  nummerges= qh_setsize(qh facet_mergeset);
    +  if (qh ANGLEmerge)
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh POSTmerging) {
    +    zadd_(Zmergesettot2, nummerges);
    +  }else {
    +    zadd_(Zmergesettot, nummerges);
    +    zmax_(Zmergesetmax, nummerges);
    +  }
    +  trace2((qh ferr, 2021, "qh_getmergeset: %d merges found\n", nummerges));
    +} /* getmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset_initial( facetlist )
    +    determine initial qh.facet_mergeset for facets
    +    tests all facet/neighbor pairs on facetlist
    +
    +  returns:
    +    sorted qh.facet_mergeset with nonconvex ridges
    +    sets facet->tested, ridge->tested, and ridge->nonconvex
    +
    +  notes:
    +    uses visit_id, assumes ridge->nonconvex is False
    +
    +  see:
    +    qh_getmergeset()
    +
    +  design:
    +    for each facet on facetlist
    +      for each untested neighbor of facet
    +        test facet and neighbor for convexity
    +        if non-convex
    +          append merge to qh.facet_mergeset
    +          mark one of the ridges as nonconvex
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset_initial(facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  qh visit_id++;
    +  FORALLfacet_(facetlist) {
    +    facet->visitid= qh visit_id;
    +    facet->tested= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh visit_id) {
    +        if (qh_test_appendmerge(facet, neighbor)) {
    +          FOREACHridge_(neighbor->ridges) {
    +            if (facet == otherfacet_(ridge, neighbor)) {
    +              ridge->nonconvex= True;
    +              break;    /* only one ridge is marked nonconvex */
    +            }
    +          }
    +        }
    +      }
    +    }
    +    FOREACHridge_(facet->ridges)
    +      ridge->tested= True;
    +  }
    +  nummerges= qh_setsize(qh facet_mergeset);
    +  if (qh ANGLEmerge)
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh POSTmerging) {
    +    zadd_(Zmergeinittot2, nummerges);
    +  }else {
    +    zadd_(Zmergeinittot, nummerges);
    +    zmax_(Zmergeinitmax, nummerges);
    +  }
    +  trace2((qh ferr, 2022, "qh_getmergeset_initial: %d merges found\n", nummerges));
    +} /* getmergeset_initial */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge( hashtable, hashsize, ridge, oldvertex )
    +    add ridge to hashtable without oldvertex
    +
    +  notes:
    +    assumes hashtable is large enough
    +
    +  design:
    +    determine hash value for ridge without oldvertex
    +    find next empty slot for ridge
    +*/
    +void qh_hashridge(setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  hash= qh_gethash(hashsize, ridge->vertices, qh hull_dim-1, 0, oldvertex);
    +  while (True) {
    +    if (!(ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +      SETelem_(hashtable, hash)= ridge;
    +      break;
    +    }else if (ridgeA == ridge)
    +      break;
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +} /* hashridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge_find( hashtable, hashsize, ridge, vertex, oldvertex, hashslot )
    +    returns matching ridge without oldvertex in hashtable
    +      for ridge without vertex
    +    if oldvertex is NULL
    +      matches with any one skip
    +
    +  returns:
    +    matching ridge or NULL
    +    if no match,
    +      if ridge already in   table
    +        hashslot= -1
    +      else
    +        hashslot= next NULL index
    +
    +  notes:
    +    assumes hashtable is large enough
    +    can't match ridge to itself
    +
    +  design:
    +    get hash value for ridge without vertex
    +    for each hashslot
    +      return match if ridge matches ridgeA without oldvertex
    +*/
    +ridgeT *qh_hashridge_find(setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  *hashslot= 0;
    +  zinc_(Zhashridge);
    +  hash= qh_gethash(hashsize, ridge->vertices, qh hull_dim-1, 0, vertex);
    +  while ((ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +    if (ridgeA == ridge)
    +      *hashslot= -1;
    +    else {
    +      zinc_(Zhashridgetest);
    +      if (qh_setequal_except(ridge->vertices, vertex, ridgeA->vertices, oldvertex))
    +        return ridgeA;
    +    }
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +  if (!*hashslot)
    +    *hashslot= hash;
    +  return NULL;
    +} /* hashridge_find */
    +
    +
    +/*---------------------------------
    +
    +  qh_makeridges( facet )
    +    creates explicit ridges between simplicial facets
    +
    +  returns:
    +    facet with ridges and without qh_MERGEridge
    +    ->simplicial is False
    +
    +  notes:
    +    allows qh_MERGEridge flag
    +    uses existing ridges
    +    duplicate neighbors ok if ridges already exist (qh_mergecycle_ridges)
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    look for qh_MERGEridge neighbors
    +    mark neighbors that already have ridges
    +    for each unprocessed neighbor of facet
    +      create a ridge for neighbor and facet
    +    if any qh_MERGEridge neighbors
    +      delete qh_MERGEridge flags (already handled by qh_mark_dupridges)
    +*/
    +void qh_makeridges(facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int neighbor_i, neighbor_n;
    +  boolT toporient, mergeridge= False;
    +
    +  if (!facet->simplicial)
    +    return;
    +  trace4((qh ferr, 4027, "qh_makeridges: make ridges for f%d\n", facet->id));
    +  facet->simplicial= False;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      mergeridge= True;
    +    else
    +      neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges)
    +    otherfacet_(ridge, facet)->seen= True;
    +  FOREACHneighbor_i_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      continue;  /* fixed by qh_mark_dupridges */
    +    else if (!neighbor->seen) {  /* no current ridges */
    +      ridge= qh_newridge();
    +      ridge->vertices= qh_setnew_delnthsorted(facet->vertices, qh hull_dim,
    +                                                          neighbor_i, 0);
    +      toporient= facet->toporient ^ (neighbor_i & 0x1);
    +      if (toporient) {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }else {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }
    +#if 0 /* this also works */
    +      flip= (facet->toporient ^ neighbor->toporient)^(skip1 & 0x1) ^ (skip2 & 0x1);
    +      if (facet->toporient ^ (skip1 & 0x1) ^ flip) {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }else {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }
    +#endif
    +      qh_setappend(&(facet->ridges), ridge);
    +      qh_setappend(&(neighbor->ridges), ridge);
    +    }
    +  }
    +  if (mergeridge) {
    +    while (qh_setdel(facet->neighbors, qh_MERGEridge))
    +      ; /* delete each one */
    +  }
    +} /* makeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mark_dupridges( facetlist )
    +    add duplicated ridges to qh.facet_mergeset
    +    facet->dupridge is true
    +
    +  returns:
    +    duplicate ridges on qh.facet_mergeset
    +    ->mergeridge/->mergeridge2 set
    +    duplicate ridges marked by qh_MERGEridge and both sides facet->dupridge
    +    no MERGEridges in neighbor sets
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +    could rename vertices that pinch the horizon (thus removing subridge)
    +    uses qh.visit_id
    +
    +  design:
    +    for all facets on facetlist
    +      if facet contains a duplicate ridge
    +        for each neighbor of facet
    +          if neighbor marked qh_MERGEridge (one side of the merge)
    +            set facet->mergeridge
    +          else
    +            if neighbor contains a duplicate ridge
    +            and the back link is qh_MERGEridge
    +              append duplicate ridge to qh.facet_mergeset
    +   for each duplicate ridge
    +     make ridge sets in preparation for merging
    +     remove qh_MERGEridge from neighbor set
    +   for each duplicate ridge
    +     restore the missing neighbor from the neighbor set that was qh_MERGEridge
    +     add the missing ridge for this neighbor
    +*/
    +void qh_mark_dupridges(facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  int nummerge=0;
    +  mergeT *merge, **mergep;
    +
    +
    +  trace4((qh ferr, 4028, "qh_mark_dupridges: identify duplicate ridges\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->dupridge) {
    +      FOREACHneighbor_(facet) {
    +        if (neighbor == qh_MERGEridge) {
    +          facet->mergeridge= True;
    +          continue;
    +        }
    +        if (neighbor->dupridge
    +        && !qh_setin(neighbor->neighbors, facet)) { /* qh_MERGEridge */
    +          qh_appendmergeset(facet, neighbor, MRGridge, NULL);
    +          facet->mergeridge2= True;
    +          facet->mergeridge= True;
    +          nummerge++;
    +        }
    +      }
    +    }
    +  }
    +  if (!nummerge)
    +    return;
    +  FORALLfacet_(facetlist) {            /* gets rid of qh_MERGEridge */
    +    if (facet->mergeridge && !facet->mergeridge2)
    +      qh_makeridges(facet);
    +  }
    +  FOREACHmerge_(qh facet_mergeset) {   /* restore the missing neighbors */
    +    if (merge->type == MRGridge) {
    +      qh_setappend(&merge->facet2->neighbors, merge->facet1);
    +      qh_makeridges(merge->facet1);   /* and the missing ridges */
    +    }
    +  }
    +  trace1((qh ferr, 1012, "qh_mark_dupridges: found %d duplicated ridges\n",
    +                nummerge));
    +} /* mark_dupridges */
    +
    +/*---------------------------------
    +
    +  qh_maydropneighbor( facet )
    +    drop neighbor relationship if no ridge between facet and neighbor
    +
    +  returns:
    +    neighbor sets updated
    +    appends degenerate facets to qh.facet_mergeset
    +
    +  notes:
    +    won't cause redundant facets since vertex inclusion is the same
    +    may drop vertex and neighbor if no ridge
    +    uses qh.visit_id
    +
    +  design:
    +    visit all neighbors with ridges
    +    for each unvisited neighbor of facet
    +      delete neighbor and facet from the neighbor sets
    +      if neighbor becomes degenerate
    +        append neighbor to qh.degen_mergeset
    +    if facet is degenerate
    +      append facet to qh.degen_mergeset
    +*/
    +void qh_maydropneighbor(facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  realT angledegen= qh_ANGLEdegen;
    +  facetT *neighbor, **neighborp;
    +
    +  qh visit_id++;
    +  trace4((qh ferr, 4029, "qh_maydropneighbor: test f%d for no ridges to a neighbor\n",
    +          facet->id));
    +  FOREACHridge_(facet->ridges) {
    +    ridge->top->visitid= qh visit_id;
    +    ridge->bottom->visitid= qh visit_id;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid != qh visit_id) {
    +      trace0((qh ferr, 17, "qh_maydropneighbor: facets f%d and f%d are no longer neighbors during p%d\n",
    +            facet->id, neighbor->id, qh furthest_id));
    +      zinc_(Zdropneighbor);
    +      qh_setdel(facet->neighbors, neighbor);
    +      neighborp--;  /* repeat, deleted a neighbor */
    +      qh_setdel(neighbor->neighbors, facet);
    +      if (qh_setsize(neighbor->neighbors) < qh hull_dim) {
    +        zinc_(Zdropdegen);
    +        qh_appendmergeset(neighbor, neighbor, MRGdegen, &angledegen);
    +        trace2((qh ferr, 2023, "qh_maydropneighbors: f%d is degenerate.\n", neighbor->id));
    +      }
    +    }
    +  }
    +  if (qh_setsize(facet->neighbors) < qh hull_dim) {
    +    zinc_(Zdropdegen);
    +    qh_appendmergeset(facet, facet, MRGdegen, &angledegen);
    +    trace2((qh ferr, 2024, "qh_maydropneighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* maydropneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_merge_degenredundant()
    +    merge all degenerate and redundant facets
    +    qh.degen_mergeset contains merges from qh_degen_redundant_neighbors()
    +
    +  returns:
    +    number of merges performed
    +    resets facet->degenerate/redundant
    +    if deleted (visible) facet has no neighbors
    +      sets ->f.replace to NULL
    +
    +  notes:
    +    redundant merges happen before degenerate ones
    +    merging and renaming vertices can result in degen/redundant facets
    +
    +  design:
    +    for each merge on qh.degen_mergeset
    +      if redundant merge
    +        if non-redundant facet merged into redundant facet
    +          recheck facet for redundancy
    +        else
    +          merge redundant facet into other facet
    +*/
    +int qh_merge_degenredundant(void) {
    +  int size;
    +  mergeT *merge;
    +  facetT *bestneighbor, *facet1, *facet2;
    +  realT dist, mindist, maxdist;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +  mergeType mergetype;
    +
    +  while ((merge= (mergeT*)qh_setdellast(qh degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(merge, (int)sizeof(mergeT));
    +    if (facet1->visible)
    +      continue;
    +    facet1->degenerate= False;
    +    facet1->redundant= False;
    +    if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +      qhmem.IStracing= qh IStracing= qh TRACElevel;
    +    if (mergetype == MRGredundant) {
    +      zinc_(Zneighbor);
    +      while (facet2->visible) {
    +        if (!facet2->f.replace) {
    +          qh_fprintf(qh ferr, 6097, "qhull internal error (qh_merge_degenredunant): f%d redundant but f%d has no replacement\n",
    +               facet1->id, facet2->id);
    +          qh_errexit2(qh_ERRqhull, facet1, facet2);
    +        }
    +        facet2= facet2->f.replace;
    +      }
    +      if (facet1 == facet2) {
    +        qh_degen_redundant_facet(facet1); /* in case of others */
    +        continue;
    +      }
    +      trace2((qh ferr, 2025, "qh_merge_degenredundant: facet f%d is contained in f%d, will merge\n",
    +            facet1->id, facet2->id));
    +      qh_mergefacet(facet1, facet2, NULL, NULL, !qh_MERGEapex);
    +      /* merge distance is already accounted for */
    +      nummerges++;
    +    }else {  /* mergetype == MRGdegen, other merges may have fixed */
    +      if (!(size= qh_setsize(facet1->neighbors))) {
    +        zinc_(Zdelfacetdup);
    +        trace2((qh ferr, 2026, "qh_merge_degenredundant: facet f%d has no neighbors.  Deleted\n", facet1->id));
    +        qh_willdelete(facet1, NULL);
    +        FOREACHvertex_(facet1->vertices) {
    +          qh_setdel(vertex->neighbors, facet1);
    +          if (!SETfirst_(vertex->neighbors)) {
    +            zinc_(Zdegenvertex);
    +            trace2((qh ferr, 2027, "qh_merge_degenredundant: deleted v%d because f%d has no neighbors\n",
    +                 vertex->id, facet1->id));
    +            vertex->deleted= True;
    +            qh_setappend(&qh del_vertices, vertex);
    +          }
    +        }
    +        nummerges++;
    +      }else if (size < qh hull_dim) {
    +        bestneighbor= qh_findbestneighbor(facet1, &dist, &mindist, &maxdist);
    +        trace2((qh ferr, 2028, "qh_merge_degenredundant: facet f%d has %d neighbors, merge into f%d dist %2.2g\n",
    +              facet1->id, size, bestneighbor->id, dist));
    +        qh_mergefacet(facet1, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +        nummerges++;
    +        if (qh PRINTstatistics) {
    +          zinc_(Zdegen);
    +          wadd_(Wdegentot, dist);
    +          wmax_(Wdegenmax, dist);
    +        }
    +      } /* else, another merge fixed the degeneracy and redundancy tested */
    +    }
    +  }
    +  return nummerges;
    +} /* merge_degenredundant */
    +
    +/*---------------------------------
    +
    +  qh_merge_nonconvex( facet1, facet2, mergetype )
    +    remove non-convex ridge between facet1 into facet2
    +    mergetype gives why the facet's are non-convex
    +
    +  returns:
    +    merges one of the facets into the best neighbor
    +
    +  design:
    +    if one of the facets is a new facet
    +      prefer merging new facet into old facet
    +    find best neighbors for both facets
    +    merge the nearest facet into its best neighbor
    +    update the statistics
    +*/
    +void qh_merge_nonconvex(facetT *facet1, facetT *facet2, mergeType mergetype) {
    +  facetT *bestfacet, *bestneighbor, *neighbor;
    +  realT dist, dist2, mindist, mindist2, maxdist, maxdist2;
    +
    +  if (qh TRACEmerge-1 == zzval_(Ztotmerge))
    +    qhmem.IStracing= qh IStracing= qh TRACElevel;
    +  trace3((qh ferr, 3003, "qh_merge_nonconvex: merge #%d for f%d and f%d type %d\n",
    +      zzval_(Ztotmerge) + 1, facet1->id, facet2->id, mergetype));
    +  /* concave or coplanar */
    +  if (!facet1->newfacet) {
    +    bestfacet= facet2;   /* avoid merging old facet if new is ok */
    +    facet2= facet1;
    +    facet1= bestfacet;
    +  }else
    +    bestfacet= facet1;
    +  bestneighbor= qh_findbestneighbor(bestfacet, &dist, &mindist, &maxdist);
    +  neighbor= qh_findbestneighbor(facet2, &dist2, &mindist2, &maxdist2);
    +  if (dist < dist2) {
    +    qh_mergefacet(bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else if (qh AVOIDold && !facet2->newfacet
    +  && ((mindist >= -qh MAXcoplanar && maxdist <= qh max_outside)
    +       || dist * 1.5 < dist2)) {
    +    zinc_(Zavoidold);
    +    wadd_(Wavoidoldtot, dist);
    +    wmax_(Wavoidoldmax, dist);
    +    trace2((qh ferr, 2029, "qh_merge_nonconvex: avoid merging old facet f%d dist %2.2g.  Use f%d dist %2.2g instead\n",
    +           facet2->id, dist2, facet1->id, dist2));
    +    qh_mergefacet(bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else {
    +    qh_mergefacet(facet2, neighbor, &mindist2, &maxdist2, !qh_MERGEapex);
    +    dist= dist2;
    +  }
    +  if (qh PRINTstatistics) {
    +    if (mergetype == MRGanglecoplanar) {
    +      zinc_(Zacoplanar);
    +      wadd_(Wacoplanartot, dist);
    +      wmax_(Wacoplanarmax, dist);
    +    }else if (mergetype == MRGconcave) {
    +      zinc_(Zconcave);
    +      wadd_(Wconcavetot, dist);
    +      wmax_(Wconcavemax, dist);
    +    }else { /* MRGcoplanar */
    +      zinc_(Zcoplanar);
    +      wadd_(Wcoplanartot, dist);
    +      wmax_(Wcoplanarmax, dist);
    +    }
    +  }
    +} /* merge_nonconvex */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle( samecycle, newfacet )
    +    merge a cycle of facets starting at samecycle into a newfacet
    +    newfacet is a horizon facet with ->normal
    +    samecycle facets are simplicial from an apex
    +
    +  returns:
    +    initializes vertex neighbors on first merge
    +    samecycle deleted (placed on qh.visible_list)
    +    newfacet at end of qh.facet_list
    +    deleted vertices on qh.del_vertices
    +
    +  see:
    +    qh_mergefacet()
    +    called by qh_mergecycle_all() for multiple, same cycle facets
    +
    +  design:
    +    make vertex neighbors if necessary
    +    make ridges for newfacet
    +    merge neighbor sets of samecycle into newfacet
    +    merge ridges of samecycle into newfacet
    +    merge vertex neighbors of samecycle into newfacet
    +    make apex of samecycle the apex of newfacet
    +    if newfacet wasn't a new facet
    +      add its vertices to qh.newvertex_list
    +    delete samecycle facets a make newfacet a newfacet
    +*/
    +void qh_mergecycle(facetT *samecycle, facetT *newfacet) {
    +  int traceonce= False, tracerestore= 0;
    +  vertexT *apex;
    +#ifndef qh_NOtrace
    +  facetT *same;
    +#endif
    +
    +  if (newfacet->tricoplanar) {
    +    if (!qh TRInormals) {
    +      qh_fprintf(qh ferr, 6224, "Qhull internal error (qh_mergecycle): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh_ERRqhull, newfacet, NULL);
    +    }
    +    newfacet->tricoplanar= False;
    +    newfacet->keepcentrum= False;
    +  }
    +  if (!qh VERTEXneighbors)
    +    qh_vertexneighbors();
    +  zzinc_(Ztotmerge);
    +  if (qh REPORTfreq2 && qh POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh mergereport + qh REPORTfreq2)
    +      qh_tracemerging();
    +  }
    +#ifndef qh_NOtrace
    +  if (qh TRACEmerge == zzval_(Ztotmerge))
    +    qhmem.IStracing= qh IStracing= qh TRACElevel;
    +  trace2((qh ferr, 2030, "qh_mergecycle: merge #%d for facets from cycle f%d into coplanar horizon f%d\n",
    +        zzval_(Ztotmerge), samecycle->id, newfacet->id));
    +  if (newfacet == qh tracefacet) {
    +    tracerestore= qh IStracing;
    +    qh IStracing= 4;
    +    qh_fprintf(qh ferr, 8068, "qh_mergecycle: ========= trace merge %d of samecycle %d into trace f%d, furthest is p%d\n",
    +               zzval_(Ztotmerge), samecycle->id, newfacet->id,  qh furthest_id);
    +    traceonce= True;
    +  }
    +  if (qh IStracing >=4) {
    +    qh_fprintf(qh ferr, 8069, "  same cycle:");
    +    FORALLsame_cycle_(samecycle)
    +      qh_fprintf(qh ferr, 8070, " f%d", same->id);
    +    qh_fprintf(qh ferr, 8071, "\n");
    +  }
    +  if (qh IStracing >=4)
    +    qh_errprint("MERGING CYCLE", samecycle, newfacet, NULL, NULL);
    +#endif /* !qh_NOtrace */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_makeridges(newfacet);
    +  qh_mergecycle_neighbors(samecycle, newfacet);
    +  qh_mergecycle_ridges(samecycle, newfacet);
    +  qh_mergecycle_vneighbors(samecycle, newfacet);
    +  if (SETfirstt_(newfacet->vertices, vertexT) != apex)
    +    qh_setaddnth(&newfacet->vertices, 0, apex);  /* apex has last id */
    +  if (!newfacet->newfacet)
    +    qh_newvertices(newfacet->vertices);
    +  qh_mergecycle_facets(samecycle, newfacet);
    +  qh_tracemerge(samecycle, newfacet);
    +  /* check for degen_redundant_neighbors after qh_forcedmerges() */
    +  if (traceonce) {
    +    qh_fprintf(qh ferr, 8072, "qh_mergecycle: end of trace facet\n");
    +    qh IStracing= tracerestore;
    +  }
    +} /* mergecycle */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_all( facetlist, wasmerge )
    +    merge all samecycles of coplanar facets into horizon
    +    don't merge facets with ->mergeridge (these already have ->normal)
    +    all facets are simplicial from apex
    +    all facet->cycledone == False
    +
    +  returns:
    +    all newfacets merged into coplanar horizon facets
    +    deleted vertices on  qh.del_vertices
    +    sets wasmerge if any merge
    +
    +  see:
    +    calls qh_mergecycle for multiple, same cycle facets
    +
    +  design:
    +    for each facet on facetlist
    +      skip facets with duplicate ridges and normals
    +      check that facet is in a samecycle (->mergehorizon)
    +      if facet only member of samecycle
    +        sets vertex->delridge for all vertices except apex
    +        merge facet into horizon
    +      else
    +        mark all facets in samecycle
    +        remove facets with duplicate ridges from samecycle
    +        merge samecycle into horizon (deletes facets from facetlist)
    +*/
    +void qh_mergecycle_all(facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *same, *prev, *horizon;
    +  facetT *samecycle= NULL, *nextfacet, *nextsame;
    +  vertexT *apex, *vertex, **vertexp;
    +  int cycles=0, total=0, facets, nummerge;
    +
    +  trace2((qh ferr, 2031, "qh_mergecycle_all: begin\n"));
    +  for (facet= facetlist; facet && (nextfacet= facet->next); facet= nextfacet) {
    +    if (facet->normal)
    +      continue;
    +    if (!facet->mergehorizon) {
    +      qh_fprintf(qh ferr, 6225, "Qhull internal error (qh_mergecycle_all): f%d without normal\n", facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    horizon= SETfirstt_(facet->neighbors, facetT);
    +    if (facet->f.samecycle == facet) {
    +      zinc_(Zonehorizon);
    +      /* merge distance done in qh_findhorizon */
    +      apex= SETfirstt_(facet->vertices, vertexT);
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex != apex)
    +          vertex->delridge= True;
    +      }
    +      horizon->f.newcycle= NULL;
    +      qh_mergefacet(facet, horizon, NULL, NULL, qh_MERGEapex);
    +    }else {
    +      samecycle= facet;
    +      facets= 0;
    +      prev= facet;
    +      for (same= facet->f.samecycle; same;  /* FORALLsame_cycle_(facet) */
    +           same= (same == facet ? NULL :nextsame)) { /* ends at facet */
    +        nextsame= same->f.samecycle;
    +        if (same->cycledone || same->visible)
    +          qh_infiniteloop(same);
    +        same->cycledone= True;
    +        if (same->normal) {
    +          prev->f.samecycle= same->f.samecycle; /* unlink ->mergeridge */
    +          same->f.samecycle= NULL;
    +        }else {
    +          prev= same;
    +          facets++;
    +        }
    +      }
    +      while (nextfacet && nextfacet->cycledone)  /* will delete samecycle */
    +        nextfacet= nextfacet->next;
    +      horizon->f.newcycle= NULL;
    +      qh_mergecycle(samecycle, horizon);
    +      nummerge= horizon->nummerge + facets;
    +      if (nummerge > qh_MAXnummerge)
    +        horizon->nummerge= qh_MAXnummerge;
    +      else
    +        horizon->nummerge= (short unsigned int)nummerge;
    +      zzinc_(Zcyclehorizon);
    +      total += facets;
    +      zzadd_(Zcyclefacettot, facets);
    +      zmax_(Zcyclefacetmax, facets);
    +    }
    +    cycles++;
    +  }
    +  if (cycles)
    +    *wasmerge= True;
    +  trace1((qh ferr, 1013, "qh_mergecycle_all: merged %d same cycles or facets into coplanar horizons\n", cycles));
    +} /* mergecycle_all */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_facets( samecycle, newfacet )
    +    finish merge of samecycle into newfacet
    +
    +  returns:
    +    samecycle prepended to visible_list for later deletion and partitioning
    +      each facet->f.replace == newfacet
    +
    +    newfacet moved to end of qh.facet_list
    +      makes newfacet a newfacet (get's facet1->id if it was old)
    +      sets newfacet->newmerge
    +      clears newfacet->center (unless merging into a large facet)
    +      clears newfacet->tested and ridge->tested for facet1
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  design:
    +    make newfacet a new facet and set its flags
    +    move samecycle facets to qh.visible_list for later deletion
    +    unless newfacet is large
    +      remove its centrum
    +*/
    +void qh_mergecycle_facets(facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *next;
    +
    +  trace4((qh ferr, 4030, "qh_mergecycle_facets: make newfacet new and samecycle deleted\n"));
    +  qh_removefacet(newfacet);  /* append as a newfacet to end of qh facet_list */
    +  qh_appendfacet(newfacet);
    +  newfacet->newfacet= True;
    +  newfacet->simplicial= False;
    +  newfacet->newmerge= True;
    +
    +  for (same= samecycle->f.samecycle; same; same= (same == samecycle ?  NULL : next)) {
    +    next= same->f.samecycle;  /* reused by willdelete */
    +    qh_willdelete(same, newfacet);
    +  }
    +  if (newfacet->center
    +      && qh_setsize(newfacet->vertices) <= qh hull_dim + qh_MAXnewcentrum) {
    +    qh_memfree(newfacet->center, qh normal_size);
    +    newfacet->center= NULL;
    +  }
    +  trace3((qh ferr, 3004, "qh_mergecycle_facets: merged facets from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_facets */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_neighbors( samecycle, newfacet )
    +    add neighbors for samecycle facets to newfacet
    +
    +  returns:
    +    newfacet with updated neighbors and vice-versa
    +    newfacet has ridges
    +    all neighbors of newfacet marked with qh.visit_id
    +    samecycle facets marked with qh.visit_id-1
    +    ridges updated for simplicial neighbors of samecycle with a ridge
    +
    +  notes:
    +    assumes newfacet not in samecycle
    +    usually, samecycle facets are new, simplicial facets without internal ridges
    +      not so if horizon facet is coplanar to two different samecycles
    +
    +  see:
    +    qh_mergeneighbors()
    +
    +  design:
    +    check samecycle
    +    delete neighbors from newfacet that are also in samecycle
    +    for each neighbor of a facet in samecycle
    +      if neighbor is simplicial
    +        if first visit
    +          move the neighbor relation to newfacet
    +          update facet links for its ridges
    +        else
    +          make ridges for neighbor
    +          remove samecycle reference
    +      else
    +        update neighbor sets
    +*/
    +void qh_mergecycle_neighbors(facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor, **neighborp;
    +  int delneighbors= 0, newneighbors= 0;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +
    +  samevisitid= ++qh visit_id;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->visitid == samevisitid || same->visible)
    +      qh_infiniteloop(samecycle);
    +    same->visitid= samevisitid;
    +  }
    +  newfacet->visitid= ++qh visit_id;
    +  trace4((qh ferr, 4031, "qh_mergecycle_neighbors: delete shared neighbors from newfacet\n"));
    +  FOREACHneighbor_(newfacet) {
    +    if (neighbor->visitid == samevisitid) {
    +      SETref_(neighbor)= NULL;  /* samecycle neighbors deleted */
    +      delneighbors++;
    +    }else
    +      neighbor->visitid= qh visit_id;
    +  }
    +  qh_setcompact(newfacet->neighbors);
    +
    +  trace4((qh ferr, 4032, "qh_mergecycle_neighbors: update neighbors\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHneighbor_(same) {
    +      if (neighbor->visitid == samevisitid)
    +        continue;
    +      if (neighbor->simplicial) {
    +        if (neighbor->visitid != qh visit_id) {
    +          qh_setappend(&newfacet->neighbors, neighbor);
    +          qh_setreplace(neighbor->neighbors, same, newfacet);
    +          newneighbors++;
    +          neighbor->visitid= qh visit_id;
    +          FOREACHridge_(neighbor->ridges) { /* update ridge in case of qh_makeridges */
    +            if (ridge->top == same) {
    +              ridge->top= newfacet;
    +              break;
    +            }else if (ridge->bottom == same) {
    +              ridge->bottom= newfacet;
    +              break;
    +            }
    +          }
    +        }else {
    +          qh_makeridges(neighbor);
    +          qh_setdel(neighbor->neighbors, same);
    +          /* same can't be horizon facet for neighbor */
    +        }
    +      }else { /* non-simplicial neighbor */
    +        qh_setdel(neighbor->neighbors, same);
    +        if (neighbor->visitid != qh visit_id) {
    +          qh_setappend(&neighbor->neighbors, newfacet);
    +          qh_setappend(&newfacet->neighbors, neighbor);
    +          neighbor->visitid= qh visit_id;
    +          newneighbors++;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh ferr, 2032, "qh_mergecycle_neighbors: deleted %d neighbors and added %d\n",
    +             delneighbors, newneighbors));
    +} /* mergecycle_neighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_ridges( samecycle, newfacet )
    +    add ridges/neighbors for facets in samecycle to newfacet
    +    all new/old neighbors of newfacet marked with qh.visit_id
    +    facets in samecycle marked with qh.visit_id-1
    +    newfacet marked with qh.visit_id
    +
    +  returns:
    +    newfacet has merged ridges
    +
    +  notes:
    +    ridge already updated for simplicial neighbors of samecycle with a ridge
    +
    +  see:
    +    qh_mergeridges()
    +    qh_makeridges()
    +
    +  design:
    +    remove ridges between newfacet and samecycle
    +    for each facet in samecycle
    +      for each ridge in facet
    +        update facet pointers in ridge
    +        skip ridges processed in qh_mergecycle_neighors
    +        free ridges between newfacet and samecycle
    +        free ridges between facets of samecycle (on 2nd visit)
    +        append remaining ridges to newfacet
    +      if simpilicial facet
    +        for each neighbor of facet
    +          if simplicial facet
    +          and not samecycle facet or newfacet
    +            make ridge between neighbor and newfacet
    +*/
    +void qh_mergecycle_ridges(facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor= NULL;
    +  int numold=0, numnew=0;
    +  int neighbor_i, neighbor_n;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +  boolT toporient;
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh ferr, 4033, "qh_mergecycle_ridges: delete shared ridges from newfacet\n"));
    +  samevisitid= qh visit_id -1;
    +  FOREACHridge_(newfacet->ridges) {
    +    neighbor= otherfacet_(ridge, newfacet);
    +    if (neighbor->visitid == samevisitid)
    +      SETref_(ridge)= NULL; /* ridge free'd below */
    +  }
    +  qh_setcompact(newfacet->ridges);
    +
    +  trace4((qh ferr, 4034, "qh_mergecycle_ridges: add ridges to newfacet\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHridge_(same->ridges) {
    +      if (ridge->top == same) {
    +        ridge->top= newfacet;
    +        neighbor= ridge->bottom;
    +      }else if (ridge->bottom == same) {
    +        ridge->bottom= newfacet;
    +        neighbor= ridge->top;
    +      }else if (ridge->top == newfacet || ridge->bottom == newfacet) {
    +        qh_setappend(&newfacet->ridges, ridge);
    +        numold++;  /* already set by qh_mergecycle_neighbors */
    +        continue;
    +      }else {
    +        qh_fprintf(qh ferr, 6098, "qhull internal error (qh_mergecycle_ridges): bad ridge r%d\n", ridge->id);
    +        qh_errexit(qh_ERRqhull, NULL, ridge);
    +      }
    +      if (neighbor == newfacet) {
    +        qh_setfree(&(ridge->vertices));
    +        qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else if (neighbor->visitid == samevisitid) {
    +        qh_setdel(neighbor->ridges, ridge);
    +        qh_setfree(&(ridge->vertices));
    +        qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else {
    +        qh_setappend(&newfacet->ridges, ridge);
    +        numold++;
    +      }
    +    }
    +    if (same->ridges)
    +      qh_settruncate(same->ridges, 0);
    +    if (!same->simplicial)
    +      continue;
    +    FOREACHneighbor_i_(same) {       /* note: !newfact->simplicial */
    +      if (neighbor->visitid != samevisitid && neighbor->simplicial) {
    +        ridge= qh_newridge();
    +        ridge->vertices= qh_setnew_delnthsorted(same->vertices, qh hull_dim,
    +                                                          neighbor_i, 0);
    +        toporient= same->toporient ^ (neighbor_i & 0x1);
    +        if (toporient) {
    +          ridge->top= newfacet;
    +          ridge->bottom= neighbor;
    +        }else {
    +          ridge->top= neighbor;
    +          ridge->bottom= newfacet;
    +        }
    +        qh_setappend(&(newfacet->ridges), ridge);
    +        qh_setappend(&(neighbor->ridges), ridge);
    +        numnew++;
    +      }
    +    }
    +  }
    +
    +  trace2((qh ferr, 2033, "qh_mergecycle_ridges: found %d old ridges and %d new ones\n",
    +             numold, numnew));
    +} /* mergecycle_ridges */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_vneighbors( samecycle, newfacet )
    +    create vertex neighbors for newfacet from vertices of facets in samecycle
    +    samecycle marked with visitid == qh.visit_id - 1
    +
    +  returns:
    +    newfacet vertices with updated neighbors
    +    marks newfacet with qh.visit_id-1
    +    deletes vertices that are merged away
    +    sets delridge on all vertices (faster here than in mergecycle_ridges)
    +
    +  see:
    +    qh_mergevertex_neighbors()
    +
    +  design:
    +    for each vertex of samecycle facet
    +      set vertex->delridge
    +      delete samecycle facets from vertex neighbors
    +      append newfacet to vertex neighbors
    +      if vertex only in newfacet
    +        delete it from newfacet
    +        add it to qh.del_vertices for later deletion
    +*/
    +void qh_mergecycle_vneighbors(facetT *samecycle, facetT *newfacet) {
    +  facetT *neighbor, **neighborp;
    +  unsigned int mergeid;
    +  vertexT *vertex, **vertexp, *apex;
    +  setT *vertices;
    +
    +  trace4((qh ferr, 4035, "qh_mergecycle_vneighbors: update vertex neighbors for newfacet\n"));
    +  mergeid= qh visit_id - 1;
    +  newfacet->visitid= mergeid;
    +  vertices= qh_basevertices(samecycle); /* temp */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_setappend(&vertices, apex);
    +  FOREACHvertex_(vertices) {
    +    vertex->delridge= True;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == mergeid)
    +        SETref_(neighbor)= NULL;
    +    }
    +    qh_setcompact(vertex->neighbors);
    +    qh_setappend(&vertex->neighbors, newfacet);
    +    if (!SETsecond_(vertex->neighbors)) {
    +      zinc_(Zcyclevertex);
    +      trace2((qh ferr, 2034, "qh_mergecycle_vneighbors: deleted v%d when merging cycle f%d into f%d\n",
    +        vertex->id, samecycle->id, newfacet->id));
    +      qh_setdelsorted(newfacet->vertices, vertex);
    +      vertex->deleted= True;
    +      qh_setappend(&qh del_vertices, vertex);
    +    }
    +  }
    +  qh_settempfree(&vertices);
    +  trace3((qh ferr, 3005, "qh_mergecycle_vneighbors: merged vertices from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergefacet( facet1, facet2, mindist, maxdist, mergeapex )
    +    merges facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging new facet into coplanar horizon
    +
    +  returns:
    +    qh.max_outside and qh.min_vertex updated
    +    initializes vertex neighbors on first merge
    +
    +  returns:
    +    facet2 contains facet1's vertices, neighbors, and ridges
    +      facet2 moved to end of qh.facet_list
    +      makes facet2 a newfacet
    +      sets facet2->newmerge set
    +      clears facet2->center (unless merging into a large facet)
    +      clears facet2->tested and ridge->tested for facet1
    +
    +    facet1 prepended to visible_list for later deletion and partitioning
    +      facet1->f.replace == facet2
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  notes:
    +    mindist/maxdist may be NULL (only if both NULL)
    +    traces merge if fmax_(maxdist,-mindist) > TRACEdist
    +
    +  see:
    +    qh_mergecycle()
    +
    +  design:
    +    trace merge and check for degenerate simplex
    +    make ridges for both facets
    +    update qh.max_outside, qh.max_vertex, qh.min_vertex
    +    update facet2->maxoutside and keepcentrum
    +    update facet2->nummerge
    +    update tested flags for facet2
    +    if facet1 is simplicial
    +      merge facet1 into facet2
    +    else
    +      merge facet1's neighbors into facet2
    +      merge facet1's ridges into facet2
    +      merge facet1's vertices into facet2
    +      merge facet1's vertex neighbors into facet2
    +      add facet2's vertices to qh.new_vertexlist
    +      unless qh_MERGEapex
    +        test facet2 for degenerate or redundant neighbors
    +      move facet1 to qh.visible_list for later deletion
    +      move facet2 to end of qh.newfacet_list
    +*/
    +void qh_mergefacet(facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex) {
    +  boolT traceonce= False;
    +  vertexT *vertex, **vertexp;
    +  int tracerestore=0, nummerge;
    +
    +  if (facet1->tricoplanar || facet2->tricoplanar) {
    +    if (!qh TRInormals) {
    +      qh_fprintf(qh ferr, 6226, "Qhull internal error (qh_mergefacet): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit2(qh_ERRqhull, facet1, facet2);
    +    }
    +    if (facet2->tricoplanar) {
    +      facet2->tricoplanar= False;
    +      facet2->keepcentrum= False;
    +    }
    +  }
    +  zzinc_(Ztotmerge);
    +  if (qh REPORTfreq2 && qh POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh mergereport + qh REPORTfreq2)
    +      qh_tracemerging();
    +  }
    +#ifndef qh_NOtrace
    +  if (qh build_cnt >= qh RERUN) {
    +    if (mindist && (-*mindist > qh TRACEdist || *maxdist > qh TRACEdist)) {
    +      tracerestore= 0;
    +      qh IStracing= qh TRACElevel;
    +      traceonce= True;
    +      qh_fprintf(qh ferr, 8075, "qh_mergefacet: ========= trace wide merge #%d(%2.2g) for f%d into f%d, last point was p%d\n", zzval_(Ztotmerge),
    +             fmax_(-*mindist, *maxdist), facet1->id, facet2->id, qh furthest_id);
    +    }else if (facet1 == qh tracefacet || facet2 == qh tracefacet) {
    +      tracerestore= qh IStracing;
    +      qh IStracing= 4;
    +      traceonce= True;
    +      qh_fprintf(qh ferr, 8076, "qh_mergefacet: ========= trace merge #%d involving f%d, furthest is p%d\n",
    +                 zzval_(Ztotmerge), qh tracefacet_id,  qh furthest_id);
    +    }
    +  }
    +  if (qh IStracing >= 2) {
    +    realT mergemin= -2;
    +    realT mergemax= -2;
    +
    +    if (mindist) {
    +      mergemin= *mindist;
    +      mergemax= *maxdist;
    +    }
    +    qh_fprintf(qh ferr, 8077, "qh_mergefacet: #%d merge f%d into f%d, mindist= %2.2g, maxdist= %2.2g\n",
    +    zzval_(Ztotmerge), facet1->id, facet2->id, mergemin, mergemax);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (facet1 == facet2 || facet1->visible || facet2->visible) {
    +    qh_fprintf(qh ferr, 6099, "qhull internal error (qh_mergefacet): either f%d and f%d are the same or one is a visible facet\n",
    +             facet1->id, facet2->id);
    +    qh_errexit2(qh_ERRqhull, facet1, facet2);
    +  }
    +  if (qh num_facets - qh num_visible <= qh hull_dim + 1) {
    +    qh_fprintf(qh ferr, 6227, "\n\
    +qhull precision error: Only %d facets remain.  Can not merge another\n\
    +pair.  The input is too degenerate or the convexity constraints are\n\
    +too strong.\n", qh hull_dim+1);
    +    if (qh hull_dim >= 5 && !qh MERGEexact)
    +      qh_fprintf(qh ferr, 8079, "Option 'Qx' may avoid this problem.\n");
    +    qh_errexit(qh_ERRprec, NULL, NULL);
    +  }
    +  if (!qh VERTEXneighbors)
    +    qh_vertexneighbors();
    +  qh_makeridges(facet1);
    +  qh_makeridges(facet2);
    +  if (qh IStracing >=4)
    +    qh_errprint("MERGING", facet1, facet2, NULL, NULL);
    +  if (mindist) {
    +    maximize_(qh max_outside, *maxdist);
    +    maximize_(qh max_vertex, *maxdist);
    +#if qh_MAXoutside
    +    maximize_(facet2->maxoutside, *maxdist);
    +#endif
    +    minimize_(qh min_vertex, *mindist);
    +    if (!facet2->keepcentrum
    +    && (*maxdist > qh WIDEfacet || *mindist < -qh WIDEfacet)) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidefacet);
    +    }
    +  }
    +  nummerge= facet1->nummerge + facet2->nummerge + 1;
    +  if (nummerge >= qh_MAXnummerge)
    +    facet2->nummerge= qh_MAXnummerge;
    +  else
    +    facet2->nummerge= (short unsigned int)nummerge;
    +  facet2->newmerge= True;
    +  facet2->dupridge= False;
    +  qh_updatetested(facet1, facet2);
    +  if (qh hull_dim > 2 && qh_setsize(facet1->vertices) == qh hull_dim)
    +    qh_mergesimplex(facet1, facet2, mergeapex);
    +  else {
    +    qh vertex_visit++;
    +    FOREACHvertex_(facet2->vertices)
    +      vertex->visitid= qh vertex_visit;
    +    if (qh hull_dim == 2)
    +      qh_mergefacet2d(facet1, facet2);
    +    else {
    +      qh_mergeneighbors(facet1, facet2);
    +      qh_mergevertices(facet1->vertices, &facet2->vertices);
    +    }
    +    qh_mergeridges(facet1, facet2);
    +    qh_mergevertex_neighbors(facet1, facet2);
    +    if (!facet2->newfacet)
    +      qh_newvertices(facet2->vertices);
    +  }
    +  if (!mergeapex)
    +    qh_degen_redundant_neighbors(facet2, facet1);
    +  if (facet2->coplanar || !facet2->newfacet) {
    +    zinc_(Zmergeintohorizon);
    +  }else if (!facet1->newfacet && facet2->newfacet) {
    +    zinc_(Zmergehorizon);
    +  }else {
    +    zinc_(Zmergenew);
    +  }
    +  qh_willdelete(facet1, facet2);
    +  qh_removefacet(facet2);  /* append as a newfacet to end of qh facet_list */
    +  qh_appendfacet(facet2);
    +  facet2->newfacet= True;
    +  facet2->tested= False;
    +  qh_tracemerge(facet1, facet2);
    +  if (traceonce) {
    +    qh_fprintf(qh ferr, 8080, "qh_mergefacet: end of wide tracing\n");
    +    qh IStracing= tracerestore;
    +  }
    +} /* mergefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergefacet2d( facet1, facet2 )
    +    in 2d, merges neighbors and vertices of facet1 into facet2
    +
    +  returns:
    +    build ridges for neighbors if necessary
    +    facet2 looks like a simplicial facet except for centrum, ridges
    +      neighbors are opposite the corresponding vertex
    +      maintains orientation of facet2
    +
    +  notes:
    +    qh_mergefacet() retains non-simplicial structures
    +      they are not needed in 2d, but later routines may use them
    +    preserves qh.vertex_visit for qh_mergevertex_neighbors()
    +
    +  design:
    +    get vertices and neighbors
    +    determine new vertices and neighbors
    +    set new vertices and neighbors and adjust orientation
    +    make ridges for new neighbor if needed
    +*/
    +void qh_mergefacet2d(facetT *facet1, facetT *facet2) {
    +  vertexT *vertex1A, *vertex1B, *vertex2A, *vertex2B, *vertexA, *vertexB;
    +  facetT *neighbor1A, *neighbor1B, *neighbor2A, *neighbor2B, *neighborA, *neighborB;
    +
    +  vertex1A= SETfirstt_(facet1->vertices, vertexT);
    +  vertex1B= SETsecondt_(facet1->vertices, vertexT);
    +  vertex2A= SETfirstt_(facet2->vertices, vertexT);
    +  vertex2B= SETsecondt_(facet2->vertices, vertexT);
    +  neighbor1A= SETfirstt_(facet1->neighbors, facetT);
    +  neighbor1B= SETsecondt_(facet1->neighbors, facetT);
    +  neighbor2A= SETfirstt_(facet2->neighbors, facetT);
    +  neighbor2B= SETsecondt_(facet2->neighbors, facetT);
    +  if (vertex1A == vertex2A) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1A;
    +  }else if (vertex1A == vertex2B) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1A;
    +  }else if (vertex1B == vertex2A) {
    +    vertexA= vertex1A;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1B;
    +  }else { /* 1B == 2B */
    +    vertexA= vertex1A;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1B;
    +  }
    +  /* vertexB always from facet2, neighborB always from facet1 */
    +  if (vertexA->id > vertexB->id) {
    +    SETfirst_(facet2->vertices)= vertexA;
    +    SETsecond_(facet2->vertices)= vertexB;
    +    if (vertexB == vertex2A)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborA;
    +    SETsecond_(facet2->neighbors)= neighborB;
    +  }else {
    +    SETfirst_(facet2->vertices)= vertexB;
    +    SETsecond_(facet2->vertices)= vertexA;
    +    if (vertexB == vertex2B)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborB;
    +    SETsecond_(facet2->neighbors)= neighborA;
    +  }
    +  qh_makeridges(neighborB);
    +  qh_setreplace(neighborB->neighbors, facet1, facet2);
    +  trace4((qh ferr, 4036, "qh_mergefacet2d: merged v%d and neighbor f%d of f%d into f%d\n",
    +       vertexA->id, neighborB->id, facet1->id, facet2->id));
    +} /* mergefacet2d */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeneighbors( facet1, facet2 )
    +    merges the neighbors of facet1 into facet2
    +
    +  see:
    +    qh_mergecycle_neighbors()
    +
    +  design:
    +    for each neighbor of facet1
    +      if neighbor is also a neighbor of facet2
    +        if neighbor is simpilicial
    +          make ridges for later deletion as a degenerate facet
    +        update its neighbor set
    +      else
    +        move the neighbor relation to facet2
    +    remove the neighbor relation for facet1 and facet2
    +*/
    +void qh_mergeneighbors(facetT *facet1, facetT *facet2) {
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh ferr, 4037, "qh_mergeneighbors: merge neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  qh visit_id++;
    +  FOREACHneighbor_(facet2) {
    +    neighbor->visitid= qh visit_id;
    +  }
    +  FOREACHneighbor_(facet1) {
    +    if (neighbor->visitid == qh visit_id) {
    +      if (neighbor->simplicial)    /* is degen, needs ridges */
    +        qh_makeridges(neighbor);
    +      if (SETfirstt_(neighbor->neighbors, facetT) != facet1) /*keep newfacet->horizon*/
    +        qh_setdel(neighbor->neighbors, facet1);
    +      else {
    +        qh_setdel(neighbor->neighbors, facet2);
    +        qh_setreplace(neighbor->neighbors, facet1, facet2);
    +      }
    +    }else if (neighbor != facet2) {
    +      qh_setappend(&(facet2->neighbors), neighbor);
    +      qh_setreplace(neighbor->neighbors, facet1, facet2);
    +    }
    +  }
    +  qh_setdel(facet1->neighbors, facet2);  /* here for makeridges */
    +  qh_setdel(facet2->neighbors, facet1);
    +} /* mergeneighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeridges( facet1, facet2 )
    +    merges the ridge set of facet1 into facet2
    +
    +  returns:
    +    may delete all ridges for a vertex
    +    sets vertex->delridge on deleted ridges
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    delete ridges between facet1 and facet2
    +      mark (delridge) vertices on these ridges for later testing
    +    for each remaining ridge
    +      rename facet1 to facet2
    +*/
    +void qh_mergeridges(facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh ferr, 4038, "qh_mergeridges: merge ridges of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  FOREACHridge_(facet2->ridges) {
    +    if ((ridge->top == facet1) || (ridge->bottom == facet1)) {
    +      FOREACHvertex_(ridge->vertices)
    +        vertex->delridge= True;
    +      qh_delridge(ridge);  /* expensive in high-d, could rebuild */
    +      ridgep--; /*repeat*/
    +    }
    +  }
    +  FOREACHridge_(facet1->ridges) {
    +    if (ridge->top == facet1)
    +      ridge->top= facet2;
    +    else
    +      ridge->bottom= facet2;
    +    qh_setappend(&(facet2->ridges), ridge);
    +  }
    +} /* mergeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergesimplex( facet1, facet2, mergeapex )
    +    merge simplicial facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging samecycle into horizon facet
    +      vertex id is latest (most recently created)
    +    facet1 may be contained in facet2
    +    ridges exist for both facets
    +
    +  returns:
    +    facet2 with updated vertices, ridges, neighbors
    +    updated neighbors for facet1's vertices
    +    facet1 not deleted
    +    sets vertex->delridge on deleted ridges
    +
    +  notes:
    +    special case code since this is the most common merge
    +    called from qh_mergefacet()
    +
    +  design:
    +    if qh_MERGEapex
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to facet2
    +    else
    +      for each ridge between facet1 and facet2
    +        set vertex->delridge
    +      determine the apex for facet1 (i.e., vertex to be merged)
    +      unless apex already in facet2
    +        insert apex into vertices for facet2
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to qh.new_vertexlist if necessary
    +      for each vertex of facet1
    +        if apex
    +          rename facet1 to facet2 in its vertex neighbors
    +        else
    +          delete facet1 from vertex neighors
    +          if only in facet2
    +            add vertex to qh.del_vertices for later deletion
    +      for each ridge of facet1
    +        delete ridges between facet1 and facet2
    +        append other ridges to facet2 after renaming facet to facet2
    +*/
    +void qh_mergesimplex(facetT *facet1, facetT *facet2, boolT mergeapex) {
    +  vertexT *vertex, **vertexp, *apex;
    +  ridgeT *ridge, **ridgep;
    +  boolT issubset= False;
    +  int vertex_i= -1, vertex_n;
    +  facetT *neighbor, **neighborp, *otherfacet;
    +
    +  if (mergeapex) {
    +    if (!facet2->newfacet)
    +      qh_newvertices(facet2->vertices);  /* apex is new */
    +    apex= SETfirstt_(facet1->vertices, vertexT);
    +    if (SETfirstt_(facet2->vertices, vertexT) != apex)
    +      qh_setaddnth(&facet2->vertices, 0, apex);  /* apex has last id */
    +    else
    +      issubset= True;
    +  }else {
    +    zinc_(Zmergesimplex);
    +    FOREACHvertex_(facet1->vertices)
    +      vertex->seen= False;
    +    FOREACHridge_(facet1->ridges) {
    +      if (otherfacet_(ridge, facet1) == facet2) {
    +        FOREACHvertex_(ridge->vertices) {
    +          vertex->seen= True;
    +          vertex->delridge= True;
    +        }
    +        break;
    +      }
    +    }
    +    FOREACHvertex_(facet1->vertices) {
    +      if (!vertex->seen)
    +        break;  /* must occur */
    +    }
    +    apex= vertex;
    +    trace4((qh ferr, 4039, "qh_mergesimplex: merge apex v%d of f%d into facet f%d\n",
    +          apex->id, facet1->id, facet2->id));
    +    FOREACHvertex_i_(facet2->vertices) {
    +      if (vertex->id < apex->id) {
    +        break;
    +      }else if (vertex->id == apex->id) {
    +        issubset= True;
    +        break;
    +      }
    +    }
    +    if (!issubset)
    +      qh_setaddnth(&facet2->vertices, vertex_i, apex);
    +    if (!facet2->newfacet)
    +      qh_newvertices(facet2->vertices);
    +    else if (!apex->newlist) {
    +      qh_removevertex(apex);
    +      qh_appendvertex(apex);
    +    }
    +  }
    +  trace4((qh ferr, 4040, "qh_mergesimplex: update vertex neighbors of f%d\n",
    +          facet1->id));
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex == apex && !issubset)
    +      qh_setreplace(vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(vertex, facet1, facet2);
    +    }
    +  }
    +  trace4((qh ferr, 4041, "qh_mergesimplex: merge ridges and neighbors of f%d into f%d\n",
    +          facet1->id, facet2->id));
    +  qh visit_id++;
    +  FOREACHneighbor_(facet2)
    +    neighbor->visitid= qh visit_id;
    +  FOREACHridge_(facet1->ridges) {
    +    otherfacet= otherfacet_(ridge, facet1);
    +    if (otherfacet == facet2) {
    +      qh_setdel(facet2->ridges, ridge);
    +      qh_setfree(&(ridge->vertices));
    +      qh_memfree(ridge, (int)sizeof(ridgeT));
    +      qh_setdel(facet2->neighbors, facet1);
    +    }else {
    +      qh_setappend(&facet2->ridges, ridge);
    +      if (otherfacet->visitid != qh visit_id) {
    +        qh_setappend(&facet2->neighbors, otherfacet);
    +        qh_setreplace(otherfacet->neighbors, facet1, facet2);
    +        otherfacet->visitid= qh visit_id;
    +      }else {
    +        if (otherfacet->simplicial)    /* is degen, needs ridges */
    +          qh_makeridges(otherfacet);
    +        if (SETfirstt_(otherfacet->neighbors, facetT) != facet1)
    +          qh_setdel(otherfacet->neighbors, facet1);
    +        else {   /*keep newfacet->neighbors->horizon*/
    +          qh_setdel(otherfacet->neighbors, facet2);
    +          qh_setreplace(otherfacet->neighbors, facet1, facet2);
    +        }
    +      }
    +      if (ridge->top == facet1) /* wait until after qh_makeridges */
    +        ridge->top= facet2;
    +      else
    +        ridge->bottom= facet2;
    +    }
    +  }
    +  SETfirst_(facet1->ridges)= NULL; /* it will be deleted */
    +  trace3((qh ferr, 3006, "qh_mergesimplex: merged simplex f%d apex v%d into facet f%d\n",
    +          facet1->id, getid_(apex), facet2->id));
    +} /* mergesimplex */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_del( vertex, facet1, facet2 )
    +    delete a vertex because of merging facet1 into facet2
    +
    +  returns:
    +    deletes vertex from facet2
    +    adds vertex to qh.del_vertices for later deletion
    +*/
    +void qh_mergevertex_del(vertexT *vertex, facetT *facet1, facetT *facet2) {
    +
    +  zinc_(Zmergevertex);
    +  trace2((qh ferr, 2035, "qh_mergevertex_del: deleted v%d when merging f%d into f%d\n",
    +          vertex->id, facet1->id, facet2->id));
    +  qh_setdelsorted(facet2->vertices, vertex);
    +  vertex->deleted= True;
    +  qh_setappend(&qh del_vertices, vertex);
    +} /* mergevertex_del */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_neighbors( facet1, facet2 )
    +    merge the vertex neighbors of facet1 to facet2
    +
    +  returns:
    +    if vertex is current qh.vertex_visit
    +      deletes facet1 from vertex->neighbors
    +    else
    +      renames facet1 to facet2 in vertex->neighbors
    +    deletes vertices if only one neighbor
    +
    +  notes:
    +    assumes vertex neighbor sets are good
    +*/
    +void qh_mergevertex_neighbors(facetT *facet1, facetT *facet2) {
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh ferr, 4042, "qh_mergevertex_neighbors: merge vertex neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  if (qh tracevertex) {
    +    qh_fprintf(qh ferr, 8081, "qh_mergevertex_neighbors: of f%d and f%d at furthest p%d f0= %p\n",
    +             facet1->id, facet2->id, qh furthest_id, qh tracevertex->neighbors->e[0].p);
    +    qh_errprint("TRACE", NULL, NULL, NULL, qh tracevertex);
    +  }
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex->visitid != qh vertex_visit)
    +      qh_setreplace(vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(vertex, facet1, facet2);
    +    }
    +  }
    +  if (qh tracevertex)
    +    qh_errprint("TRACE", NULL, NULL, NULL, qh tracevertex);
    +} /* mergevertex_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergevertices( vertices1, vertices2 )
    +    merges the vertex set of facet1 into facet2
    +
    +  returns:
    +    replaces vertices2 with merged set
    +    preserves vertex_visit for qh_mergevertex_neighbors
    +    updates qh.newvertex_list
    +
    +  design:
    +    create a merged set of both vertices (in inverse id order)
    +*/
    +void qh_mergevertices(setT *vertices1, setT **vertices2) {
    +  int newsize= qh_setsize(vertices1)+qh_setsize(*vertices2) - qh hull_dim + 1;
    +  setT *mergedvertices;
    +  vertexT *vertex, **vertexp, **vertex2= SETaddr_(*vertices2, vertexT);
    +
    +  mergedvertices= qh_settemp(newsize);
    +  FOREACHvertex_(vertices1) {
    +    if (!*vertex2 || vertex->id > (*vertex2)->id)
    +      qh_setappend(&mergedvertices, vertex);
    +    else {
    +      while (*vertex2 && (*vertex2)->id > vertex->id)
    +        qh_setappend(&mergedvertices, *vertex2++);
    +      if (!*vertex2 || (*vertex2)->id < vertex->id)
    +        qh_setappend(&mergedvertices, vertex);
    +      else
    +        qh_setappend(&mergedvertices, *vertex2++);
    +    }
    +  }
    +  while (*vertex2)
    +    qh_setappend(&mergedvertices, *vertex2++);
    +  if (newsize < qh_setsize(mergedvertices)) {
    +    qh_fprintf(qh ferr, 6100, "qhull internal error (qh_mergevertices): facets did not share a ridge\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(vertices2);
    +  *vertices2= mergedvertices;
    +  qh_settemppop();
    +} /* mergevertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_neighbor_intersections( vertex )
    +    return intersection of all vertices in vertex->neighbors except for vertex
    +
    +  returns:
    +    returns temporary set of vertices
    +    does not include vertex
    +    NULL if a neighbor is simplicial
    +    NULL if empty set
    +
    +  notes:
    +    used for renaming vertices
    +
    +  design:
    +    initialize the intersection set with vertices of the first two neighbors
    +    delete vertex from the intersection
    +    for each remaining neighbor
    +      intersect its vertex set with the intersection set
    +      return NULL if empty
    +    return the intersection set
    +*/
    +setT *qh_neighbor_intersections(vertexT *vertex) {
    +  facetT *neighbor, **neighborp, *neighborA, *neighborB;
    +  setT *intersect;
    +  int neighbor_i, neighbor_n;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->simplicial)
    +      return NULL;
    +  }
    +  neighborA= SETfirstt_(vertex->neighbors, facetT);
    +  neighborB= SETsecondt_(vertex->neighbors, facetT);
    +  zinc_(Zintersectnum);
    +  if (!neighborA)
    +    return NULL;
    +  if (!neighborB)
    +    intersect= qh_setcopy(neighborA->vertices, 0);
    +  else
    +    intersect= qh_vertexintersect_new(neighborA->vertices, neighborB->vertices);
    +  qh_settemppush(intersect);
    +  qh_setdelsorted(intersect, vertex);
    +  FOREACHneighbor_i_(vertex) {
    +    if (neighbor_i >= 2) {
    +      zinc_(Zintersectnum);
    +      qh_vertexintersect(&intersect, neighbor->vertices);
    +      if (!SETfirst_(intersect)) {
    +        zinc_(Zintersectfail);
    +        qh_settempfree(&intersect);
    +        return NULL;
    +      }
    +    }
    +  }
    +  trace3((qh ferr, 3007, "qh_neighbor_intersections: %d vertices in neighbor intersection of v%d\n",
    +          qh_setsize(intersect), vertex->id));
    +  return intersect;
    +} /* neighbor_intersections */
    +
    +/*---------------------------------
    +
    +  qh_newvertices( vertices )
    +    add vertices to end of qh.vertex_list (marks as new vertices)
    +
    +  returns:
    +    vertices on qh.newvertex_list
    +    vertex->newlist set
    +*/
    +void qh_newvertices(setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(vertex);
    +      qh_appendvertex(vertex);
    +    }
    +  }
    +} /* newvertices */
    +
    +/*---------------------------------
    +
    +  qh_reducevertices()
    +    reduce extra vertices, shared vertices, and redundant vertices
    +    facet->newmerge is set if merged since last call
    +    if !qh.MERGEvertices, only removes extra vertices
    +
    +  returns:
    +    True if also merged degen_redundant facets
    +    vertices are renamed if possible
    +    clears facet->newmerge and vertex->delridge
    +
    +  notes:
    +    ignored if 2-d
    +
    +  design:
    +    merge any degenerate or redundant facets
    +    for each newly merged facet
    +      remove extra vertices
    +    if qh.MERGEvertices
    +      for each newly merged facet
    +        for each vertex
    +          if vertex was on a deleted ridge
    +            rename vertex if it is shared
    +      remove delridge flag from new vertices
    +*/
    +boolT qh_reducevertices(void) {
    +  int numshare=0, numrename= 0;
    +  boolT degenredun= False;
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh hull_dim == 2)
    +    return False;
    +  if (qh_merge_degenredundant())
    +    degenredun= True;
    + LABELrestart:
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      if (!qh MERGEvertices)
    +        newfacet->newmerge= False;
    +      qh_remove_extravertices(newfacet);
    +    }
    +  }
    +  if (!qh MERGEvertices)
    +    return False;
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      newfacet->newmerge= False;
    +      FOREACHvertex_(newfacet->vertices) {
    +        if (vertex->delridge) {
    +          if (qh_rename_sharedvertex(vertex, newfacet)) {
    +            numshare++;
    +            vertexp--; /* repeat since deleted vertex */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FORALLvertex_(qh newvertex_list) {
    +    if (vertex->delridge && !vertex->deleted) {
    +      vertex->delridge= False;
    +      if (qh hull_dim >= 4 && qh_redundant_vertex(vertex)) {
    +        numrename++;
    +        if (qh_merge_degenredundant()) {
    +          degenredun= True;
    +          goto LABELrestart;
    +        }
    +      }
    +    }
    +  }
    +  trace1((qh ferr, 1014, "qh_reducevertices: renamed %d shared vertices and %d redundant vertices. Degen? %d\n",
    +          numshare, numrename, degenredun));
    +  return degenredun;
    +} /* reducevertices */
    +
    +/*---------------------------------
    +
    +  qh_redundant_vertex( vertex )
    +    detect and rename a redundant vertex
    +    vertices have full vertex->neighbors
    +
    +  returns:
    +    returns true if find a redundant vertex
    +      deletes vertex(vertex->deleted)
    +
    +  notes:
    +    only needed if vertex->delridge and hull_dim >= 4
    +    may add degenerate facets to qh.facet_mergeset
    +    doesn't change vertex->neighbors or create redundant facets
    +
    +  design:
    +    intersect vertices of all facet neighbors of vertex
    +    determine ridges for these vertices
    +    if find a new vertex for vertex amoung these ridges and vertices
    +      rename vertex to the new vertex
    +*/
    +vertexT *qh_redundant_vertex(vertexT *vertex) {
    +  vertexT *newvertex= NULL;
    +  setT *vertices, *ridges;
    +
    +  trace3((qh ferr, 3008, "qh_redundant_vertex: check if v%d can be renamed\n", vertex->id));
    +  if ((vertices= qh_neighbor_intersections(vertex))) {
    +    ridges= qh_vertexridges(vertex);
    +    if ((newvertex= qh_find_newvertex(vertex, vertices, ridges)))
    +      qh_renamevertex(vertex, newvertex, ridges, NULL, NULL);
    +    qh_settempfree(&ridges);
    +    qh_settempfree(&vertices);
    +  }
    +  return newvertex;
    +} /* redundant_vertex */
    +
    +/*---------------------------------
    +
    +  qh_remove_extravertices( facet )
    +    remove extra vertices from non-simplicial facets
    +
    +  returns:
    +    returns True if it finds them
    +
    +  design:
    +    for each vertex in facet
    +      if vertex not in a ridge (i.e., no longer used)
    +        delete vertex from facet
    +        delete facet from vertice's neighbors
    +        unless vertex in another facet
    +          add vertex to qh.del_vertices for later deletion
    +*/
    +boolT qh_remove_extravertices(facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  boolT foundrem= False;
    +
    +  trace4((qh ferr, 4043, "qh_remove_extravertices: test f%d for extra vertices\n",
    +          facet->id));
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHridge_(facet->ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->seen= True;
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      foundrem= True;
    +      zinc_(Zremvertex);
    +      qh_setdelsorted(facet->vertices, vertex);
    +      qh_setdel(vertex->neighbors, facet);
    +      if (!qh_setsize(vertex->neighbors)) {
    +        vertex->deleted= True;
    +        qh_setappend(&qh del_vertices, vertex);
    +        zinc_(Zremvertexdel);
    +        trace2((qh ferr, 2036, "qh_remove_extravertices: v%d deleted because it's lost all ridges\n", vertex->id));
    +      }else
    +        trace3((qh ferr, 3009, "qh_remove_extravertices: v%d removed from f%d because it's lost all ridges\n", vertex->id, facet->id));
    +      vertexp--; /*repeat*/
    +    }
    +  }
    +  return foundrem;
    +} /* remove_extravertices */
    +
    +/*---------------------------------
    +
    +  qh_rename_sharedvertex( vertex, facet )
    +    detect and rename if shared vertex in facet
    +    vertices have full ->neighbors
    +
    +  returns:
    +    newvertex or NULL
    +    the vertex may still exist in other facets (i.e., a neighbor was pinched)
    +    does not change facet->neighbors
    +    updates vertex->neighbors
    +
    +  notes:
    +    a shared vertex for a facet is only in ridges to one neighbor
    +    this may undo a pinched facet
    +
    +    it does not catch pinches involving multiple facets.  These appear
    +      to be difficult to detect, since an exhaustive search is too expensive.
    +
    +  design:
    +    if vertex only has two neighbors
    +      determine the ridges that contain the vertex
    +      determine the vertices shared by both neighbors
    +      if can find a new vertex in this set
    +        rename the vertex to the new vertex
    +*/
    +vertexT *qh_rename_sharedvertex(vertexT *vertex, facetT *facet) {
    +  facetT *neighbor, **neighborp, *neighborA= NULL;
    +  setT *vertices, *ridges;
    +  vertexT *newvertex;
    +
    +  if (qh_setsize(vertex->neighbors) == 2) {
    +    neighborA= SETfirstt_(vertex->neighbors, facetT);
    +    if (neighborA == facet)
    +      neighborA= SETsecondt_(vertex->neighbors, facetT);
    +  }else if (qh hull_dim == 3)
    +    return NULL;
    +  else {
    +    qh visit_id++;
    +    FOREACHneighbor_(facet)
    +      neighbor->visitid= qh visit_id;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == qh visit_id) {
    +        if (neighborA)
    +          return NULL;
    +        neighborA= neighbor;
    +      }
    +    }
    +    if (!neighborA) {
    +      qh_fprintf(qh ferr, 6101, "qhull internal error (qh_rename_sharedvertex): v%d's neighbors not in f%d\n",
    +        vertex->id, facet->id);
    +      qh_errprint("ERRONEOUS", facet, NULL, NULL, vertex);
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  /* the vertex is shared by facet and neighborA */
    +  ridges= qh_settemp(qh TEMPsize);
    +  neighborA->visitid= ++qh visit_id;
    +  qh_vertexridges_facet(vertex, facet, &ridges);
    +  trace2((qh ferr, 2037, "qh_rename_sharedvertex: p%d(v%d) is shared by f%d(%d ridges) and f%d\n",
    +    qh_pointid(vertex->point), vertex->id, facet->id, qh_setsize(ridges), neighborA->id));
    +  zinc_(Zintersectnum);
    +  vertices= qh_vertexintersect_new(facet->vertices, neighborA->vertices);
    +  qh_setdel(vertices, vertex);
    +  qh_settemppush(vertices);
    +  if ((newvertex= qh_find_newvertex(vertex, vertices, ridges)))
    +    qh_renamevertex(vertex, newvertex, ridges, facet, neighborA);
    +  qh_settempfree(&vertices);
    +  qh_settempfree(&ridges);
    +  return newvertex;
    +} /* rename_sharedvertex */
    +
    +/*---------------------------------
    +
    +  qh_renameridgevertex( ridge, oldvertex, newvertex )
    +    renames oldvertex as newvertex in ridge
    +
    +  returns:
    +
    +  design:
    +    delete oldvertex from ridge
    +    if newvertex already in ridge
    +      copy ridge->noconvex to another ridge if possible
    +      delete the ridge
    +    else
    +      insert newvertex into the ridge
    +      adjust the ridge's orientation
    +*/
    +void qh_renameridgevertex(ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex) {
    +  int nth= 0, oldnth;
    +  facetT *temp;
    +  vertexT *vertex, **vertexp;
    +
    +  oldnth= qh_setindex(ridge->vertices, oldvertex);
    +  qh_setdelnthsorted(ridge->vertices, oldnth);
    +  FOREACHvertex_(ridge->vertices) {
    +    if (vertex == newvertex) {
    +      zinc_(Zdelridge);
    +      if (ridge->nonconvex) /* only one ridge has nonconvex set */
    +        qh_copynonconvex(ridge);
    +      trace2((qh ferr, 2038, "qh_renameridgevertex: ridge r%d deleted.  It contained both v%d and v%d\n",
    +        ridge->id, oldvertex->id, newvertex->id));
    +      qh_delridge(ridge);
    +      return;
    +    }
    +    if (vertex->id < newvertex->id)
    +      break;
    +    nth++;
    +  }
    +  qh_setaddnth(&ridge->vertices, nth, newvertex);
    +  if (abs(oldnth - nth)%2) {
    +    trace3((qh ferr, 3010, "qh_renameridgevertex: swapped the top and bottom of ridge r%d\n",
    +            ridge->id));
    +    temp= ridge->top;
    +    ridge->top= ridge->bottom;
    +    ridge->bottom= temp;
    +  }
    +} /* renameridgevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_renamevertex( oldvertex, newvertex, ridges, oldfacet, neighborA )
    +    renames oldvertex as newvertex in ridges
    +    gives oldfacet/neighborA if oldvertex is shared between two facets
    +
    +  returns:
    +    oldvertex may still exist afterwards
    +
    +
    +  notes:
    +    can not change neighbors of newvertex (since it's a subset)
    +
    +  design:
    +    for each ridge in ridges
    +      rename oldvertex to newvertex and delete degenerate ridges
    +    if oldfacet not defined
    +      for each neighbor of oldvertex
    +        delete oldvertex from neighbor's vertices
    +        remove extra vertices from neighbor
    +      add oldvertex to qh.del_vertices
    +    else if oldvertex only between oldfacet and neighborA
    +      delete oldvertex from oldfacet and neighborA
    +      add oldvertex to qh.del_vertices
    +    else oldvertex is in oldfacet and neighborA and other facets (i.e., pinched)
    +      delete oldvertex from oldfacet
    +      delete oldfacet from oldvertice's neighbors
    +      remove extra vertices (e.g., oldvertex) from neighborA
    +*/
    +void qh_renamevertex(vertexT *oldvertex, vertexT *newvertex, setT *ridges, facetT *oldfacet, facetT *neighborA) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  boolT istrace= False;
    +
    +  if (qh IStracing >= 2 || oldvertex->id == qh tracevertex_id ||
    +        newvertex->id == qh tracevertex_id)
    +    istrace= True;
    +  FOREACHridge_(ridges)
    +    qh_renameridgevertex(ridge, oldvertex, newvertex);
    +  if (!oldfacet) {
    +    zinc_(Zrenameall);
    +    if (istrace)
    +      qh_fprintf(qh ferr, 8082, "qh_renamevertex: renamed v%d to v%d in several facets\n",
    +               oldvertex->id, newvertex->id);
    +    FOREACHneighbor_(oldvertex) {
    +      qh_maydropneighbor(neighbor);
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +      if (qh_remove_extravertices(neighbor))
    +        neighborp--; /* neighbor may be deleted */
    +    }
    +    if (!oldvertex->deleted) {
    +      oldvertex->deleted= True;
    +      qh_setappend(&qh del_vertices, oldvertex);
    +    }
    +  }else if (qh_setsize(oldvertex->neighbors) == 2) {
    +    zinc_(Zrenameshare);
    +    if (istrace)
    +      qh_fprintf(qh ferr, 8083, "qh_renamevertex: renamed v%d to v%d in oldfacet f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id);
    +    FOREACHneighbor_(oldvertex)
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +    oldvertex->deleted= True;
    +    qh_setappend(&qh del_vertices, oldvertex);
    +  }else {
    +    zinc_(Zrenamepinch);
    +    if (istrace || qh IStracing)
    +      qh_fprintf(qh ferr, 8084, "qh_renamevertex: renamed pinched v%d to v%d between f%d and f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id, neighborA->id);
    +    qh_setdelsorted(oldfacet->vertices, oldvertex);
    +    qh_setdel(oldvertex->neighbors, oldfacet);
    +    qh_remove_extravertices(neighborA);
    +  }
    +} /* renamevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_test_appendmerge( facet, neighbor )
    +    tests facet/neighbor for convexity
    +    appends to mergeset if non-convex
    +    if pre-merging,
    +      nop if qh.SKIPconvex, or qh.MERGEexact and coplanar
    +
    +  returns:
    +    true if appends facet/neighbor to mergeset
    +    sets facet->center as needed
    +    does not change facet->seen
    +
    +  design:
    +    if qh.cos_max is defined
    +      if the angle between facet normals is too shallow
    +        append an angle-coplanar merge to qh.mergeset
    +        return True
    +    make facet's centrum if needed
    +    if facet's centrum is above the neighbor
    +      set isconcave
    +    else
    +      if facet's centrum is not below the neighbor
    +        set iscoplanar
    +      make neighbor's centrum if needed
    +      if neighbor's centrum is above the facet
    +        set isconcave
    +      else if neighbor's centrum is not below the facet
    +        set iscoplanar
    +   if isconcave or iscoplanar
    +     get angle if needed
    +     append concave or coplanar merge to qh.mergeset
    +*/
    +boolT qh_test_appendmerge(facetT *facet, facetT *neighbor) {
    +  realT dist, dist2= -REALmax, angle= -REALmax;
    +  boolT isconcave= False, iscoplanar= False, okangle= False;
    +
    +  if (qh SKIPconvex && !qh POSTmerging)
    +    return False;
    +  if ((!qh MERGEexact || qh POSTmerging) && qh cos_max < REALmax/2) {
    +    angle= qh_getangle(facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +    if (angle > qh cos_max) {
    +      zinc_(Zcoplanarangle);
    +      qh_appendmergeset(facet, neighbor, MRGanglecoplanar, &angle);
    +      trace2((qh ferr, 2039, "qh_test_appendmerge: coplanar angle %4.4g between f%d and f%d\n",
    +         angle, facet->id, neighbor->id));
    +      return True;
    +    }else
    +      okangle= True;
    +  }
    +  if (!facet->center)
    +    facet->center= qh_getcentrum(facet);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(facet->center, neighbor, &dist);
    +  if (dist > qh centrum_radius)
    +    isconcave= True;
    +  else {
    +    if (dist > -qh centrum_radius)
    +      iscoplanar= True;
    +    if (!neighbor->center)
    +      neighbor->center= qh_getcentrum(neighbor);
    +    zzinc_(Zcentrumtests);
    +    qh_distplane(neighbor->center, facet, &dist2);
    +    if (dist2 > qh centrum_radius)
    +      isconcave= True;
    +    else if (!iscoplanar && dist2 > -qh centrum_radius)
    +      iscoplanar= True;
    +  }
    +  if (!isconcave && (!iscoplanar || (qh MERGEexact && !qh POSTmerging)))
    +    return False;
    +  if (!okangle && qh ANGLEmerge) {
    +    angle= qh_getangle(facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +  }
    +  if (isconcave) {
    +    zinc_(Zconcaveridge);
    +    if (qh ANGLEmerge)
    +      angle += qh_ANGLEconcave + 0.5;
    +    qh_appendmergeset(facet, neighbor, MRGconcave, &angle);
    +    trace0((qh ferr, 18, "qh_test_appendmerge: concave f%d to f%d dist %4.4g and reverse dist %4.4g angle %4.4g during p%d\n",
    +           facet->id, neighbor->id, dist, dist2, angle, qh furthest_id));
    +  }else /* iscoplanar */ {
    +    zinc_(Zcoplanarcentrum);
    +    qh_appendmergeset(facet, neighbor, MRGcoplanar, &angle);
    +    trace2((qh ferr, 2040, "qh_test_appendmerge: coplanar f%d to f%d dist %4.4g, reverse dist %4.4g angle %4.4g\n",
    +              facet->id, neighbor->id, dist, dist2, angle));
    +  }
    +  return True;
    +} /* test_appendmerge */
    +
    +/*---------------------------------
    +
    +  qh_test_vneighbors()
    +    test vertex neighbors for convexity
    +    tests all facets on qh.newfacet_list
    +
    +  returns:
    +    true if non-convex vneighbors appended to qh.facet_mergeset
    +    initializes vertex neighbors if needed
    +
    +  notes:
    +    assumes all facet neighbors have been tested
    +    this can be expensive
    +    this does not guarantee that a centrum is below all facets
    +      but it is unlikely
    +    uses qh.visit_id
    +
    +  design:
    +    build vertex neighbors if necessary
    +    for all new facets
    +      for all vertices
    +        for each unvisited facet neighbor of the vertex
    +          test new facet and neighbor for convexity
    +*/
    +boolT qh_test_vneighbors(void /* qh.newfacet_list */) {
    +  facetT *newfacet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +
    +  trace1((qh ferr, 1015, "qh_test_vneighbors: testing vertex neighbors for convexity\n"));
    +  if (!qh VERTEXneighbors)
    +    qh_vertexneighbors();
    +  FORALLnew_facets
    +    newfacet->seen= False;
    +  FORALLnew_facets {
    +    newfacet->seen= True;
    +    newfacet->visitid= qh visit_id++;
    +    FOREACHneighbor_(newfacet)
    +      newfacet->visitid= qh visit_id;
    +    FOREACHvertex_(newfacet->vertices) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->seen || neighbor->visitid == qh visit_id)
    +          continue;
    +        if (qh_test_appendmerge(newfacet, neighbor))
    +          nummerges++;
    +      }
    +    }
    +  }
    +  zadd_(Ztestvneighbor, nummerges);
    +  trace1((qh ferr, 1016, "qh_test_vneighbors: found %d non-convex, vertex neighbors\n",
    +           nummerges));
    +  return (nummerges > 0);
    +} /* test_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_tracemerge( facet1, facet2 )
    +    print trace message after merge
    +*/
    +void qh_tracemerge(facetT *facet1, facetT *facet2) {
    +  boolT waserror= False;
    +
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 4)
    +    qh_errprint("MERGED", facet2, NULL, NULL, NULL);
    +  if (facet2 == qh tracefacet || (qh tracevertex && qh tracevertex->newlist)) {
    +    qh_fprintf(qh ferr, 8085, "qh_tracemerge: trace facet and vertex after merge of f%d and f%d, furthest p%d\n", facet1->id, facet2->id, qh furthest_id);
    +    if (facet2 != qh tracefacet)
    +      qh_errprint("TRACE", qh tracefacet,
    +        (qh tracevertex && qh tracevertex->neighbors) ?
    +           SETfirstt_(qh tracevertex->neighbors, facetT) : NULL,
    +        NULL, qh tracevertex);
    +  }
    +  if (qh tracevertex) {
    +    if (qh tracevertex->deleted)
    +      qh_fprintf(qh ferr, 8086, "qh_tracemerge: trace vertex deleted at furthest p%d\n",
    +            qh furthest_id);
    +    else
    +      qh_checkvertex(qh tracevertex);
    +  }
    +  if (qh tracefacet) {
    +    qh_checkfacet(qh tracefacet, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh_ERRqhull, qh tracefacet, NULL);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (qh CHECKfrequently || qh IStracing >= 4) { /* can't check polygon here */
    +    qh_checkfacet(facet2, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +} /* tracemerge */
    +
    +/*---------------------------------
    +
    +  qh_tracemerging()
    +    print trace message during POSTmerging
    +
    +  returns:
    +    updates qh.mergereport
    +
    +  notes:
    +    called from qh_mergecycle() and qh_mergefacet()
    +
    +  see:
    +    qh_buildtracing()
    +*/
    +void qh_tracemerging(void) {
    +  realT cpu;
    +  int total;
    +  time_t timedata;
    +  struct tm *tp;
    +
    +  qh mergereport= zzval_(Ztotmerge);
    +  time(&timedata);
    +  tp= localtime(&timedata);
    +  cpu= qh_CPUclock;
    +  cpu /= qh_SECticks;
    +  total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  qh_fprintf(qh ferr, 8087, "\n\
    +At %d:%d:%d & %2.5g CPU secs, qhull has merged %d facets.  The hull\n\
    +  contains %d facets and %d vertices.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu,
    +      total, qh num_facets - qh num_visible,
    +      qh num_vertices-qh_setsize(qh del_vertices));
    +} /* tracemerging */
    +
    +/*---------------------------------
    +
    +  qh_updatetested( facet1, facet2 )
    +    clear facet2->tested and facet1->ridge->tested for merge
    +
    +  returns:
    +    deletes facet2->center unless it's already large
    +      if so, clears facet2->ridge->tested
    +
    +  design:
    +    clear facet2->tested
    +    clear ridge->tested for facet1's ridges
    +    if facet2 has a centrum
    +      if facet2 is large
    +        set facet2->keepcentrum
    +      else if facet2 has 3 vertices due to many merges, or not large and post merging
    +        clear facet2->keepcentrum
    +      unless facet2->keepcentrum
    +        clear facet2->center to recompute centrum later
    +        clear ridge->tested for facet2's ridges
    +*/
    +void qh_updatetested(facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  int size;
    +
    +  facet2->tested= False;
    +  FOREACHridge_(facet1->ridges)
    +    ridge->tested= False;
    +  if (!facet2->center)
    +    return;
    +  size= qh_setsize(facet2->vertices);
    +  if (!facet2->keepcentrum) {
    +    if (size > qh hull_dim + qh_MAXnewcentrum) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidevertices);
    +    }
    +  }else if (size <= qh hull_dim + qh_MAXnewcentrum) {
    +    /* center and keepcentrum was set */
    +    if (size == qh hull_dim || qh POSTmerging)
    +      facet2->keepcentrum= False; /* if many merges need to recompute centrum */
    +  }
    +  if (!facet2->keepcentrum) {
    +    qh_memfree(facet2->center, qh normal_size);
    +    facet2->center= NULL;
    +    FOREACHridge_(facet2->ridges)
    +      ridge->tested= False;
    +  }
    +} /* updatetested */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges( vertex )
    +    return temporary set of ridges adjacent to a vertex
    +    vertex->neighbors defined
    +
    +  ntoes:
    +    uses qh.visit_id
    +    does not include implicit ridges for simplicial facets
    +
    +  design:
    +    for each neighbor of vertex
    +      add ridges that include the vertex to ridges
    +*/
    +setT *qh_vertexridges(vertexT *vertex) {
    +  facetT *neighbor, **neighborp;
    +  setT *ridges= qh_settemp(qh TEMPsize);
    +  int size;
    +
    +  qh visit_id++;
    +  FOREACHneighbor_(vertex)
    +    neighbor->visitid= qh visit_id;
    +  FOREACHneighbor_(vertex) {
    +    if (*neighborp)   /* no new ridges in last neighbor */
    +      qh_vertexridges_facet(vertex, neighbor, &ridges);
    +  }
    +  if (qh PRINTstatistics || qh IStracing) {
    +    size= qh_setsize(ridges);
    +    zinc_(Zvertexridge);
    +    zadd_(Zvertexridgetot, size);
    +    zmax_(Zvertexridgemax, size);
    +    trace3((qh ferr, 3011, "qh_vertexridges: found %d ridges for v%d\n",
    +             size, vertex->id));
    +  }
    +  return ridges;
    +} /* vertexridges */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges_facet( vertex, facet, ridges )
    +    add adjacent ridges for vertex in facet
    +    neighbor->visitid==qh.visit_id if it hasn't been visited
    +
    +  returns:
    +    ridges updated
    +    sets facet->visitid to qh.visit_id-1
    +
    +  design:
    +    for each ridge of facet
    +      if ridge of visited neighbor (i.e., unprocessed)
    +        if vertex in ridge
    +          append ridge to vertex
    +    mark facet processed
    +*/
    +void qh_vertexridges_facet(vertexT *vertex, facetT *facet, setT **ridges) {
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor;
    +
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh visit_id
    +    && qh_setin(ridge->vertices, vertex))
    +      qh_setappend(ridges, ridge);
    +  }
    +  facet->visitid= qh visit_id-1;
    +} /* vertexridges_facet */
    +
    +/*---------------------------------
    +
    +  qh_willdelete( facet, replace )
    +    moves facet to visible list
    +    sets facet->f.replace to replace (may be NULL)
    +
    +  returns:
    +    bumps qh.num_visible
    +*/
    +void qh_willdelete(facetT *facet, facetT *replace) {
    +
    +  qh_removefacet(facet);
    +  qh_prependfacet(facet, &qh visible_list);
    +  qh num_visible++;
    +  facet->visible= True;
    +  facet->f.replace= replace;
    +} /* willdelete */
    +
    +#else /* qh_NOmerge */
    +void qh_premerge(vertexT *apex, realT maxcentrum, realT maxangle) {
    +}
    +void qh_postmerge(const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +}
    +boolT qh_checkzero(boolT testall) {
    +   }
    +#endif /* qh_NOmerge */
    +
    diff --git a/xs/src/qhull/src/libqhull/merge.h b/xs/src/qhull/src/libqhull/merge.h
    new file mode 100644
    index 0000000000..7f5ec3fb61
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/merge.h
    @@ -0,0 +1,178 @@
    +/*
      ---------------------------------
    +
    +   merge.h
    +   header file for merge.c
    +
    +   see qh-merge.htm and merge.c
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull/merge.h#1 $$Change: 1981 $
    +   $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmerge
    +#define qhDEFmerge 1
    +
    +#include "libqhull.h"
    +
    +
    +/*============ -constants- ==============*/
    +
    +/*----------------------------------
    +
    +  qh_ANGLEredundant
    +    indicates redundant merge in mergeT->angle
    +*/
    +#define qh_ANGLEredundant 6.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEdegen
    +    indicates degenerate facet in mergeT->angle
    +*/
    +#define qh_ANGLEdegen     5.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEconcave
    +    offset to indicate concave facets in mergeT->angle
    +
    +  notes:
    +    concave facets are assigned the range of [2,4] in mergeT->angle
    +    roundoff error may make the angle less than 2
    +*/
    +#define qh_ANGLEconcave  1.5
    +
    +/*----------------------------------
    +
    +  MRG... (mergeType)
    +    indicates the type of a merge (mergeT->type)
    +*/
    +typedef enum {  /* in sort order for facet_mergeset */
    +  MRGnone= 0,
    +  MRGcoplanar,          /* centrum coplanar */
    +  MRGanglecoplanar,     /* angle coplanar */
    +                        /* could detect half concave ridges */
    +  MRGconcave,           /* concave ridge */
    +  MRGflip,              /* flipped facet. facet1 == facet2 */
    +  MRGridge,             /* duplicate ridge (qh_MERGEridge) */
    +                        /* degen and redundant go onto degen_mergeset */
    +  MRGdegen,             /* degenerate facet (!enough neighbors) facet1 == facet2 */
    +  MRGredundant,         /* redundant facet (vertex subset) */
    +                        /* merge_degenredundant assumes degen < redundant */
    +  MRGmirror,            /* mirror facet from qh_triangulate */
    +  ENDmrg
    +} mergeType;
    +
    +/*----------------------------------
    +
    +  qh_MERGEapex
    +    flag for qh_mergefacet() to indicate an apex merge
    +*/
    +#define qh_MERGEapex     True
    +
    +/*============ -structures- ====================*/
    +
    +/*----------------------------------
    +
    +  mergeT
    +    structure used to merge facets
    +*/
    +
    +typedef struct mergeT mergeT;
    +struct mergeT {         /* initialize in qh_appendmergeset */
    +  realT   angle;        /* angle between normals of facet1 and facet2 */
    +  facetT *facet1;       /* will merge facet1 into facet2 */
    +  facetT *facet2;
    +  mergeType type;
    +};
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FOREACHmerge_( merges ) {...}
    +    assign 'merge' to each merge in merges
    +
    +  notes:
    +    uses 'mergeT *merge, **mergep;'
    +    if qh_mergefacet(),
    +      restart since qh.facet_mergeset may change
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHmerge_( merges ) FOREACHsetelement_(mergeT, merges, merge)
    +
    +/*============ prototypes in alphabetical order after pre/postmerge =======*/
    +
    +void    qh_premerge(vertexT *apex, realT maxcentrum, realT maxangle);
    +void    qh_postmerge(const char *reason, realT maxcentrum, realT maxangle,
    +             boolT vneighbors);
    +void    qh_all_merges(boolT othermerge, boolT vneighbors);
    +void    qh_appendmergeset(facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle);
    +setT   *qh_basevertices( facetT *samecycle);
    +void    qh_checkconnect(void /* qh.new_facets */);
    +boolT   qh_checkzero(boolT testall);
    +int     qh_compareangle(const void *p1, const void *p2);
    +int     qh_comparemerge(const void *p1, const void *p2);
    +int     qh_comparevisit(const void *p1, const void *p2);
    +void    qh_copynonconvex(ridgeT *atridge);
    +void    qh_degen_redundant_facet(facetT *facet);
    +void    qh_degen_redundant_neighbors(facetT *facet, facetT *delfacet);
    +vertexT *qh_find_newvertex(vertexT *oldvertex, setT *vertices, setT *ridges);
    +void    qh_findbest_test(boolT testcentrum, facetT *facet, facetT *neighbor,
    +           facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp);
    +facetT *qh_findbestneighbor(facetT *facet, realT *distp, realT *mindistp, realT *maxdistp);
    +void    qh_flippedmerges(facetT *facetlist, boolT *wasmerge);
    +void    qh_forcedmerges( boolT *wasmerge);
    +void    qh_getmergeset(facetT *facetlist);
    +void    qh_getmergeset_initial(facetT *facetlist);
    +void    qh_hashridge(setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex);
    +ridgeT *qh_hashridge_find(setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot);
    +void    qh_makeridges(facetT *facet);
    +void    qh_mark_dupridges(facetT *facetlist);
    +void    qh_maydropneighbor(facetT *facet);
    +int     qh_merge_degenredundant(void);
    +void    qh_merge_nonconvex( facetT *facet1, facetT *facet2, mergeType mergetype);
    +void    qh_mergecycle(facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_all(facetT *facetlist, boolT *wasmerge);
    +void    qh_mergecycle_facets( facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_neighbors(facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_ridges(facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_vneighbors( facetT *samecycle, facetT *newfacet);
    +void    qh_mergefacet(facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex);
    +void    qh_mergefacet2d(facetT *facet1, facetT *facet2);
    +void    qh_mergeneighbors(facetT *facet1, facetT *facet2);
    +void    qh_mergeridges(facetT *facet1, facetT *facet2);
    +void    qh_mergesimplex(facetT *facet1, facetT *facet2, boolT mergeapex);
    +void    qh_mergevertex_del(vertexT *vertex, facetT *facet1, facetT *facet2);
    +void    qh_mergevertex_neighbors(facetT *facet1, facetT *facet2);
    +void    qh_mergevertices(setT *vertices1, setT **vertices);
    +setT   *qh_neighbor_intersections(vertexT *vertex);
    +void    qh_newvertices(setT *vertices);
    +boolT   qh_reducevertices(void);
    +vertexT *qh_redundant_vertex(vertexT *vertex);
    +boolT   qh_remove_extravertices(facetT *facet);
    +vertexT *qh_rename_sharedvertex(vertexT *vertex, facetT *facet);
    +void    qh_renameridgevertex(ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex);
    +void    qh_renamevertex(vertexT *oldvertex, vertexT *newvertex, setT *ridges,
    +                        facetT *oldfacet, facetT *neighborA);
    +boolT   qh_test_appendmerge(facetT *facet, facetT *neighbor);
    +boolT   qh_test_vneighbors(void /* qh.newfacet_list */);
    +void    qh_tracemerge(facetT *facet1, facetT *facet2);
    +void    qh_tracemerging(void);
    +void    qh_updatetested( facetT *facet1, facetT *facet2);
    +setT   *qh_vertexridges(vertexT *vertex);
    +void    qh_vertexridges_facet(vertexT *vertex, facetT *facet, setT **ridges);
    +void    qh_willdelete(facetT *facet, facetT *replace);
    +
    +#endif /* qhDEFmerge */
    diff --git a/xs/src/qhull/src/libqhull/poly.c b/xs/src/qhull/src/libqhull/poly.c
    new file mode 100644
    index 0000000000..b8db6a9ef7
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/poly.c
    @@ -0,0 +1,1205 @@
    +/*
      ---------------------------------
    +
    +   poly.c
    +   implements polygons and simplices
    +
    +   see qh-poly.htm, poly.h and libqhull.h
    +
    +   infrequent code is in poly2.c
    +   (all but top 50 and their callers 12/3/95)
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/poly.c#3 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_appendfacet( facet )
    +    appends facet to end of qh.facet_list,
    +
    +  returns:
    +    updates qh.newfacet_list, facet_next, facet_list
    +    increments qh.numfacets
    +
    +  notes:
    +    assumes qh.facet_list/facet_tail is defined (createsimplex)
    +
    +  see:
    +    qh_removefacet()
    +
    +*/
    +void qh_appendfacet(facetT *facet) {
    +  facetT *tail= qh facet_tail;
    +
    +  if (tail == qh newfacet_list)
    +    qh newfacet_list= facet;
    +  if (tail == qh facet_next)
    +    qh facet_next= facet;
    +  facet->previous= tail->previous;
    +  facet->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= facet;
    +  else
    +    qh facet_list= facet;
    +  tail->previous= facet;
    +  qh num_facets++;
    +  trace4((qh ferr, 4044, "qh_appendfacet: append f%d to facet_list\n", facet->id));
    +} /* appendfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendvertex( vertex )
    +    appends vertex to end of qh.vertex_list,
    +
    +  returns:
    +    sets vertex->newlist
    +    updates qh.vertex_list, newvertex_list
    +    increments qh.num_vertices
    +
    +  notes:
    +    assumes qh.vertex_list/vertex_tail is defined (createsimplex)
    +
    +*/
    +void qh_appendvertex(vertexT *vertex) {
    +  vertexT *tail= qh vertex_tail;
    +
    +  if (tail == qh newvertex_list)
    +    qh newvertex_list= vertex;
    +  vertex->newlist= True;
    +  vertex->previous= tail->previous;
    +  vertex->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= vertex;
    +  else
    +    qh vertex_list= vertex;
    +  tail->previous= vertex;
    +  qh num_vertices++;
    +  trace4((qh ferr, 4045, "qh_appendvertex: append v%d to vertex_list\n", vertex->id));
    +} /* appendvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_attachnewfacets( )
    +    attach horizon facets to new facets in qh.newfacet_list
    +    newfacets have neighbor and ridge links to horizon but not vice versa
    +    only needed for qh.ONLYgood
    +
    +  returns:
    +    set qh.NEWfacets
    +    horizon facets linked to new facets
    +      ridges changed from visible facets to new facets
    +      simplicial ridges deleted
    +    qh.visible_list, no ridges valid
    +    facet->f.replace is a newfacet (if any)
    +
    +  design:
    +    delete interior ridges and neighbor sets by
    +      for each visible, non-simplicial facet
    +        for each ridge
    +          if last visit or if neighbor is simplicial
    +            if horizon neighbor
    +              delete ridge for horizon's ridge set
    +            delete ridge
    +        erase neighbor set
    +    attach horizon facets and new facets by
    +      for all new facets
    +        if corresponding horizon facet is simplicial
    +          locate corresponding visible facet {may be more than one}
    +          link visible facet to new facet
    +          replace visible facet with new facet in horizon
    +        else it's non-simplicial
    +          for all visible neighbors of the horizon facet
    +            link visible neighbor to new facet
    +            delete visible neighbor from horizon facet
    +          append new facet to horizon's neighbors
    +          the first ridge of the new facet is the horizon ridge
    +          link the new facet into the horizon ridge
    +*/
    +void qh_attachnewfacets(void /* qh.visible_list, newfacet_list */) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *horizon, *visible;
    +  ridgeT *ridge, **ridgep;
    +
    +  qh NEWfacets= True;
    +  trace3((qh ferr, 3012, "qh_attachnewfacets: delete interior ridges\n"));
    +  qh visit_id++;
    +  FORALLvisible_facets {
    +    visible->visitid= qh visit_id;
    +    if (visible->ridges) {
    +      FOREACHridge_(visible->ridges) {
    +        neighbor= otherfacet_(ridge, visible);
    +        if (neighbor->visitid == qh visit_id
    +            || (!neighbor->visible && neighbor->simplicial)) {
    +          if (!neighbor->visible)  /* delete ridge for simplicial horizon */
    +            qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(&(ridge->vertices)); /* delete on 2nd visit */
    +          qh_memfree(ridge, (int)sizeof(ridgeT));
    +        }
    +      }
    +      SETfirst_(visible->ridges)= NULL;
    +    }
    +    SETfirst_(visible->neighbors)= NULL;
    +  }
    +  trace1((qh ferr, 1017, "qh_attachnewfacets: attach horizon facets to new facets\n"));
    +  FORALLnew_facets {
    +    horizon= SETfirstt_(newfacet->neighbors, facetT);
    +    if (horizon->simplicial) {
    +      visible= NULL;
    +      FOREACHneighbor_(horizon) {   /* may have more than one horizon ridge */
    +        if (neighbor->visible) {
    +          if (visible) {
    +            if (qh_setequal_skip(newfacet->vertices, 0, horizon->vertices,
    +                                  SETindex_(horizon->neighbors, neighbor))) {
    +              visible= neighbor;
    +              break;
    +            }
    +          }else
    +            visible= neighbor;
    +        }
    +      }
    +      if (visible) {
    +        visible->f.replace= newfacet;
    +        qh_setreplace(horizon->neighbors, visible, newfacet);
    +      }else {
    +        qh_fprintf(qh ferr, 6102, "qhull internal error (qh_attachnewfacets): couldn't find visible facet for horizon f%d of newfacet f%d\n",
    +                 horizon->id, newfacet->id);
    +        qh_errexit2(qh_ERRqhull, horizon, newfacet);
    +      }
    +    }else { /* non-simplicial, with a ridge for newfacet */
    +      FOREACHneighbor_(horizon) {    /* may hold for many new facets */
    +        if (neighbor->visible) {
    +          neighbor->f.replace= newfacet;
    +          qh_setdelnth(horizon->neighbors,
    +                        SETindex_(horizon->neighbors, neighbor));
    +          neighborp--; /* repeat */
    +        }
    +      }
    +      qh_setappend(&horizon->neighbors, newfacet);
    +      ridge= SETfirstt_(newfacet->ridges, ridgeT);
    +      if (ridge->top == horizon)
    +        ridge->bottom= newfacet;
    +      else
    +        ridge->top= newfacet;
    +      }
    +  } /* newfacets */
    +  if (qh PRINTstatistics) {
    +    FORALLvisible_facets {
    +      if (!visible->f.replace)
    +        zinc_(Zinsidevisible);
    +    }
    +  }
    +} /* attachnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_checkflipped( facet, dist, allerror )
    +    checks facet orientation to interior point
    +
    +    if allerror set,
    +      tests against qh.DISTround
    +    else
    +      tests against 0 since tested against DISTround before
    +
    +  returns:
    +    False if it flipped orientation (sets facet->flipped)
    +    distance if non-NULL
    +*/
    +boolT qh_checkflipped(facetT *facet, realT *distp, boolT allerror) {
    +  realT dist;
    +
    +  if (facet->flipped && !distp)
    +    return False;
    +  zzinc_(Zdistcheck);
    +  qh_distplane(qh interior_point, facet, &dist);
    +  if (distp)
    +    *distp= dist;
    +  if ((allerror && dist > -qh DISTround)|| (!allerror && dist >= 0.0)) {
    +    facet->flipped= True;
    +    zzinc_(Zflippedfacets);
    +    trace0((qh ferr, 19, "qh_checkflipped: facet f%d is flipped, distance= %6.12g during p%d\n",
    +              facet->id, dist, qh furthest_id));
    +    qh_precision("flipped facet");
    +    return False;
    +  }
    +  return True;
    +} /* checkflipped */
    +
    +/*---------------------------------
    +
    +  qh_delfacet( facet )
    +    removes facet from facet_list and frees up its memory
    +
    +  notes:
    +    assumes vertices and ridges already freed
    +*/
    +void qh_delfacet(facetT *facet) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh ferr, 4046, "qh_delfacet: delete f%d\n", facet->id));
    +  if (facet == qh tracefacet)
    +    qh tracefacet= NULL;
    +  if (facet == qh GOODclosest)
    +    qh GOODclosest= NULL;
    +  qh_removefacet(facet);
    +  if (!facet->tricoplanar || facet->keepcentrum) {
    +    qh_memfree_(facet->normal, qh normal_size, freelistp);
    +    if (qh CENTERtype == qh_ASvoronoi) {   /* braces for macro calls */
    +      qh_memfree_(facet->center, qh center_size, freelistp);
    +    }else /* AScentrum */ {
    +      qh_memfree_(facet->center, qh normal_size, freelistp);
    +    }
    +  }
    +  qh_setfree(&(facet->neighbors));
    +  if (facet->ridges)
    +    qh_setfree(&(facet->ridges));
    +  qh_setfree(&(facet->vertices));
    +  if (facet->outsideset)
    +    qh_setfree(&(facet->outsideset));
    +  if (facet->coplanarset)
    +    qh_setfree(&(facet->coplanarset));
    +  qh_memfree_(facet, (int)sizeof(facetT), freelistp);
    +} /* delfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_deletevisible()
    +    delete visible facets and vertices
    +
    +  returns:
    +    deletes each facet and removes from facetlist
    +    at exit, qh.visible_list empty (== qh.newfacet_list)
    +
    +  notes:
    +    ridges already deleted
    +    horizon facets do not reference facets on qh.visible_list
    +    new facets in qh.newfacet_list
    +    uses   qh.visit_id;
    +*/
    +void qh_deletevisible(void /*qh.visible_list*/) {
    +  facetT *visible, *nextfacet;
    +  vertexT *vertex, **vertexp;
    +  int numvisible= 0, numdel= qh_setsize(qh del_vertices);
    +
    +  trace1((qh ferr, 1018, "qh_deletevisible: delete %d visible facets and %d vertices\n",
    +         qh num_visible, numdel));
    +  for (visible= qh visible_list; visible && visible->visible;
    +                visible= nextfacet) { /* deleting current */
    +    nextfacet= visible->next;
    +    numvisible++;
    +    qh_delfacet(visible);
    +  }
    +  if (numvisible != qh num_visible) {
    +    qh_fprintf(qh ferr, 6103, "qhull internal error (qh_deletevisible): qh num_visible %d is not number of visible facets %d\n",
    +             qh num_visible, numvisible);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  qh num_visible= 0;
    +  zadd_(Zvisfacettot, numvisible);
    +  zmax_(Zvisfacetmax, numvisible);
    +  zzadd_(Zdelvertextot, numdel);
    +  zmax_(Zdelvertexmax, numdel);
    +  FOREACHvertex_(qh del_vertices)
    +    qh_delvertex(vertex);
    +  qh_settruncate(qh del_vertices, 0);
    +} /* deletevisible */
    +
    +/*---------------------------------
    +
    +  qh_facetintersect( facetA, facetB, skipa, skipB, prepend )
    +    return vertices for intersection of two simplicial facets
    +    may include 1 prepended entry (if more, need to settemppush)
    +
    +  returns:
    +    returns set of qh.hull_dim-1 + prepend vertices
    +    returns skipped index for each test and checks for exactly one
    +
    +  notes:
    +    does not need settemp since set in quick memory
    +
    +  see also:
    +    qh_vertexintersect and qh_vertexintersect_new
    +    use qh_setnew_delnthsorted to get nth ridge (no skip information)
    +
    +  design:
    +    locate skipped vertex by scanning facet A's neighbors
    +    locate skipped vertex by scanning facet B's neighbors
    +    intersect the vertex sets
    +*/
    +setT *qh_facetintersect(facetT *facetA, facetT *facetB,
    +                         int *skipA,int *skipB, int prepend) {
    +  setT *intersect;
    +  int dim= qh hull_dim, i, j;
    +  facetT **neighborsA, **neighborsB;
    +
    +  neighborsA= SETaddr_(facetA->neighbors, facetT);
    +  neighborsB= SETaddr_(facetB->neighbors, facetT);
    +  i= j= 0;
    +  if (facetB == *neighborsA++)
    +    *skipA= 0;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 1;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 2;
    +  else {
    +    for (i=3; i < dim; i++) {
    +      if (facetB == *neighborsA++) {
    +        *skipA= i;
    +        break;
    +      }
    +    }
    +  }
    +  if (facetA == *neighborsB++)
    +    *skipB= 0;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 1;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 2;
    +  else {
    +    for (j=3; j < dim; j++) {
    +      if (facetA == *neighborsB++) {
    +        *skipB= j;
    +        break;
    +      }
    +    }
    +  }
    +  if (i >= dim || j >= dim) {
    +    qh_fprintf(qh ferr, 6104, "qhull internal error (qh_facetintersect): f%d or f%d not in others neighbors\n",
    +            facetA->id, facetB->id);
    +    qh_errexit2(qh_ERRqhull, facetA, facetB);
    +  }
    +  intersect= qh_setnew_delnthsorted(facetA->vertices, qh hull_dim, *skipA, prepend);
    +  trace4((qh ferr, 4047, "qh_facetintersect: f%d skip %d matches f%d skip %d\n",
    +          facetA->id, *skipA, facetB->id, *skipB));
    +  return(intersect);
    +} /* facetintersect */
    +
    +/*---------------------------------
    +
    +  qh_gethash( hashsize, set, size, firstindex, skipelem )
    +    return hashvalue for a set with firstindex and skipelem
    +
    +  notes:
    +    returned hash is in [0,hashsize)
    +    assumes at least firstindex+1 elements
    +    assumes skipelem is NULL, in set, or part of hash
    +
    +    hashes memory addresses which may change over different runs of the same data
    +    using sum for hash does badly in high d
    +*/
    +int qh_gethash(int hashsize, setT *set, int size, int firstindex, void *skipelem) {
    +  void **elemp= SETelemaddr_(set, firstindex, void);
    +  ptr_intT hash = 0, elem;
    +  unsigned result;
    +  int i;
    +#ifdef _MSC_VER                   /* Microsoft Visual C++ -- warn about 64-bit issues */
    +#pragma warning( push)            /* WARN64 -- ptr_intT holds a 64-bit pointer */
    +#pragma warning( disable : 4311)  /* 'type cast': pointer truncation from 'void*' to 'ptr_intT' */
    +#endif
    +
    +  switch (size-firstindex) {
    +  case 1:
    +    hash= (ptr_intT)(*elemp) - (ptr_intT) skipelem;
    +    break;
    +  case 2:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] - (ptr_intT) skipelem;
    +    break;
    +  case 3:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      - (ptr_intT) skipelem;
    +    break;
    +  case 4:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] - (ptr_intT) skipelem;
    +    break;
    +  case 5:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4] - (ptr_intT) skipelem;
    +    break;
    +  case 6:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4]+ (ptr_intT)elemp[5]
    +      - (ptr_intT) skipelem;
    +    break;
    +  default:
    +    hash= 0;
    +    i= 3;
    +    do {     /* this is about 10% in 10-d */
    +      if ((elem= (ptr_intT)*elemp++) != (ptr_intT)skipelem) {
    +        hash ^= (elem << i) + (elem >> (32-i));
    +        i += 3;
    +        if (i >= 32)
    +          i -= 32;
    +      }
    +    }while (*elemp);
    +    break;
    +  }
    +  if (hashsize<0) {
    +    qh_fprintf(qh ferr, 6202, "qhull internal error: negative hashsize %d passed to qh_gethash [poly.c]\n", hashsize);
    +    qh_errexit2(qh_ERRqhull, NULL, NULL);
    +  }
    +  result= (unsigned)hash;
    +  result %= (unsigned)hashsize;
    +  /* result= 0; for debugging */
    +  return result;
    +#ifdef _MSC_VER
    +#pragma warning( pop)
    +#endif
    +} /* gethash */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacet( vertices, toporient, horizon )
    +    creates a toporient? facet from vertices
    +
    +  returns:
    +    returns newfacet
    +      adds newfacet to qh.facet_list
    +      newfacet->vertices= vertices
    +      if horizon
    +        newfacet->neighbor= horizon, but not vice versa
    +    newvertex_list updated with vertices
    +*/
    +facetT *qh_makenewfacet(setT *vertices, boolT toporient,facetT *horizon) {
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(vertex);
    +      qh_appendvertex(vertex);
    +    }
    +  }
    +  newfacet= qh_newfacet();
    +  newfacet->vertices= vertices;
    +  newfacet->toporient= (unsigned char)toporient;
    +  if (horizon)
    +    qh_setappend(&(newfacet->neighbors), horizon);
    +  qh_appendfacet(newfacet);
    +  return(newfacet);
    +} /* makenewfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_makenewplanes()
    +    make new hyperplanes for facets on qh.newfacet_list
    +
    +  returns:
    +    all facets have hyperplanes or are marked for   merging
    +    doesn't create hyperplane if horizon is coplanar (will merge)
    +    updates qh.min_vertex if qh.JOGGLEmax
    +
    +  notes:
    +    facet->f.samecycle is defined for facet->mergehorizon facets
    +*/
    +void qh_makenewplanes(void /* newfacet_list */) {
    +  facetT *newfacet;
    +
    +  FORALLnew_facets {
    +    if (!newfacet->mergehorizon)
    +      qh_setfacetplane(newfacet);
    +  }
    +  if (qh JOGGLEmax < REALmax/2)
    +    minimize_(qh min_vertex, -wwval_(Wnewvertexmax));
    +} /* makenewplanes */
    +
    +/*---------------------------------
    +
    +  qh_makenew_nonsimplicial( visible, apex, numnew )
    +    make new facets for ridges of a visible facet
    +
    +  returns:
    +    first newfacet, bumps numnew as needed
    +    attaches new facets if !qh.ONLYgood
    +    marks ridge neighbors for simplicial visible
    +    if (qh.ONLYgood)
    +      ridges on newfacet, horizon, and visible
    +    else
    +      ridge and neighbors between newfacet and   horizon
    +      visible facet's ridges are deleted
    +
    +  notes:
    +    qh.visit_id if visible has already been processed
    +    sets neighbor->seen for building f.samecycle
    +      assumes all 'seen' flags initially false
    +
    +  design:
    +    for each ridge of visible facet
    +      get neighbor of visible facet
    +      if neighbor was already processed
    +        delete the ridge (will delete all visible facets later)
    +      if neighbor is a horizon facet
    +        create a new facet
    +        if neighbor coplanar
    +          adds newfacet to f.samecycle for later merging
    +        else
    +          updates neighbor's neighbor set
    +          (checks for non-simplicial facet with multiple ridges to visible facet)
    +        updates neighbor's ridge set
    +        (checks for simplicial neighbor to non-simplicial visible facet)
    +        (deletes ridge if neighbor is simplicial)
    +
    +*/
    +#ifndef qh_NOmerge
    +facetT *qh_makenew_nonsimplicial(facetT *visible, vertexT *apex, int *numnew) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor, *newfacet= NULL, *samecycle;
    +  setT *vertices;
    +  boolT toporient;
    +  int ridgeid;
    +
    +  FOREACHridge_(visible->ridges) {
    +    ridgeid= ridge->id;
    +    neighbor= otherfacet_(ridge, visible);
    +    if (neighbor->visible) {
    +      if (!qh ONLYgood) {
    +        if (neighbor->visitid == qh visit_id) {
    +          qh_setfree(&(ridge->vertices));  /* delete on 2nd visit */
    +          qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +        }
    +      }
    +    }else {  /* neighbor is an horizon facet */
    +      toporient= (ridge->top == visible);
    +      vertices= qh_setnew(qh hull_dim); /* makes sure this is quick */
    +      qh_setappend(&vertices, apex);
    +      qh_setappend_set(&vertices, ridge->vertices);
    +      newfacet= qh_makenewfacet(vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar) {
    +        newfacet->mergehorizon= True;
    +        if (!neighbor->seen) {
    +          newfacet->f.samecycle= newfacet;
    +          neighbor->f.newcycle= newfacet;
    +        }else {
    +          samecycle= neighbor->f.newcycle;
    +          newfacet->f.samecycle= samecycle->f.samecycle;
    +          samecycle->f.samecycle= newfacet;
    +        }
    +      }
    +      if (qh ONLYgood) {
    +        if (!neighbor->simplicial)
    +          qh_setappend(&(newfacet->ridges), ridge);
    +      }else {  /* qh_attachnewfacets */
    +        if (neighbor->seen) {
    +          if (neighbor->simplicial) {
    +            qh_fprintf(qh ferr, 6105, "qhull internal error (qh_makenew_nonsimplicial): simplicial f%d sharing two ridges with f%d\n",
    +                   neighbor->id, visible->id);
    +            qh_errexit2(qh_ERRqhull, neighbor, visible);
    +          }
    +          qh_setappend(&(neighbor->neighbors), newfacet);
    +        }else
    +          qh_setreplace(neighbor->neighbors, visible, newfacet);
    +        if (neighbor->simplicial) {
    +          qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(&(ridge->vertices));
    +          qh_memfree(ridge, (int)sizeof(ridgeT));
    +        }else {
    +          qh_setappend(&(newfacet->ridges), ridge);
    +          if (toporient)
    +            ridge->top= newfacet;
    +          else
    +            ridge->bottom= newfacet;
    +        }
    +      trace4((qh ferr, 4048, "qh_makenew_nonsimplicial: created facet f%d from v%d and r%d of horizon f%d\n",
    +            newfacet->id, apex->id, ridgeid, neighbor->id));
    +      }
    +    }
    +    neighbor->seen= True;
    +  } /* for each ridge */
    +  if (!qh ONLYgood)
    +    SETfirst_(visible->ridges)= NULL;
    +  return newfacet;
    +} /* makenew_nonsimplicial */
    +#else /* qh_NOmerge */
    +facetT *qh_makenew_nonsimplicial(facetT *visible, vertexT *apex, int *numnew) {
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_makenew_simplicial( visible, apex, numnew )
    +    make new facets for simplicial visible facet and apex
    +
    +  returns:
    +    attaches new facets if (!qh.ONLYgood)
    +      neighbors between newfacet and horizon
    +
    +  notes:
    +    nop if neighbor->seen or neighbor->visible(see qh_makenew_nonsimplicial)
    +
    +  design:
    +    locate neighboring horizon facet for visible facet
    +    determine vertices and orientation
    +    create new facet
    +    if coplanar,
    +      add new facet to f.samecycle
    +    update horizon facet's neighbor list
    +*/
    +facetT *qh_makenew_simplicial(facetT *visible, vertexT *apex, int *numnew) {
    +  facetT *neighbor, **neighborp, *newfacet= NULL;
    +  setT *vertices;
    +  boolT flip, toporient;
    +  int horizonskip= 0, visibleskip= 0;
    +
    +  FOREACHneighbor_(visible) {
    +    if (!neighbor->seen && !neighbor->visible) {
    +      vertices= qh_facetintersect(neighbor,visible, &horizonskip, &visibleskip, 1);
    +      SETfirst_(vertices)= apex;
    +      flip= ((horizonskip & 0x1) ^ (visibleskip & 0x1));
    +      if (neighbor->toporient)
    +        toporient= horizonskip & 0x1;
    +      else
    +        toporient= (horizonskip & 0x1) ^ 0x1;
    +      newfacet= qh_makenewfacet(vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar && (qh PREmerge || qh MERGEexact)) {
    +#ifndef qh_NOmerge
    +        newfacet->f.samecycle= newfacet;
    +        newfacet->mergehorizon= True;
    +#endif
    +      }
    +      if (!qh ONLYgood)
    +        SETelem_(neighbor->neighbors, horizonskip)= newfacet;
    +      trace4((qh ferr, 4049, "qh_makenew_simplicial: create facet f%d top %d from v%d and horizon f%d skip %d top %d and visible f%d skip %d, flip? %d\n",
    +            newfacet->id, toporient, apex->id, neighbor->id, horizonskip,
    +              neighbor->toporient, visible->id, visibleskip, flip));
    +    }
    +  }
    +  return newfacet;
    +} /* makenew_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_matchneighbor( newfacet, newskip, hashsize, hashcount )
    +    either match subridge of newfacet with neighbor or add to hash_table
    +
    +  returns:
    +    duplicate ridges are unmatched and marked by qh_DUPLICATEridge
    +
    +  notes:
    +    ridge is newfacet->vertices w/o newskip vertex
    +    do not allocate memory (need to free hash_table cleanly)
    +    uses linear hash chains
    +
    +  see also:
    +    qh_matchduplicates
    +
    +  design:
    +    for each possible matching facet in qh.hash_table
    +      if vertices match
    +        set ismatch, if facets have opposite orientation
    +        if ismatch and matching facet doesn't have a match
    +          match the facets by updating their neighbor sets
    +        else
    +          indicate a duplicate ridge
    +          set facet hyperplane for later testing
    +          add facet to hashtable
    +          unless the other facet was already a duplicate ridge
    +            mark both facets with a duplicate ridge
    +            add other facet (if defined) to hash table
    +*/
    +void qh_matchneighbor(facetT *newfacet, int newskip, int hashsize, int *hashcount) {
    +  boolT newfound= False;   /* True, if new facet is already in hash chain */
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *matchfacet;
    +  int skip, matchskip;
    +
    +  hash= qh_gethash(hashsize, newfacet->vertices, qh hull_dim, 1,
    +                     SETelem_(newfacet->vertices, newskip));
    +  trace4((qh ferr, 4050, "qh_matchneighbor: newfacet f%d skip %d hash %d hashcount %d\n",
    +          newfacet->id, newskip, hash, *hashcount));
    +  zinc_(Zhashlookup);
    +  for (scan= hash; (facet= SETelemt_(qh hash_table, scan, facetT));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (facet == newfacet) {
    +      newfound= True;
    +      continue;
    +    }
    +    zinc_(Zhashtests);
    +    if (qh_matchvertices(1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +      if (SETelem_(newfacet->vertices, newskip) ==
    +          SETelem_(facet->vertices, skip)) {
    +        qh_precision("two facets with the same vertices");
    +        qh_fprintf(qh ferr, 6106, "qhull precision error: Vertex sets are the same for f%d and f%d.  Can not force output.\n",
    +          facet->id, newfacet->id);
    +        qh_errexit2(qh_ERRprec, facet, newfacet);
    +      }
    +      ismatch= (same == (boolT)((newfacet->toporient ^ facet->toporient)));
    +      matchfacet= SETelemt_(facet->neighbors, skip, facetT);
    +      if (ismatch && !matchfacet) {
    +        SETelem_(facet->neighbors, skip)= newfacet;
    +        SETelem_(newfacet->neighbors, newskip)= facet;
    +        (*hashcount)--;
    +        trace4((qh ferr, 4051, "qh_matchneighbor: f%d skip %d matched with new f%d skip %d\n",
    +           facet->id, skip, newfacet->id, newskip));
    +        return;
    +      }
    +      if (!qh PREmerge && !qh MERGEexact) {
    +        qh_precision("a ridge with more than two neighbors");
    +        qh_fprintf(qh ferr, 6107, "qhull precision error: facets f%d, f%d and f%d meet at a ridge with more than 2 neighbors.  Can not continue.\n",
    +                 facet->id, newfacet->id, getid_(matchfacet));
    +        qh_errexit2(qh_ERRprec, facet, newfacet);
    +      }
    +      SETelem_(newfacet->neighbors, newskip)= qh_DUPLICATEridge;
    +      newfacet->dupridge= True;
    +      if (!newfacet->normal)
    +        qh_setfacetplane(newfacet);
    +      qh_addhash(newfacet, qh hash_table, hashsize, hash);
    +      (*hashcount)++;
    +      if (!facet->normal)
    +        qh_setfacetplane(facet);
    +      if (matchfacet != qh_DUPLICATEridge) {
    +        SETelem_(facet->neighbors, skip)= qh_DUPLICATEridge;
    +        facet->dupridge= True;
    +        if (!facet->normal)
    +          qh_setfacetplane(facet);
    +        if (matchfacet) {
    +          matchskip= qh_setindex(matchfacet->neighbors, facet);
    +          if (matchskip<0) {
    +              qh_fprintf(qh ferr, 6260, "qhull internal error (qh_matchneighbor): matchfacet f%d is in f%d neighbors but not vice versa.  Can not continue.\n",
    +                  matchfacet->id, facet->id);
    +              qh_errexit2(qh_ERRqhull, matchfacet, facet);
    +          }
    +          SETelem_(matchfacet->neighbors, matchskip)= qh_DUPLICATEridge; /* matchskip>=0 by QH6260 */
    +          matchfacet->dupridge= True;
    +          if (!matchfacet->normal)
    +            qh_setfacetplane(matchfacet);
    +          qh_addhash(matchfacet, qh hash_table, hashsize, hash);
    +          *hashcount += 2;
    +        }
    +      }
    +      trace4((qh ferr, 4052, "qh_matchneighbor: new f%d skip %d duplicates ridge for f%d skip %d matching f%d ismatch %d at hash %d\n",
    +           newfacet->id, newskip, facet->id, skip,
    +           (matchfacet == qh_DUPLICATEridge ? -2 : getid_(matchfacet)),
    +           ismatch, hash));
    +      return; /* end of duplicate ridge */
    +    }
    +  }
    +  if (!newfound)
    +    SETelem_(qh hash_table, scan)= newfacet;  /* same as qh_addhash */
    +  (*hashcount)++;
    +  trace4((qh ferr, 4053, "qh_matchneighbor: no match for f%d skip %d at hash %d\n",
    +           newfacet->id, newskip, hash));
    +} /* matchneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchnewfacets()
    +    match newfacets in qh.newfacet_list to their newfacet neighbors
    +
    +  returns:
    +    qh.newfacet_list with full neighbor sets
    +      get vertices with nth neighbor by deleting nth vertex
    +    if qh.PREmerge/MERGEexact or qh.FORCEoutput
    +      sets facet->flippped if flipped normal (also prevents point partitioning)
    +    if duplicate ridges and qh.PREmerge/MERGEexact
    +      sets facet->dupridge
    +      missing neighbor links identifies extra ridges to be merging (qh_MERGEridge)
    +
    +  notes:
    +    newfacets already have neighbor[0] (horizon facet)
    +    assumes qh.hash_table is NULL
    +    vertex->neighbors has not been updated yet
    +    do not allocate memory after qh.hash_table (need to free it cleanly)
    +
    +  design:
    +    delete neighbor sets for all new facets
    +    initialize a hash table
    +    for all new facets
    +      match facet with neighbors
    +    if unmatched facets (due to duplicate ridges)
    +      for each new facet with a duplicate ridge
    +        match it with a facet
    +    check for flipped facets
    +*/
    +void qh_matchnewfacets(void /* qh.newfacet_list */) {
    +  int numnew=0, hashcount=0, newskip;
    +  facetT *newfacet, *neighbor;
    +  int dim= qh hull_dim, hashsize, neighbor_i, neighbor_n;
    +  setT *neighbors;
    +#ifndef qh_NOtrace
    +  int facet_i, facet_n, numfree= 0;
    +  facetT *facet;
    +#endif
    +
    +  trace1((qh ferr, 1019, "qh_matchnewfacets: match neighbors for new facets.\n"));
    +  FORALLnew_facets {
    +    numnew++;
    +    {  /* inline qh_setzero(newfacet->neighbors, 1, qh hull_dim); */
    +      neighbors= newfacet->neighbors;
    +      neighbors->e[neighbors->maxsize].i= dim+1; /*may be overwritten*/
    +      memset((char *)SETelemaddr_(neighbors, 1, void), 0, dim * SETelemsize);
    +    }
    +  }
    +
    +  qh_newhashtable(numnew*(qh hull_dim-1)); /* twice what is normally needed,
    +                                     but every ridge could be DUPLICATEridge */
    +  hashsize= qh_setsize(qh hash_table);
    +  FORALLnew_facets {
    +    for (newskip=1; newskip0 because hull_dim>1 and numnew>0 */
    +      qh_matchneighbor(newfacet, newskip, hashsize, &hashcount);
    +#if 0   /* use the following to trap hashcount errors */
    +    {
    +      int count= 0, k;
    +      facetT *facet, *neighbor;
    +
    +      count= 0;
    +      FORALLfacet_(qh newfacet_list) {  /* newfacet already in use */
    +        for (k=1; k < qh hull_dim; k++) {
    +          neighbor= SETelemt_(facet->neighbors, k, facetT);
    +          if (!neighbor || neighbor == qh_DUPLICATEridge)
    +            count++;
    +        }
    +        if (facet == newfacet)
    +          break;
    +      }
    +      if (count != hashcount) {
    +        qh_fprintf(qh ferr, 8088, "qh_matchnewfacets: after adding facet %d, hashcount %d != count %d\n",
    +                 newfacet->id, hashcount, count);
    +        qh_errexit(qh_ERRqhull, newfacet, NULL);
    +      }
    +    }
    +#endif  /* end of trap code */
    +  }
    +  if (hashcount) {
    +    FORALLnew_facets {
    +      if (newfacet->dupridge) {
    +        FOREACHneighbor_i_(newfacet) {
    +          if (neighbor == qh_DUPLICATEridge) {
    +            qh_matchduplicates(newfacet, neighbor_i, hashsize, &hashcount);
    +                    /* this may report MERGEfacet */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (hashcount) {
    +    qh_fprintf(qh ferr, 6108, "qhull internal error (qh_matchnewfacets): %d neighbors did not match up\n",
    +        hashcount);
    +    qh_printhashtable(qh ferr);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 2) {
    +    FOREACHfacet_i_(qh hash_table) {
    +      if (!facet)
    +        numfree++;
    +    }
    +    qh_fprintf(qh ferr, 8089, "qh_matchnewfacets: %d new facets, %d unused hash entries .  hashsize %d\n",
    +             numnew, numfree, qh_setsize(qh hash_table));
    +  }
    +#endif /* !qh_NOtrace */
    +  qh_setfree(&qh hash_table);
    +  if (qh PREmerge || qh MERGEexact) {
    +    if (qh IStracing >= 4)
    +      qh_printfacetlist(qh newfacet_list, NULL, qh_ALL);
    +    FORALLnew_facets {
    +      if (newfacet->normal)
    +        qh_checkflipped(newfacet, NULL, qh_ALL);
    +    }
    +  }else if (qh FORCEoutput)
    +    qh_checkflipped_all(qh newfacet_list);  /* prints warnings for flipped */
    +} /* matchnewfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchvertices( firstindex, verticesA, skipA, verticesB, skipB, same )
    +    tests whether vertices match with a single skip
    +    starts match at firstindex since all new facets have a common vertex
    +
    +  returns:
    +    true if matched vertices
    +    skip index for each set
    +    sets same iff vertices have the same orientation
    +
    +  notes:
    +    assumes skipA is in A and both sets are the same size
    +
    +  design:
    +    set up pointers
    +    scan both sets checking for a match
    +    test orientation
    +*/
    +boolT qh_matchvertices(int firstindex, setT *verticesA, int skipA,
    +       setT *verticesB, int *skipB, boolT *same) {
    +  vertexT **elemAp, **elemBp, **skipBp=NULL, **skipAp;
    +
    +  elemAp= SETelemaddr_(verticesA, firstindex, vertexT);
    +  elemBp= SETelemaddr_(verticesB, firstindex, vertexT);
    +  skipAp= SETelemaddr_(verticesA, skipA, vertexT);
    +  do if (elemAp != skipAp) {
    +    while (*elemAp != *elemBp++) {
    +      if (skipBp)
    +        return False;
    +      skipBp= elemBp;  /* one extra like FOREACH */
    +    }
    +  }while (*(++elemAp));
    +  if (!skipBp)
    +    skipBp= ++elemBp;
    +  *skipB= SETindex_(verticesB, skipB); /* i.e., skipBp - verticesB */
    +  *same= !((skipA & 0x1) ^ (*skipB & 0x1)); /* result is 0 or 1 */
    +  trace4((qh ferr, 4054, "qh_matchvertices: matched by skip %d(v%d) and skip %d(v%d) same? %d\n",
    +          skipA, (*skipAp)->id, *skipB, (*(skipBp-1))->id, *same));
    +  return(True);
    +} /* matchvertices */
    +
    +/*---------------------------------
    +
    +  qh_newfacet()
    +    return a new facet
    +
    +  returns:
    +    all fields initialized or cleared   (NULL)
    +    preallocates neighbors set
    +*/
    +facetT *qh_newfacet(void) {
    +  facetT *facet;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_((int)sizeof(facetT), freelistp, facet, facetT);
    +  memset((char *)facet, (size_t)0, sizeof(facetT));
    +  if (qh facet_id == qh tracefacet_id)
    +    qh tracefacet= facet;
    +  facet->id= qh facet_id++;
    +  facet->neighbors= qh_setnew(qh hull_dim);
    +#if !qh_COMPUTEfurthest
    +  facet->furthestdist= 0.0;
    +#endif
    +#if qh_MAXoutside
    +  if (qh FORCEoutput && qh APPROXhull)
    +    facet->maxoutside= qh MINoutside;
    +  else
    +    facet->maxoutside= qh DISTround;
    +#endif
    +  facet->simplicial= True;
    +  facet->good= True;
    +  facet->newfacet= True;
    +  trace4((qh ferr, 4055, "qh_newfacet: created facet f%d\n", facet->id));
    +  return(facet);
    +} /* newfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_newridge()
    +    return a new ridge
    +*/
    +ridgeT *qh_newridge(void) {
    +  ridgeT *ridge;
    +  void **freelistp;   /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_((int)sizeof(ridgeT), freelistp, ridge, ridgeT);
    +  memset((char *)ridge, (size_t)0, sizeof(ridgeT));
    +  zinc_(Ztotridges);
    +  if (qh ridge_id == UINT_MAX) {
    +    qh_fprintf(qh ferr, 7074, "\
    +qhull warning: more than 2^32 ridges.  Qhull results are OK.  Since the ridge ID wraps around to 0, two ridges may have the same identifier.\n");
    +  }
    +  ridge->id= qh ridge_id++;
    +  trace4((qh ferr, 4056, "qh_newridge: created ridge r%d\n", ridge->id));
    +  return(ridge);
    +} /* newridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointid(  point )
    +    return id for a point,
    +    returns qh_IDnone(-3) if null, qh_IDinterior(-2) if interior, or qh_IDunknown(-1) if not known
    +
    +  alternative code if point is in qh.first_point...
    +    unsigned long id;
    +    id= ((unsigned long)point - (unsigned long)qh.first_point)/qh.normal_size;
    +
    +  notes:
    +    Valid points are non-negative
    +    WARN64 -- id truncated to 32-bits, at most 2G points
    +    NOerrors returned (QhullPoint::id)
    +    if point not in point array
    +      the code does a comparison of unrelated pointers.
    +*/
    +int qh_pointid(pointT *point) {
    +  ptr_intT offset, id;
    +
    +  if (!point)
    +    return qh_IDnone;
    +  else if (point == qh interior_point)
    +    return qh_IDinterior;
    +  else if (point >= qh first_point
    +  && point < qh first_point + qh num_points * qh hull_dim) {
    +    offset= (ptr_intT)(point - qh first_point);
    +    id= offset / qh hull_dim;
    +  }else if ((id= qh_setindex(qh other_points, point)) != -1)
    +    id += qh num_points;
    +  else
    +    return qh_IDunknown;
    +  return (int)id;
    +} /* pointid */
    +
    +/*---------------------------------
    +
    +  qh_removefacet( facet )
    +    unlinks facet from qh.facet_list,
    +
    +  returns:
    +    updates qh.facet_list .newfacet_list .facet_next visible_list
    +    decrements qh.num_facets
    +
    +  see:
    +    qh_appendfacet
    +*/
    +void qh_removefacet(facetT *facet) {
    +  facetT *next= facet->next, *previous= facet->previous;
    +
    +  if (facet == qh newfacet_list)
    +    qh newfacet_list= next;
    +  if (facet == qh facet_next)
    +    qh facet_next= next;
    +  if (facet == qh visible_list)
    +    qh visible_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st facet in qh facet_list */
    +    qh facet_list= next;
    +    qh facet_list->previous= NULL;
    +  }
    +  qh num_facets--;
    +  trace4((qh ferr, 4057, "qh_removefacet: remove f%d from facet_list\n", facet->id));
    +} /* removefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_removevertex( vertex )
    +    unlinks vertex from qh.vertex_list,
    +
    +  returns:
    +    updates qh.vertex_list .newvertex_list
    +    decrements qh.num_vertices
    +*/
    +void qh_removevertex(vertexT *vertex) {
    +  vertexT *next= vertex->next, *previous= vertex->previous;
    +
    +  if (vertex == qh newvertex_list)
    +    qh newvertex_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st vertex in qh vertex_list */
    +    qh vertex_list= vertex->next;
    +    qh vertex_list->previous= NULL;
    +  }
    +  qh num_vertices--;
    +  trace4((qh ferr, 4058, "qh_removevertex: remove v%d from vertex_list\n", vertex->id));
    +} /* removevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_updatevertices()
    +    update vertex neighbors and delete interior vertices
    +
    +  returns:
    +    if qh.VERTEXneighbors, updates neighbors for each vertex
    +      if qh.newvertex_list,
    +         removes visible neighbors  from vertex neighbors
    +      if qh.newfacet_list
    +         adds new facets to vertex neighbors
    +    if qh.visible_list
    +       interior vertices added to qh.del_vertices for later partitioning
    +
    +  design:
    +    if qh.VERTEXneighbors
    +      deletes references to visible facets from vertex neighbors
    +      appends new facets to the neighbor list for each vertex
    +      checks all vertices of visible facets
    +        removes visible facets from neighbor lists
    +        marks unused vertices for deletion
    +*/
    +void qh_updatevertices(void /*qh.newvertex_list, newfacet_list, visible_list*/) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *visible;
    +  vertexT *vertex, **vertexp;
    +
    +  trace3((qh ferr, 3013, "qh_updatevertices: delete interior vertices and update vertex->neighbors\n"));
    +  if (qh VERTEXneighbors) {
    +    FORALLvertex_(qh newvertex_list) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visible)
    +          SETref_(neighbor)= NULL;
    +      }
    +      qh_setcompact(vertex->neighbors);
    +    }
    +    FORALLnew_facets {
    +      FOREACHvertex_(newfacet->vertices)
    +        qh_setappend(&vertex->neighbors, newfacet);
    +    }
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          FOREACHneighbor_(vertex) { /* this can happen under merging */
    +            if (!neighbor->visible)
    +              break;
    +          }
    +          if (neighbor)
    +            qh_setdel(vertex->neighbors, visible);
    +          else {
    +            vertex->deleted= True;
    +            qh_setappend(&qh del_vertices, vertex);
    +            trace2((qh ferr, 2041, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(vertex->point), vertex->id, visible->id));
    +          }
    +        }
    +      }
    +    }
    +  }else {  /* !VERTEXneighbors */
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          vertex->deleted= True;
    +          qh_setappend(&qh del_vertices, vertex);
    +          trace2((qh ferr, 2042, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(vertex->point), vertex->id, visible->id));
    +        }
    +      }
    +    }
    +  }
    +} /* updatevertices */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/poly.h b/xs/src/qhull/src/libqhull/poly.h
    new file mode 100644
    index 0000000000..af8b42077f
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/poly.h
    @@ -0,0 +1,296 @@
    +/*
      ---------------------------------
    +
    +   poly.h
    +   header file for poly.c and poly2.c
    +
    +   see qh-poly.htm, libqhull.h and poly.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/poly.h#3 $$Change: 2047 $
    +   $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFpoly
    +#define qhDEFpoly 1
    +
    +#include "libqhull.h"
    +
    +/*===============   constants ========================== */
    +
    +/*----------------------------------
    +
    +  ALGORITHMfault
    +    use as argument to checkconvex() to report errors during buildhull
    +*/
    +#define qh_ALGORITHMfault 0
    +
    +/*----------------------------------
    +
    +  DATAfault
    +    use as argument to checkconvex() to report errors during initialhull
    +*/
    +#define qh_DATAfault 1
    +
    +/*----------------------------------
    +
    +  DUPLICATEridge
    +    special value for facet->neighbor to indicate a duplicate ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_DUPLICATEridge (facetT *)1L
    +
    +/*----------------------------------
    +
    +  MERGEridge       flag in facet
    +    special value for facet->neighbor to indicate a merged ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_MERGEridge (facetT *)2L
    +
    +
    +/*============ -structures- ====================*/
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacet_( facetlist ) { ... }
    +    assign 'facet' to each facet in facetlist
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +
    +  see:
    +    FORALLfacets
    +*/
    +#define FORALLfacet_( facetlist ) if (facetlist ) for ( facet=( facetlist ); facet && facet->next; facet= facet->next )
    +
    +/*----------------------------------
    +
    +  FORALLnew_facets { ... }
    +    assign 'newfacet' to each facet in qh.newfacet_list
    +
    +  notes:
    +    uses 'facetT *newfacet;'
    +    at exit, newfacet==NULL
    +*/
    +#define FORALLnew_facets for ( newfacet=qh newfacet_list;newfacet && newfacet->next;newfacet=newfacet->next )
    +
    +/*----------------------------------
    +
    +  FORALLvertex_( vertexlist ) { ... }
    +    assign 'vertex' to each vertex in vertexlist
    +
    +  notes:
    +    uses 'vertexT *vertex;'
    +    at exit, vertex==NULL
    +*/
    +#define FORALLvertex_( vertexlist ) for (vertex=( vertexlist );vertex && vertex->next;vertex= vertex->next )
    +
    +/*----------------------------------
    +
    +  FORALLvisible_facets { ... }
    +    assign 'visible' to each visible facet in qh.visible_list
    +
    +  notes:
    +    uses 'vacetT *visible;'
    +    at exit, visible==NULL
    +*/
    +#define FORALLvisible_facets for (visible=qh visible_list; visible && visible->visible; visible= visible->next)
    +
    +/*----------------------------------
    +
    +  FORALLsame_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    stops when it returns to newfacet
    +*/
    +#define FORALLsame_(newfacet) for (same= newfacet->f.samecycle; same != newfacet; same= same->f.samecycle)
    +
    +/*----------------------------------
    +
    +  FORALLsame_cycle_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    at exit, same == NULL
    +*/
    +#define FORALLsame_cycle_(newfacet) \
    +     for (same= newfacet->f.samecycle; \
    +         same; same= (same == newfacet ?  NULL : same->f.samecycle))
    +
    +/*----------------------------------
    +
    +  FOREACHneighborA_( facet ) { ... }
    +    assign 'neighborA' to each neighbor in facet->neighbors
    +
    +  FOREACHneighborA_( vertex ) { ... }
    +    assign 'neighborA' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighborA, **neighborAp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighborA_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighborA)
    +
    +/*----------------------------------
    +
    +  FOREACHvisible_( facets ) { ... }
    +    assign 'visible' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *facet, *facetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvisible_(facets) FOREACHsetelement_(facetT, facets, visible)
    +
    +/*----------------------------------
    +
    +  FOREACHnewfacet_( facets ) { ... }
    +    assign 'newfacet' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *newfacet, *newfacetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHnewfacet_(facets) FOREACHsetelement_(facetT, facets, newfacet)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexA_( vertices ) { ... }
    +    assign 'vertexA' to each vertex in vertices
    +
    +  notes:
    +    uses 'vertexT *vertexA, *vertexAp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexA_(vertices) FOREACHsetelement_(vertexT, vertices, vertexA)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexreverse12_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices
    +    reverse order of first two vertices
    +
    +  notes:
    +    uses 'vertexT *vertex, *vertexp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexreverse12_(vertices) FOREACHsetelementreverse12_(vertexT, vertices, vertex)
    +
    +
    +/*=============== prototypes poly.c in alphabetical order ================*/
    +
    +void    qh_appendfacet(facetT *facet);
    +void    qh_appendvertex(vertexT *vertex);
    +void    qh_attachnewfacets(void /* qh.visible_list, qh.newfacet_list */);
    +boolT   qh_checkflipped(facetT *facet, realT *dist, boolT allerror);
    +void    qh_delfacet(facetT *facet);
    +void    qh_deletevisible(void /*qh.visible_list, qh.horizon_list*/);
    +setT   *qh_facetintersect(facetT *facetA, facetT *facetB, int *skipAp,int *skipBp, int extra);
    +int     qh_gethash(int hashsize, setT *set, int size, int firstindex, void *skipelem);
    +facetT *qh_makenewfacet(setT *vertices, boolT toporient, facetT *facet);
    +void    qh_makenewplanes(void /* newfacet_list */);
    +facetT *qh_makenew_nonsimplicial(facetT *visible, vertexT *apex, int *numnew);
    +facetT *qh_makenew_simplicial(facetT *visible, vertexT *apex, int *numnew);
    +void    qh_matchneighbor(facetT *newfacet, int newskip, int hashsize,
    +                          int *hashcount);
    +void    qh_matchnewfacets(void);
    +boolT   qh_matchvertices(int firstindex, setT *verticesA, int skipA,
    +                          setT *verticesB, int *skipB, boolT *same);
    +facetT *qh_newfacet(void);
    +ridgeT *qh_newridge(void);
    +int     qh_pointid(pointT *point);
    +void    qh_removefacet(facetT *facet);
    +void    qh_removevertex(vertexT *vertex);
    +void    qh_updatevertices(void);
    +
    +
    +/*========== -prototypes poly2.c in alphabetical order ===========*/
    +
    +void    qh_addhash(void* newelem, setT *hashtable, int hashsize, int hash);
    +void    qh_check_bestdist(void);
    +void    qh_check_dupridge(facetT *facet1, realT dist1, facetT *facet2, realT dist2);
    +void    qh_check_maxout(void);
    +void    qh_check_output(void);
    +void    qh_check_point(pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2);
    +void    qh_check_points(void);
    +void    qh_checkconvex(facetT *facetlist, int fault);
    +void    qh_checkfacet(facetT *facet, boolT newmerge, boolT *waserrorp);
    +void    qh_checkflipped_all(facetT *facetlist);
    +void    qh_checkpolygon(facetT *facetlist);
    +void    qh_checkvertex(vertexT *vertex);
    +void    qh_clearcenters(qh_CENTER type);
    +void    qh_createsimplex(setT *vertices);
    +void    qh_delridge(ridgeT *ridge);
    +void    qh_delvertex(vertexT *vertex);
    +setT   *qh_facet3vertex(facetT *facet);
    +facetT *qh_findbestfacet(pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +facetT *qh_findbestlower(facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart);
    +facetT *qh_findfacet_all(pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart);
    +int     qh_findgood(facetT *facetlist, int goodhorizon);
    +void    qh_findgood_all(facetT *facetlist);
    +void    qh_furthestnext(void /* qh.facet_list */);
    +void    qh_furthestout(facetT *facet);
    +void    qh_infiniteloop(facetT *facet);
    +void    qh_initbuild(void);
    +void    qh_initialhull(setT *vertices);
    +setT   *qh_initialvertices(int dim, setT *maxpoints, pointT *points, int numpoints);
    +vertexT *qh_isvertex(pointT *point, setT *vertices);
    +vertexT *qh_makenewfacets(pointT *point /*horizon_list, visible_list*/);
    +void    qh_matchduplicates(facetT *atfacet, int atskip, int hashsize, int *hashcount);
    +void    qh_nearcoplanar(void /* qh.facet_list */);
    +vertexT *qh_nearvertex(facetT *facet, pointT *point, realT *bestdistp);
    +int     qh_newhashtable(int newsize);
    +vertexT *qh_newvertex(pointT *point);
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp);
    +void    qh_outcoplanar(void /* facet_list */);
    +pointT *qh_point(int id);
    +void    qh_point_add(setT *set, pointT *point, void *elem);
    +setT   *qh_pointfacet(void /*qh.facet_list*/);
    +setT   *qh_pointvertex(void /*qh.facet_list*/);
    +void    qh_prependfacet(facetT *facet, facetT **facetlist);
    +void    qh_printhashtable(FILE *fp);
    +void    qh_printlists(void);
    +void    qh_resetlists(boolT stats, boolT resetVisible /*qh.newvertex_list qh.newfacet_list qh.visible_list*/);
    +void    qh_setvoronoi_all(void);
    +void    qh_triangulate(void /*qh.facet_list*/);
    +void    qh_triangulate_facet(facetT *facetA, vertexT **first_vertex);
    +void    qh_triangulate_link(facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB);
    +void    qh_triangulate_mirror(facetT *facetA, facetT *facetB);
    +void    qh_triangulate_null(facetT *facetA);
    +void    qh_vertexintersect(setT **vertexsetA,setT *vertexsetB);
    +setT   *qh_vertexintersect_new(setT *vertexsetA,setT *vertexsetB);
    +void    qh_vertexneighbors(void /*qh.facet_list*/);
    +boolT   qh_vertexsubset(setT *vertexsetA, setT *vertexsetB);
    +
    +
    +#endif /* qhDEFpoly */
    diff --git a/xs/src/qhull/src/libqhull/poly2.c b/xs/src/qhull/src/libqhull/poly2.c
    new file mode 100644
    index 0000000000..de3e6ad0bb
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/poly2.c
    @@ -0,0 +1,3222 @@
    +/*
      ---------------------------------
    +
    +   poly2.c
    +   implements polygons and simplices
    +
    +   see qh-poly.htm, poly.h and libqhull.h
    +
    +   frequently used code is in poly.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/poly2.c#11 $$Change: 2069 $
    +   $DateTime: 2016/01/18 22:05:03 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_addhash( newelem, hashtable, hashsize, hash )
    +    add newelem to linear hash table at hash if not already there
    +*/
    +void qh_addhash(void* newelem, setT *hashtable, int hashsize, int hash) {
    +  int scan;
    +  void *elem;
    +
    +  for (scan= (int)hash; (elem= SETelem_(hashtable, scan));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (elem == newelem)
    +      break;
    +  }
    +  /* loop terminates because qh_HASHfactor >= 1.1 by qh_initbuffers */
    +  if (!elem)
    +    SETelem_(hashtable, scan)= newelem;
    +} /* addhash */
    +
    +/*---------------------------------
    +
    +  qh_check_bestdist()
    +    check that all points are within max_outside of the nearest facet
    +    if qh.ONLYgood,
    +      ignores !good facets
    +
    +  see:
    +    qh_check_maxout(), qh_outerinner()
    +
    +  notes:
    +    only called from qh_check_points()
    +      seldom used since qh.MERGING is almost always set
    +    if notverified>0 at end of routine
    +      some points were well inside the hull.  If the hull contains
    +      a lens-shaped component, these points were not verified.  Use
    +      options 'Qi Tv' to verify all points.  (Exhaustive check also verifies)
    +
    +  design:
    +    determine facet for each point (if any)
    +    for each point
    +      start with the assigned facet or with the first facet
    +      find the best facet for the point and check all coplanar facets
    +      error if point is outside of facet
    +*/
    +void qh_check_bestdist(void) {
    +  boolT waserror= False, unassigned;
    +  facetT *facet, *bestfacet, *errfacet1= NULL, *errfacet2= NULL;
    +  facetT *facetlist;
    +  realT dist, maxoutside, maxdist= -REALmax;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0, notverified= 0;
    +  setT *facets;
    +
    +  trace1((qh ferr, 1020, "qh_check_bestdist: check points below nearest facet.  Facet_list f%d\n",
    +      qh facet_list->id));
    +  maxoutside= qh_maxouter();
    +  maxoutside += qh DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh ferr, 1021, "qh_check_bestdist: check that all points are within %2.2g of best facet\n", maxoutside));
    +  facets= qh_pointfacet(/*qh.facet_list*/);
    +  if (!qh_QUICKhelp && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 8091, "\n\
    +qhull output completed.  Verifying that %d points are\n\
    +below %2.2g of the nearest %sfacet.\n",
    +             qh_setsize(facets), maxoutside, (qh ONLYgood ?  "good " : ""));
    +  FOREACHfacet_i_(facets) {  /* for each point with facet assignment */
    +    if (facet)
    +      unassigned= False;
    +    else {
    +      unassigned= True;
    +      facet= qh facet_list;
    +    }
    +    point= qh_point(facet_i);
    +    if (point == qh GOODpointp)
    +      continue;
    +    qh_distplane(point, facet, &dist);
    +    numpart++;
    +    bestfacet= qh_findbesthorizon(!qh_IScheckmax, point, facet, qh_NOupper, &dist, &numpart);
    +    /* occurs after statistics reported */
    +    maximize_(maxdist, dist);
    +    if (dist > maxoutside) {
    +      if (qh ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +        notgood++;
    +      else {
    +        waserror= True;
    +        qh_fprintf(qh ferr, 6109, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +                facet_i, bestfacet->id, dist, maxoutside);
    +        if (errfacet1 != bestfacet) {
    +          errfacet2= errfacet1;
    +          errfacet1= bestfacet;
    +        }
    +      }
    +    }else if (unassigned && dist < -qh MAXcoplanar)
    +      notverified++;
    +  }
    +  qh_settempfree(&facets);
    +  if (notverified && !qh DELAUNAY && !qh_QUICKhelp && qh PRINTprecision)
    +    qh_fprintf(qh ferr, 8092, "\n%d points were well inside the hull.  If the hull contains\n\
    +a lens-shaped component, these points were not verified.  Use\n\
    +options 'Qci Tv' to verify all points.\n", notverified);
    +  if (maxdist > qh outside_err) {
    +    qh_fprintf(qh ferr, 6110, "qhull precision error (qh_check_bestdist): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +              maxdist, qh outside_err);
    +    qh_errexit2(qh_ERRprec, errfacet1, errfacet2);
    +  }else if (waserror && qh outside_err > REALmax/2)
    +    qh_errexit2(qh_ERRprec, errfacet1, errfacet2);
    +  /* else if waserror, the error was logged to qh.ferr but does not effect the output */
    +  trace0((qh ferr, 20, "qh_check_bestdist: max distance outside %2.2g\n", maxdist));
    +} /* check_bestdist */
    +
    +/*---------------------------------
    +
    +  qh_check_dupridge(facet1, dist1, facet2, dist2)
    +    Check duplicate ridge between facet1 and facet2 for wide merge
    +    dist1 is the maximum distance of facet1's vertices to facet2
    +    dist2 is the maximum distance of facet2's vertices to facet1
    +
    +  Returns
    +    Level 1 log of the duplicate ridge with the minimum distance between vertices
    +    Throws error if the merge will increase the maximum facet width by qh_WIDEduplicate (100x)
    +
    +  called from:
    +    qh_forcedmerges()
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_dupridge(facetT *facet1, realT dist1, facetT *facet2, realT dist2) {
    +  vertexT *vertex, **vertexp, *vertexA, **vertexAp;
    +  realT dist, innerplane, mergedist, outerplane, prevdist, ratio;
    +  realT minvertex= REALmax;
    +
    +  mergedist= fmin_(dist1, dist2);
    +  qh_outerinner(NULL, &outerplane, &innerplane);  /* ratio from qh_printsummary */
    +  prevdist= fmax_(outerplane, innerplane);
    +  maximize_(prevdist, qh ONEmerge + qh DISTround);
    +  maximize_(prevdist, qh MINoutside + qh DISTround);
    +  ratio= mergedist/prevdist;
    +  FOREACHvertex_(facet1->vertices) {     /* The duplicate ridge is between facet1 and facet2, so either facet can be tested */
    +    FOREACHvertexA_(facet1->vertices) {
    +      if (vertex > vertexA){   /* Test each pair once */
    +        dist= qh_pointdist(vertex->point, vertexA->point, qh hull_dim);
    +        minimize_(minvertex, dist);
    +      }
    +    }
    +  }
    +  trace0((qh ferr, 16, "qh_check_dupridge: duplicate ridge between f%d and f%d due to nearly-coincident vertices (%2.2g), dist %2.2g, reverse dist %2.2g, ratio %2.2g while processing p%d\n",
    +        facet1->id, facet2->id, minvertex, dist1, dist2, ratio, qh furthest_id));
    +  if (ratio > qh_WIDEduplicate) {
    +    qh_fprintf(qh ferr, 6271, "qhull precision error (qh_check_dupridge): wide merge (%.0f times wider) due to duplicate ridge with nearly coincident points (%2.2g) between f%d and f%d, merge dist %2.2g, while processing p%d\n- Ignore error with option 'Q12'\n- To be fixed in a later version of Qhull\n",
    +          ratio, minvertex, facet1->id, facet2->id, mergedist, qh furthest_id);
    +    if (qh DELAUNAY)
    +      qh_fprintf(qh ferr, 8145, "- A bounding box for the input sites may alleviate this error.\n");
    +    if(minvertex > qh_WIDEduplicate*prevdist)
    +      qh_fprintf(qh ferr, 8146, "- Vertex distance %2.2g is greater than %d times maximum distance %2.2g\n  Please report to bradb@shore.net with steps to reproduce and all output\n",
    +          minvertex, qh_WIDEduplicate, prevdist);
    +    if (!qh NOwide)
    +      qh_errexit2(qh_ERRqhull, facet1, facet2);
    +  }
    +} /* check_dupridge */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_maxout()
    +    updates qh.max_outside by checking all points against bestfacet
    +    if qh.ONLYgood, ignores !good facets
    +
    +  returns:
    +    updates facet->maxoutside via qh_findbesthorizon()
    +    sets qh.maxoutdone
    +    if printing qh.min_vertex (qh_outerinner),
    +      it is updated to the current vertices
    +    removes inside/coplanar points from coplanarset as needed
    +
    +  notes:
    +    defines coplanar as min_vertex instead of MAXcoplanar
    +    may not need to check near-inside points because of qh.MAXcoplanar
    +      and qh.KEEPnearinside (before it was -DISTround)
    +
    +  see also:
    +    qh_check_bestdist()
    +
    +  design:
    +    if qh.min_vertex is needed
    +      for all neighbors of all vertices
    +        test distance from vertex to neighbor
    +    determine facet for each point (if any)
    +    for each point with an assigned facet
    +      find the best facet for the point and check all coplanar facets
    +        (updates outer planes)
    +    remove near-inside points from coplanar sets
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_maxout(void) {
    +  facetT *facet, *bestfacet, *neighbor, **neighborp, *facetlist;
    +  realT dist, maxoutside, minvertex, old_maxoutside;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0;
    +  setT *facets, *vertices;
    +  vertexT *vertex;
    +
    +  trace1((qh ferr, 1022, "qh_check_maxout: check and update maxoutside for each facet.\n"));
    +  maxoutside= minvertex= 0;
    +  if (qh VERTEXneighbors
    +  && (qh PRINTsummary || qh KEEPinside || qh KEEPcoplanar
    +        || qh TRACElevel || qh PRINTstatistics
    +        || qh PRINTout[0] == qh_PRINTsummary || qh PRINTout[0] == qh_PRINTnone)) {
    +    trace1((qh ferr, 1023, "qh_check_maxout: determine actual maxoutside and minvertex\n"));
    +    vertices= qh_pointvertex(/*qh.facet_list*/);
    +    FORALLvertices {
    +      FOREACHneighbor_(vertex) {
    +        zinc_(Zdistvertex);  /* distance also computed by main loop below */
    +        qh_distplane(vertex->point, neighbor, &dist);
    +        minimize_(minvertex, dist);
    +        if (-dist > qh TRACEdist || dist > qh TRACEdist
    +        || neighbor == qh tracefacet || vertex == qh tracevertex)
    +          qh_fprintf(qh ferr, 8093, "qh_check_maxout: p%d(v%d) is %.2g from f%d\n",
    +                    qh_pointid(vertex->point), vertex->id, dist, neighbor->id);
    +      }
    +    }
    +    if (qh MERGING) {
    +      wmin_(Wminvertex, qh min_vertex);
    +    }
    +    qh min_vertex= minvertex;
    +    qh_settempfree(&vertices);
    +  }
    +  facets= qh_pointfacet(/*qh.facet_list*/);
    +  do {
    +    old_maxoutside= fmax_(qh max_outside, maxoutside);
    +    FOREACHfacet_i_(facets) {     /* for each point with facet assignment */
    +      if (facet) {
    +        point= qh_point(facet_i);
    +        if (point == qh GOODpointp)
    +          continue;
    +        zzinc_(Ztotcheck);
    +        qh_distplane(point, facet, &dist);
    +        numpart++;
    +        bestfacet= qh_findbesthorizon(qh_IScheckmax, point, facet, !qh_NOupper, &dist, &numpart);
    +        if (bestfacet && dist > maxoutside) {
    +          if (qh ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +            notgood++;
    +          else
    +            maxoutside= dist;
    +        }
    +        if (dist > qh TRACEdist || (bestfacet && bestfacet == qh tracefacet))
    +          qh_fprintf(qh ferr, 8094, "qh_check_maxout: p%d is %.2g above f%d\n",
    +                     qh_pointid(point), dist, (bestfacet ? bestfacet->id : UINT_MAX));
    +      }
    +    }
    +  }while
    +    (maxoutside > 2*old_maxoutside);
    +    /* if qh.maxoutside increases substantially, qh_SEARCHdist is not valid
    +          e.g., RBOX 5000 s Z1 G1e-13 t1001200614 | qhull */
    +  zzadd_(Zcheckpart, numpart);
    +  qh_settempfree(&facets);
    +  wval_(Wmaxout)= maxoutside - qh max_outside;
    +  wmax_(Wmaxoutside, qh max_outside);
    +  qh max_outside= maxoutside;
    +  qh_nearcoplanar(/*qh.facet_list*/);
    +  qh maxoutdone= True;
    +  trace1((qh ferr, 1024, "qh_check_maxout: maxoutside %2.2g, min_vertex %2.2g, outside of not good %d\n",
    +       maxoutside, qh min_vertex, notgood));
    +} /* check_maxout */
    +#else /* qh_NOmerge */
    +void qh_check_maxout(void) {
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_output()
    +    performs the checks at the end of qhull algorithm
    +    Maybe called after voronoi output.  Will recompute otherwise centrums are Voronoi centers instead
    +*/
    +void qh_check_output(void) {
    +  int i;
    +
    +  if (qh STOPcone)
    +    return;
    +  if (qh VERIFYoutput | qh IStracing | qh CHECKfrequently) {
    +    qh_checkpolygon(qh facet_list);
    +    qh_checkflipped_all(qh facet_list);
    +    qh_checkconvex(qh facet_list, qh_ALGORITHMfault);
    +  }else if (!qh MERGING && qh_newstats(qhstat precision, &i)) {
    +    qh_checkflipped_all(qh facet_list);
    +    qh_checkconvex(qh facet_list, qh_ALGORITHMfault);
    +  }
    +} /* check_output */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_check_point( point, facet, maxoutside, maxdist, errfacet1, errfacet2 )
    +    check that point is less than maxoutside from facet
    +*/
    +void qh_check_point(pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2) {
    +  realT dist;
    +
    +  /* occurs after statistics reported */
    +  qh_distplane(point, facet, &dist);
    +  if (dist > *maxoutside) {
    +    if (*errfacet1 != facet) {
    +      *errfacet2= *errfacet1;
    +      *errfacet1= facet;
    +    }
    +    qh_fprintf(qh ferr, 6111, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +              qh_pointid(point), facet->id, dist, *maxoutside);
    +  }
    +  maximize_(*maxdist, dist);
    +} /* qh_check_point */
    +
    +
    +/*---------------------------------
    +
    +  qh_check_points()
    +    checks that all points are inside all facets
    +
    +  notes:
    +    if many points and qh_check_maxout not called (i.e., !qh.MERGING),
    +       calls qh_findbesthorizon (seldom done).
    +    ignores flipped facets
    +    maxoutside includes 2 qh.DISTrounds
    +      one qh.DISTround for the computed distances in qh_check_points
    +    qh_printafacet and qh_printsummary needs only one qh.DISTround
    +    the computation for qh.VERIFYdirect does not account for qh.other_points
    +
    +  design:
    +    if many points
    +      use qh_check_bestdist()
    +    else
    +      for all facets
    +        for all points
    +          check that point is inside facet
    +*/
    +void qh_check_points(void) {
    +  facetT *facet, *errfacet1= NULL, *errfacet2= NULL;
    +  realT total, maxoutside, maxdist= -REALmax;
    +  pointT *point, **pointp, *pointtemp;
    +  boolT testouter;
    +
    +  maxoutside= qh_maxouter();
    +  maxoutside += qh DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh ferr, 1025, "qh_check_points: check all points below %2.2g of all facet planes\n",
    +          maxoutside));
    +  if (qh num_good)   /* miss counts other_points and !good facets */
    +     total= (float)qh num_good * (float)qh num_points;
    +  else
    +     total= (float)qh num_facets * (float)qh num_points;
    +  if (total >= qh_VERIFYdirect && !qh maxoutdone) {
    +    if (!qh_QUICKhelp && qh SKIPcheckmax && qh MERGING)
    +      qh_fprintf(qh ferr, 7075, "qhull input warning: merging without checking outer planes('Q5' or 'Po').\n\
    +Verify may report that a point is outside of a facet.\n");
    +    qh_check_bestdist();
    +  }else {
    +    if (qh_MAXoutside && qh maxoutdone)
    +      testouter= True;
    +    else
    +      testouter= False;
    +    if (!qh_QUICKhelp) {
    +      if (qh MERGEexact)
    +        qh_fprintf(qh ferr, 7076, "qhull input warning: exact merge ('Qx').  Verify may report that a point\n\
    +is outside of a facet.  See qh-optq.htm#Qx\n");
    +      else if (qh SKIPcheckmax || qh NOnearinside)
    +        qh_fprintf(qh ferr, 7077, "qhull input warning: no outer plane check ('Q5') or no processing of\n\
    +near-inside points ('Q8').  Verify may report that a point is outside\n\
    +of a facet.\n");
    +    }
    +    if (qh PRINTprecision) {
    +      if (testouter)
    +        qh_fprintf(qh ferr, 8098, "\n\
    +Output completed.  Verifying that all points are below outer planes of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              (qh ONLYgood ?  "good " : ""), total);
    +      else
    +        qh_fprintf(qh ferr, 8099, "\n\
    +Output completed.  Verifying that all points are below %2.2g of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              maxoutside, (qh ONLYgood ?  "good " : ""), total);
    +    }
    +    FORALLfacets {
    +      if (!facet->good && qh ONLYgood)
    +        continue;
    +      if (facet->flipped)
    +        continue;
    +      if (!facet->normal) {
    +        qh_fprintf(qh ferr, 7061, "qhull warning (qh_check_points): missing normal for facet f%d\n", facet->id);
    +        continue;
    +      }
    +      if (testouter) {
    +#if qh_MAXoutside
    +        maxoutside= facet->maxoutside + 2* qh DISTround;
    +        /* one DISTround to actual point and another to computed point */
    +#endif
    +      }
    +      FORALLpoints {
    +        if (point != qh GOODpointp)
    +          qh_check_point(point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +      FOREACHpoint_(qh other_points) {
    +        if (point != qh GOODpointp)
    +          qh_check_point(point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +    }
    +    if (maxdist > qh outside_err) {
    +      qh_fprintf(qh ferr, 6112, "qhull precision error (qh_check_points): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +                maxdist, qh outside_err );
    +      qh_errexit2( qh_ERRprec, errfacet1, errfacet2 );
    +    }else if (errfacet1 && qh outside_err > REALmax/2)
    +        qh_errexit2( qh_ERRprec, errfacet1, errfacet2 );
    +    /* else if errfacet1, the error was logged to qh.ferr but does not effect the output */
    +    trace0((qh ferr, 21, "qh_check_points: max distance outside %2.2g\n", maxdist));
    +  }
    +} /* check_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkconvex( facetlist, fault )
    +    check that each ridge in facetlist is convex
    +    fault = qh_DATAfault if reporting errors
    +          = qh_ALGORITHMfault otherwise
    +
    +  returns:
    +    counts Zconcaveridges and Zcoplanarridges
    +    errors if concaveridge or if merging an coplanar ridge
    +
    +  note:
    +    if not merging,
    +      tests vertices for neighboring simplicial facets
    +    else if ZEROcentrum,
    +      tests vertices for neighboring simplicial   facets
    +    else
    +      tests centrums of neighboring facets
    +
    +  design:
    +    for all facets
    +      report flipped facets
    +      if ZEROcentrum and simplicial neighbors
    +        test vertices for neighboring simplicial facets
    +      else
    +        test centrum against all neighbors
    +*/
    +void qh_checkconvex(facetT *facetlist, int fault) {
    +  facetT *facet, *neighbor, **neighborp, *errfacet1=NULL, *errfacet2=NULL;
    +  vertexT *vertex;
    +  realT dist;
    +  pointT *centrum;
    +  boolT waserror= False, centrum_warning= False, tempcentrum= False, allsimplicial;
    +  int neighbor_i;
    +
    +  trace1((qh ferr, 1026, "qh_checkconvex: check all ridges are convex\n"));
    +  if (!qh RERUN) {
    +    zzval_(Zconcaveridges)= 0;
    +    zzval_(Zcoplanarridges)= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped) {
    +      qh_precision("flipped facet");
    +      qh_fprintf(qh ferr, 6113, "qhull precision error: f%d is flipped(interior point is outside)\n",
    +               facet->id);
    +      errfacet1= facet;
    +      waserror= True;
    +      continue;
    +    }
    +    if (qh MERGING && (!qh ZEROcentrum || !facet->simplicial || facet->tricoplanar))
    +      allsimplicial= False;
    +    else {
    +      allsimplicial= True;
    +      neighbor_i= 0;
    +      FOREACHneighbor_(facet) {
    +        vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +        if (!neighbor->simplicial || neighbor->tricoplanar) {
    +          allsimplicial= False;
    +          continue;
    +        }
    +        qh_distplane(vertex->point, neighbor, &dist);
    +        if (dist > -qh DISTround) {
    +          if (fault == qh_DATAfault) {
    +            qh_precision("coplanar or concave ridge");
    +            qh_fprintf(qh ferr, 6114, "qhull precision error: initial simplex is not convex. Distance=%.2g\n", dist);
    +            qh_errexit(qh_ERRsingular, NULL, NULL);
    +          }
    +          if (dist > qh DISTround) {
    +            zzinc_(Zconcaveridges);
    +            qh_precision("concave ridge");
    +            qh_fprintf(qh ferr, 6115, "qhull precision error: f%d is concave to f%d, since p%d(v%d) is %6.4g above\n",
    +              facet->id, neighbor->id, qh_pointid(vertex->point), vertex->id, dist);
    +            errfacet1= facet;
    +            errfacet2= neighbor;
    +            waserror= True;
    +          }else if (qh ZEROcentrum) {
    +            if (dist > 0) {     /* qh_checkzero checks that dist < - qh DISTround */
    +              zzinc_(Zcoplanarridges);
    +              qh_precision("coplanar ridge");
    +              qh_fprintf(qh ferr, 6116, "qhull precision error: f%d is clearly not convex to f%d, since p%d(v%d) is %6.4g above\n",
    +                facet->id, neighbor->id, qh_pointid(vertex->point), vertex->id, dist);
    +              errfacet1= facet;
    +              errfacet2= neighbor;
    +              waserror= True;
    +            }
    +          }else {
    +            zzinc_(Zcoplanarridges);
    +            qh_precision("coplanar ridge");
    +            trace0((qh ferr, 22, "qhull precision error: f%d may be coplanar to f%d, since p%d(v%d) is within %6.4g during p%d\n",
    +              facet->id, neighbor->id, qh_pointid(vertex->point), vertex->id, dist, qh furthest_id));
    +          }
    +        }
    +      }
    +    }
    +    if (!allsimplicial) {
    +      if (qh CENTERtype == qh_AScentrum) {
    +        if (!facet->center)
    +          facet->center= qh_getcentrum(facet);
    +        centrum= facet->center;
    +      }else {
    +        if (!centrum_warning && (!facet->simplicial || facet->tricoplanar)) {
    +           centrum_warning= True;
    +           qh_fprintf(qh ferr, 7062, "qhull warning: recomputing centrums for convexity test.  This may lead to false, precision errors.\n");
    +        }
    +        centrum= qh_getcentrum(facet);
    +        tempcentrum= True;
    +      }
    +      FOREACHneighbor_(facet) {
    +        if (qh ZEROcentrum && facet->simplicial && neighbor->simplicial)
    +          continue;
    +        if (facet->tricoplanar || neighbor->tricoplanar)
    +          continue;
    +        zzinc_(Zdistconvex);
    +        qh_distplane(centrum, neighbor, &dist);
    +        if (dist > qh DISTround) {
    +          zzinc_(Zconcaveridges);
    +          qh_precision("concave ridge");
    +          qh_fprintf(qh ferr, 6117, "qhull precision error: f%d is concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }else if (dist >= 0.0) {   /* if arithmetic always rounds the same,
    +                                     can test against centrum radius instead */
    +          zzinc_(Zcoplanarridges);
    +          qh_precision("coplanar ridge");
    +          qh_fprintf(qh ferr, 6118, "qhull precision error: f%d is coplanar or concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }
    +      }
    +      if (tempcentrum)
    +        qh_memfree(centrum, qh normal_size);
    +    }
    +  }
    +  if (waserror && !qh FORCEoutput)
    +    qh_errexit2(qh_ERRprec, errfacet1, errfacet2);
    +} /* checkconvex */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkfacet( facet, newmerge, waserror )
    +    checks for consistency errors in facet
    +    newmerge set if from merge.c
    +
    +  returns:
    +    sets waserror if any error occurs
    +
    +  checks:
    +    vertex ids are inverse sorted
    +    unless newmerge, at least hull_dim neighbors and vertices (exactly if simplicial)
    +    if non-simplicial, at least as many ridges as neighbors
    +    neighbors are not duplicated
    +    ridges are not duplicated
    +    in 3-d, ridges=verticies
    +    (qh.hull_dim-1) ridge vertices
    +    neighbors are reciprocated
    +    ridge neighbors are facet neighbors and a ridge for every neighbor
    +    simplicial neighbors match facetintersect
    +    vertex intersection matches vertices of common ridges
    +    vertex neighbors and facet vertices agree
    +    all ridges have distinct vertex sets
    +
    +  notes:
    +    uses neighbor->seen
    +
    +  design:
    +    check sets
    +    check vertices
    +    check sizes of neighbors and vertices
    +    check for qh_MERGEridge and qh_DUPLICATEridge flags
    +    check neighbor set
    +    check ridge set
    +    check ridges, neighbors, and vertices
    +*/
    +void qh_checkfacet(facetT *facet, boolT newmerge, boolT *waserrorp) {
    +  facetT *neighbor, **neighborp, *errother=NULL;
    +  ridgeT *ridge, **ridgep, *errridge= NULL, *ridge2;
    +  vertexT *vertex, **vertexp;
    +  unsigned previousid= INT_MAX;
    +  int numneighbors, numvertices, numridges=0, numRvertices=0;
    +  boolT waserror= False;
    +  int skipA, skipB, ridge_i, ridge_n, i;
    +  setT *intersection;
    +
    +  if (facet->visible) {
    +    qh_fprintf(qh ferr, 6119, "qhull internal error (qh_checkfacet): facet f%d is on the visible_list\n",
    +      facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  if (!facet->normal) {
    +    qh_fprintf(qh ferr, 6120, "qhull internal error (qh_checkfacet): facet f%d does not have  a normal\n",
    +      facet->id);
    +    waserror= True;
    +  }
    +  qh_setcheck(facet->vertices, "vertices for f", facet->id);
    +  qh_setcheck(facet->ridges, "ridges for f", facet->id);
    +  qh_setcheck(facet->outsideset, "outsideset for f", facet->id);
    +  qh_setcheck(facet->coplanarset, "coplanarset for f", facet->id);
    +  qh_setcheck(facet->neighbors, "neighbors for f", facet->id);
    +  FOREACHvertex_(facet->vertices) {
    +    if (vertex->deleted) {
    +      qh_fprintf(qh ferr, 6121, "qhull internal error (qh_checkfacet): deleted vertex v%d in f%d\n", vertex->id, facet->id);
    +      qh_errprint("ERRONEOUS", NULL, NULL, NULL, vertex);
    +      waserror= True;
    +    }
    +    if (vertex->id >= previousid) {
    +      qh_fprintf(qh ferr, 6122, "qhull internal error (qh_checkfacet): vertices of f%d are not in descending id order at v%d\n", facet->id, vertex->id);
    +      waserror= True;
    +      break;
    +    }
    +    previousid= vertex->id;
    +  }
    +  numneighbors= qh_setsize(facet->neighbors);
    +  numvertices= qh_setsize(facet->vertices);
    +  numridges= qh_setsize(facet->ridges);
    +  if (facet->simplicial) {
    +    if (numvertices+numneighbors != 2*qh hull_dim
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh ferr, 6123, "qhull internal error (qh_checkfacet): for simplicial facet f%d, #vertices %d + #neighbors %d != 2*qh hull_dim\n",
    +                facet->id, numvertices, numneighbors);
    +      qh_setprint(qh ferr, "", facet->neighbors);
    +      waserror= True;
    +    }
    +  }else { /* non-simplicial */
    +    if (!newmerge
    +    &&(numvertices < qh hull_dim || numneighbors < qh hull_dim)
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh ferr, 6124, "qhull internal error (qh_checkfacet): for facet f%d, #vertices %d or #neighbors %d < qh hull_dim\n",
    +         facet->id, numvertices, numneighbors);
    +       waserror= True;
    +    }
    +    /* in 3-d, can get a vertex twice in an edge list, e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv TP624 TW1e-13 T4 */
    +    if (numridges < numneighbors
    +    ||(qh hull_dim == 3 && numvertices > numridges && !qh NEWfacets)
    +    ||(qh hull_dim == 2 && numridges + numvertices + numneighbors != 6)) {
    +      if (!facet->degenerate && !facet->redundant) {
    +        qh_fprintf(qh ferr, 6125, "qhull internal error (qh_checkfacet): for facet f%d, #ridges %d < #neighbors %d or(3-d) > #vertices %d or(2-d) not all 2\n",
    +            facet->id, numridges, numneighbors, numvertices);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge) {
    +      qh_fprintf(qh ferr, 6126, "qhull internal error (qh_checkfacet): facet f%d still has a MERGE or DUP neighbor\n", facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (!qh_setin(neighbor->neighbors, facet)) {
    +      qh_fprintf(qh ferr, 6127, "qhull internal error (qh_checkfacet): facet f%d has neighbor f%d, but f%d does not have neighbor f%d\n",
    +              facet->id, neighbor->id, neighbor->id, facet->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    if (!neighbor->seen) {
    +      qh_fprintf(qh ferr, 6128, "qhull internal error (qh_checkfacet): facet f%d has a duplicate neighbor f%d\n",
    +              facet->id, neighbor->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    qh_setcheck(ridge->vertices, "vertices for r", ridge->id);
    +    ridge->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge->seen) {
    +      qh_fprintf(qh ferr, 6129, "qhull internal error (qh_checkfacet): facet f%d has a duplicate ridge r%d\n",
    +              facet->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    ridge->seen= True;
    +    numRvertices= qh_setsize(ridge->vertices);
    +    if (numRvertices != qh hull_dim - 1) {
    +      qh_fprintf(qh ferr, 6130, "qhull internal error (qh_checkfacet): ridge between f%d and f%d has %d vertices\n",
    +                ridge->top->id, ridge->bottom->id, numRvertices);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    neighbor= otherfacet_(ridge, facet);
    +    neighbor->seen= True;
    +    if (!qh_setin(facet->neighbors, neighbor)) {
    +      qh_fprintf(qh ferr, 6131, "qhull internal error (qh_checkfacet): for facet f%d, neighbor f%d of ridge r%d not in facet\n",
    +           facet->id, neighbor->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +  }
    +  if (!facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen) {
    +        qh_fprintf(qh ferr, 6132, "qhull internal error (qh_checkfacet): facet f%d does not have a ridge for neighbor f%d\n",
    +              facet->id, neighbor->id);
    +        errother= neighbor;
    +        waserror= True;
    +      }
    +      intersection= qh_vertexintersect_new(facet->vertices, neighbor->vertices);
    +      qh_settemppush(intersection);
    +      FOREACHvertex_(facet->vertices) {
    +        vertex->seen= False;
    +        vertex->seen2= False;
    +      }
    +      FOREACHvertex_(intersection)
    +        vertex->seen= True;
    +      FOREACHridge_(facet->ridges) {
    +        if (neighbor != otherfacet_(ridge, facet))
    +            continue;
    +        FOREACHvertex_(ridge->vertices) {
    +          if (!vertex->seen) {
    +            qh_fprintf(qh ferr, 6133, "qhull internal error (qh_checkfacet): vertex v%d in r%d not in f%d intersect f%d\n",
    +                  vertex->id, ridge->id, facet->id, neighbor->id);
    +            qh_errexit(qh_ERRqhull, facet, ridge);
    +          }
    +          vertex->seen2= True;
    +        }
    +      }
    +      if (!newmerge) {
    +        FOREACHvertex_(intersection) {
    +          if (!vertex->seen2) {
    +            if (qh IStracing >=3 || !qh MERGING) {
    +              qh_fprintf(qh ferr, 6134, "qhull precision error (qh_checkfacet): vertex v%d in f%d intersect f%d but\n\
    + not in a ridge.  This is ok under merging.  Last point was p%d\n",
    +                     vertex->id, facet->id, neighbor->id, qh furthest_id);
    +              if (!qh FORCEoutput && !qh MERGING) {
    +                qh_errprint("ERRONEOUS", facet, neighbor, NULL, vertex);
    +                if (!qh MERGING)
    +                  qh_errexit(qh_ERRqhull, NULL, NULL);
    +              }
    +            }
    +          }
    +        }
    +      }
    +      qh_settempfree(&intersection);
    +    }
    +  }else { /* simplicial */
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->simplicial) {
    +        skipA= SETindex_(facet->neighbors, neighbor);
    +        skipB= qh_setindex(neighbor->neighbors, facet);
    +        if (skipA<0 || skipB<0 || !qh_setequal_skip(facet->vertices, skipA, neighbor->vertices, skipB)) {
    +          qh_fprintf(qh ferr, 6135, "qhull internal error (qh_checkfacet): facet f%d skip %d and neighbor f%d skip %d do not match \n",
    +                   facet->id, skipA, neighbor->id, skipB);
    +          errother= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (qh hull_dim < 5 && (qh IStracing > 2 || qh CHECKfrequently)) {
    +    FOREACHridge_i_(facet->ridges) {           /* expensive */
    +      for (i=ridge_i+1; i < ridge_n; i++) {
    +        ridge2= SETelemt_(facet->ridges, i, ridgeT);
    +        if (qh_setequal(ridge->vertices, ridge2->vertices)) {
    +          qh_fprintf(qh ferr, 6227, "Qhull internal error (qh_checkfacet): ridges r%d and r%d have the same vertices\n",
    +                  ridge->id, ridge2->id);
    +          errridge= ridge;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint("ERRONEOUS", facet, errother, errridge, NULL);
    +    *waserrorp= True;
    +  }
    +} /* checkfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkflipped_all( facetlist )
    +    checks orientation of facets in list against interior point
    +*/
    +void qh_checkflipped_all(facetT *facetlist) {
    +  facetT *facet;
    +  boolT waserror= False;
    +  realT dist;
    +
    +  if (facetlist == qh facet_list)
    +    zzval_(Zflippedfacets)= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->normal && !qh_checkflipped(facet, &dist, !qh_ALL)) {
    +      qh_fprintf(qh ferr, 6136, "qhull precision error: facet f%d is flipped, distance= %6.12g\n",
    +              facet->id, dist);
    +      if (!qh FORCEoutput) {
    +        qh_errprint("ERRONEOUS", facet, NULL, NULL, NULL);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_fprintf(qh ferr, 8101, "\n\
    +A flipped facet occurs when its distance to the interior point is\n\
    +greater than %2.2g, the maximum roundoff error.\n", -qh DISTround);
    +    qh_errexit(qh_ERRprec, NULL, NULL);
    +  }
    +} /* checkflipped_all */
    +
    +/*---------------------------------
    +
    +  qh_checkpolygon( facetlist )
    +    checks the correctness of the structure
    +
    +  notes:
    +    call with either qh.facet_list or qh.newfacet_list
    +    checks num_facets and num_vertices if qh.facet_list
    +
    +  design:
    +    for each facet
    +      checks facet and outside set
    +    initializes vertexlist
    +    for each facet
    +      checks vertex set
    +    if checking all facets(qh.facetlist)
    +      check facet count
    +      if qh.VERTEXneighbors
    +        check vertex neighbors and count
    +      check vertex count
    +*/
    +void qh_checkpolygon(facetT *facetlist) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp, *vertexlist;
    +  int numfacets= 0, numvertices= 0, numridges= 0;
    +  int totvneighbors= 0, totvertices= 0;
    +  boolT waserror= False, nextseen= False, visibleseen= False;
    +
    +  trace1((qh ferr, 1027, "qh_checkpolygon: check all facets from f%d\n", facetlist->id));
    +  if (facetlist != qh facet_list || qh ONLYgood)
    +    nextseen= True;
    +  FORALLfacet_(facetlist) {
    +    if (facet == qh visible_list)
    +      visibleseen= True;
    +    if (!facet->visible) {
    +      if (!nextseen) {
    +        if (facet == qh facet_next)
    +          nextseen= True;
    +        else if (qh_setsize(facet->outsideset)) {
    +          if (!qh NARROWhull
    +#if !qh_COMPUTEfurthest
    +               || facet->furthestdist >= qh MINoutside
    +#endif
    +                        ) {
    +            qh_fprintf(qh ferr, 6137, "qhull internal error (qh_checkpolygon): f%d has outside points before qh facet_next\n",
    +                     facet->id);
    +            qh_errexit(qh_ERRqhull, facet, NULL);
    +          }
    +        }
    +      }
    +      numfacets++;
    +      qh_checkfacet(facet, False, &waserror);
    +    }
    +  }
    +  if (qh visible_list && !visibleseen && facetlist == qh facet_list) {
    +    qh_fprintf(qh ferr, 6138, "qhull internal error (qh_checkpolygon): visible list f%d no longer on facet list\n", qh visible_list->id);
    +    qh_printlists();
    +    qh_errexit(qh_ERRqhull, qh visible_list, NULL);
    +  }
    +  if (facetlist == qh facet_list)
    +    vertexlist= qh vertex_list;
    +  else if (facetlist == qh newfacet_list)
    +    vertexlist= qh newvertex_list;
    +  else
    +    vertexlist= NULL;
    +  FORALLvertex_(vertexlist) {
    +    vertex->seen= False;
    +    vertex->visitid= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visible)
    +      continue;
    +    if (facet->simplicial)
    +      numridges += qh hull_dim;
    +    else
    +      numridges += qh_setsize(facet->ridges);
    +    FOREACHvertex_(facet->vertices) {
    +      vertex->visitid++;
    +      if (!vertex->seen) {
    +        vertex->seen= True;
    +        numvertices++;
    +        if (qh_pointid(vertex->point) == qh_IDunknown) {
    +          qh_fprintf(qh ferr, 6139, "qhull internal error (qh_checkpolygon): unknown point %p for vertex v%d first_point %p\n",
    +                   vertex->point, vertex->id, qh first_point);
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  qh vertex_visit += (unsigned int)numfacets;
    +  if (facetlist == qh facet_list) {
    +    if (numfacets != qh num_facets - qh num_visible) {
    +      qh_fprintf(qh ferr, 6140, "qhull internal error (qh_checkpolygon): actual number of facets is %d, cumulative facet count is %d - %d visible facets\n",
    +              numfacets, qh num_facets, qh num_visible);
    +      waserror= True;
    +    }
    +    qh vertex_visit++;
    +    if (qh VERTEXneighbors) {
    +      FORALLvertices {
    +        qh_setcheck(vertex->neighbors, "neighbors for v", vertex->id);
    +        if (vertex->deleted)
    +          continue;
    +        totvneighbors += qh_setsize(vertex->neighbors);
    +      }
    +      FORALLfacet_(facetlist)
    +        totvertices += qh_setsize(facet->vertices);
    +      if (totvneighbors != totvertices) {
    +        qh_fprintf(qh ferr, 6141, "qhull internal error (qh_checkpolygon): vertex neighbors inconsistent.  Totvneighbors %d, totvertices %d\n",
    +                totvneighbors, totvertices);
    +        waserror= True;
    +      }
    +    }
    +    if (numvertices != qh num_vertices - qh_setsize(qh del_vertices)) {
    +      qh_fprintf(qh ferr, 6142, "qhull internal error (qh_checkpolygon): actual number of vertices is %d, cumulative vertex count is %d\n",
    +              numvertices, qh num_vertices - qh_setsize(qh del_vertices));
    +      waserror= True;
    +    }
    +    if (qh hull_dim == 2 && numvertices != numfacets) {
    +      qh_fprintf(qh ferr, 6143, "qhull internal error (qh_checkpolygon): #vertices %d != #facets %d\n",
    +        numvertices, numfacets);
    +      waserror= True;
    +    }
    +    if (qh hull_dim == 3 && numvertices + numfacets - numridges/2 != 2) {
    +      qh_fprintf(qh ferr, 7063, "qhull warning: #vertices %d + #facets %d - #edges %d != 2\n\
    +        A vertex appears twice in a edge list.  May occur during merging.",
    +        numvertices, numfacets, numridges/2);
    +      /* occurs if lots of merging and a vertex ends up twice in an edge list.  e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv */
    +    }
    +  }
    +  if (waserror)
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +} /* checkpolygon */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkvertex( vertex )
    +    check vertex for consistency
    +    checks vertex->neighbors
    +
    +  notes:
    +    neighbors checked efficiently in checkpolygon
    +*/
    +void qh_checkvertex(vertexT *vertex) {
    +  boolT waserror= False;
    +  facetT *neighbor, **neighborp, *errfacet=NULL;
    +
    +  if (qh_pointid(vertex->point) == qh_IDunknown) {
    +    qh_fprintf(qh ferr, 6144, "qhull internal error (qh_checkvertex): unknown point id %p\n", vertex->point);
    +    waserror= True;
    +  }
    +  if (vertex->id >= qh vertex_id) {
    +    qh_fprintf(qh ferr, 6145, "qhull internal error (qh_checkvertex): unknown vertex id %d\n", vertex->id);
    +    waserror= True;
    +  }
    +  if (!waserror && !vertex->deleted) {
    +    if (qh_setsize(vertex->neighbors)) {
    +      FOREACHneighbor_(vertex) {
    +        if (!qh_setin(neighbor->vertices, vertex)) {
    +          qh_fprintf(qh ferr, 6146, "qhull internal error (qh_checkvertex): neighbor f%d does not contain v%d\n", neighbor->id, vertex->id);
    +          errfacet= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint("ERRONEOUS", NULL, NULL, NULL, vertex);
    +    qh_errexit(qh_ERRqhull, errfacet, NULL);
    +  }
    +} /* checkvertex */
    +
    +/*---------------------------------
    +
    +  qh_clearcenters( type )
    +    clear old data from facet->center
    +
    +  notes:
    +    sets new centertype
    +    nop if CENTERtype is the same
    +*/
    +void qh_clearcenters(qh_CENTER type) {
    +  facetT *facet;
    +
    +  if (qh CENTERtype != type) {
    +    FORALLfacets {
    +      if (facet->tricoplanar && !facet->keepcentrum)
    +          facet->center= NULL;  /* center is owned by the ->keepcentrum facet */
    +      else if (qh CENTERtype == qh_ASvoronoi){
    +        if (facet->center) {
    +          qh_memfree(facet->center, qh center_size);
    +          facet->center= NULL;
    +        }
    +      }else /* qh.CENTERtype == qh_AScentrum */ {
    +        if (facet->center) {
    +          qh_memfree(facet->center, qh normal_size);
    +          facet->center= NULL;
    +        }
    +      }
    +    }
    +    qh CENTERtype= type;
    +  }
    +  trace2((qh ferr, 2043, "qh_clearcenters: switched to center type %d\n", type));
    +} /* clearcenters */
    +
    +/*---------------------------------
    +
    +  qh_createsimplex( vertices )
    +    creates a simplex from a set of vertices
    +
    +  returns:
    +    initializes qh.facet_list to the simplex
    +    initializes qh.newfacet_list, .facet_tail
    +    initializes qh.vertex_list, .newvertex_list, .vertex_tail
    +
    +  design:
    +    initializes lists
    +    for each vertex
    +      create a new facet
    +    for each new facet
    +      create its neighbor set
    +*/
    +void qh_createsimplex(setT *vertices) {
    +  facetT *facet= NULL, *newfacet;
    +  boolT toporient= True;
    +  int vertex_i, vertex_n, nth;
    +  setT *newfacets= qh_settemp(qh hull_dim+1);
    +  vertexT *vertex;
    +
    +  qh facet_list= qh newfacet_list= qh facet_tail= qh_newfacet();
    +  qh num_facets= qh num_vertices= qh num_visible= 0;
    +  qh vertex_list= qh newvertex_list= qh vertex_tail= qh_newvertex(NULL);
    +  FOREACHvertex_i_(vertices) {
    +    newfacet= qh_newfacet();
    +    newfacet->vertices= qh_setnew_delnthsorted(vertices, vertex_n,
    +                                                vertex_i, 0);
    +    newfacet->toporient= (unsigned char)toporient;
    +    qh_appendfacet(newfacet);
    +    newfacet->newfacet= True;
    +    qh_appendvertex(vertex);
    +    qh_setappend(&newfacets, newfacet);
    +    toporient ^= True;
    +  }
    +  FORALLnew_facets {
    +    nth= 0;
    +    FORALLfacet_(qh newfacet_list) {
    +      if (facet != newfacet)
    +        SETelem_(newfacet->neighbors, nth++)= facet;
    +    }
    +    qh_settruncate(newfacet->neighbors, qh hull_dim);
    +  }
    +  qh_settempfree(&newfacets);
    +  trace1((qh ferr, 1028, "qh_createsimplex: created simplex\n"));
    +} /* createsimplex */
    +
    +/*---------------------------------
    +
    +  qh_delridge( ridge )
    +    deletes ridge from data structures it belongs to
    +    frees up its memory
    +
    +  notes:
    +    in merge.c, caller sets vertex->delridge for each vertex
    +    ridges also freed in qh_freeqhull
    +*/
    +void qh_delridge(ridgeT *ridge) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  qh_setdel(ridge->top->ridges, ridge);
    +  qh_setdel(ridge->bottom->ridges, ridge);
    +  qh_setfree(&(ridge->vertices));
    +  qh_memfree_(ridge, (int)sizeof(ridgeT), freelistp);
    +} /* delridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_delvertex( vertex )
    +    deletes a vertex and frees its memory
    +
    +  notes:
    +    assumes vertex->adjacencies have been updated if needed
    +    unlinks from vertex_list
    +*/
    +void qh_delvertex(vertexT *vertex) {
    +
    +  if (vertex == qh tracevertex)
    +    qh tracevertex= NULL;
    +  qh_removevertex(vertex);
    +  qh_setfree(&vertex->neighbors);
    +  qh_memfree(vertex, (int)sizeof(vertexT));
    +} /* delvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_facet3vertex(  )
    +    return temporary set of 3-d vertices in qh_ORIENTclock order
    +
    +  design:
    +    if simplicial facet
    +      build set from facet->vertices with facet->toporient
    +    else
    +      for each ridge in order
    +        build set from ridge's vertices
    +*/
    +setT *qh_facet3vertex(facetT *facet) {
    +  ridgeT *ridge, *firstridge;
    +  vertexT *vertex;
    +  int cntvertices, cntprojected=0;
    +  setT *vertices;
    +
    +  cntvertices= qh_setsize(facet->vertices);
    +  vertices= qh_settemp(cntvertices);
    +  if (facet->simplicial) {
    +    if (cntvertices != 3) {
    +      qh_fprintf(qh ferr, 6147, "qhull internal error (qh_facet3vertex): only %d vertices for simplicial facet f%d\n",
    +                  cntvertices, facet->id);
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    qh_setappend(&vertices, SETfirst_(facet->vertices));
    +    if (facet->toporient ^ qh_ORIENTclock)
    +      qh_setappend(&vertices, SETsecond_(facet->vertices));
    +    else
    +      qh_setaddnth(&vertices, 0, SETsecond_(facet->vertices));
    +    qh_setappend(&vertices, SETelem_(facet->vertices, 2));
    +  }else {
    +    ridge= firstridge= SETfirstt_(facet->ridges, ridgeT);   /* no infinite */
    +    while ((ridge= qh_nextridge3d(ridge, facet, &vertex))) {
    +      qh_setappend(&vertices, vertex);
    +      if (++cntprojected > cntvertices || ridge == firstridge)
    +        break;
    +    }
    +    if (!ridge || cntprojected != cntvertices) {
    +      qh_fprintf(qh ferr, 6148, "qhull internal error (qh_facet3vertex): ridges for facet %d don't match up.  got at least %d\n",
    +                  facet->id, cntprojected);
    +      qh_errexit(qh_ERRqhull, facet, ridge);
    +    }
    +  }
    +  return vertices;
    +} /* facet3vertex */
    +
    +/*---------------------------------
    +
    +  qh_findbestfacet( point, bestoutside, bestdist, isoutside )
    +    find facet that is furthest below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    if bestoutside is set (e.g., qh_ALL)
    +      returns best facet that is not upperdelaunay
    +      if Delaunay and inside, point is outside circumsphere of bestfacet
    +    else
    +      returns first facet below point
    +      if point is inside, returns nearest, !upperdelaunay facet
    +    distance to facet
    +    isoutside set if outside of facet
    +
    +  notes:
    +    For tricoplanar facets, this finds one of the tricoplanar facets closest
    +    to the point.  For Delaunay triangulations, the point may be inside a
    +    different tricoplanar facet. See locate a facet with qh_findbestfacet()
    +
    +    If inside, qh_findbestfacet performs an exhaustive search
    +       this may be too conservative.  Sometimes it is clearly required.
    +
    +    qh_findbestfacet is not used by qhull.
    +    uses qh.visit_id and qh.coplanarset
    +
    +  see:
    +    qh_findbest
    +*/
    +facetT *qh_findbestfacet(pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside) {
    +  facetT *bestfacet= NULL;
    +  int numpart, totpart= 0;
    +
    +  bestfacet= qh_findbest(point, qh facet_list,
    +                            bestoutside, !qh_ISnewfacets, bestoutside /* qh_NOupper */,
    +                            bestdist, isoutside, &totpart);
    +  if (*bestdist < -qh DISTround) {
    +    bestfacet= qh_findfacet_all(point, bestdist, isoutside, &numpart);
    +    totpart += numpart;
    +    if ((isoutside && *isoutside && bestoutside)
    +    || (isoutside && !*isoutside && bestfacet->upperdelaunay)) {
    +      bestfacet= qh_findbest(point, bestfacet,
    +                            bestoutside, False, bestoutside,
    +                            bestdist, isoutside, &totpart);
    +      totpart += numpart;
    +    }
    +  }
    +  trace3((qh ferr, 3014, "qh_findbestfacet: f%d dist %2.2g isoutside %d totpart %d\n",
    +      bestfacet->id, *bestdist, (isoutside ? *isoutside : UINT_MAX), totpart));
    +  return bestfacet;
    +} /* findbestfacet */
    +
    +/*---------------------------------
    +
    +  qh_findbestlower( facet, point, bestdist, numpart )
    +    returns best non-upper, non-flipped neighbor of facet for point
    +    if needed, searches vertex neighbors
    +
    +  returns:
    +    returns bestdist and updates numpart
    +
    +  notes:
    +    if Delaunay and inside, point is outside of circumsphere of bestfacet
    +    called by qh_findbest() for points above an upperdelaunay facet
    +
    +*/
    +facetT *qh_findbestlower(facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  realT dist;
    +  vertexT *vertex;
    +  boolT isoutside= False;  /* not used */
    +
    +  zinc_(Zbestlower);
    +  FOREACHneighbor_(upperfacet) {
    +    if (neighbor->upperdelaunay || neighbor->flipped)
    +      continue;
    +    (*numpart)++;
    +    qh_distplane(point, neighbor, &dist);
    +    if (dist > bestdist) {
    +      bestfacet= neighbor;
    +      bestdist= dist;
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerv);
    +    /* rarely called, numpart does not count nearvertex computations */
    +    vertex= qh_nearvertex(upperfacet, point, &dist);
    +    qh_vertexneighbors();
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay || neighbor->flipped)
    +        continue;
    +      (*numpart)++;
    +      qh_distplane(point, neighbor, &dist);
    +      if (dist > bestdist) {
    +        bestfacet= neighbor;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerall);  /* invoked once per point in outsideset */
    +    zmax_(Zbestloweralln, qh num_facets);
    +    /* [dec'15] Previously reported as QH6228 */
    +    trace3((qh ferr, 3025, "qh_findbestlower: all neighbors of facet %d are flipped or upper Delaunay.  Search all facets\n",
    +        upperfacet->id));
    +    /* rarely called */
    +    bestfacet= qh_findfacet_all(point, &bestdist, &isoutside, numpart);
    +  }
    +  *bestdistp= bestdist;
    +  trace3((qh ferr, 3015, "qh_findbestlower: f%d dist %2.2g for f%d p%d\n",
    +          bestfacet->id, bestdist, upperfacet->id, qh_pointid(point)));
    +  return bestfacet;
    +} /* findbestlower */
    +
    +/*---------------------------------
    +
    +  qh_findfacet_all( point, bestdist, isoutside, numpart )
    +    exhaustive search for facet below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns first facet below point
    +    if point is inside,
    +      returns nearest facet
    +    distance to facet
    +    isoutside if point is outside of the hull
    +    number of distance tests
    +
    +  notes:
    +    primarily for library users, rarely used by Qhull
    +*/
    +facetT *qh_findfacet_all(pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart) {
    +  facetT *bestfacet= NULL, *facet;
    +  realT dist;
    +  int totpart= 0;
    +
    +  *bestdist= -REALmax;
    +  *isoutside= False;
    +  FORALLfacets {
    +    if (facet->flipped || !facet->normal)
    +      continue;
    +    totpart++;
    +    qh_distplane(point, facet, &dist);
    +    if (dist > *bestdist) {
    +      *bestdist= dist;
    +      bestfacet= facet;
    +      if (dist > qh MINoutside) {
    +        *isoutside= True;
    +        break;
    +      }
    +    }
    +  }
    +  *numpart= totpart;
    +  trace3((qh ferr, 3016, "qh_findfacet_all: f%d dist %2.2g isoutside %d totpart %d\n",
    +          getid_(bestfacet), *bestdist, *isoutside, totpart));
    +  return bestfacet;
    +} /* findfacet_all */
    +
    +/*---------------------------------
    +
    +  qh_findgood( facetlist, goodhorizon )
    +    identify good facets for qh.PRINTgood
    +    if qh.GOODvertex>0
    +      facet includes point as vertex
    +      if !match, returns goodhorizon
    +      inactive if qh.MERGING
    +    if qh.GOODpoint
    +      facet is visible or coplanar (>0) or not visible (<0)
    +    if qh.GOODthreshold
    +      facet->normal matches threshold
    +    if !goodhorizon and !match,
    +      selects facet with closest angle
    +      sets GOODclosest
    +
    +  returns:
    +    number of new, good facets found
    +    determines facet->good
    +    may update qh.GOODclosest
    +
    +  notes:
    +    qh_findgood_all further reduces the good region
    +
    +  design:
    +    count good facets
    +    mark good facets for qh.GOODpoint
    +    mark good facets for qh.GOODthreshold
    +    if necessary
    +      update qh.GOODclosest
    +*/
    +int qh_findgood(facetT *facetlist, int goodhorizon) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT angle, bestangle= REALmax, dist;
    +  int  numgood=0;
    +
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh GOODvertex>0 && !qh MERGING) {
    +    FORALLfacet_(facetlist) {
    +      if (!qh_isvertex(qh GOODvertexp, facet->vertices)) {
    +        facet->good= False;
    +        numgood--;
    +      }
    +    }
    +  }
    +  if (qh GOODpoint && numgood) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        zinc_(Zdistgood);
    +        qh_distplane(qh GOODpointp, facet, &dist);
    +        if ((qh GOODpoint > 0) ^ (dist > 0.0)) {
    +          facet->good= False;
    +          numgood--;
    +        }
    +      }
    +    }
    +  }
    +  if (qh GOODthreshold && (numgood || goodhorizon || qh GOODclosest)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        if (!qh_inthresholds(facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && (!goodhorizon || qh GOODclosest)) {
    +      if (qh GOODclosest) {
    +        if (qh GOODclosest->visible)
    +          qh GOODclosest= NULL;
    +        else {
    +          qh_inthresholds(qh GOODclosest->normal, &angle);
    +          if (angle < bestangle)
    +            bestfacet= qh GOODclosest;
    +        }
    +      }
    +      if (bestfacet && bestfacet != qh GOODclosest) {
    +        if (qh GOODclosest)
    +          qh GOODclosest->good= False;
    +        qh GOODclosest= bestfacet;
    +        bestfacet->good= True;
    +        numgood++;
    +        trace2((qh ferr, 2044, "qh_findgood: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +        return numgood;
    +      }
    +    }else if (qh GOODclosest) { /* numgood > 0 */
    +      qh GOODclosest->good= False;
    +      qh GOODclosest= NULL;
    +    }
    +  }
    +  zadd_(Zgoodfacet, numgood);
    +  trace2((qh ferr, 2045, "qh_findgood: found %d good facets with %d good horizon\n",
    +               numgood, goodhorizon));
    +  if (!numgood && qh GOODvertex>0 && !qh MERGING)
    +    return goodhorizon;
    +  return numgood;
    +} /* findgood */
    +
    +/*---------------------------------
    +
    +  qh_findgood_all( facetlist )
    +    apply other constraints for good facets (used by qh.PRINTgood)
    +    if qh.GOODvertex
    +      facet includes (>0) or doesn't include (<0) point as vertex
    +      if last good facet and ONLYgood, prints warning and continues
    +    if qh.SPLITthresholds
    +      facet->normal matches threshold, or if none, the closest one
    +    calls qh_findgood
    +    nop if good not used
    +
    +  returns:
    +    clears facet->good if not good
    +    sets qh.num_good
    +
    +  notes:
    +    this is like qh_findgood but more restrictive
    +
    +  design:
    +    uses qh_findgood to mark good facets
    +    marks facets for qh.GOODvertex
    +    marks facets for qh.SPLITthreholds
    +*/
    +void qh_findgood_all(facetT *facetlist) {
    +  facetT *facet, *bestfacet=NULL;
    +  realT angle, bestangle= REALmax;
    +  int  numgood=0, startgood;
    +
    +  if (!qh GOODvertex && !qh GOODthreshold && !qh GOODpoint
    +  && !qh SPLITthresholds)
    +    return;
    +  if (!qh ONLYgood)
    +    qh_findgood(qh facet_list, 0);
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh GOODvertex <0 || (qh GOODvertex > 0 && qh MERGING)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && ((qh GOODvertex > 0) ^ !!qh_isvertex(qh GOODvertexp, facet->vertices))) {
    +        if (!--numgood) {
    +          if (qh ONLYgood) {
    +            qh_fprintf(qh ferr, 7064, "qhull warning: good vertex p%d does not match last good facet f%d.  Ignored.\n",
    +               qh_pointid(qh GOODvertexp), facet->id);
    +            return;
    +          }else if (qh GOODvertex > 0)
    +            qh_fprintf(qh ferr, 7065, "qhull warning: point p%d is not a vertex('QV%d').\n",
    +                qh GOODvertex-1, qh GOODvertex-1);
    +          else
    +            qh_fprintf(qh ferr, 7066, "qhull warning: point p%d is a vertex for every facet('QV-%d').\n",
    +                -qh GOODvertex - 1, -qh GOODvertex - 1);
    +        }
    +        facet->good= False;
    +      }
    +    }
    +  }
    +  startgood= numgood;
    +  if (qh SPLITthresholds) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good) {
    +        if (!qh_inthresholds(facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && bestfacet) {
    +      bestfacet->good= True;
    +      numgood++;
    +      trace0((qh ferr, 23, "qh_findgood_all: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +      return;
    +    }
    +  }
    +  qh num_good= numgood;
    +  trace0((qh ferr, 24, "qh_findgood_all: %d good facets remain out of %d facets\n",
    +        numgood, startgood));
    +} /* findgood_all */
    +
    +/*---------------------------------
    +
    +  qh_furthestnext()
    +    set qh.facet_next to facet with furthest of all furthest points
    +    searches all facets on qh.facet_list
    +
    +  notes:
    +    this may help avoid precision problems
    +*/
    +void qh_furthestnext(void /* qh.facet_list */) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FORALLfacets {
    +    if (facet->outsideset) {
    +#if qh_COMPUTEfurthest
    +      pointT *furthest;
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(furthest, facet, &dist);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist > bestdist) {
    +        bestfacet= facet;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    qh_removefacet(bestfacet);
    +    qh_prependfacet(bestfacet, &qh facet_next);
    +    trace1((qh ferr, 1029, "qh_furthestnext: made f%d next facet(dist %.2g)\n",
    +            bestfacet->id, bestdist));
    +  }
    +} /* furthestnext */
    +
    +/*---------------------------------
    +
    +  qh_furthestout( facet )
    +    make furthest outside point the last point of outsideset
    +
    +  returns:
    +    updates facet->outsideset
    +    clears facet->notfurthest
    +    sets facet->furthestdist
    +
    +  design:
    +    determine best point of outsideset
    +    make it the last point of outsideset
    +*/
    +void qh_furthestout(facetT *facet) {
    +  pointT *point, **pointp, *bestpoint= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FOREACHpoint_(facet->outsideset) {
    +    qh_distplane(point, facet, &dist);
    +    zinc_(Zcomputefurthest);
    +    if (dist > bestdist) {
    +      bestpoint= point;
    +      bestdist= dist;
    +    }
    +  }
    +  if (bestpoint) {
    +    qh_setdel(facet->outsideset, point);
    +    qh_setappend(&facet->outsideset, point);
    +#if !qh_COMPUTEfurthest
    +    facet->furthestdist= bestdist;
    +#endif
    +  }
    +  facet->notfurthest= False;
    +  trace3((qh ferr, 3017, "qh_furthestout: p%d is furthest outside point of f%d\n",
    +          qh_pointid(point), facet->id));
    +} /* furthestout */
    +
    +
    +/*---------------------------------
    +
    +  qh_infiniteloop( facet )
    +    report infinite loop error due to facet
    +*/
    +void qh_infiniteloop(facetT *facet) {
    +
    +  qh_fprintf(qh ferr, 6149, "qhull internal error (qh_infiniteloop): potential infinite loop detected\n");
    +  qh_errexit(qh_ERRqhull, facet, NULL);
    +} /* qh_infiniteloop */
    +
    +/*---------------------------------
    +
    +  qh_initbuild()
    +    initialize hull and outside sets with point array
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +    if qh.GOODpoint
    +      adds qh.GOODpoint to initial hull
    +
    +  returns:
    +    qh_facetlist with initial hull
    +    points partioned into outside sets, coplanar sets, or inside
    +    initializes qh.GOODpointp, qh.GOODvertexp,
    +
    +  design:
    +    initialize global variables used during qh_buildhull
    +    determine precision constants and points with max/min coordinate values
    +      if qh.SCALElast, scale last coordinate(for 'd')
    +    build initial simplex
    +    partition input points into facets of initial simplex
    +    set up lists
    +    if qh.ONLYgood
    +      check consistency
    +      add qh.GOODvertex if defined
    +*/
    +void qh_initbuild( void) {
    +  setT *maxpoints, *vertices;
    +  facetT *facet;
    +  int i, numpart;
    +  realT dist;
    +  boolT isoutside;
    +
    +  qh furthest_id= qh_IDunknown;
    +  qh lastreport= 0;
    +  qh facet_id= qh vertex_id= qh ridge_id= 0;
    +  qh visit_id= qh vertex_visit= 0;
    +  qh maxoutdone= False;
    +
    +  if (qh GOODpoint > 0)
    +    qh GOODpointp= qh_point(qh GOODpoint-1);
    +  else if (qh GOODpoint < 0)
    +    qh GOODpointp= qh_point(-qh GOODpoint-1);
    +  if (qh GOODvertex > 0)
    +    qh GOODvertexp= qh_point(qh GOODvertex-1);
    +  else if (qh GOODvertex < 0)
    +    qh GOODvertexp= qh_point(-qh GOODvertex-1);
    +  if ((qh GOODpoint
    +       && (qh GOODpointp < qh first_point  /* also catches !GOODpointp */
    +           || qh GOODpointp > qh_point(qh num_points-1)))
    +    || (qh GOODvertex
    +        && (qh GOODvertexp < qh first_point  /* also catches !GOODvertexp */
    +            || qh GOODvertexp > qh_point(qh num_points-1)))) {
    +    qh_fprintf(qh ferr, 6150, "qhull input error: either QGn or QVn point is > p%d\n",
    +             qh num_points-1);
    +    qh_errexit(qh_ERRinput, NULL, NULL);
    +  }
    +  maxpoints= qh_maxmin(qh first_point, qh num_points, qh hull_dim);
    +  if (qh SCALElast)
    +    qh_scalelast(qh first_point, qh num_points, qh hull_dim,
    +               qh MINlastcoord, qh MAXlastcoord, qh MAXwidth);
    +  qh_detroundoff();
    +  if (qh DELAUNAY && qh upper_threshold[qh hull_dim-1] > REALmax/2
    +                  && qh lower_threshold[qh hull_dim-1] < -REALmax/2) {
    +    for (i=qh_PRINTEND; i--; ) {
    +      if (qh PRINTout[i] == qh_PRINTgeom && qh DROPdim < 0
    +          && !qh GOODthreshold && !qh SPLITthresholds)
    +        break;  /* in this case, don't set upper_threshold */
    +    }
    +    if (i < 0) {
    +      if (qh UPPERdelaunay) { /* matches qh.upperdelaunay in qh_setfacetplane */
    +        qh lower_threshold[qh hull_dim-1]= qh ANGLEround * qh_ZEROdelaunay;
    +        qh GOODthreshold= True;
    +      }else {
    +        qh upper_threshold[qh hull_dim-1]= -qh ANGLEround * qh_ZEROdelaunay;
    +        if (!qh GOODthreshold)
    +          qh SPLITthresholds= True; /* build upper-convex hull even if Qg */
    +          /* qh_initqhull_globals errors if Qg without Pdk/etc. */
    +      }
    +    }
    +  }
    +  vertices= qh_initialvertices(qh hull_dim, maxpoints, qh first_point, qh num_points);
    +  qh_initialhull(vertices);  /* initial qh facet_list */
    +  qh_partitionall(vertices, qh first_point, qh num_points);
    +  if (qh PRINToptions1st || qh TRACElevel || qh IStracing) {
    +    if (qh TRACElevel || qh IStracing)
    +      qh_fprintf(qh ferr, 8103, "\nTrace level %d for %s | %s\n",
    +         qh IStracing ? qh IStracing : qh TRACElevel, qh rbox_command, qh qhull_command);
    +    qh_fprintf(qh ferr, 8104, "Options selected for Qhull %s:\n%s\n", qh_version, qh qhull_options);
    +  }
    +  qh_resetlists(False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  qh facet_next= qh facet_list;
    +  qh_furthestnext(/* qh.facet_list */);
    +  if (qh PREmerge) {
    +    qh cos_max= qh premerge_cos;
    +    qh centrum_radius= qh premerge_centrum;
    +  }
    +  if (qh ONLYgood) {
    +    if (qh GOODvertex > 0 && qh MERGING) {
    +      qh_fprintf(qh ferr, 6151, "qhull input error: 'Qg QVn' (only good vertex) does not work with merging.\nUse 'QJ' to joggle the input or 'Q0' to turn off merging.\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (!(qh GOODthreshold || qh GOODpoint
    +         || (!qh MERGEexact && !qh PREmerge && qh GOODvertexp))) {
    +      qh_fprintf(qh ferr, 6152, "qhull input error: 'Qg' (ONLYgood) needs a good threshold('Pd0D0'), a\n\
    +good point(QGn or QG-n), or a good vertex with 'QJ' or 'Q0' (QVn).\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh GOODvertex > 0  && !qh MERGING  /* matches qh_partitionall */
    +        && !qh_isvertex(qh GOODvertexp, vertices)) {
    +      facet= qh_findbestnew(qh GOODvertexp, qh facet_list,
    +                          &dist, !qh_ALL, &isoutside, &numpart);
    +      zadd_(Zdistgood, numpart);
    +      if (!isoutside) {
    +        qh_fprintf(qh ferr, 6153, "qhull input error: point for QV%d is inside initial simplex.  It can not be made a vertex.\n",
    +               qh_pointid(qh GOODvertexp));
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +      if (!qh_addpoint(qh GOODvertexp, facet, False)) {
    +        qh_settempfree(&vertices);
    +        qh_settempfree(&maxpoints);
    +        return;
    +      }
    +    }
    +    qh_findgood(qh facet_list, 0);
    +  }
    +  qh_settempfree(&vertices);
    +  qh_settempfree(&maxpoints);
    +  trace1((qh ferr, 1030, "qh_initbuild: initial hull created and points partitioned\n"));
    +} /* initbuild */
    +
    +/*---------------------------------
    +
    +  qh_initialhull( vertices )
    +    constructs the initial hull as a DIM3 simplex of vertices
    +
    +  design:
    +    creates a simplex (initializes lists)
    +    determines orientation of simplex
    +    sets hyperplanes for facets
    +    doubles checks orientation (in case of axis-parallel facets with Gaussian elimination)
    +    checks for flipped facets and qh.NARROWhull
    +    checks the result
    +*/
    +void qh_initialhull(setT *vertices) {
    +  facetT *facet, *firstfacet, *neighbor, **neighborp;
    +  realT dist, angle, minangle= REALmax;
    +#ifndef qh_NOtrace
    +  int k;
    +#endif
    +
    +  qh_createsimplex(vertices);  /* qh.facet_list */
    +  qh_resetlists(False, qh_RESETvisible);
    +  qh facet_next= qh facet_list;      /* advance facet when processed */
    +  qh interior_point= qh_getcenter(vertices);
    +  firstfacet= qh facet_list;
    +  qh_setfacetplane(firstfacet);
    +  zinc_(Znumvisibility); /* needs to be in printsummary */
    +  qh_distplane(qh interior_point, firstfacet, &dist);
    +  if (dist > 0) {
    +    FORALLfacets
    +      facet->toporient ^= (unsigned char)True;
    +  }
    +  FORALLfacets
    +    qh_setfacetplane(facet);
    +  FORALLfacets {
    +    if (!qh_checkflipped(facet, NULL, qh_ALL)) {/* due to axis-parallel facet */
    +      trace1((qh ferr, 1031, "qh_initialhull: initial orientation incorrect.  Correct all facets\n"));
    +      facet->flipped= False;
    +      FORALLfacets {
    +        facet->toporient ^= (unsigned char)True;
    +        qh_orientoutside(facet);
    +      }
    +      break;
    +    }
    +  }
    +  FORALLfacets {
    +    if (!qh_checkflipped(facet, NULL, !qh_ALL)) {  /* can happen with 'R0.1' */
    +      if (qh DELAUNAY && ! qh ATinfinity) {
    +        if (qh UPPERdelaunay)
    +          qh_fprintf(qh ferr, 6240, "Qhull precision error: Initial simplex is cocircular or cospherical.  Option 'Qs' searches all points.  Can not compute the upper Delaunay triangulation or upper Voronoi diagram of cocircular/cospherical points.\n");
    +        else
    +          qh_fprintf(qh ferr, 6239, "Qhull precision error: Initial simplex is cocircular or cospherical.  Use option 'Qz' for the Delaunay triangulation or Voronoi diagram of cocircular/cospherical points.  Option 'Qz' adds a point \"at infinity\".    Use option 'Qs' to search all points for the initial simplex.\n");
    +        qh_errexit(qh_ERRinput, NULL, NULL);
    +      }
    +      qh_precision("initial simplex is flat");
    +      qh_fprintf(qh ferr, 6154, "Qhull precision error: Initial simplex is flat (facet %d is coplanar with the interior point)\n",
    +                 facet->id);
    +      qh_errexit(qh_ERRsingular, NULL, NULL);  /* calls qh_printhelp_singular */
    +    }
    +    FOREACHneighbor_(facet) {
    +      angle= qh_getangle(facet->normal, neighbor->normal);
    +      minimize_( minangle, angle);
    +    }
    +  }
    +  if (minangle < qh_MAXnarrow && !qh NOnarrow) {
    +    realT diff= 1.0 + minangle;
    +
    +    qh NARROWhull= True;
    +    qh_option("_narrow-hull", NULL, &diff);
    +    if (minangle < qh_WARNnarrow && !qh RERUN && qh PRINTprecision)
    +      qh_printhelp_narrowhull(qh ferr, minangle);
    +  }
    +  zzval_(Zprocessed)= qh hull_dim+1;
    +  qh_checkpolygon(qh facet_list);
    +  qh_checkconvex(qh facet_list,   qh_DATAfault);
    +#ifndef qh_NOtrace
    +  if (qh IStracing >= 1) {
    +    qh_fprintf(qh ferr, 8105, "qh_initialhull: simplex constructed, interior point:");
    +    for (k=0; k < qh hull_dim; k++)
    +      qh_fprintf(qh ferr, 8106, " %6.4g", qh interior_point[k]);
    +    qh_fprintf(qh ferr, 8107, "\n");
    +  }
    +#endif
    +} /* initialhull */
    +
    +/*---------------------------------
    +
    +  qh_initialvertices( dim, maxpoints, points, numpoints )
    +    determines a non-singular set of initial vertices
    +    maxpoints may include duplicate points
    +
    +  returns:
    +    temporary set of dim+1 vertices in descending order by vertex id
    +    if qh.RANDOMoutside && !qh.ALLpoints
    +      picks random points
    +    if dim >= qh_INITIALmax,
    +      uses min/max x and max points with non-zero determinants
    +
    +  notes:
    +    unless qh.ALLpoints,
    +      uses maxpoints as long as determinate is non-zero
    +*/
    +setT *qh_initialvertices(int dim, setT *maxpoints, pointT *points, int numpoints) {
    +  pointT *point, **pointp;
    +  setT *vertices, *simplex, *tested;
    +  realT randr;
    +  int idx, point_i, point_n, k;
    +  boolT nearzero= False;
    +
    +  vertices= qh_settemp(dim + 1);
    +  simplex= qh_settemp(dim+1);
    +  if (qh ALLpoints)
    +    qh_maxsimplex(dim, NULL, points, numpoints, &simplex);
    +  else if (qh RANDOMoutside) {
    +    while (qh_setsize(simplex) != dim+1) {
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor(qh num_points * randr);
    +      while (qh_setin(simplex, qh_point(idx))) {
    +        idx++; /* in case qh_RANDOMint always returns the same value */
    +        idx= idx < qh num_points ? idx : 0;
    +      }
    +      qh_setappend(&simplex, qh_point(idx));
    +    }
    +  }else if (qh hull_dim >= qh_INITIALmax) {
    +    tested= qh_settemp(dim+1);
    +    qh_setappend(&simplex, SETfirst_(maxpoints));   /* max and min X coord */
    +    qh_setappend(&simplex, SETsecond_(maxpoints));
    +    qh_maxsimplex(fmin_(qh_INITIALsearch, dim), maxpoints, points, numpoints, &simplex);
    +    k= qh_setsize(simplex);
    +    FOREACHpoint_i_(maxpoints) {
    +      if (point_i & 0x1) {     /* first pick up max. coord. points */
    +        if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +          qh_detsimplex(point, simplex, k, &nearzero);
    +          if (nearzero)
    +            qh_setappend(&tested, point);
    +          else {
    +            qh_setappend(&simplex, point);
    +            if (++k == dim)  /* use search for last point */
    +              break;
    +          }
    +        }
    +      }
    +    }
    +    while (k != dim && (point= (pointT*)qh_setdellast(maxpoints))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(point, simplex, k, &nearzero);
    +        if (nearzero)
    +          qh_setappend(&tested, point);
    +        else {
    +          qh_setappend(&simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    idx= 0;
    +    while (k != dim && (point= qh_point(idx++))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(point, simplex, k, &nearzero);
    +        if (!nearzero){
    +          qh_setappend(&simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    qh_settempfree(&tested);
    +    qh_maxsimplex(dim, maxpoints, points, numpoints, &simplex);
    +  }else
    +    qh_maxsimplex(dim, maxpoints, points, numpoints, &simplex);
    +  FOREACHpoint_(simplex)
    +    qh_setaddnth(&vertices, 0, qh_newvertex(point)); /* descending order */
    +  qh_settempfree(&simplex);
    +  return vertices;
    +} /* initialvertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_isvertex( point, vertices )
    +    returns vertex if point is in vertex set, else returns NULL
    +
    +  notes:
    +    for qh.GOODvertex
    +*/
    +vertexT *qh_isvertex(pointT *point, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (vertex->point == point)
    +      return vertex;
    +  }
    +  return NULL;
    +} /* isvertex */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacets( point )
    +    make new facets from point and qh.visible_list
    +
    +  returns:
    +    qh.newfacet_list= list of new facets with hyperplanes and ->newfacet
    +    qh.newvertex_list= list of vertices in new facets with ->newlist set
    +
    +    if (qh.ONLYgood)
    +      newfacets reference horizon facets, but not vice versa
    +      ridges reference non-simplicial horizon ridges, but not vice versa
    +      does not change existing facets
    +    else
    +      sets qh.NEWfacets
    +      new facets attached to horizon facets and ridges
    +      for visible facets,
    +        visible->r.replace is corresponding new facet
    +
    +  see also:
    +    qh_makenewplanes() -- make hyperplanes for facets
    +    qh_attachnewfacets() -- attachnewfacets if not done here(qh ONLYgood)
    +    qh_matchnewfacets() -- match up neighbors
    +    qh_updatevertices() -- update vertex neighbors and delvertices
    +    qh_deletevisible() -- delete visible facets
    +    qh_checkpolygon() --check the result
    +    qh_triangulate() -- triangulate a non-simplicial facet
    +
    +  design:
    +    for each visible facet
    +      make new facets to its horizon facets
    +      update its f.replace
    +      clear its neighbor set
    +*/
    +vertexT *qh_makenewfacets(pointT *point /*visible_list*/) {
    +  facetT *visible, *newfacet= NULL, *newfacet2= NULL, *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  qh newfacet_list= qh facet_tail;
    +  qh newvertex_list= qh vertex_tail;
    +  apex= qh_newvertex(point);
    +  qh_appendvertex(apex);
    +  qh visit_id++;
    +  if (!qh ONLYgood)
    +    qh NEWfacets= True;
    +  FORALLvisible_facets {
    +    FOREACHneighbor_(visible)
    +      neighbor->seen= False;
    +    if (visible->ridges) {
    +      visible->visitid= qh visit_id;
    +      newfacet2= qh_makenew_nonsimplicial(visible, apex, &numnew);
    +    }
    +    if (visible->simplicial)
    +      newfacet= qh_makenew_simplicial(visible, apex, &numnew);
    +    if (!qh ONLYgood) {
    +      if (newfacet2)  /* newfacet is null if all ridges defined */
    +        newfacet= newfacet2;
    +      if (newfacet)
    +        visible->f.replace= newfacet;
    +      else
    +        zinc_(Zinsidevisible);
    +      SETfirst_(visible->neighbors)= NULL;
    +    }
    +  }
    +  trace1((qh ferr, 1032, "qh_makenewfacets: created %d new facets from point p%d to horizon\n",
    +          numnew, qh_pointid(point)));
    +  if (qh IStracing >= 4)
    +    qh_printfacetlist(qh newfacet_list, NULL, qh_ALL);
    +  return apex;
    +} /* makenewfacets */
    +
    +/*---------------------------------
    +
    +  qh_matchduplicates( atfacet, atskip, hashsize, hashcount )
    +    match duplicate ridges in qh.hash_table for atfacet/atskip
    +    duplicates marked with ->dupridge and qh_DUPLICATEridge
    +
    +  returns:
    +    picks match with worst merge (min distance apart)
    +    updates hashcount
    +
    +  see also:
    +    qh_matchneighbor
    +
    +  notes:
    +
    +  design:
    +    compute hash value for atfacet and atskip
    +    repeat twice -- once to make best matches, once to match the rest
    +      for each possible facet in qh.hash_table
    +        if it is a matching facet and pass 2
    +          make match
    +          unless tricoplanar, mark match for merging (qh_MERGEridge)
    +          [e.g., tricoplanar RBOX s 1000 t993602376 | QHULL C-1e-3 d Qbb FA Qt]
    +        if it is a matching facet and pass 1
    +          test if this is a better match
    +      if pass 1,
    +        make best match (it will not be merged)
    +*/
    +#ifndef qh_NOmerge
    +void qh_matchduplicates(facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *newfacet, *maxmatch= NULL, *maxmatch2= NULL, *nextfacet;
    +  int skip, newskip, nextskip= 0, maxskip= 0, maxskip2= 0, makematch;
    +  realT maxdist= -REALmax, mindist, dist2, low, high;
    +
    +  hash= qh_gethash(hashsize, atfacet->vertices, qh hull_dim, 1,
    +                     SETelem_(atfacet->vertices, atskip));
    +  trace2((qh ferr, 2046, "qh_matchduplicates: find duplicate matches for f%d skip %d hash %d hashcount %d\n",
    +          atfacet->id, atskip, hash, *hashcount));
    +  for (makematch= 0; makematch < 2; makematch++) {
    +    qh visit_id++;
    +    for (newfacet= atfacet, newskip= atskip; newfacet; newfacet= nextfacet, newskip= nextskip) {
    +      zinc_(Zhashlookup);
    +      nextfacet= NULL;
    +      newfacet->visitid= qh visit_id;
    +      for (scan= hash; (facet= SETelemt_(qh hash_table, scan, facetT));
    +           scan= (++scan >= hashsize ? 0 : scan)) {
    +        if (!facet->dupridge || facet->visitid == qh visit_id)
    +          continue;
    +        zinc_(Zhashtests);
    +        if (qh_matchvertices(1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +          ismatch= (same == (boolT)(newfacet->toporient ^ facet->toporient));
    +          if (SETelemt_(facet->neighbors, skip, facetT) != qh_DUPLICATEridge) {
    +            if (!makematch) {
    +              qh_fprintf(qh ferr, 6155, "qhull internal error (qh_matchduplicates): missing dupridge at f%d skip %d for new f%d skip %d hash %d\n",
    +                     facet->id, skip, newfacet->id, newskip, hash);
    +              qh_errexit2(qh_ERRqhull, facet, newfacet);
    +            }
    +          }else if (ismatch && makematch) {
    +            if (SETelemt_(newfacet->neighbors, newskip, facetT) == qh_DUPLICATEridge) {
    +              SETelem_(facet->neighbors, skip)= newfacet;
    +              if (newfacet->tricoplanar)
    +                SETelem_(newfacet->neighbors, newskip)= facet;
    +              else
    +                SETelem_(newfacet->neighbors, newskip)= qh_MERGEridge;
    +              *hashcount -= 2; /* removed two unmatched facets */
    +              trace4((qh ferr, 4059, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d merge\n",
    +                    facet->id, skip, newfacet->id, newskip));
    +            }
    +          }else if (ismatch) {
    +            mindist= qh_getdistance(facet, newfacet, &low, &high);
    +            dist2= qh_getdistance(newfacet, facet, &low, &high);
    +            minimize_(mindist, dist2);
    +            if (mindist > maxdist) {
    +              maxdist= mindist;
    +              maxmatch= facet;
    +              maxskip= skip;
    +              maxmatch2= newfacet;
    +              maxskip2= newskip;
    +            }
    +            trace3((qh ferr, 3018, "qh_matchduplicates: duplicate f%d skip %d new f%d skip %d at dist %2.2g, max is now f%d f%d\n",
    +                    facet->id, skip, newfacet->id, newskip, mindist,
    +                    maxmatch->id, maxmatch2->id));
    +          }else { /* !ismatch */
    +            nextfacet= facet;
    +            nextskip= skip;
    +          }
    +        }
    +        if (makematch && !facet
    +        && SETelemt_(facet->neighbors, skip, facetT) == qh_DUPLICATEridge) {
    +          qh_fprintf(qh ferr, 6156, "qhull internal error (qh_matchduplicates): no MERGEridge match for duplicate f%d skip %d at hash %d\n",
    +                     newfacet->id, newskip, hash);
    +          qh_errexit(qh_ERRqhull, newfacet, NULL);
    +        }
    +      }
    +    } /* end of for each new facet at hash */
    +    if (!makematch) {
    +      if (!maxmatch) {
    +        qh_fprintf(qh ferr, 6157, "qhull internal error (qh_matchduplicates): no maximum match at duplicate f%d skip %d at hash %d\n",
    +                     atfacet->id, atskip, hash);
    +        qh_errexit(qh_ERRqhull, atfacet, NULL);
    +      }
    +      SETelem_(maxmatch->neighbors, maxskip)= maxmatch2; /* maxmatch!=0 by QH6157 */
    +      SETelem_(maxmatch2->neighbors, maxskip2)= maxmatch;
    +      *hashcount -= 2; /* removed two unmatched facets */
    +      zzinc_(Zmultiridge);
    +      trace0((qh ferr, 25, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d keep\n",
    +              maxmatch->id, maxskip, maxmatch2->id, maxskip2));
    +      qh_precision("ridge with multiple neighbors");
    +      if (qh IStracing >= 4)
    +        qh_errprint("DUPLICATED/MATCH", maxmatch, maxmatch2, NULL, NULL);
    +    }
    +  }
    +} /* matchduplicates */
    +
    +/*---------------------------------
    +
    +  qh_nearcoplanar()
    +    for all facets, remove near-inside points from facet->coplanarset
    +    coplanar points defined by innerplane from qh_outerinner()
    +
    +  returns:
    +    if qh KEEPcoplanar && !qh KEEPinside
    +      facet->coplanarset only contains coplanar points
    +    if qh.JOGGLEmax
    +      drops inner plane by another qh.JOGGLEmax diagonal since a
    +        vertex could shift out while a coplanar point shifts in
    +
    +  notes:
    +    used for qh.PREmerge and qh.JOGGLEmax
    +    must agree with computation of qh.NEARcoplanar in qh_detroundoff()
    +  design:
    +    if not keeping coplanar or inside points
    +      free all coplanar sets
    +    else if not keeping both coplanar and inside points
    +      remove !coplanar or !inside points from coplanar sets
    +*/
    +void qh_nearcoplanar(void /* qh.facet_list */) {
    +  facetT *facet;
    +  pointT *point, **pointp;
    +  int numpart;
    +  realT dist, innerplane;
    +
    +  if (!qh KEEPcoplanar && !qh KEEPinside) {
    +    FORALLfacets {
    +      if (facet->coplanarset)
    +        qh_setfree( &facet->coplanarset);
    +    }
    +  }else if (!qh KEEPcoplanar || !qh KEEPinside) {
    +    qh_outerinner(NULL, NULL, &innerplane);
    +    if (qh JOGGLEmax < REALmax/2)
    +      innerplane -= qh JOGGLEmax * sqrt((realT)qh hull_dim);
    +    numpart= 0;
    +    FORALLfacets {
    +      if (facet->coplanarset) {
    +        FOREACHpoint_(facet->coplanarset) {
    +          numpart++;
    +          qh_distplane(point, facet, &dist);
    +          if (dist < innerplane) {
    +            if (!qh KEEPinside)
    +              SETref_(point)= NULL;
    +          }else if (!qh KEEPcoplanar)
    +            SETref_(point)= NULL;
    +        }
    +        qh_setcompact(facet->coplanarset);
    +      }
    +    }
    +    zzadd_(Zcheckpart, numpart);
    +  }
    +} /* nearcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_nearvertex( facet, point, bestdist )
    +    return nearest vertex in facet to point
    +
    +  returns:
    +    vertex and its distance
    +
    +  notes:
    +    if qh.DELAUNAY
    +      distance is measured in the input set
    +    searches neighboring tricoplanar facets (requires vertexneighbors)
    +      Slow implementation.  Recomputes vertex set for each point.
    +    The vertex set could be stored in the qh.keepcentrum facet.
    +*/
    +vertexT *qh_nearvertex(facetT *facet, pointT *point, realT *bestdistp) {
    +  realT bestdist= REALmax, dist;
    +  vertexT *bestvertex= NULL, *vertex, **vertexp, *apex;
    +  coordT *center;
    +  facetT *neighbor, **neighborp;
    +  setT *vertices;
    +  int dim= qh hull_dim;
    +
    +  if (qh DELAUNAY)
    +    dim--;
    +  if (facet->tricoplanar) {
    +    if (!qh VERTEXneighbors || !facet->center) {
    +      qh_fprintf(qh ferr, 6158, "qhull internal error (qh_nearvertex): qh.VERTEXneighbors and facet->center required for tricoplanar facets\n");
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +    }
    +    vertices= qh_settemp(qh TEMPsize);
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    center= facet->center;
    +    FOREACHneighbor_(apex) {
    +      if (neighbor->center == center) {
    +        FOREACHvertex_(neighbor->vertices)
    +          qh_setappend(&vertices, vertex);
    +      }
    +    }
    +  }else
    +    vertices= facet->vertices;
    +  FOREACHvertex_(vertices) {
    +    dist= qh_pointdist(vertex->point, point, -dim);
    +    if (dist < bestdist) {
    +      bestdist= dist;
    +      bestvertex= vertex;
    +    }
    +  }
    +  if (facet->tricoplanar)
    +    qh_settempfree(&vertices);
    +  *bestdistp= sqrt(bestdist);
    +  if (!bestvertex) {
    +      qh_fprintf(qh ferr, 6261, "qhull internal error (qh_nearvertex): did not find bestvertex for f%d p%d\n", facet->id, qh_pointid(point));
    +      qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  trace3((qh ferr, 3019, "qh_nearvertex: v%d dist %2.2g for f%d p%d\n",
    +        bestvertex->id, *bestdistp, facet->id, qh_pointid(point))); /* bestvertex!=0 by QH2161 */
    +  return bestvertex;
    +} /* nearvertex */
    +
    +/*---------------------------------
    +
    +  qh_newhashtable( newsize )
    +    returns size of qh.hash_table of at least newsize slots
    +
    +  notes:
    +    assumes qh.hash_table is NULL
    +    qh_HASHfactor determines the number of extra slots
    +    size is not divisible by 2, 3, or 5
    +*/
    +int qh_newhashtable(int newsize) {
    +  int size;
    +
    +  size= ((newsize+1)*qh_HASHfactor) | 0x1;  /* odd number */
    +  while (True) {
    +    if (newsize<0 || size<0) {
    +        qh_fprintf(qhmem.ferr, 6236, "qhull error (qh_newhashtable): negative request (%d) or size (%d).  Did int overflow due to high-D?\n", newsize, size); /* WARN64 */
    +        qh_errexit(qhmem_ERRmem, NULL, NULL);
    +    }
    +    if ((size%3) && (size%5))
    +      break;
    +    size += 2;
    +    /* loop terminates because there is an infinite number of primes */
    +  }
    +  qh hash_table= qh_setnew(size);
    +  qh_setzero(qh hash_table, 0, size);
    +  return size;
    +} /* newhashtable */
    +
    +/*---------------------------------
    +
    +  qh_newvertex( point )
    +    returns a new vertex for point
    +*/
    +vertexT *qh_newvertex(pointT *point) {
    +  vertexT *vertex;
    +
    +  zinc_(Ztotvertices);
    +  vertex= (vertexT *)qh_memalloc((int)sizeof(vertexT));
    +  memset((char *) vertex, (size_t)0, sizeof(vertexT));
    +  if (qh vertex_id == UINT_MAX) {
    +    qh_memfree(vertex, (int)sizeof(vertexT));
    +    qh_fprintf(qh ferr, 6159, "qhull error: more than 2^32 vertices.  vertexT.id field overflows.  Vertices would not be sorted correctly.\n");
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }
    +  if (qh vertex_id == qh tracevertex_id)
    +    qh tracevertex= vertex;
    +  vertex->id= qh vertex_id++;
    +  vertex->point= point;
    +  trace4((qh ferr, 4060, "qh_newvertex: vertex p%d(v%d) created\n", qh_pointid(vertex->point),
    +          vertex->id));
    +  return(vertex);
    +} /* newvertex */
    +
    +/*---------------------------------
    +
    +  qh_nextridge3d( atridge, facet, vertex )
    +    return next ridge and vertex for a 3d facet
    +    returns NULL on error
    +    [for QhullFacet::nextRidge3d] Does not call qh_errexit nor access qh_qh.
    +
    +  notes:
    +    in qh_ORIENTclock order
    +    this is a O(n^2) implementation to trace all ridges
    +    be sure to stop on any 2nd visit
    +    same as QhullRidge::nextRidge3d
    +    does not use qh_qh or qh_errexit [QhullFacet.cpp]
    +
    +  design:
    +    for each ridge
    +      exit if it is the ridge after atridge
    +*/
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +  vertexT *atvertex, *vertex, *othervertex;
    +  ridgeT *ridge, **ridgep;
    +
    +  if ((atridge->top == facet) ^ qh_ORIENTclock)
    +    atvertex= SETsecondt_(atridge->vertices, vertexT);
    +  else
    +    atvertex= SETfirstt_(atridge->vertices, vertexT);
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge == atridge)
    +      continue;
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      othervertex= SETsecondt_(ridge->vertices, vertexT);
    +      vertex= SETfirstt_(ridge->vertices, vertexT);
    +    }else {
    +      vertex= SETsecondt_(ridge->vertices, vertexT);
    +      othervertex= SETfirstt_(ridge->vertices, vertexT);
    +    }
    +    if (vertex == atvertex) {
    +      if (vertexp)
    +        *vertexp= othervertex;
    +      return ridge;
    +    }
    +  }
    +  return NULL;
    +} /* nextridge3d */
    +#else /* qh_NOmerge */
    +void qh_matchduplicates(facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +}
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_outcoplanar()
    +    move points from all facets' outsidesets to their coplanarsets
    +
    +  notes:
    +    for post-processing under qh.NARROWhull
    +
    +  design:
    +    for each facet
    +      for each outside point for facet
    +        partition point into coplanar set
    +*/
    +void qh_outcoplanar(void /* facet_list */) {
    +  pointT *point, **pointp;
    +  facetT *facet;
    +  realT dist;
    +
    +  trace1((qh ferr, 1033, "qh_outcoplanar: move outsideset to coplanarset for qh NARROWhull\n"));
    +  FORALLfacets {
    +    FOREACHpoint_(facet->outsideset) {
    +      qh num_outside--;
    +      if (qh KEEPcoplanar || qh KEEPnearinside) {
    +        qh_distplane(point, facet, &dist);
    +        zinc_(Zpartition);
    +        qh_partitioncoplanar(point, facet, &dist);
    +      }
    +    }
    +    qh_setfree(&facet->outsideset);
    +  }
    +} /* outcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_point( id )
    +    return point for a point id, or NULL if unknown
    +
    +  alternative code:
    +    return((pointT *)((unsigned   long)qh.first_point
    +           + (unsigned long)((id)*qh.normal_size)));
    +*/
    +pointT *qh_point(int id) {
    +
    +  if (id < 0)
    +    return NULL;
    +  if (id < qh num_points)
    +    return qh first_point + id * qh hull_dim;
    +  id -= qh num_points;
    +  if (id < qh_setsize(qh other_points))
    +    return SETelemt_(qh other_points, id, pointT);
    +  return NULL;
    +} /* point */
    +
    +/*---------------------------------
    +
    +  qh_point_add( set, point, elem )
    +    stores elem at set[point.id]
    +
    +  returns:
    +    access function for qh_pointfacet and qh_pointvertex
    +
    +  notes:
    +    checks point.id
    +*/
    +void qh_point_add(setT *set, pointT *point, void *elem) {
    +  int id, size;
    +
    +  SETreturnsize_(set, size);
    +  if ((id= qh_pointid(point)) < 0)
    +    qh_fprintf(qh ferr, 7067, "qhull internal warning (point_add): unknown point %p id %d\n",
    +      point, id);
    +  else if (id >= size) {
    +    qh_fprintf(qh ferr, 6160, "qhull internal errror(point_add): point p%d is out of bounds(%d)\n",
    +             id, size);
    +    qh_errexit(qh_ERRqhull, NULL, NULL);
    +  }else
    +    SETelem_(set, id)= elem;
    +} /* point_add */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointfacet()
    +    return temporary set of facet for each point
    +    the set is indexed by point id
    +
    +  notes:
    +    vertices assigned to one of the facets
    +    coplanarset assigned to the facet
    +    outside set assigned to the facet
    +    NULL if no facet for point (inside)
    +      includes qh.GOODpointp
    +
    +  access:
    +    FOREACHfacet_i_(facets) { ... }
    +    SETelem_(facets, i)
    +
    +  design:
    +    for each facet
    +      add each vertex
    +      add each coplanar point
    +      add each outside point
    +*/
    +setT *qh_pointfacet(void /*qh.facet_list*/) {
    +  int numpoints= qh num_points + qh_setsize(qh other_points);
    +  setT *facets;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp;
    +
    +  facets= qh_settemp(numpoints);
    +  qh_setzero(facets, 0, numpoints);
    +  qh vertex_visit++;
    +  FORALLfacets {
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        vertex->visitid= qh vertex_visit;
    +        qh_point_add(facets, vertex->point, facet);
    +      }
    +    }
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(facets, point, facet);
    +    FOREACHpoint_(facet->outsideset)
    +      qh_point_add(facets, point, facet);
    +  }
    +  return facets;
    +} /* pointfacet */
    +
    +/*---------------------------------
    +
    +  qh_pointvertex(  )
    +    return temporary set of vertices indexed by point id
    +    entry is NULL if no vertex for a point
    +      this will include qh.GOODpointp
    +
    +  access:
    +    FOREACHvertex_i_(vertices) { ... }
    +    SETelem_(vertices, i)
    +*/
    +setT *qh_pointvertex(void /*qh.facet_list*/) {
    +  int numpoints= qh num_points + qh_setsize(qh other_points);
    +  setT *vertices;
    +  vertexT *vertex;
    +
    +  vertices= qh_settemp(numpoints);
    +  qh_setzero(vertices, 0, numpoints);
    +  FORALLvertices
    +    qh_point_add(vertices, vertex->point, vertex);
    +  return vertices;
    +} /* pointvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_prependfacet( facet, facetlist )
    +    prepend facet to the start of a facetlist
    +
    +  returns:
    +    increments qh.numfacets
    +    updates facetlist, qh.facet_list, facet_next
    +
    +  notes:
    +    be careful of prepending since it can lose a pointer.
    +      e.g., can lose _next by deleting and then prepending before _next
    +*/
    +void qh_prependfacet(facetT *facet, facetT **facetlist) {
    +  facetT *prevfacet, *list;
    +
    +
    +  trace4((qh ferr, 4061, "qh_prependfacet: prepend f%d before f%d\n",
    +          facet->id, getid_(*facetlist)));
    +  if (!*facetlist)
    +    (*facetlist)= qh facet_tail;
    +  list= *facetlist;
    +  prevfacet= list->previous;
    +  facet->previous= prevfacet;
    +  if (prevfacet)
    +    prevfacet->next= facet;
    +  list->previous= facet;
    +  facet->next= *facetlist;
    +  if (qh facet_list == list)  /* this may change *facetlist */
    +    qh facet_list= facet;
    +  if (qh facet_next == list)
    +    qh facet_next= facet;
    +  *facetlist= facet;
    +  qh num_facets++;
    +} /* prependfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhashtable( fp )
    +    print hash table to fp
    +
    +  notes:
    +    not in I/O to avoid bringing io.c in
    +
    +  design:
    +    for each hash entry
    +      if defined
    +        if unmatched or will merge (NULL, qh_MERGEridge, qh_DUPLICATEridge)
    +          print entry and neighbors
    +*/
    +void qh_printhashtable(FILE *fp) {
    +  facetT *facet, *neighbor;
    +  int id, facet_i, facet_n, neighbor_i= 0, neighbor_n= 0;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHfacet_i_(qh hash_table) {
    +    if (facet) {
    +      FOREACHneighbor_i_(facet) {
    +        if (!neighbor || neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge)
    +          break;
    +      }
    +      if (neighbor_i == neighbor_n)
    +        continue;
    +      qh_fprintf(fp, 9283, "hash %d f%d ", facet_i, facet->id);
    +      FOREACHvertex_(facet->vertices)
    +        qh_fprintf(fp, 9284, "v%d ", vertex->id);
    +      qh_fprintf(fp, 9285, "\n neighbors:");
    +      FOREACHneighbor_i_(facet) {
    +        if (neighbor == qh_MERGEridge)
    +          id= -3;
    +        else if (neighbor == qh_DUPLICATEridge)
    +          id= -2;
    +        else
    +          id= getid_(neighbor);
    +        qh_fprintf(fp, 9286, " %d", id);
    +      }
    +      qh_fprintf(fp, 9287, "\n");
    +    }
    +  }
    +} /* printhashtable */
    +
    +
    +/*---------------------------------
    +
    +  qh_printlists( fp )
    +    print out facet and vertex list for debugging (without 'f/v' tags)
    +*/
    +void qh_printlists(void) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int count= 0;
    +
    +  qh_fprintf(qh ferr, 8108, "qh_printlists: facets:");
    +  FORALLfacets {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh ferr, 8109, "\n     ");
    +    qh_fprintf(qh ferr, 8110, " %d", facet->id);
    +  }
    +  qh_fprintf(qh ferr, 8111, "\n  new facets %d visible facets %d next facet for qh_addpoint %d\n  vertices(new %d):",
    +     getid_(qh newfacet_list), getid_(qh visible_list), getid_(qh facet_next),
    +     getid_(qh newvertex_list));
    +  count = 0;
    +  FORALLvertices {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh ferr, 8112, "\n     ");
    +    qh_fprintf(qh ferr, 8113, " %d", vertex->id);
    +  }
    +  qh_fprintf(qh ferr, 8114, "\n");
    +} /* printlists */
    +
    +/*---------------------------------
    +
    +  qh_resetlists( stats, qh_RESETvisible )
    +    reset newvertex_list, newfacet_list, visible_list
    +    if stats,
    +      maintains statistics
    +
    +  returns:
    +    visible_list is empty if qh_deletevisible was called
    +*/
    +void qh_resetlists(boolT stats, boolT resetVisible /*qh.newvertex_list newfacet_list visible_list*/) {
    +  vertexT *vertex;
    +  facetT *newfacet, *visible;
    +  int totnew=0, totver=0;
    +
    +  if (stats) {
    +    FORALLvertex_(qh newvertex_list)
    +      totver++;
    +    FORALLnew_facets
    +      totnew++;
    +    zadd_(Zvisvertextot, totver);
    +    zmax_(Zvisvertexmax, totver);
    +    zadd_(Znewfacettot, totnew);
    +    zmax_(Znewfacetmax, totnew);
    +  }
    +  FORALLvertex_(qh newvertex_list)
    +    vertex->newlist= False;
    +  qh newvertex_list= NULL;
    +  FORALLnew_facets
    +    newfacet->newfacet= False;
    +  qh newfacet_list= NULL;
    +  if (resetVisible) {
    +    FORALLvisible_facets {
    +      visible->f.replace= NULL;
    +      visible->visible= False;
    +    }
    +    qh num_visible= 0;
    +  }
    +  qh visible_list= NULL; /* may still have visible facets via qh_triangulate */
    +  qh NEWfacets= False;
    +} /* resetlists */
    +
    +/*---------------------------------
    +
    +  qh_setvoronoi_all()
    +    compute Voronoi centers for all facets
    +    includes upperDelaunay facets if qh.UPPERdelaunay ('Qu')
    +
    +  returns:
    +    facet->center is the Voronoi center
    +
    +  notes:
    +    this is unused/untested code
    +      please email bradb@shore.net if this works ok for you
    +
    +  use:
    +    FORALLvertices {...} to locate the vertex for a point.
    +    FOREACHneighbor_(vertex) {...} to visit the Voronoi centers for a Voronoi cell.
    +*/
    +void qh_setvoronoi_all(void) {
    +  facetT *facet;
    +
    +  qh_clearcenters(qh_ASvoronoi);
    +  qh_vertexneighbors();
    +
    +  FORALLfacets {
    +    if (!facet->normal || !facet->upperdelaunay || qh UPPERdelaunay) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(facet->vertices);
    +    }
    +  }
    +} /* setvoronoi_all */
    +
    +#ifndef qh_NOmerge
    +
    +/*---------------------------------
    +
    +  qh_triangulate()
    +    triangulate non-simplicial facets on qh.facet_list,
    +    if qh VORONOI, sets Voronoi centers of non-simplicial facets
    +    nop if hasTriangulation
    +
    +  returns:
    +    all facets simplicial
    +    each tricoplanar facet has ->f.triowner == owner of ->center,normal,etc.
    +
    +  notes:
    +    call after qh_check_output since may switch to Voronoi centers
    +    Output may overwrite ->f.triowner with ->f.area
    +*/
    +void qh_triangulate(void /*qh.facet_list*/) {
    +  facetT *facet, *nextfacet, *owner;
    +  int onlygood= qh ONLYgood;
    +  facetT *neighbor, *visible= NULL, *facet1, *facet2, *new_facet_list= NULL;
    +  facetT *orig_neighbor= NULL, *otherfacet;
    +  vertexT *new_vertex_list= NULL;
    +  mergeT *merge;
    +  mergeType mergetype;
    +  int neighbor_i, neighbor_n;
    +
    +  if (qh hasTriangulation)
    +      return;
    +  trace1((qh ferr, 1034, "qh_triangulate: triangulate non-simplicial facets\n"));
    +  if (qh hull_dim == 2)
    +    return;
    +  if (qh VORONOI) {  /* otherwise lose Voronoi centers [could rebuild vertex set from tricoplanar] */
    +    qh_clearcenters(qh_ASvoronoi);
    +    qh_vertexneighbors();
    +  }
    +  qh ONLYgood= False; /* for makenew_nonsimplicial */
    +  qh visit_id++;
    +  qh NEWfacets= True;
    +  qh degen_mergeset= qh_settemp(qh TEMPsize);
    +  qh newvertex_list= qh vertex_tail;
    +  for (facet= qh facet_list; facet && facet->next; facet= nextfacet) { /* non-simplicial facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible || facet->simplicial)
    +      continue;
    +    /* triangulate all non-simplicial facets, otherwise merging does not work, e.g., RBOX c P-0.1 P+0.1 P+0.1 D3 | QHULL d Qt Tv */
    +    if (!new_facet_list)
    +      new_facet_list= facet;  /* will be moved to end */
    +    qh_triangulate_facet(facet, &new_vertex_list);
    +  }
    +  trace2((qh ferr, 2047, "qh_triangulate: delete null facets from f%d -- apex same as second vertex\n", getid_(new_facet_list)));
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* null facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible)
    +      continue;
    +    if (facet->ridges) {
    +      if (qh_setsize(facet->ridges) > 0) {
    +        qh_fprintf(qh ferr, 6161, "qhull error (qh_triangulate): ridges still defined for f%d\n", facet->id);
    +        qh_errexit(qh_ERRqhull, facet, NULL);
    +      }
    +      qh_setfree(&facet->ridges);
    +    }
    +    if (SETfirst_(facet->vertices) == SETsecond_(facet->vertices)) {
    +      zinc_(Ztrinull);
    +      qh_triangulate_null(facet);
    +    }
    +  }
    +  trace2((qh ferr, 2048, "qh_triangulate: delete %d or more mirror facets -- same vertices and neighbors\n", qh_setsize(qh degen_mergeset)));
    +  qh visible_list= qh facet_tail;
    +  while ((merge= (mergeT*)qh_setdellast(qh degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(merge, (int)sizeof(mergeT));
    +    if (mergetype == MRGmirror) {
    +      zinc_(Ztrimirror);
    +      qh_triangulate_mirror(facet1, facet2);
    +    }
    +  }
    +  qh_settempfree(&qh degen_mergeset);
    +  trace2((qh ferr, 2049, "qh_triangulate: update neighbor lists for vertices from v%d\n", getid_(new_vertex_list)));
    +  qh newvertex_list= new_vertex_list;  /* all vertices of new facets */
    +  qh visible_list= NULL;
    +  qh_updatevertices(/*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +  qh_resetlists(False, !qh_RESETvisible /*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +
    +  trace2((qh ferr, 2050, "qh_triangulate: identify degenerate tricoplanar facets from f%d\n", getid_(new_facet_list)));
    +  trace2((qh ferr, 2051, "qh_triangulate: and replace facet->f.triowner with tricoplanar facets that own center, normal, etc.\n"));
    +  FORALLfacet_(new_facet_list) {
    +    if (facet->tricoplanar && !facet->visible) {
    +      FOREACHneighbor_i_(facet) {
    +        if (neighbor_i == 0) {  /* first iteration */
    +          if (neighbor->tricoplanar)
    +            orig_neighbor= neighbor->f.triowner;
    +          else
    +            orig_neighbor= neighbor;
    +        }else {
    +          if (neighbor->tricoplanar)
    +            otherfacet= neighbor->f.triowner;
    +          else
    +            otherfacet= neighbor;
    +          if (orig_neighbor == otherfacet) {
    +            zinc_(Ztridegen);
    +            facet->degenerate= True;
    +            break;
    +          }
    +        }
    +      }
    +    }
    +  }
    +
    +  trace2((qh ferr, 2052, "qh_triangulate: delete visible facets -- non-simplicial, null, and mirrored facets\n"));
    +  owner= NULL;
    +  visible= NULL;
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* may delete facet */
    +    nextfacet= facet->next;
    +    if (facet->visible) {
    +      if (facet->tricoplanar) { /* a null or mirrored facet */
    +        qh_delfacet(facet);
    +        qh num_visible--;
    +      }else {  /* a non-simplicial facet followed by its tricoplanars */
    +        if (visible && !owner) {
    +          /*  RBOX 200 s D5 t1001471447 | QHULL Qt C-0.01 Qx Qc Tv Qt -- f4483 had 6 vertices/neighbors and 8 ridges */
    +          trace2((qh ferr, 2053, "qh_triangulate: all tricoplanar facets degenerate for non-simplicial facet f%d\n",
    +                       visible->id));
    +          qh_delfacet(visible);
    +          qh num_visible--;
    +        }
    +        visible= facet;
    +        owner= NULL;
    +      }
    +    }else if (facet->tricoplanar) {
    +      if (facet->f.triowner != visible || visible==NULL) {
    +        qh_fprintf(qh ferr, 6162, "qhull error (qh_triangulate): tricoplanar facet f%d not owned by its visible, non-simplicial facet f%d\n", facet->id, getid_(visible));
    +        qh_errexit2(qh_ERRqhull, facet, visible);
    +      }
    +      if (owner)
    +        facet->f.triowner= owner;
    +      else if (!facet->degenerate) {
    +        owner= facet;
    +        nextfacet= visible->next; /* rescan tricoplanar facets with owner, visible!=0 by QH6162 */
    +        facet->keepcentrum= True;  /* one facet owns ->normal, etc. */
    +        facet->coplanarset= visible->coplanarset;
    +        facet->outsideset= visible->outsideset;
    +        visible->coplanarset= NULL;
    +        visible->outsideset= NULL;
    +        if (!qh TRInormals) { /* center and normal copied to tricoplanar facets */
    +          visible->center= NULL;
    +          visible->normal= NULL;
    +        }
    +        qh_delfacet(visible);
    +        qh num_visible--;
    +      }
    +    }
    +  }
    +  if (visible && !owner) {
    +    trace2((qh ferr, 2054, "qh_triangulate: all tricoplanar facets degenerate for last non-simplicial facet f%d\n",
    +                 visible->id));
    +    qh_delfacet(visible);
    +    qh num_visible--;
    +  }
    +  qh NEWfacets= False;
    +  qh ONLYgood= onlygood; /* restore value */
    +  if (qh CHECKfrequently)
    +    qh_checkpolygon(qh facet_list);
    +  qh hasTriangulation= True;
    +} /* triangulate */
    +
    +
    +/*---------------------------------
    +
    +  qh_triangulate_facet(qh, facetA, &firstVertex )
    +    triangulate a non-simplicial facet
    +      if qh.CENTERtype=qh_ASvoronoi, sets its Voronoi center
    +  returns:
    +    qh.newfacet_list == simplicial facets
    +      facet->tricoplanar set and ->keepcentrum false
    +      facet->degenerate set if duplicated apex
    +      facet->f.trivisible set to facetA
    +      facet->center copied from facetA (created if qh_ASvoronoi)
    +        qh_eachvoronoi, qh_detvridge, qh_detvridge3 assume centers copied
    +      facet->normal,offset,maxoutside copied from facetA
    +
    +  notes:
    +      only called by qh_triangulate
    +      qh_makenew_nonsimplicial uses neighbor->seen for the same
    +      if qh.TRInormals, newfacet->normal will need qh_free
    +        if qh.TRInormals and qh_AScentrum, newfacet->center will need qh_free
    +        keepcentrum is also set on Zwidefacet in qh_mergefacet
    +        freed by qh_clearcenters
    +
    +  see also:
    +      qh_addpoint() -- add a point
    +      qh_makenewfacets() -- construct a cone of facets for a new vertex
    +
    +  design:
    +      if qh_ASvoronoi,
    +         compute Voronoi center (facet->center)
    +      select first vertex (highest ID to preserve ID ordering of ->vertices)
    +      triangulate from vertex to ridges
    +      copy facet->center, normal, offset
    +      update vertex neighbors
    +*/
    +void qh_triangulate_facet(facetT *facetA, vertexT **first_vertex) {
    +  facetT *newfacet;
    +  facetT *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  trace3((qh ferr, 3020, "qh_triangulate_facet: triangulate facet f%d\n", facetA->id));
    +
    +  if (qh IStracing >= 4)
    +    qh_printfacet(qh ferr, facetA);
    +  FOREACHneighbor_(facetA) {
    +    neighbor->seen= False;
    +    neighbor->coplanar= False;
    +  }
    +  if (qh CENTERtype == qh_ASvoronoi && !facetA->center  /* matches upperdelaunay in qh_setfacetplane() */
    +        && fabs_(facetA->normal[qh hull_dim -1]) >= qh ANGLEround * qh_ZEROdelaunay) {
    +    facetA->center= qh_facetcenter(facetA->vertices);
    +  }
    +  qh_willdelete(facetA, NULL);
    +  qh newfacet_list= qh facet_tail;
    +  facetA->visitid= qh visit_id;
    +  apex= SETfirstt_(facetA->vertices, vertexT);
    +  qh_makenew_nonsimplicial(facetA, apex, &numnew);
    +  SETfirst_(facetA->neighbors)= NULL;
    +  FORALLnew_facets {
    +    newfacet->tricoplanar= True;
    +    newfacet->f.trivisible= facetA;
    +    newfacet->degenerate= False;
    +    newfacet->upperdelaunay= facetA->upperdelaunay;
    +    newfacet->good= facetA->good;
    +    if (qh TRInormals) { /* 'Q11' triangulate duplicates ->normal and ->center */
    +      newfacet->keepcentrum= True;
    +      if(facetA->normal){
    +        newfacet->normal= qh_memalloc(qh normal_size);
    +        memcpy((char *)newfacet->normal, facetA->normal, qh normal_size);
    +      }
    +      if (qh CENTERtype == qh_AScentrum)
    +        newfacet->center= qh_getcentrum(newfacet);
    +      else if (qh CENTERtype == qh_ASvoronoi && facetA->center){
    +        newfacet->center= qh_memalloc(qh center_size);
    +        memcpy((char *)newfacet->center, facetA->center, qh center_size);
    +      }
    +    }else {
    +      newfacet->keepcentrum= False;
    +      /* one facet will have keepcentrum=True at end of qh_triangulate */
    +      newfacet->normal= facetA->normal;
    +      newfacet->center= facetA->center;
    +    }
    +    newfacet->offset= facetA->offset;
    +#if qh_MAXoutside
    +    newfacet->maxoutside= facetA->maxoutside;
    +#endif
    +  }
    +  qh_matchnewfacets(/*qh.newfacet_list*/);
    +  zinc_(Ztricoplanar);
    +  zadd_(Ztricoplanartot, numnew);
    +  zmax_(Ztricoplanarmax, numnew);
    +  qh visible_list= NULL;
    +  if (!(*first_vertex))
    +    (*first_vertex)= qh newvertex_list;
    +  qh newvertex_list= NULL;
    +  qh_updatevertices(/*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +  qh_resetlists(False, !qh_RESETvisible /*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +} /* triangulate_facet */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_link(oldfacetA, facetA, oldfacetB, facetB)
    +    relink facetA to facetB via oldfacets
    +  returns:
    +    adds mirror facets to qh degen_mergeset (4-d and up only)
    +  design:
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_link(facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB) {
    +  int errmirror= False;
    +
    +  trace3((qh ferr, 3021, "qh_triangulate_link: relink old facets f%d and f%d between neighbors f%d and f%d\n",
    +         oldfacetA->id, oldfacetB->id, facetA->id, facetB->id));
    +  if (qh_setin(facetA->neighbors, facetB)) {
    +    if (!qh_setin(facetB->neighbors, facetA))
    +      errmirror= True;
    +    else
    +      qh_appendmergeset(facetA, facetB, MRGmirror, NULL);
    +  }else if (qh_setin(facetB->neighbors, facetA))
    +    errmirror= True;
    +  if (errmirror) {
    +    qh_fprintf(qh ferr, 6163, "qhull error (qh_triangulate_link): mirror facets f%d and f%d do not match for old facets f%d and f%d\n",
    +       facetA->id, facetB->id, oldfacetA->id, oldfacetB->id);
    +    qh_errexit2(qh_ERRqhull, facetA, facetB);
    +  }
    +  qh_setreplace(facetB->neighbors, oldfacetB, facetA);
    +  qh_setreplace(facetA->neighbors, oldfacetA, facetB);
    +} /* triangulate_link */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_mirror(facetA, facetB)
    +    delete mirrored facets from qh_triangulate_null() and qh_triangulate_mirror
    +      a mirrored facet shares the same vertices of a logical ridge
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_mirror(facetT *facetA, facetT *facetB) {
    +  facetT *neighbor, *neighborB;
    +  int neighbor_i, neighbor_n;
    +
    +  trace3((qh ferr, 3022, "qh_triangulate_mirror: delete mirrored facets f%d and f%d\n",
    +         facetA->id, facetB->id));
    +  FOREACHneighbor_i_(facetA) {
    +    neighborB= SETelemt_(facetB->neighbors, neighbor_i, facetT);
    +    if (neighbor == neighborB)
    +      continue; /* occurs twice */
    +    qh_triangulate_link(facetA, neighbor, facetB, neighborB);
    +  }
    +  qh_willdelete(facetA, NULL);
    +  qh_willdelete(facetB, NULL);
    +} /* triangulate_mirror */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_null(facetA)
    +    remove null facetA from qh_triangulate_facet()
    +      a null facet has vertex #1 (apex) == vertex #2
    +  returns:
    +    adds facetA to ->visible for deletion after qh_updatevertices
    +    qh degen_mergeset contains mirror facets (4-d and up only)
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_null(facetT *facetA) {
    +  facetT *neighbor, *otherfacet;
    +
    +  trace3((qh ferr, 3023, "qh_triangulate_null: delete null facet f%d\n", facetA->id));
    +  neighbor= SETfirstt_(facetA->neighbors, facetT);
    +  otherfacet= SETsecondt_(facetA->neighbors, facetT);
    +  qh_triangulate_link(facetA, neighbor, facetA, otherfacet);
    +  qh_willdelete(facetA, NULL);
    +} /* triangulate_null */
    +
    +#else /* qh_NOmerge */
    +void qh_triangulate(void) {
    +}
    +#endif /* qh_NOmerge */
    +
    +   /*---------------------------------
    +
    +  qh_vertexintersect( vertexsetA, vertexsetB )
    +    intersects two vertex sets (inverse id ordered)
    +    vertexsetA is a temporary set at the top of qhmem.tempstack
    +
    +  returns:
    +    replaces vertexsetA with the intersection
    +
    +  notes:
    +    could overwrite vertexsetA if currently too slow
    +*/
    +void qh_vertexintersect(setT **vertexsetA,setT *vertexsetB) {
    +  setT *intersection;
    +
    +  intersection= qh_vertexintersect_new(*vertexsetA, vertexsetB);
    +  qh_settempfree(vertexsetA);
    +  *vertexsetA= intersection;
    +  qh_settemppush(intersection);
    +} /* vertexintersect */
    +
    +/*---------------------------------
    +
    +  qh_vertexintersect_new(  )
    +    intersects two vertex sets (inverse id ordered)
    +
    +  returns:
    +    a new set
    +*/
    +setT *qh_vertexintersect_new(setT *vertexsetA,setT *vertexsetB) {
    +  setT *intersection= qh_setnew(qh hull_dim - 1);
    +  vertexT **vertexA= SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= SETaddr_(vertexsetB, vertexT);
    +
    +  while (*vertexA && *vertexB) {
    +    if (*vertexA  == *vertexB) {
    +      qh_setappend(&intersection, *vertexA);
    +      vertexA++; vertexB++;
    +    }else {
    +      if ((*vertexA)->id > (*vertexB)->id)
    +        vertexA++;
    +      else
    +        vertexB++;
    +    }
    +  }
    +  return intersection;
    +} /* vertexintersect_new */
    +
    +/*---------------------------------
    +
    +  qh_vertexneighbors()
    +    for each vertex in qh.facet_list,
    +      determine its neighboring facets
    +
    +  returns:
    +    sets qh.VERTEXneighbors
    +      nop if qh.VERTEXneighbors already set
    +      qh_addpoint() will maintain them
    +
    +  notes:
    +    assumes all vertex->neighbors are NULL
    +
    +  design:
    +    for each facet
    +      for each vertex
    +        append facet to vertex->neighbors
    +*/
    +void qh_vertexneighbors(void /*qh.facet_list*/) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh VERTEXneighbors)
    +    return;
    +  trace1((qh ferr, 1035, "qh_vertexneighbors: determining neighboring facets for each vertex\n"));
    +  qh vertex_visit++;
    +  FORALLfacets {
    +    if (facet->visible)
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh vertex_visit) {
    +        vertex->visitid= qh vertex_visit;
    +        vertex->neighbors= qh_setnew(qh hull_dim);
    +      }
    +      qh_setappend(&vertex->neighbors, facet);
    +    }
    +  }
    +  qh VERTEXneighbors= True;
    +} /* vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_vertexsubset( vertexsetA, vertexsetB )
    +    returns True if vertexsetA is a subset of vertexsetB
    +    assumes vertexsets are sorted
    +
    +  note:
    +    empty set is a subset of any other set
    +*/
    +boolT qh_vertexsubset(setT *vertexsetA, setT *vertexsetB) {
    +  vertexT **vertexA= (vertexT **) SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= (vertexT **) SETaddr_(vertexsetB, vertexT);
    +
    +  while (True) {
    +    if (!*vertexA)
    +      return True;
    +    if (!*vertexB)
    +      return False;
    +    if ((*vertexA)->id > (*vertexB)->id)
    +      return False;
    +    if (*vertexA  == *vertexB)
    +      vertexA++;
    +    vertexB++;
    +  }
    +  return False; /* avoid warnings */
    +} /* vertexsubset */
    diff --git a/xs/src/qhull/src/libqhull/qh-geom.htm b/xs/src/qhull/src/libqhull/qh-geom.htm
    new file mode 100644
    index 0000000000..6dc7465ebe
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qh-geom.htm
    @@ -0,0 +1,295 @@
    +
    +
    +
    +
    +geom.c, geom2.c -- geometric and floating point routines
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    geom.c, geom2.c, random.c -- geometric and floating point routines

    +
    +

    Geometrically, a vertex is a point with d coordinates +and a facet is a halfspace. A halfspace is defined by an +oriented hyperplane through the facet's vertices. A hyperplane +is defined by d normalized coefficients and an offset. A +point is above a facet if its distance to the facet is +positive.

    + +

    Qhull uses floating point coordinates for input points, +vertices, halfspace equations, centrums, and an interior point.

    + +

    Qhull may be configured for single precision or double +precision floating point arithmetic (see realT +).

    + +

    Each floating point operation may incur round-off error (see +Merge). The maximum error for distance +computations is determined at initialization. The roundoff error +in halfspace computation is accounted for by computing the +distance from vertices to the halfspace.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to geom.c, +geom2.c, geom.h, +random.c, random.h +

    + + + +

    »geometric data types +and constants

    + +
      +
    • coordT coordinates and +coefficients are stored as realT
    • +
    • pointT a point is an array +of DIM3 coordinates
    • +
    + +

    »mathematical macros

    + +
      +
    • fabs_ returns the absolute +value of a
    • +
    • fmax_ returns the maximum +value of a and b
    • +
    • fmin_ returns the minimum +value of a and b
    • +
    • maximize_ maximize a value +
    • +
    • minimize_ minimize a value +
    • +
    • det2_ compute a 2-d +determinate
    • +
    • det3_ compute a 3-d +determinate
    • +
    • dX, dY, dZ compute the difference +between two coordinates
    • +
    + +

    »mathematical functions

    + + + +

    »computational geometry functions

    + + + +

    »point array functions

    + + +

    »geometric facet functions

    + + +

    »geometric roundoff functions

    +
      +
    • qh_detjoggle determine +default joggle for points and distance roundoff error
    • +
    • qh_detroundoff +determine maximum roundoff error and other precision constants
    • +
    • qh_distround compute +maximum roundoff error due to a distance computation to a +normalized hyperplane
    • +
    • qh_divzero divide by a +number that is nearly zero
    • +
    • qh_maxouter return maximum outer +plane
    • +
    • qh_outerinner return actual +outer and inner planes +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-globa.htm b/xs/src/qhull/src/libqhull/qh-globa.htm new file mode 100644 index 0000000000..c87508b663 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-globa.htm @@ -0,0 +1,165 @@ + + + + +global.c -- global variables and their functions + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    global.c -- global variables and their functions

    +
    +

    Qhull uses a global data structure, qh, to store +globally defined constants, lists, sets, and variables. This +allows multiple instances of Qhull to execute at the same time. +The structure may be statically allocated or +dynamically allocated with malloc(). See +QHpointer. +

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to global.c and +libqhull.h

    + + + +

    »Qhull's global +variables

    + + + +

    »Global variable and +initialization routines

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-io.htm b/xs/src/qhull/src/libqhull/qh-io.htm new file mode 100644 index 0000000000..5cb591d877 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-io.htm @@ -0,0 +1,305 @@ + + + + +io.c -- input and output operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    io.c -- input and output operations

    +
    + +

    Qhull provides a wide range of input +and output options. To organize the code, most output formats use +the same driver:

    + +
    +    qh_printbegin( fp, format, facetlist, facets, printall );
    +
    +    FORALLfacet_( facetlist )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    FOREACHfacet_( facets )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    qh_printend( fp, format );
    +
    + +

    Note the 'printall' flag. It selects whether or not +qh_skipfacet() is tested.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom +GlobalIo • +MemMerge • +PolyQhull • +SetStat • +User

    + +

    Index to io.c and io.h

    + + + +

    »io.h constants and types

    + +
      +
    • qh_MAXfirst maximum length +of first two lines of stdin
    • +
    • qh_WHITESPACE possible +values of white space
    • +
    • printvridgeT function to +print results of qh_printvdiagram or qh_eachvoronoi
    • +
    + +

    »User level functions

    + + + +

    »Print functions for all +output formats

    + + + +

    »Text output functions

    + + +

    »Text utility functions

    + + +

    »Geomview output functions

    + +

    »Geomview utility functions

    + +

    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-mem.htm b/xs/src/qhull/src/libqhull/qh-mem.htm new file mode 100644 index 0000000000..b993b22297 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-mem.htm @@ -0,0 +1,145 @@ + + + + +mem.c -- memory operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    mem.c -- memory operations

    +
    +

    Qhull uses quick-fit memory allocation. It maintains a +set of free lists for a variety of small allocations. A +small request returns a block from the best fitting free +list. If the free list is empty, Qhull allocates a block +from a reserved buffer.

    +

    Use 'T5' to trace memory allocations.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to mem.c and +mem.h

    + +

    »mem.h data types and constants

    +
      +
    • ptr_intT for casting +a void* to an integer-type
    • +
    • qhmemT global memory +structure for mem.c
    • +
    • qh_NOmem disable memory allocation
    • +
    +

    »mem.h macros

    + +

    »User level +functions

    + + +

    »Initialization and +termination functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-merge.htm b/xs/src/qhull/src/libqhull/qh-merge.htm new file mode 100644 index 0000000000..54b97c88ee --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-merge.htm @@ -0,0 +1,366 @@ + + + + +merge.c -- facet merge operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    merge.c -- facet merge operations

    +
    +

    Qhull handles precision problems by merged facets or joggled input. +Except for redundant vertices, it corrects a problem by +merging two facets. When done, all facets are clearly +convex. See Imprecision in Qhull +for further information.

    +

    Users may joggle the input ('QJn') +instead of merging facets.

    +

    Qhull detects and corrects the following problems:

    +
      +
    • More than two facets meeting at a ridge. When +Qhull creates facets, it creates an even number +of facets for each ridge. A convex hull always +has two facets for each ridge. More than two +facets may be created if non-adjacent facets +share a vertex. This is called a duplicate +ridge. In 2-d, a duplicate ridge would +create a loop of facets.
    • +
    +
      +
    • A facet contained in another facet. Facet +merging may leave all vertices of one facet as a +subset of the vertices of another facet. This is +called a redundant facet.
    • +
    +
      +
    • A facet with fewer than three neighbors. Facet +merging may leave a facet with one or two +neighbors. This is called a degenerate facet. +
    • +
    +
      +
    • A facet with flipped orientation. A +facet's hyperplane may define a halfspace that +does not include the interior point.This is +called a flipped facet.
    • +
    +
      +
    • A coplanar horizon facet. A +newly processed point may be coplanar with an +horizon facet. Qhull creates a new facet without +a hyperplane. It links new facets for the same +horizon facet together. This is called a samecycle. +The new facet or samecycle is merged into the +horizon facet.
    • +
    +
      +
    • Concave facets. A facet's centrum may be +above a neighboring facet. If so, the facets meet +at a concave angle.
    • +
    +
      +
    • Coplanar facets. A facet's centrum may be +coplanar with a neighboring facet (i.e., it is +neither clearly below nor clearly above the +facet's hyperplane). Qhull removes coplanar +facets in independent sets sorted by angle.
    • +
    +
      +
    • Redundant vertex. A vertex may have fewer +than three neighboring facets. If so, it is +redundant and may be renamed to an adjacent +vertex without changing the topological +structure.This is called a redundant vertex. +
    • +
    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to merge.c and +merge.h

    + + +

    »merge.h data +types, macros, and global sets

    +
      +
    • mergeT structure to +identify a merge of two facets
    • +
    • FOREACHmerge_ +assign 'merge' to each merge in merges
    • +
    • qh global sets +qh.facet_mergeset contains non-convex merges +while qh.degen_mergeset contains degenerate and +redundant facets
    • +
    +

    »merge.h +constants

    + +

    »top-level merge +functions

    + + +

    »functions for +identifying merges

    + + +

    »functions for +determining the best merge

    + + +

    »functions for +merging facets

    + + +

    »functions for +merging a cycle of facets

    +

    If a point is coplanar with an horizon facet, the +corresponding new facets are linked together (a samecycle) +for merging.

    + +

    »functions +for renaming a vertex

    + + +

    »functions +for identifying vertices for renaming

    + + +

    »functions for check and +trace

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-poly.htm b/xs/src/qhull/src/libqhull/qh-poly.htm new file mode 100644 index 0000000000..c8f6b38b0d --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-poly.htm @@ -0,0 +1,485 @@ + + + + +poly.c, poly2.c -- polyhedron operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    poly.c, poly2.c -- polyhedron operations

    +
    + +

    Qhull uses dimension-free terminology. Qhull builds a +polyhedron in dimension d. A polyhedron is a +simplicial complex of faces with geometric information for the +top and bottom-level faces. A (d-1)-face is a facet, +a (d-2)-face is a ridge, and a 0-face +is a vertex. For example in 3-d, a facet is a polygon +and a ridge is an edge. A facet is built from a ridge (the base) +and a vertex (the apex). See +Qhull's data structures.

    + +

    Qhull's primary data structure is a polyhedron. A +polyhedron is a list of facets. Each facet has a set of +neighboring facets and a set of vertices. Each facet has a +hyperplane. For example, a tetrahedron has four facets. +If its vertices are a, b, c, d, and its facets +are 1, 2, 3, 4, the tetrahedron is

    +
    +
      +
    • facet 1
        +
      • vertices: b c d
      • +
      • neighbors: 2 3 4
      • +
      +
    • +
    • facet 2
        +
      • vertices: a c d
      • +
      • neighbors: 1 3 4
      • +
      +
    • +
    • facet 3
        +
      • vertices: a b d
      • +
      • neighbors: 1 2 4
      • +
      +
    • +
    • facet 4
        +
      • vertices: a b c
      • +
      • neighbors: 1 2 3
      • +
      +
    • +
    +
    +

    A facet may be simplicial or non-simplicial. In 3-d, a +simplicial facet has three vertices and three +neighbors. A nonsimplicial facet has more than +three vertices and more than three neighbors. A +nonsimplicial facet has a set of ridges and a centrum.

    +

    +A simplicial facet has an orientation. An orientation +is either top or bottom. +The flag, facet->toporient, +defines the orientation of the facet's vertices. For example in 3-d, +'top' is left-handed orientation (i.e., the vertex order follows the direction +of the left-hand fingers when the thumb is pointing away from the center). +Except for axis-parallel facets in 5-d and higher, topological orientation +determines the geometric orientation of the facet's hyperplane. + +

    A nonsimplicial facet is due to merging two or more +facets. The facet's ridge set determine a simplicial +decomposition of the facet. Each ridge is a 1-face (i.e., +it has two vertices and two neighboring facets). The +orientation of a ridge is determined by the order of the +neighboring facets. The flag, facet->toporient,is +ignored.

    +

    A nonsimplicial facet has a centrum for testing +convexity. A centrum is a point on the facet's +hyperplane that is near the center of the facet. Except +for large facets, it is the arithmetic average of the +facet's vertices.

    +

    A nonsimplicial facet is an approximation that is +defined by offsets from the facet's hyperplane. When +Qhull finishes, the outer plane is above all +points while the inner plane is below the facet's +vertices. This guarantees that any exact convex hull +passes between the inner and outer planes. The outer +plane is defined by facet->maxoutside while +the inner plane is computed from the facet's vertices.

    + +

    Qhull 3.1 includes triangulation of non-simplicial facets +('Qt'). +These facets, +called tricoplanar, share the same normal. centrum, and Voronoi center. +One facet (keepcentrum) owns these data structures. +While tricoplanar facets are more accurate than the simplicial facets from +joggled input, they +may have zero area or flipped orientation. + +

    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to poly.c, +poly2.c, poly.h, +and libqhull.h

    + +

    »Data +types and global lists for polyhedrons

    + +

    »poly.h constants

    +
      +
    • ALGORITHMfault +flag to not report errors in qh_checkconvex()
    • +
    • DATAfault flag to +report errors in qh_checkconvex()
    • +
    • DUPLICATEridge +special value for facet->neighbor to indicate +a duplicate ridge
    • +
    • MERGEridge +special value for facet->neighbor to indicate +a merged ridge
    • +
    +

    »Global FORALL +macros

    + +

    »FORALL macros

    + +

    »FOREACH macros

    + +

    »Indexed +FOREACH macros

    +
      +
    • FOREACHfacet_i_ +assign 'facet' and 'facet_i' to each facet in +facet set
    • +
    • FOREACHneighbor_i_ +assign 'neighbor' and 'neighbor_i' to each facet +in facet->neighbors or vertex->neighbors
    • +
    • FOREACHpoint_i_ +assign 'point' and 'point_i' to each point in +points set
    • +
    • FOREACHridge_i_ +assign 'ridge' and 'ridge_i' to each ridge in +ridges set
    • +
    • FOREACHvertex_i_ +assign 'vertex' and 'vertex_i' to each vertex in +vertices set
    • +
    • FOREACHvertexreverse12_ +assign 'vertex' to each vertex in vertex set; +reverse the order of first two vertices
    • +
    +

    »Other macros for polyhedrons

    +
      +
    • getid_ return ID for +a facet, ridge, or vertex
    • +
    • otherfacet_ +return neighboring facet for a ridge in a facet
    • +
    +

    »Facetlist +functions

    + +

    »Facet +functions

    + +

    »Vertex, +ridge, and point functions

    + +

    »Hashtable functions

    + +

    »Allocation and +deallocation functions

    + +

    »Check +functions

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-qhull.htm b/xs/src/qhull/src/libqhull/qh-qhull.htm new file mode 100644 index 0000000000..5212c64226 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-qhull.htm @@ -0,0 +1,279 @@ + + + + +libqhull.c -- top-level functions and basic data types + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    libqhull.c -- top-level functions and basic data types

    +
    +

    Qhull implements the Quickhull algorithm for computing +the convex hull. The Quickhull algorithm combines two +well-known algorithms: the 2-d quickhull algorithm and +the n-d beneath-beyond algorithm. See +Description of Qhull.

    +

    This section provides an index to the top-level +functions and base data types. The top-level header file, libqhull.h, +contains prototypes for these functions.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to libqhull.c, +libqhull.h, and +unix.c

    + + +

    »libqhull.h and unix.c +data types and constants

    +
      +
    • flagT Boolean flag as +a bit
    • +
    • boolT boolean value, +either True or False
    • +
    • CENTERtype to +distinguish facet->center
    • +
    • qh_PRINT output +formats for printing (qh.PRINTout)
    • +
    • qh_ALL argument flag +for selecting everything
    • +
    • qh_ERR Qhull exit +codes for indicating errors
    • +
    • qh_FILEstderr Fake stderr +to distinguish error output from normal output [C++ only]
    • +
    • qh_prompt version and long prompt for Qhull
    • +
    • qh_prompt2 synopsis for Qhull
    • +
    • qh_prompt3 concise prompt for Qhull
    • +
    • qh_version version stamp
    • +
    + +

    »libqhull.h other +macros

    +
      +
    • traceN print trace +message if qh.IStracing >= N.
    • +
    • QHULL_UNUSED declare an + unused variable to avoid warnings.
    • +
    + +

    »Quickhull +routines in call order

    + + +

    »Top-level routines for initializing and terminating Qhull (in other modules)

    + + +

    »Top-level routines for reading and modifying the input (in other modules)

    + + +

    »Top-level routines for calling Qhull (in other modules)

    + + +

    »Top-level routines for returning results (in other modules)

    + + +

    »Top-level routines for testing and debugging (in other modules)

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-set.htm b/xs/src/qhull/src/libqhull/qh-set.htm new file mode 100644 index 0000000000..06e71bbc92 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-set.htm @@ -0,0 +1,308 @@ + + + + +qset.c -- set data type and operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    qset.c -- set data type and operations

    +
    +

    Qhull's data structures are constructed from sets. The +functions and macros in qset.c construct, iterate, and +modify these sets. They are the most frequently called +functions in Qhull. For this reason, efficiency is the +primary concern.

    +

    In Qhull, a set is represented by an unordered +array of pointers with a maximum size and a NULL +terminator (setT). +Most sets correspond to mathematical sets +(i.e., the pointers are unique). Some sets are sorted to +enforce uniqueness. Some sets are ordered. For example, +the order of vertices in a ridge determine the ridge's +orientation. If you reverse the order of adjacent +vertices, the orientation reverses. Some sets are not +mathematical sets. They may be indexed as an array and +they may include NULL pointers.

    +

    The most common operation on a set is to iterate its +members. This is done with a 'FOREACH...' macro. Each set +has a custom macro. For example, 'FOREACHvertex_' +iterates over a set of vertices. Each vertex is assigned +to the variable 'vertex' from the pointer 'vertexp'.

    +

    Most sets are constructed by appending elements to the +set. The last element of a set is either NULL or the +index of the terminating NULL for a partially full set. +If a set is full, appending an element copies the set to +a larger array.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly +• QhullSet +• StatUser +

    +

    Index to qset.c and +qset.h

    + +

    »Data types and +constants

    +
      +
    • SETelemsize size +of a set element in bytes
    • +
    • setT a set with a +maximum size and a current size
    • +
    • qh global sets +global sets for temporary sets, etc.
    • +
    +

    »FOREACH macros

    + +

    »Access and +size macros

    + +

    »Internal macros

    +
      +
    • SETsizeaddr_ +return pointer to end element of a set (indicates +current size)
    • +
    + +

    »address macros

    +
      +
    • SETaddr_ return +address of a set's elements
    • +
    • SETelemaddr_ +return address of the n'th element of a set
    • +
    • SETref_ l.h.s. for +modifying the current element in a FOREACH +iteration
    • +
    + +

    »Allocation and +deallocation functions

    + + +

    »Access and +predicate functions

    + + +

    »Add functions

    + + +

    »Check and print functions

    + + +

    »Copy, compact, and zero functions

    + + +

    »Delete functions

    + + +

    »Temporary set functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-stat.htm b/xs/src/qhull/src/libqhull/qh-stat.htm new file mode 100644 index 0000000000..b968540312 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-stat.htm @@ -0,0 +1,163 @@ + + + + +stat.c -- statistical operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    stat.c -- statistical operations

    +
    +

    Qhull records many statistics. These functions and +macros make it inexpensive to add a statistic. +

    As with Qhull's global variables, the statistics data structure is +accessed by a macro, 'qhstat'. If qh_QHpointer is defined, the macro +is 'qh_qhstat->', otherwise the macro is 'qh_qhstat.'. +Statistics +may be turned off in user.h. If so, all but the 'zz' +statistics are ignored.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to stat.c and +stat.h

    + + +

    »stat.h types

    +
      +
    • intrealT union of +integer and real
    • +
    • qhstat global data +structure for statistics
    • +
    +

    »stat.h +constants

    +
      +
    • qh_KEEPstatistics 0 turns off most statistics
    • +
    • Z..., W... integer (Z) and real (W) statistics +
    • +
    • ZZstat Z.../W... statistics that +remain defined if qh_KEEPstatistics=0 +
    • +
    • ztype zdoc, zinc, etc. +for definining statistics
    • +
    +

    »stat.h macros

    +
      +
    • MAYdebugx called +frequently for error trapping
    • +
    • zadd_/wadd_ add value +to an integer or real statistic
    • +
    • zdef_ define a +statistic
    • +
    • zinc_ increment an +integer statistic
    • +
    • zmax_/wmax_ update +integer or real maximum statistic
    • +
    • zmin_/wmin_ update +integer or real minimum statistic
    • +
    • zval_/wval_ set or +return value of a statistic
    • +
    + +

    »stat.c +functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qh-user.htm b/xs/src/qhull/src/libqhull/qh-user.htm new file mode 100644 index 0000000000..6682f4b2fb --- /dev/null +++ b/xs/src/qhull/src/libqhull/qh-user.htm @@ -0,0 +1,271 @@ + + + + +user.c -- user-definable operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    +

    user.c -- user-definable operations

    +
    +

    This section contains functions and constants that the +user may want to change.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to user.c, usermem.c, userprintf.c, userprintf_rbox.c and +user.h

    + + +

    »Qhull library constants

    + + + +

    »user.h data +types and configuration macros

    + + +

    »definition constants

    +
      +
    • qh_DEFAULTbox +define default box size for rbox, 'Qbb', and 'QbB' (Geomview expects 0.5)
    • +
    • qh_INFINITE on +output, indicates Voronoi center at infinity
    • +
    • qh_ORIENTclock +define convention for orienting facets
    • +
    • qh_ZEROdelaunay +define facets that are ignored in Delaunay triangulations
    • +
    + +

    »joggle constants

    + + +

    »performance +related constants

    + + +

    »memory constants

    + + +

    »conditional compilation

    +
      +
    • compiler defined symbols, +e.g., _STDC_ and _cplusplus + +
    • qh_COMPUTEfurthest + compute furthest distance to an outside point instead of storing it with the facet +
    • qh_KEEPstatistics + enable statistic gathering and reporting with option 'Ts' +
    • qh_MAXoutside +record outer plane for each facet +
    • qh_NOmerge +disable facet merging +
    • qh_NOtrace +disable tracing with option 'T4' +
    • qh_QHpointer +access global data with pointer or static structure +
    • qh_QUICKhelp +use abbreviated help messages, e.g., for degenerate inputs +
    + +

    »merge +constants

    + + +

    »user.c +functions

    + + +

    »usermem.c +functions

    +
      +
    • qh_exit exit program, same as exit(). May be redefined as throw "QH10003.." by libqhullcpp/usermem_r-cpp.cpp
    • +
    • qh_fprintf_stderr print to stderr when qh.ferr is not defined.
    • +
    • qh_free free memory, same as free().
    • +
    • qh_malloc allocate memory, same as malloc()
    • +
    + +

    »userprintf.c + and userprintf_rbox,c functions

    +
      +
    • qh_fprintf print +information from Qhull, sames as fprintf().
    • +
    • qh_fprintf_rbox print +information from Rbox, sames as fprintf().
    • +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull/qhull-exports.def b/xs/src/qhull/src/libqhull/qhull-exports.def new file mode 100644 index 0000000000..11a42b57ee --- /dev/null +++ b/xs/src/qhull/src/libqhull/qhull-exports.def @@ -0,0 +1,417 @@ +; qhull-exports.def -- msvc module-definition file +; +; Generated from depends.exe by cut-and-paste of exported symbols by mingw gcc +; [mar'11] 399 symbols +; Annotate as DATA qh_last_random qh_qh qh_qhstat qhmem rbox rbox_inuse +; Annotate as __declspec for outside access in win32 -- qh_qh qh_qhstat +; Same as ../libqhullp/qhull_p-exports.def without qh_save_qhull and qh_restore_qhull +; +; $Id: //main/2015/qhull/src/libqhull/qhull-exports.def#3 $$Change: 2047 $ +; $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $ +; +; Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, and qhull-warn.pri +VERSION 7.0 +EXPORTS +qh_addhash +qh_addpoint +qh_all_merges +qh_allstatA +qh_allstatB +qh_allstatC +qh_allstatD +qh_allstatE +qh_allstatE2 +qh_allstatF +qh_allstatG +qh_allstatH +qh_allstatI +qh_allstatistics +qh_appendfacet +qh_appendmergeset +qh_appendprint +qh_appendvertex +qh_argv_to_command +qh_argv_to_command_size +qh_attachnewfacets +qh_backnormal +qh_basevertices +qh_build_withrestart +qh_buildhull +qh_buildtracing +qh_check_bestdist +qh_check_dupridge +qh_check_maxout +qh_check_output +qh_check_point +qh_check_points +qh_checkconnect +qh_checkconvex +qh_checkfacet +qh_checkflags +qh_checkflipped +qh_checkflipped_all +qh_checkpolygon +qh_checkvertex +qh_checkzero +qh_clear_outputflags +qh_clearcenters +qh_clock +qh_collectstatistics +qh_compare_facetarea +qh_compare_facetmerge +qh_compare_facetvisit +qh_compare_vertexpoint +qh_compareangle +qh_comparemerge +qh_comparevisit +qh_copyfilename +qh_copynonconvex +qh_copypoints +qh_countfacets +qh_createsimplex +qh_crossproduct +qh_degen_redundant_facet +qh_degen_redundant_neighbors +qh_deletevisible +qh_delfacet +qh_delridge +qh_delvertex +qh_determinant +qh_detjoggle +qh_detroundoff +qh_detsimplex +qh_detvnorm +qh_detvridge +qh_detvridge3 +qh_dfacet +qh_distnorm +qh_distplane +qh_distround +qh_divzero +qh_dvertex +qh_eachvoronoi +qh_eachvoronoi_all +qh_errexit +qh_errexit2 +qh_errexit_rbox +qh_errprint +qh_exit +qh_facet2point +qh_facet3vertex +qh_facetarea +qh_facetarea_simplex +qh_facetcenter +qh_facetintersect +qh_facetvertices +qh_find_newvertex +qh_findbest +qh_findbest_test +qh_findbestfacet +qh_findbesthorizon +qh_findbestlower +qh_findbestneighbor +qh_findbestnew +qh_findfacet_all +qh_findgood +qh_findgood_all +qh_findgooddist +qh_findhorizon +qh_flippedmerges +qh_forcedmerges +qh_fprintf +qh_fprintf_rbox +qh_fprintf_stderr +qh_free +qh_freebuffers +qh_freebuild +qh_freeqhull +qh_freeqhull2 +qh_freestatistics +qh_furthestnext +qh_furthestout +qh_gausselim +qh_geomplanes +qh_getangle +qh_getarea +qh_getcenter +qh_getcentrum +qh_getdistance +qh_gethash +qh_getmergeset +qh_getmergeset_initial +qh_gram_schmidt +qh_hashridge +qh_hashridge_find +qh_infiniteloop +qh_init_A +qh_init_B +qh_init_qhull_command +qh_initbuild +qh_initflags +qh_initialhull +qh_initialvertices +qh_initqhull_buffers +qh_initqhull_globals +qh_initqhull_mem +qh_initqhull_outputflags +qh_initqhull_start +qh_initqhull_start2 +qh_initstatistics +qh_initthresholds +qh_inthresholds +qh_isvertex +qh_joggleinput +; Mark as DATA, otherwise links a separate qh_last_random. No __declspec. +qh_last_random DATA +qh_lib_check +qh_makenew_nonsimplicial +qh_makenew_simplicial +qh_makenewfacet +qh_makenewfacets +qh_makenewplanes +qh_makeridges +qh_malloc +qh_mark_dupridges +qh_markkeep +qh_markvoronoi +qh_matchduplicates +qh_matchneighbor +qh_matchnewfacets +qh_matchvertices +qh_maxabsval +qh_maxmin +qh_maxouter +qh_maxsimplex +qh_maydropneighbor +qh_memalloc +qh_memfree +qh_memfreeshort +qh_meminit +qh_meminitbuffers +qh_memsetup +qh_memsize +qh_memstatistics +qh_memtotal +qh_merge_degenredundant +qh_merge_nonconvex +qh_mergecycle +qh_mergecycle_all +qh_mergecycle_facets +qh_mergecycle_neighbors +qh_mergecycle_ridges +qh_mergecycle_vneighbors +qh_mergefacet +qh_mergefacet2d +qh_mergeneighbors +qh_mergeridges +qh_mergesimplex +qh_mergevertex_del +qh_mergevertex_neighbors +qh_mergevertices +qh_minabsval +qh_mindiff +qh_nearcoplanar +qh_nearvertex +qh_neighbor_intersections +qh_new_qhull +qh_newfacet +qh_newhashtable +qh_newridge +qh_newstats +qh_newvertex +qh_newvertices +qh_nextfurthest +qh_nextridge3d +qh_normalize +qh_normalize2 +qh_nostatistic +qh_option +qh_order_vertexneighbors +qh_orientoutside +qh_out1 +qh_out2n +qh_out3n +qh_outcoplanar +qh_outerinner +qh_partitionall +qh_partitioncoplanar +qh_partitionpoint +qh_partitionvisible +qh_point +qh_point_add +qh_pointdist +qh_pointfacet +qh_pointid +qh_pointvertex +qh_postmerge +qh_precision +qh_premerge +qh_prepare_output +qh_prependfacet +qh_printafacet +qh_printallstatistics +qh_printbegin +qh_printcenter +qh_printcentrum +qh_printend +qh_printend4geom +qh_printextremes +qh_printextremes_2d +qh_printextremes_d +qh_printfacet +qh_printfacet2geom +qh_printfacet2geom_points +qh_printfacet2math +qh_printfacet3geom_nonsimplicial +qh_printfacet3geom_points +qh_printfacet3geom_simplicial +qh_printfacet3math +qh_printfacet3vertex +qh_printfacet4geom_nonsimplicial +qh_printfacet4geom_simplicial +qh_printfacetNvertex_nonsimplicial +qh_printfacetNvertex_simplicial +qh_printfacetheader +qh_printfacetlist +qh_printfacetridges +qh_printfacets +qh_printhashtable +qh_printhelp_degenerate +qh_printhelp_narrowhull +qh_printhelp_singular +qh_printhyperplaneintersection +qh_printline3geom +qh_printlists +qh_printmatrix +qh_printneighborhood +qh_printpoint +qh_printpoint3 +qh_printpointid +qh_printpoints +qh_printpoints_out +qh_printpointvect +qh_printpointvect2 +qh_printridge +qh_printspheres +qh_printstatistics +qh_printstatlevel +qh_printstats +qh_printsummary +qh_printvdiagram +qh_printvdiagram2 +qh_printvertex +qh_printvertexlist +qh_printvertices +qh_printvneighbors +qh_printvnorm +qh_printvoronoi +qh_printvridge +qh_produce_output +qh_produce_output2 +qh_projectdim3 +qh_projectinput +qh_projectpoint +qh_projectpoints +; Mark as DATA, otherwise links a separate qh_qh. qh_qh and qh_qhstat requires __declspec +qh_qh DATA +qh_qhstat DATA +qh_qhull +qh_rand +qh_randomfactor +qh_randommatrix +qh_rboxpoints +qh_readfeasible +qh_readpoints +qh_reducevertices +qh_redundant_vertex +qh_remove_extravertices +qh_removefacet +qh_removevertex +qh_rename_sharedvertex +qh_renameridgevertex +qh_renamevertex +qh_resetlists +qh_rotateinput +qh_rotatepoints +qh_roundi +qh_scaleinput +qh_scalelast +qh_scalepoints +qh_setaddnth +qh_setaddsorted +qh_setappend +qh_setappend2ndlast +qh_setappend_set +qh_setcheck +qh_setcompact +qh_setcopy +qh_setdel +qh_setdelaunay +qh_setdellast +qh_setdelnth +qh_setdelnthsorted +qh_setdelsorted +qh_setduplicate +qh_setequal +qh_setequal_except +qh_setequal_skip +qh_setfacetplane +qh_setfeasible +qh_setfree +qh_setfree2 +qh_setfreelong +qh_sethalfspace +qh_sethalfspace_all +qh_sethyperplane_det +qh_sethyperplane_gauss +qh_setin +qh_setindex +qh_setlarger +qh_setlast +qh_setnew +qh_setnew_delnthsorted +qh_setprint +qh_setreplace +qh_setsize +qh_settemp +qh_settempfree +qh_settempfree_all +qh_settemppop +qh_settemppush +qh_settruncate +qh_setunique +qh_setvoronoi_all +qh_setzero +qh_sharpnewfacets +qh_skipfacet +qh_skipfilename +qh_srand +qh_stddev +qh_strtod +qh_strtol +qh_test_appendmerge +qh_test_vneighbors +qh_tracemerge +qh_tracemerging +qh_triangulate +qh_triangulate_facet +qh_triangulate_link +qh_triangulate_mirror +qh_triangulate_null +qh_updatetested +qh_updatevertices +qh_user_memsizes +qh_version +qh_version2 +qh_vertexintersect +qh_vertexintersect_new +qh_vertexneighbors +qh_vertexridges +qh_vertexridges_facet +qh_vertexsubset +qh_voronoi_center +qh_willdelete +; Mark as DATA, otherwise links a separate qhmem. No __declspec +qhmem DATA +rbox DATA +rbox_inuse DATA diff --git a/xs/src/qhull/src/libqhull/qhull_a.h b/xs/src/qhull/src/libqhull/qhull_a.h new file mode 100644 index 0000000000..729b723276 --- /dev/null +++ b/xs/src/qhull/src/libqhull/qhull_a.h @@ -0,0 +1,150 @@ +/*
      ---------------------------------
    +
    +   qhull_a.h
    +   all header files for compiling qhull with non-reentrant code
    +   included before C++ headers for user_r.h:QHULL_CRTDBG
    +
    +   see qh-qhull.htm
    +
    +   see libqhull.h for user-level definitions
    +
    +   see user.h for user-definable constants
    +
    +   defines internal functions for libqhull.c global.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/qhull_a.h#4 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +
    +   Notes:  grep for ((" and (" to catch fprintf("lkasdjf");
    +           full parens around (x?y:z)
    +           use '#include "libqhull/qhull_a.h"' to avoid name clashes
    +*/
    +
    +#ifndef qhDEFqhulla
    +#define qhDEFqhulla 1
    +
    +#include "libqhull.h"  /* Includes user_r.h and data types */
    +
    +#include "stat.h"
    +#include "random.h"
    +#include "mem.h"
    +#include "qset.h"
    +#include "geom.h"
    +#include "merge.h"
    +#include "poly.h"
    +#include "io.h"
    +
    +#include 
    +#include 
    +#include 
    +#include     /* some compilers will not need float.h */
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +/*** uncomment here and qset.c
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#if qh_CLOCKtype == 2  /* defined in user.h from libqhull.h */
    +#include 
    +#include 
    +#include 
    +#endif
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4100)  /* unreferenced formal parameter */
    +#pragma warning( disable : 4127)  /* conditional expression is constant */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/* ======= -macros- =========== */
    +
    +/*----------------------------------
    +
    +  traceN((qh ferr, 0Nnnn, "format\n", vars));
    +    calls qh_fprintf if qh.IStracing >= N
    +
    +    Add debugging traps to the end of qh_fprintf
    +
    +  notes:
    +    removing tracing reduces code size but doesn't change execution speed
    +*/
    +#ifndef qh_NOtrace
    +#define trace0(args) {if (qh IStracing) qh_fprintf args;}
    +#define trace1(args) {if (qh IStracing >= 1) qh_fprintf args;}
    +#define trace2(args) {if (qh IStracing >= 2) qh_fprintf args;}
    +#define trace3(args) {if (qh IStracing >= 3) qh_fprintf args;}
    +#define trace4(args) {if (qh IStracing >= 4) qh_fprintf args;}
    +#define trace5(args) {if (qh IStracing >= 5) qh_fprintf args;}
    +#else /* qh_NOtrace */
    +#define trace0(args) {}
    +#define trace1(args) {}
    +#define trace2(args) {}
    +#define trace3(args) {}
    +#define trace4(args) {}
    +#define trace5(args) {}
    +#endif /* qh_NOtrace */
    +
    +/*----------------------------------
    +
    +  Define an unused variable to avoid compiler warnings
    +
    +  Derived from Qt's corelib/global/qglobal.h
    +
    +*/
    +
    +#if defined(__cplusplus) && defined(__INTEL_COMPILER) && !defined(QHULL_OS_WIN)
    +template 
    +inline void qhullUnused(T &x) { (void)x; }
    +#  define QHULL_UNUSED(x) qhullUnused(x);
    +#else
    +#  define QHULL_UNUSED(x) (void)x;
    +#endif
    +
    +/***** -libqhull.c prototypes (alphabetical after qhull) ********************/
    +
    +void    qh_qhull(void);
    +boolT   qh_addpoint(pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_buildhull(void);
    +void    qh_buildtracing(pointT *furthest, facetT *facet);
    +void    qh_build_withrestart(void);
    +void    qh_errexit2(int exitcode, facetT *facet, facetT *otherfacet);
    +void    qh_findhorizon(pointT *point, facetT *facet, int *goodvisible,int *goodhorizon);
    +pointT *qh_nextfurthest(facetT **visible);
    +void    qh_partitionall(setT *vertices, pointT *points,int npoints);
    +void    qh_partitioncoplanar(pointT *point, facetT *facet, realT *dist);
    +void    qh_partitionpoint(pointT *point, facetT *facet);
    +void    qh_partitionvisible(boolT allpoints, int *numpoints);
    +void    qh_precision(const char *reason);
    +void    qh_printsummary(FILE *fp);
    +
    +/***** -global.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_appendprint(qh_PRINT format);
    +void    qh_freebuild(boolT allmem);
    +void    qh_freebuffers(void);
    +void    qh_initbuffers(coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +/***** -stat.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_allstatA(void);
    +void    qh_allstatB(void);
    +void    qh_allstatC(void);
    +void    qh_allstatD(void);
    +void    qh_allstatE(void);
    +void    qh_allstatE2(void);
    +void    qh_allstatF(void);
    +void    qh_allstatG(void);
    +void    qh_allstatH(void);
    +void    qh_freebuffers(void);
    +void    qh_initbuffers(coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +#endif /* qhDEFqhulla */
    diff --git a/xs/src/qhull/src/libqhull/qhull_p-exports.def b/xs/src/qhull/src/libqhull/qhull_p-exports.def
    new file mode 100644
    index 0000000000..cadf8a4fa2
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qhull_p-exports.def
    @@ -0,0 +1,418 @@
    +; qhull_p-exports.def -- msvc module-definition file
    +;
    +;   Generated from depends.exe by cut-and-paste of exported symbols by mingw gcc
    +;   [mar'11] 399 symbols [jan'15] added 3 symbols
    +;   Annotate as DATA qh_last_random qh_qh qh_qhstat qhmem rbox rbox_inuse
    +;   Annotate as __declspec for outside access in win32 -- qh_qh qh_qhstat
    +;
    +; $Id: //main/2011/qhull/src/libqhull/qhull-exports.def#2 $$Change: 1368 $
    +; $DateTime: 2011/04/16 08:12:32 $$Author: bbarber $
    +;
    +; Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, and qhull-warn.pri
    +VERSION 7.0
    +EXPORTS
    +qh_addhash
    +qh_addpoint
    +qh_all_merges
    +qh_allstatA
    +qh_allstatB
    +qh_allstatC
    +qh_allstatD
    +qh_allstatE
    +qh_allstatE2
    +qh_allstatF
    +qh_allstatG
    +qh_allstatH
    +qh_allstatI
    +qh_allstatistics
    +qh_appendfacet
    +qh_appendmergeset
    +qh_appendprint
    +qh_appendvertex
    +qh_argv_to_command
    +qh_argv_to_command_size
    +qh_attachnewfacets
    +qh_backnormal
    +qh_basevertices
    +qh_build_withrestart
    +qh_buildhull
    +qh_buildtracing
    +qh_check_bestdist
    +qh_check_dupridge
    +qh_check_maxout
    +qh_check_output
    +qh_check_point
    +qh_check_points
    +qh_checkconnect
    +qh_checkconvex
    +qh_checkfacet
    +qh_checkflags
    +qh_checkflipped
    +qh_checkflipped_all
    +qh_checkpolygon
    +qh_checkvertex
    +qh_checkzero
    +qh_clear_outputflags
    +qh_clearcenters
    +qh_clock
    +qh_collectstatistics
    +qh_compare_facetarea
    +qh_compare_facetmerge
    +qh_compare_facetvisit
    +qh_compare_vertexpoint
    +qh_compareangle
    +qh_comparemerge
    +qh_comparevisit
    +qh_copyfilename
    +qh_copynonconvex
    +qh_copypoints
    +qh_countfacets
    +qh_createsimplex
    +qh_crossproduct
    +qh_degen_redundant_facet
    +qh_degen_redundant_neighbors
    +qh_deletevisible
    +qh_delfacet
    +qh_delridge
    +qh_delvertex
    +qh_determinant
    +qh_detjoggle
    +qh_detroundoff
    +qh_detsimplex
    +qh_detvnorm
    +qh_detvridge
    +qh_detvridge3
    +qh_dfacet
    +qh_distnorm
    +qh_distplane
    +qh_distround
    +qh_divzero
    +qh_dvertex
    +qh_eachvoronoi
    +qh_eachvoronoi_all
    +qh_errexit
    +qh_errexit2
    +qh_errexit_rbox
    +qh_errprint
    +qh_exit
    +qh_facet2point
    +qh_facet3vertex
    +qh_facetarea
    +qh_facetarea_simplex
    +qh_facetcenter
    +qh_facetintersect
    +qh_facetvertices
    +qh_find_newvertex
    +qh_findbest
    +qh_findbest_test
    +qh_findbestfacet
    +qh_findbesthorizon
    +qh_findbestlower
    +qh_findbestneighbor
    +qh_findbestnew
    +qh_findfacet_all
    +qh_findgood
    +qh_findgood_all
    +qh_findgooddist
    +qh_findhorizon
    +qh_flippedmerges
    +qh_forcedmerges
    +qh_fprintf
    +qh_fprintf_rbox
    +qh_fprintf_stderr
    +qh_free
    +qh_freebuffers
    +qh_freebuild
    +qh_freeqhull
    +qh_freeqhull2
    +qh_freestatistics
    +qh_furthestnext
    +qh_furthestout
    +qh_gausselim
    +qh_geomplanes
    +qh_getangle
    +qh_getarea
    +qh_getcenter
    +qh_getcentrum
    +qh_getdistance
    +qh_gethash
    +qh_getmergeset
    +qh_getmergeset_initial
    +qh_gram_schmidt
    +qh_hashridge
    +qh_hashridge_find
    +qh_infiniteloop
    +qh_init_A
    +qh_init_B
    +qh_init_qhull_command
    +qh_initbuild
    +qh_initflags
    +qh_initialhull
    +qh_initialvertices
    +qh_initqhull_buffers
    +qh_initqhull_globals
    +qh_initqhull_mem
    +qh_initqhull_outputflags
    +qh_initqhull_start
    +qh_initqhull_start2
    +qh_initstatistics
    +qh_initthresholds
    +qh_inthresholds
    +qh_isvertex
    +qh_joggleinput
    +; Mark as DATA, otherwise links a separate qh_last_random.  No __declspec.
    +qh_last_random      DATA
    +qh_lib_check
    +qh_makenew_nonsimplicial
    +qh_makenew_simplicial
    +qh_makenewfacet
    +qh_makenewfacets
    +qh_makenewplanes
    +qh_makeridges
    +qh_malloc
    +qh_mark_dupridges
    +qh_markkeep
    +qh_markvoronoi
    +qh_matchduplicates
    +qh_matchneighbor
    +qh_matchnewfacets
    +qh_matchvertices
    +qh_maxabsval
    +qh_maxmin
    +qh_maxouter
    +qh_maxsimplex
    +qh_maydropneighbor
    +qh_memalloc
    +qh_memfree
    +qh_memfreeshort
    +qh_meminit
    +qh_meminitbuffers
    +qh_memsetup
    +qh_memsize
    +qh_memstatistics
    +qh_memtotal
    +qh_merge_degenredundant
    +qh_merge_nonconvex
    +qh_mergecycle
    +qh_mergecycle_all
    +qh_mergecycle_facets
    +qh_mergecycle_neighbors
    +qh_mergecycle_ridges
    +qh_mergecycle_vneighbors
    +qh_mergefacet
    +qh_mergefacet2d
    +qh_mergeneighbors
    +qh_mergeridges
    +qh_mergesimplex
    +qh_mergevertex_del
    +qh_mergevertex_neighbors
    +qh_mergevertices
    +qh_minabsval
    +qh_mindiff
    +qh_nearcoplanar
    +qh_nearvertex
    +qh_neighbor_intersections
    +qh_new_qhull
    +qh_newfacet
    +qh_newhashtable
    +qh_newridge
    +qh_newstats
    +qh_newvertex
    +qh_newvertices
    +qh_nextfurthest
    +qh_nextridge3d
    +qh_normalize
    +qh_normalize2
    +qh_nostatistic
    +qh_option
    +qh_order_vertexneighbors
    +qh_orientoutside
    +qh_out1
    +qh_out2n
    +qh_out3n
    +qh_outcoplanar
    +qh_outerinner
    +qh_partitionall
    +qh_partitioncoplanar
    +qh_partitionpoint
    +qh_partitionvisible
    +qh_point
    +qh_point_add
    +qh_pointdist
    +qh_pointfacet
    +qh_pointid
    +qh_pointvertex
    +qh_postmerge
    +qh_precision
    +qh_premerge
    +qh_prepare_output
    +qh_prependfacet
    +qh_printafacet
    +qh_printallstatistics
    +qh_printbegin
    +qh_printcenter
    +qh_printcentrum
    +qh_printend
    +qh_printend4geom
    +qh_printextremes
    +qh_printextremes_2d
    +qh_printextremes_d
    +qh_printfacet
    +qh_printfacet2geom
    +qh_printfacet2geom_points
    +qh_printfacet2math
    +qh_printfacet3geom_nonsimplicial
    +qh_printfacet3geom_points
    +qh_printfacet3geom_simplicial
    +qh_printfacet3math
    +qh_printfacet3vertex
    +qh_printfacet4geom_nonsimplicial
    +qh_printfacet4geom_simplicial
    +qh_printfacetNvertex_nonsimplicial
    +qh_printfacetNvertex_simplicial
    +qh_printfacetheader
    +qh_printfacetlist
    +qh_printfacetridges
    +qh_printfacets
    +qh_printhashtable
    +qh_printhelp_degenerate
    +qh_printhelp_narrowhull
    +qh_printhelp_singular
    +qh_printhyperplaneintersection
    +qh_printline3geom
    +qh_printlists
    +qh_printmatrix
    +qh_printneighborhood
    +qh_printpoint
    +qh_printpoint3
    +qh_printpointid
    +qh_printpoints
    +qh_printpoints_out
    +qh_printpointvect
    +qh_printpointvect2
    +qh_printridge
    +qh_printspheres
    +qh_printstatistics
    +qh_printstatlevel
    +qh_printstats
    +qh_printsummary
    +qh_printvdiagram
    +qh_printvdiagram2
    +qh_printvertex
    +qh_printvertexlist
    +qh_printvertices
    +qh_printvneighbors
    +qh_printvnorm
    +qh_printvoronoi
    +qh_printvridge
    +qh_produce_output
    +qh_produce_output2
    +qh_projectdim3
    +qh_projectinput
    +qh_projectpoint
    +qh_projectpoints
    +; Mark as DATA, otherwise links a separate qh_qh.  qh_qh and qh_qhstat requires __declspec
    +qh_qh               DATA
    +qh_qhstat           DATA
    +qh_qhull
    +qh_rand
    +qh_randomfactor
    +qh_randommatrix
    +qh_rboxpoints
    +qh_readfeasible
    +qh_readpoints
    +qh_reducevertices
    +qh_redundant_vertex
    +qh_remove_extravertices
    +qh_removefacet
    +qh_removevertex
    +qh_rename_sharedvertex
    +qh_renameridgevertex
    +qh_renamevertex
    +qh_resetlists
    +qh_restore_qhull
    +qh_rotateinput
    +qh_rotatepoints
    +qh_roundi
    +qh_save_qhull
    +qh_scaleinput
    +qh_scalelast
    +qh_scalepoints
    +qh_setaddnth
    +qh_setaddsorted
    +qh_setappend
    +qh_setappend2ndlast
    +qh_setappend_set
    +qh_setcheck
    +qh_setcompact
    +qh_setcopy
    +qh_setdel
    +qh_setdelaunay
    +qh_setdellast
    +qh_setdelnth
    +qh_setdelnthsorted
    +qh_setdelsorted
    +qh_setduplicate
    +qh_setequal
    +qh_setequal_except
    +qh_setequal_skip
    +qh_setfacetplane
    +qh_setfeasible
    +qh_setfree
    +qh_setfree2
    +qh_setfreelong
    +qh_sethalfspace
    +qh_sethalfspace_all
    +qh_sethyperplane_det
    +qh_sethyperplane_gauss
    +qh_setin
    +qh_setindex
    +qh_setlarger
    +qh_setlast
    +qh_setnew
    +qh_setnew_delnthsorted
    +qh_setprint
    +qh_setreplace
    +qh_setsize
    +qh_settemp
    +qh_settempfree
    +qh_settempfree_all
    +qh_settemppop
    +qh_settemppush
    +qh_settruncate
    +qh_setunique
    +qh_setvoronoi_all
    +qh_setzero
    +qh_sharpnewfacets
    +qh_skipfacet
    +qh_skipfilename
    +qh_srand
    +qh_stddev
    +qh_strtod
    +qh_strtol
    +qh_test_appendmerge
    +qh_test_vneighbors
    +qh_tracemerge
    +qh_tracemerging
    +qh_triangulate
    +qh_triangulate_facet
    +qh_triangulate_link
    +qh_triangulate_mirror
    +qh_triangulate_null
    +qh_updatetested
    +qh_updatevertices
    +qh_user_memsizes
    +qh_version
    +qh_version2
    +qh_vertexintersect
    +qh_vertexintersect_new
    +qh_vertexneighbors
    +qh_vertexridges
    +qh_vertexridges_facet
    +qh_vertexsubset
    +qh_voronoi_center
    +qh_willdelete
    +; Mark as DATA, otherwise links a separate qhmem.  No __declspec
    +qhmem                   DATA
    +rbox			DATA
    +rbox_inuse              DATA
    diff --git a/xs/src/qhull/src/libqhull/qset.c b/xs/src/qhull/src/libqhull/qset.c
    new file mode 100644
    index 0000000000..a969252a75
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qset.c
    @@ -0,0 +1,1340 @@
    +/*
      ---------------------------------
    +
    +   qset.c
    +   implements set manipulations needed for quickhull
    +
    +   see qh-set.htm and qset.h
    +
    +   Be careful of strict aliasing (two pointers of different types
    +   that reference the same location).  The last slot of a set is
    +   either the actual size of the set plus 1, or the NULL terminator
    +   of the set (i.e., setelemT).
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/qset.c#3 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "user.h" /* for QHULL_CRTDBG */
    +#include "qset.h"
    +#include "mem.h"
    +#include 
    +#include 
    +/*** uncomment here and qhull_a.h
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#ifndef qhDEFlibqhull
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +void    qh_errexit(int exitcode, facetT *, ridgeT *);
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... );
    +#  ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#  pragma warning( disable : 4127)  /* conditional expression is constant */
    +#  pragma warning( disable : 4706)  /* assignment within conditional function */
    +#  endif
    +#endif
    +
    +/*=============== internal macros ===========================*/
    +
    +/*============ functions in alphabetical order ===================*/
    +
    +/*----------------------------------
    +
    +  qh_setaddnth( setp, nth, newelem)
    +    adds newelem as n'th element of sorted or unsorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nth=0 is first element
    +    errors if nth is out of bounds
    +
    +  design:
    +    expand *setp if empty or full
    +    move tail of *setp up one
    +    insert newelem
    +*/
    +void qh_setaddnth(setT **setp, int nth, void *newelem) {
    +  int oldsize, i;
    +  setelemT *sizep;          /* avoid strict aliasing */
    +  setelemT *oldp, *newp;
    +
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  oldsize= sizep->i - 1;
    +  if (nth < 0 || nth > oldsize) {
    +    qh_fprintf(qhmem.ferr, 6171, "qhull internal error (qh_setaddnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", *setp);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  sizep->i++;
    +  oldp= (setelemT *)SETelemaddr_(*setp, oldsize, void);   /* NULL */
    +  newp= oldp+1;
    +  for (i=oldsize-nth+1; i--; )  /* move at least NULL  */
    +    (newp--)->p= (oldp--)->p;       /* may overwrite *sizep */
    +  newp->p= newelem;
    +} /* setaddnth */
    +
    +
    +/*----------------------------------
    +
    +  setaddsorted( setp, newelem )
    +    adds an newelem into sorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nop if newelem already in set
    +
    +  design:
    +    find newelem's position in *setp
    +    insert newelem
    +*/
    +void qh_setaddsorted(setT **setp, void *newelem) {
    +  int newindex=0;
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(*setp) {          /* could use binary search instead */
    +    if (elem < newelem)
    +      newindex++;
    +    else if (elem == newelem)
    +      return;
    +    else
    +      break;
    +  }
    +  qh_setaddnth(setp, newindex, newelem);
    +} /* setaddsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend( setp, newelem)
    +    append newelem to *setp
    +
    +  notes:
    +    *setp may be a temp set
    +    *setp and newelem may be NULL
    +
    +  design:
    +    expand *setp if empty or full
    +    append newelem to *setp
    +
    +*/
    +void qh_setappend(setT **setp, void *newelem) {
    +  setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +  setelemT *endp;
    +  int count;
    +
    +  if (!newelem)
    +    return;
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  count= (sizep->i)++ - 1;
    +  endp= (setelemT *)SETelemaddr_(*setp, count, void);
    +  (endp++)->p= newelem;
    +  endp->p= NULL;
    +} /* setappend */
    +
    +/*---------------------------------
    +
    +  qh_setappend_set( setp, setA)
    +    appends setA to *setp
    +
    +  notes:
    +    *setp can not be a temp set
    +    *setp and setA may be NULL
    +
    +  design:
    +    setup for copy
    +    expand *setp if it is too small
    +    append all elements of setA to *setp
    +*/
    +void qh_setappend_set(setT **setp, setT *setA) {
    +  int sizeA, size;
    +  setT *oldset;
    +  setelemT *sizep;
    +
    +  if (!setA)
    +    return;
    +  SETreturnsize_(setA, sizeA);
    +  if (!*setp)
    +    *setp= qh_setnew(sizeA);
    +  sizep= SETsizeaddr_(*setp);
    +  if (!(size= sizep->i))
    +    size= (*setp)->maxsize;
    +  else
    +    size--;
    +  if (size + sizeA > (*setp)->maxsize) {
    +    oldset= *setp;
    +    *setp= qh_setcopy(oldset, sizeA);
    +    qh_setfree(&oldset);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  if (sizeA > 0) {
    +    sizep->i= size+sizeA+1;   /* memcpy may overwrite */
    +    memcpy((char *)&((*setp)->e[size].p), (char *)&(setA->e[0].p), (size_t)(sizeA+1) * SETelemsize);
    +  }
    +} /* setappend_set */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend2ndlast( setp, newelem )
    +    makes newelem the next to the last element in *setp
    +
    +  notes:
    +    *setp must have at least one element
    +    newelem must be defined
    +    *setp may be a temp set
    +
    +  design:
    +    expand *setp if empty or full
    +    move last element of *setp up one
    +    insert newelem
    +*/
    +void qh_setappend2ndlast(setT **setp, void *newelem) {
    +    setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +    setelemT *endp, *lastp;
    +    int count;
    +
    +    if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +        qh_setlarger(setp);
    +        sizep= SETsizeaddr_(*setp);
    +    }
    +    count= (sizep->i)++ - 1;
    +    endp= (setelemT *)SETelemaddr_(*setp, count, void); /* NULL */
    +    lastp= endp-1;
    +    *(endp++)= *lastp;
    +    endp->p= NULL;    /* may overwrite *sizep */
    +    lastp->p= newelem;
    +} /* setappend2ndlast */
    +
    +/*---------------------------------
    +
    +  qh_setcheck( set, typename, id )
    +    check set for validity
    +    report errors with typename and id
    +
    +  design:
    +    checks that maxsize, actual size, and NULL terminator agree
    +*/
    +void qh_setcheck(setT *set, const char *tname, unsigned id) {
    +  int maxsize, size;
    +  int waserr= 0;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  maxsize= set->maxsize;
    +  if (size > maxsize || !maxsize) {
    +    qh_fprintf(qhmem.ferr, 6172, "qhull internal error (qh_setcheck): actual size %d of %s%d is greater than max size %d\n",
    +             size, tname, id, maxsize);
    +    waserr= 1;
    +  }else if (set->e[size].p) {
    +    qh_fprintf(qhmem.ferr, 6173, "qhull internal error (qh_setcheck): %s%d(size %d max %d) is not null terminated.\n",
    +             tname, id, size-1, maxsize);
    +    waserr= 1;
    +  }
    +  if (waserr) {
    +    qh_setprint(qhmem.ferr, "ERRONEOUS", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setcheck */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcompact( set )
    +    remove internal NULLs from an unsorted set
    +
    +  returns:
    +    updated set
    +
    +  notes:
    +    set may be NULL
    +    it would be faster to swap tail of set into holes, like qh_setdel
    +
    +  design:
    +    setup pointers into set
    +    skip NULLs while copying elements to start of set
    +    update the actual size
    +*/
    +void qh_setcompact(setT *set) {
    +  int size;
    +  void **destp, **elemp, **endp, **firstp;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  destp= elemp= firstp= SETaddr_(set, void);
    +  endp= destp + size;
    +  while (1) {
    +    if (!(*destp++ = *elemp++)) {
    +      destp--;
    +      if (elemp > endp)
    +        break;
    +    }
    +  }
    +  qh_settruncate(set, (int)(destp-firstp));   /* WARN64 */
    +} /* setcompact */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcopy( set, extra )
    +    make a copy of a sorted or unsorted set with extra slots
    +
    +  returns:
    +    new set
    +
    +  design:
    +    create a newset with extra slots
    +    copy the elements to the newset
    +
    +*/
    +setT *qh_setcopy(setT *set, int extra) {
    +  setT *newset;
    +  int size;
    +
    +  if (extra < 0)
    +    extra= 0;
    +  SETreturnsize_(set, size);
    +  newset= qh_setnew(size+extra);
    +  SETsizeaddr_(newset)->i= size+1;    /* memcpy may overwrite */
    +  memcpy((char *)&(newset->e[0].p), (char *)&(set->e[0].p), (size_t)(size+1) * SETelemsize);
    +  return(newset);
    +} /* setcopy */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdel( set, oldelem )
    +    delete oldelem from an unsorted set
    +
    +  returns:
    +    returns oldelem if found
    +    returns NULL otherwise
    +
    +  notes:
    +    set may be NULL
    +    oldelem must not be NULL;
    +    only deletes one copy of oldelem in set
    +
    +  design:
    +    locate oldelem
    +    update actual size if it was full
    +    move the last element to the oldelem's location
    +*/
    +void *qh_setdel(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *elemp;
    +  setelemT *lastp;
    +
    +  if (!set)
    +    return NULL;
    +  elemp= (setelemT *)SETaddr_(set, void);
    +  while (elemp->p != oldelem && elemp->p)
    +    elemp++;
    +  if (elemp->p) {
    +    sizep= SETsizeaddr_(set);
    +    if (!(sizep->i)--)         /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +    lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +    elemp->p= lastp->p;      /* may overwrite itself */
    +    lastp->p= NULL;
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdel */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdellast( set)
    +    return last element of set or NULL
    +
    +  notes:
    +    deletes element from set
    +    set may be NULL
    +
    +  design:
    +    return NULL if empty
    +    if full set
    +      delete last element and set actual size
    +    else
    +      delete last element and update actual size
    +*/
    +void *qh_setdellast(setT *set) {
    +  int setsize;  /* actually, actual_size + 1 */
    +  int maxsize;
    +  setelemT *sizep;
    +  void *returnvalue;
    +
    +  if (!set || !(set->e[0].p))
    +    return NULL;
    +  sizep= SETsizeaddr_(set);
    +  if ((setsize= sizep->i)) {
    +    returnvalue= set->e[setsize - 2].p;
    +    set->e[setsize - 2].p= NULL;
    +    sizep->i--;
    +  }else {
    +    maxsize= set->maxsize;
    +    returnvalue= set->e[maxsize - 1].p;
    +    set->e[maxsize - 1].p= NULL;
    +    sizep->i= maxsize;
    +  }
    +  return returnvalue;
    +} /* setdellast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelnth( set, nth )
    +    deletes nth element from unsorted set
    +    0 is first element
    +
    +  returns:
    +    returns the element (needs type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  design:
    +    setup points and check nth
    +    delete nth element and overwrite with last element
    +*/
    +void *qh_setdelnth(setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *elemp, *lastp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +  if (nth < 0 || nth >= sizep->i) {
    +    qh_fprintf(qhmem.ferr, 6174, "qhull internal error (qh_setdelnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  elemp= (setelemT *)SETelemaddr_(set, nth, void); /* nth valid by QH6174 */
    +  lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +  elem= elemp->p;
    +  elemp->p= lastp->p;      /* may overwrite itself */
    +  lastp->p= NULL;
    +  return elem;
    +} /* setdelnth */
    +
    +/*---------------------------------
    +
    +  qh_setdelnthsorted( set, nth )
    +    deletes nth element from sorted set
    +
    +  returns:
    +    returns the element (use type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  see also:
    +    setnew_delnthsorted
    +
    +  design:
    +    setup points and check nth
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelnthsorted(setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if (nth < 0 || (sizep->i && nth >= sizep->i-1) || nth >= set->maxsize) {
    +    qh_fprintf(qhmem.ferr, 6175, "qhull internal error (qh_setdelnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newp= (setelemT *)SETelemaddr_(set, nth, void);
    +  elem= newp->p;
    +  oldp= newp+1;
    +  while (((newp++)->p= (oldp++)->p))
    +    ; /* copy remaining elements and NULL */
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +    sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +  return elem;
    +} /* setdelnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelsorted( set, oldelem )
    +    deletes oldelem from sorted set
    +
    +  returns:
    +    returns oldelem if it was deleted
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    locate oldelem in set
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelsorted(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (!set)
    +    return NULL;
    +  newp= (setelemT *)SETaddr_(set, void);
    +  while(newp->p != oldelem && newp->p)
    +    newp++;
    +  if (newp->p) {
    +    oldp= newp+1;
    +    while (((newp++)->p= (oldp++)->p))
    +      ; /* copy remaining elements */
    +    sizep= SETsizeaddr_(set);
    +    if ((sizep->i--)==0)    /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdelsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setduplicate( set, elemsize )
    +    duplicate a set of elemsize elements
    +
    +  notes:
    +    use setcopy if retaining old elements
    +
    +  design:
    +    create a new set
    +    for each elem of the old set
    +      create a newelem
    +      append newelem to newset
    +*/
    +setT *qh_setduplicate(setT *set, int elemsize) {
    +  void          *elem, **elemp, *newElem;
    +  setT          *newSet;
    +  int           size;
    +
    +  if (!(size= qh_setsize(set)))
    +    return NULL;
    +  newSet= qh_setnew(size);
    +  FOREACHelem_(set) {
    +    newElem= qh_memalloc(elemsize);
    +    memcpy(newElem, elem, (size_t)elemsize);
    +    qh_setappend(&newSet, newElem);
    +  }
    +  return newSet;
    +} /* setduplicate */
    +
    +
    +/*---------------------------------
    +
    +  qh_setendpointer( set )
    +    Returns pointer to NULL terminator of a set's elements
    +    set can not be NULL
    +
    +*/
    +void **qh_setendpointer(setT *set) {
    +
    +  setelemT *sizep= SETsizeaddr_(set);
    +  int n= sizep->i;
    +  return (n ? &set->e[n-1].p : &sizep->p);
    +} /* qh_setendpointer */
    +
    +/*---------------------------------
    +
    +  qh_setequal( setA, setB )
    +    returns 1 if two sorted sets are equal, otherwise returns 0
    +
    +  notes:
    +    either set may be NULL
    +
    +  design:
    +    check size of each set
    +    setup pointers
    +    compare elements of each set
    +*/
    +int qh_setequal(setT *setA, setT *setB) {
    +  void **elemAp, **elemBp;
    +  int sizeA= 0, sizeB= 0;
    +
    +  if (setA) {
    +    SETreturnsize_(setA, sizeA);
    +  }
    +  if (setB) {
    +    SETreturnsize_(setB, sizeB);
    +  }
    +  if (sizeA != sizeB)
    +    return 0;
    +  if (!sizeA)
    +    return 1;
    +  elemAp= SETaddr_(setA, void);
    +  elemBp= SETaddr_(setB, void);
    +  if (!memcmp((char *)elemAp, (char *)elemBp, sizeA*SETelemsize))
    +    return 1;
    +  return 0;
    +} /* setequal */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_except( setA, skipelemA, setB, skipelemB )
    +    returns 1 if sorted setA and setB are equal except for skipelemA & B
    +
    +  returns:
    +    false if either skipelemA or skipelemB are missing
    +
    +  notes:
    +    neither set may be NULL
    +
    +    if skipelemB is NULL,
    +      can skip any one element of setB
    +
    +  design:
    +    setup pointers
    +    search for skipelemA, skipelemB, and mismatches
    +    check results
    +*/
    +int qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB) {
    +  void **elemA, **elemB;
    +  int skip=0;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  while (1) {
    +    if (*elemA == skipelemA) {
    +      skip++;
    +      elemA++;
    +    }
    +    if (skipelemB) {
    +      if (*elemB == skipelemB) {
    +        skip++;
    +        elemB++;
    +      }
    +    }else if (*elemA != *elemB) {
    +      skip++;
    +      if (!(skipelemB= *elemB++))
    +        return 0;
    +    }
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (skip != 2 || *elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_except */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_skip( setA, skipA, setB, skipB )
    +    returns 1 if sorted setA and setB are equal except for elements skipA & B
    +
    +  returns:
    +    false if different size
    +
    +  notes:
    +    neither set may be NULL
    +
    +  design:
    +    setup pointers
    +    search for mismatches while skipping skipA and skipB
    +*/
    +int qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB) {
    +  void **elemA, **elemB, **skipAp, **skipBp;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  skipAp= SETelemaddr_(setA, skipA, void);
    +  skipBp= SETelemaddr_(setB, skipB, void);
    +  while (1) {
    +    if (elemA == skipAp)
    +      elemA++;
    +    if (elemB == skipBp)
    +      elemB++;
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (*elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_skip */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree( setp )
    +    frees the space occupied by a sorted or unsorted set
    +
    +  returns:
    +    sets setp to NULL
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free array
    +    free set
    +*/
    +void qh_setfree(setT **setp) {
    +  int size;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size <= qhmem.LASTsize) {
    +      qh_memfree_(*setp, size, freelistp);
    +    }else
    +      qh_memfree(*setp, size);
    +    *setp= NULL;
    +  }
    +} /* setfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree2( setp, elemsize )
    +    frees the space occupied by a set and its elements
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free each element
    +    free set
    +*/
    +void qh_setfree2(setT **setp, int elemsize) {
    +  void          *elem, **elemp;
    +
    +  FOREACHelem_(*setp)
    +    qh_memfree(elem, elemsize);
    +  qh_setfree(setp);
    +} /* setfree2 */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_setfreelong( setp )
    +    frees a set only if it's in long memory
    +
    +  returns:
    +    sets setp to NULL if it is freed
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    if set is large
    +      free it
    +*/
    +void qh_setfreelong(setT **setp) {
    +  int size;
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size > qhmem.LASTsize) {
    +      qh_memfree(*setp, size);
    +      *setp= NULL;
    +    }
    +  }
    +} /* setfreelong */
    +
    +
    +/*---------------------------------
    +
    +  qh_setin( set, setelem )
    +    returns 1 if setelem is in a set, 0 otherwise
    +
    +  notes:
    +    set may be NULL or unsorted
    +
    +  design:
    +    scans set for setelem
    +*/
    +int qh_setin(setT *set, void *setelem) {
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(set) {
    +    if (elem == setelem)
    +      return 1;
    +  }
    +  return 0;
    +} /* setin */
    +
    +
    +/*---------------------------------
    +
    +  qh_setindex( set, atelem )
    +    returns the index of atelem in set.
    +    returns -1, if not in set or maxsize wrong
    +
    +  notes:
    +    set may be NULL and may contain nulls.
    +    NOerrors returned (qh_pointid, QhullPoint::id)
    +
    +  design:
    +    checks maxsize
    +    scans set for atelem
    +*/
    +int qh_setindex(setT *set, void *atelem) {
    +  void **elem;
    +  int size, i;
    +
    +  if (!set)
    +    return -1;
    +  SETreturnsize_(set, size);
    +  if (size > set->maxsize)
    +    return -1;
    +  elem= SETaddr_(set, void);
    +  for (i=0; i < size; i++) {
    +    if (*elem++ == atelem)
    +      return i;
    +  }
    +  return -1;
    +} /* setindex */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlarger( oldsetp )
    +    returns a larger set that contains all elements of *oldsetp
    +
    +  notes:
    +    the set is at least twice as large
    +    if temp set, updates qhmem.tempstack
    +
    +  design:
    +    creates a new set
    +    copies the old set to the new set
    +    updates pointers in tempstack
    +    deletes the old set
    +*/
    +void qh_setlarger(setT **oldsetp) {
    +  int size= 1;
    +  setT *newset, *set, **setp, *oldset;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (*oldsetp) {
    +    oldset= *oldsetp;
    +    SETreturnsize_(oldset, size);
    +    qhmem.cntlarger++;
    +    qhmem.totlarger += size+1;
    +    newset= qh_setnew(2 * size);
    +    oldp= (setelemT *)SETaddr_(oldset, void);
    +    newp= (setelemT *)SETaddr_(newset, void);
    +    memcpy((char *)newp, (char *)oldp, (size_t)(size+1) * SETelemsize);
    +    sizep= SETsizeaddr_(newset);
    +    sizep->i= size+1;
    +    FOREACHset_((setT *)qhmem.tempstack) {
    +      if (set == oldset)
    +        *(setp-1)= newset;
    +    }
    +    qh_setfree(oldsetp);
    +  }else
    +    newset= qh_setnew(3);
    +  *oldsetp= newset;
    +} /* setlarger */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlast( set )
    +    return last element of set or NULL (use type conversion)
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    return last element
    +*/
    +void *qh_setlast(setT *set) {
    +  int size;
    +
    +  if (set) {
    +    size= SETsizeaddr_(set)->i;
    +    if (!size)
    +      return SETelem_(set, set->maxsize - 1);
    +    else if (size > 1)
    +      return SETelem_(set, size - 2);
    +  }
    +  return NULL;
    +} /* setlast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew( setsize )
    +    creates and allocates space for a set
    +
    +  notes:
    +    setsize means the number of elements (!including the NULL terminator)
    +    use qh_settemp/qh_setfreetemp if set is temporary
    +
    +  design:
    +    allocate memory for set
    +    roundup memory if small set
    +    initialize as empty set
    +*/
    +setT *qh_setnew(int setsize) {
    +  setT *set;
    +  int sizereceived; /* used if !qh_NOmem */
    +  int size;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (!setsize)
    +    setsize++;
    +  size= sizeof(setT) + setsize * SETelemsize;
    +  if (size>0 && size <= qhmem.LASTsize) {
    +    qh_memalloc_(size, freelistp, set, setT);
    +#ifndef qh_NOmem
    +    sizereceived= qhmem.sizetable[ qhmem.indextable[size]];
    +    if (sizereceived > size)
    +      setsize += (sizereceived - size)/SETelemsize;
    +#endif
    +  }else
    +    set= (setT*)qh_memalloc(size);
    +  set->maxsize= setsize;
    +  set->e[setsize].i= 1;
    +  set->e[0].p= NULL;
    +  return(set);
    +} /* setnew */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew_delnthsorted( set, size, nth, prepend )
    +    creates a sorted set not containing nth element
    +    if prepend, the first prepend elements are undefined
    +
    +  notes:
    +    set must be defined
    +    checks nth
    +    see also: setdelnthsorted
    +
    +  design:
    +    create new set
    +    setup pointers and allocate room for prepend'ed entries
    +    append head of old set to new set
    +    append tail of old set to new set
    +*/
    +setT *qh_setnew_delnthsorted(setT *set, int size, int nth, int prepend) {
    +  setT *newset;
    +  void **oldp, **newp;
    +  int tailsize= size - nth -1, newsize;
    +
    +  if (tailsize < 0) {
    +    qh_fprintf(qhmem.ferr, 6176, "qhull internal error (qh_setnew_delnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newsize= size-1 + prepend;
    +  newset= qh_setnew(newsize);
    +  newset->e[newset->maxsize].i= newsize+1;  /* may be overwritten */
    +  oldp= SETaddr_(set, void);
    +  newp= SETaddr_(newset, void) + prepend;
    +  switch (nth) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)nth * SETelemsize);
    +    newp += nth;
    +    oldp += nth;
    +    break;
    +  }
    +  oldp++;
    +  switch (tailsize) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)tailsize * SETelemsize);
    +    newp += tailsize;
    +  }
    +  *newp= NULL;
    +  return(newset);
    +} /* setnew_delnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setprint( fp, string, set )
    +    print set elements to fp with identifying string
    +
    +  notes:
    +    never errors
    +*/
    +void qh_setprint(FILE *fp, const char* string, setT *set) {
    +  int size, k;
    +
    +  if (!set)
    +    qh_fprintf(fp, 9346, "%s set is null\n", string);
    +  else {
    +    SETreturnsize_(set, size);
    +    qh_fprintf(fp, 9347, "%s set=%p maxsize=%d size=%d elems=",
    +             string, set, set->maxsize, size);
    +    if (size > set->maxsize)
    +      size= set->maxsize+1;
    +    for (k=0; k < size; k++)
    +      qh_fprintf(fp, 9348, " %p", set->e[k].p);
    +    qh_fprintf(fp, 9349, "\n");
    +  }
    +} /* setprint */
    +
    +/*---------------------------------
    +
    +  qh_setreplace( set, oldelem, newelem )
    +    replaces oldelem in set with newelem
    +
    +  notes:
    +    errors if oldelem not in the set
    +    newelem may be NULL, but it turns the set into an indexed set (no FOREACH)
    +
    +  design:
    +    find oldelem
    +    replace with newelem
    +*/
    +void qh_setreplace(setT *set, void *oldelem, void *newelem) {
    +  void **elemp;
    +
    +  elemp= SETaddr_(set, void);
    +  while (*elemp != oldelem && *elemp)
    +    elemp++;
    +  if (*elemp)
    +    *elemp= newelem;
    +  else {
    +    qh_fprintf(qhmem.ferr, 6177, "qhull internal error (qh_setreplace): elem %p not found in set\n",
    +       oldelem);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setreplace */
    +
    +
    +/*---------------------------------
    +
    +  qh_setsize( set )
    +    returns the size of a set
    +
    +  notes:
    +    errors if set's maxsize is incorrect
    +    same as SETreturnsize_(set)
    +    same code for qh_setsize [qset.c] and QhullSetBase::count
    +
    +  design:
    +    determine actual size of set from maxsize
    +*/
    +int qh_setsize(setT *set) {
    +  int size;
    +  setelemT *sizep;
    +
    +  if (!set)
    +    return(0);
    +  sizep= SETsizeaddr_(set);
    +  if ((size= sizep->i)) {
    +    size--;
    +    if (size > set->maxsize) {
    +      qh_fprintf(qhmem.ferr, 6178, "qhull internal error (qh_setsize): current set size %d is greater than maximum size %d\n",
    +               size, set->maxsize);
    +      qh_setprint(qhmem.ferr, "set: ", set);
    +      qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +    }
    +  }else
    +    size= set->maxsize;
    +  return size;
    +} /* setsize */
    +
    +/*---------------------------------
    +
    +  qh_settemp( setsize )
    +    return a stacked, temporary set of upto setsize elements
    +
    +  notes:
    +    use settempfree or settempfree_all to release from qhmem.tempstack
    +    see also qh_setnew
    +
    +  design:
    +    allocate set
    +    append to qhmem.tempstack
    +
    +*/
    +setT *qh_settemp(int setsize) {
    +  setT *newset;
    +
    +  newset= qh_setnew(setsize);
    +  qh_setappend(&qhmem.tempstack, newset);
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8123, "qh_settemp: temp set %p of %d elements, depth %d\n",
    +       newset, newset->maxsize, qh_setsize(qhmem.tempstack));
    +  return newset;
    +} /* settemp */
    +
    +/*---------------------------------
    +
    +  qh_settempfree( set )
    +    free temporary set at top of qhmem.tempstack
    +
    +  notes:
    +    nop if set is NULL
    +    errors if set not from previous   qh_settemp
    +
    +  to locate errors:
    +    use 'T2' to find source and then find mis-matching qh_settemp
    +
    +  design:
    +    check top of qhmem.tempstack
    +    free it
    +*/
    +void qh_settempfree(setT **set) {
    +  setT *stackedset;
    +
    +  if (!*set)
    +    return;
    +  stackedset= qh_settemppop();
    +  if (stackedset != *set) {
    +    qh_settemppush(stackedset);
    +    qh_fprintf(qhmem.ferr, 6179, "qhull internal error (qh_settempfree): set %p(size %d) was not last temporary allocated(depth %d, set %p, size %d)\n",
    +             *set, qh_setsize(*set), qh_setsize(qhmem.tempstack)+1,
    +             stackedset, qh_setsize(stackedset));
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(set);
    +} /* settempfree */
    +
    +/*---------------------------------
    +
    +  qh_settempfree_all(  )
    +    free all temporary sets in qhmem.tempstack
    +
    +  design:
    +    for each set in tempstack
    +      free set
    +    free qhmem.tempstack
    +*/
    +void qh_settempfree_all(void) {
    +  setT *set, **setp;
    +
    +  FOREACHset_(qhmem.tempstack)
    +    qh_setfree(&set);
    +  qh_setfree(&qhmem.tempstack);
    +} /* settempfree_all */
    +
    +/*---------------------------------
    +
    +  qh_settemppop(  )
    +    pop and return temporary set from qhmem.tempstack
    +
    +  notes:
    +    the returned set is permanent
    +
    +  design:
    +    pop and check top of qhmem.tempstack
    +*/
    +setT *qh_settemppop(void) {
    +  setT *stackedset;
    +
    +  stackedset= (setT*)qh_setdellast(qhmem.tempstack);
    +  if (!stackedset) {
    +    qh_fprintf(qhmem.ferr, 6180, "qhull internal error (qh_settemppop): pop from empty temporary stack\n");
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8124, "qh_settemppop: depth %d temp set %p of %d elements\n",
    +       qh_setsize(qhmem.tempstack)+1, stackedset, qh_setsize(stackedset));
    +  return stackedset;
    +} /* settemppop */
    +
    +/*---------------------------------
    +
    +  qh_settemppush( set )
    +    push temporary set unto qhmem.tempstack (makes it temporary)
    +
    +  notes:
    +    duplicates settemp() for tracing
    +
    +  design:
    +    append set to tempstack
    +*/
    +void qh_settemppush(setT *set) {
    +  if (!set) {
    +    qh_fprintf(qhmem.ferr, 6267, "qhull error (qh_settemppush): can not push a NULL temp\n");
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setappend(&qhmem.tempstack, set);
    +  if (qhmem.IStracing >= 5)
    +    qh_fprintf(qhmem.ferr, 8125, "qh_settemppush: depth %d temp set %p of %d elements\n",
    +      qh_setsize(qhmem.tempstack), set, qh_setsize(set));
    +} /* settemppush */
    +
    +
    +/*---------------------------------
    +
    +  qh_settruncate( set, size )
    +    truncate set to size elements
    +
    +  notes:
    +    set must be defined
    +
    +  see:
    +    SETtruncate_
    +
    +  design:
    +    check size
    +    update actual size of set
    +*/
    +void qh_settruncate(setT *set, int size) {
    +
    +  if (size < 0 || size > set->maxsize) {
    +    qh_fprintf(qhmem.ferr, 6181, "qhull internal error (qh_settruncate): size %d out of bounds for set:\n", size);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i= size+1;   /* maybe overwritten */
    +  set->e[size].p= NULL;
    +} /* settruncate */
    +
    +/*---------------------------------
    +
    +  qh_setunique( set, elem )
    +    add elem to unsorted set unless it is already in set
    +
    +  notes:
    +    returns 1 if it is appended
    +
    +  design:
    +    if elem not in set
    +      append elem to set
    +*/
    +int qh_setunique(setT **set, void *elem) {
    +
    +  if (!qh_setin(*set, elem)) {
    +    qh_setappend(set, elem);
    +    return 1;
    +  }
    +  return 0;
    +} /* setunique */
    +
    +/*---------------------------------
    +
    +  qh_setzero( set, index, size )
    +    zero elements from index on
    +    set actual size of set to size
    +
    +  notes:
    +    set must be defined
    +    the set becomes an indexed set (can not use FOREACH...)
    +
    +  see also:
    +    qh_settruncate
    +
    +  design:
    +    check index and size
    +    update actual size
    +    zero elements starting at e[index]
    +*/
    +void qh_setzero(setT *set, int idx, int size) {
    +  int count;
    +
    +  if (idx < 0 || idx >= size || size > set->maxsize) {
    +    qh_fprintf(qhmem.ferr, 6182, "qhull internal error (qh_setzero): index %d or size %d out of bounds for set:\n", idx, size);
    +    qh_setprint(qhmem.ferr, "", set);
    +    qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i=  size+1;  /* may be overwritten */
    +  count= size - idx + 1;   /* +1 for NULL terminator */
    +  memset((char *)SETelemaddr_(set, idx, void), 0, (size_t)count * SETelemsize);
    +} /* setzero */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/qset.h b/xs/src/qhull/src/libqhull/qset.h
    new file mode 100644
    index 0000000000..7e4e7d14f6
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/qset.h
    @@ -0,0 +1,490 @@
    +/*
      ---------------------------------
    +
    +   qset.h
    +     header file for qset.c that implements set
    +
    +   see qh-set.htm and qset.c
    +
    +   only uses mem.c, malloc/free
    +
    +   for error handling, writes message and calls
    +      qh_errexit(qhmem_ERRqhull, NULL, NULL);
    +
    +   set operations satisfy the following properties:
    +    - sets have a max size, the actual size (if different) is stored at the end
    +    - every set is NULL terminated
    +    - sets may be sorted or unsorted, the caller must distinguish this
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/qset.h#2 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFset
    +#define qhDEFset 1
    +
    +#include 
    +
    +/*================= -structures- ===============*/
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;   /* a set is a sorted or unsorted array of pointers */
    +#endif
    +
    +/* [jan'15] Decided not to use countT.  Most sets are small.  The code uses signed tests */
    +
    +/*------------------------------------------
    +
    +setT
    +  a set or list of pointers with maximum size and actual size.
    +
    +variations:
    +  unsorted, unique   -- a list of unique pointers with NULL terminator
    +                           user guarantees uniqueness
    +  sorted             -- a sorted list of unique pointers with NULL terminator
    +                           qset.c guarantees uniqueness
    +  unsorted           -- a list of pointers terminated with NULL
    +  indexed            -- an array of pointers with NULL elements
    +
    +structure for set of n elements:
    +
    +        --------------
    +        |  maxsize
    +        --------------
    +        |  e[0] - a pointer, may be NULL for indexed sets
    +        --------------
    +        |  e[1]
    +
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[n-1]
    +        --------------
    +        |  e[n] = NULL
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[maxsize] - n+1 or NULL (determines actual size of set)
    +        --------------
    +
    +*/
    +
    +/*-- setelemT -- internal type to allow both pointers and indices
    +*/
    +typedef union setelemT setelemT;
    +union setelemT {
    +  void    *p;
    +  int      i;         /* integer used for e[maxSize] */
    +};
    +
    +struct setT {
    +  int maxsize;          /* maximum number of elements (except NULL) */
    +  setelemT e[1];        /* array of pointers, tail is NULL */
    +                        /* last slot (unless NULL) is actual size+1
    +                           e[maxsize]==NULL or e[e[maxsize]-1]==NULL */
    +                        /* this may generate a warning since e[] contains
    +                           maxsize elements */
    +};
    +
    +/*=========== -constants- =========================*/
    +
    +/*-------------------------------------
    +
    +  SETelemsize
    +    size of a set element in bytes
    +*/
    +#define SETelemsize ((int)sizeof(setelemT))
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelement_(type, set, variable)
    +     define FOREACH iterator
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     no space in "variable)" [DEC Alpha cc compiler]
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one beyond variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example:
    +     #define FOREACHfacet_( facets ) FOREACHsetelement_( facetT, facets, facet )
    +
    +   notes:
    +     use FOREACHsetelement_i_() if need index or include NULLs
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[0].p); \
    +          (variable= *variable##p++);)
    +
    +/*------------------------------------------
    +
    +   FOREACHsetelement_i_(type, set, variable)
    +     define indexed FOREACH iterator
    +
    +   declare:
    +     type *variable, variable_n, variable_i;
    +
    +   each iteration:
    +     variable is set element, may be NULL
    +     variable_i is index, variable_n is qh_setsize()
    +
    +   to repeat an element:
    +     variable_i--; variable_n-- repeats for deleted element
    +
    +   at exit:
    +     variable==NULL and variable_i==variable_n
    +
    +   example:
    +     #define FOREACHfacet_i_( facets ) FOREACHsetelement_i_( facetT, facets, facet )
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_i_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##_i= 0, variable= (type *)((set)->e[0].p), \
    +                   variable##_n= qh_setsize(set);\
    +          variable##_i < variable##_n;\
    +          variable= (type *)((set)->e[++variable##_i].p) )
    +
    +/*----------------------------------------
    +
    +   FOREACHsetelementreverse_(type, set, variable)-
    +     define FOREACH iterator in reverse order
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     also declare 'int variabletemp'
    +
    +   each iteration:
    +     variable is set element
    +
    +   to repeat an element:
    +     variabletemp++; / *repeat* /
    +
    +   at exit:
    +     variable is NULL
    +
    +   example:
    +     #define FOREACHvertexreverse_( vertices ) FOREACHsetelementreverse_( vertexT, vertices, vertex )
    +
    +   notes:
    +     use FOREACHsetelementreverse12_() to reverse first two elements
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +           variable##temp= qh_setsize(set)-1, variable= qh_setlast(set);\
    +           variable; variable= \
    +           ((--variable##temp >= 0) ? SETelemt_(set, variable##temp, type) : NULL))
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelementreverse12_(type, set, variable)-
    +     define FOREACH iterator with e[1] and e[0] reversed
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one after variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example
    +     #define FOREACHvertexreverse12_( vertices ) FOREACHsetelementreverse12_( vertexT, vertices, vertex )
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse12_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[1].p); \
    +          (variable= *variable##p); \
    +          variable##p == ((type **)&((set)->e[0].p))?variable##p += 2: \
    +              (variable##p == ((type **)&((set)->e[1].p))?variable##p--:variable##p++))
    +
    +/*-------------------------------------
    +
    +   FOREACHelem_( set )-
    +     iterate elements in a set
    +
    +   declare:
    +     void *elem, *elemp;
    +
    +   each iteration:
    +     elem is set element
    +     elemp is one beyond
    +
    +   to repeat an element:
    +     elemp--; / *repeat* /
    +
    +   at exit:
    +     elem == NULL at end of loop
    +
    +   example:
    +     FOREACHelem_(set) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHelem_(set) FOREACHsetelement_(void, set, elem)
    +
    +/*-------------------------------------
    +
    +   FOREACHset_( set )-
    +     iterate a set of sets
    +
    +   declare:
    +     setT *set, **setp;
    +
    +   each iteration:
    +     set is set element
    +     setp is one beyond
    +
    +   to repeat an element:
    +     setp--; / *repeat* /
    +
    +   at exit:
    +     set == NULL at end of loop
    +
    +   example
    +     FOREACHset_(sets) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHset_(sets) FOREACHsetelement_(setT, sets, set)
    +
    +/*-------------------------------------------
    +
    +   SETindex_( set, elem )
    +     return index of elem in set
    +
    +   notes:
    +     for use with FOREACH iteration
    +     WARN64 -- Maximum set size is 2G
    +
    +   example:
    +     i= SETindex_(ridges, ridge)
    +*/
    +#define SETindex_(set, elem) ((int)((void **)elem##p - (void **)&(set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETref_( elem )
    +     l.h.s. for modifying the current element in a FOREACH iteration
    +
    +   example:
    +     SETref_(ridge)= anotherridge;
    +*/
    +#define SETref_(elem) (elem##p[-1])
    +
    +/*-----------------------------------------
    +
    +   SETelem_(set, n)
    +     return the n'th element of set
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +      use SETelemt_() for type cast
    +*/
    +#define SETelem_(set, n)           ((set)->e[n].p)
    +
    +/*-----------------------------------------
    +
    +   SETelemt_(set, n, type)
    +     return the n'th element of set as a type
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +*/
    +#define SETelemt_(set, n, type)    ((type*)((set)->e[n].p))
    +
    +/*-----------------------------------------
    +
    +   SETelemaddr_(set, n, type)
    +     return address of the n'th element of a set
    +
    +   notes:
    +      assumes that n is valid [0..size] and set is defined
    +*/
    +#define SETelemaddr_(set, n, type) ((type **)(&((set)->e[n].p)))
    +
    +/*-----------------------------------------
    +
    +   SETfirst_(set)
    +     return first element of set
    +
    +*/
    +#define SETfirst_(set)             ((set)->e[0].p)
    +
    +/*-----------------------------------------
    +
    +   SETfirstt_(set, type)
    +     return first element of set as a type
    +
    +*/
    +#define SETfirstt_(set, type)      ((type*)((set)->e[0].p))
    +
    +/*-----------------------------------------
    +
    +   SETsecond_(set)
    +     return second element of set
    +
    +*/
    +#define SETsecond_(set)            ((set)->e[1].p)
    +
    +/*-----------------------------------------
    +
    +   SETsecondt_(set, type)
    +     return second element of set as a type
    +*/
    +#define SETsecondt_(set, type)     ((type*)((set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETaddr_(set, type)
    +       return address of set's elements
    +*/
    +#define SETaddr_(set,type)         ((type **)(&((set)->e[0].p)))
    +
    +/*-----------------------------------------
    +
    +   SETreturnsize_(set, size)
    +     return size of a set
    +
    +   notes:
    +      set must be defined
    +      use qh_setsize(set) unless speed is critical
    +*/
    +#define SETreturnsize_(set, size) (((size)= ((set)->e[(set)->maxsize].i))?(--(size)):((size)= (set)->maxsize))
    +
    +/*-----------------------------------------
    +
    +   SETempty_(set)
    +     return true(1) if set is empty
    +
    +   notes:
    +      set may be NULL
    +*/
    +#define SETempty_(set)            (!set || (SETfirst_(set) ? 0 : 1))
    +
    +/*---------------------------------
    +
    +  SETsizeaddr_(set)
    +    return pointer to 'actual size+1' of set (set CANNOT be NULL!!)
    +    Its type is setelemT* for strict aliasing
    +    All SETelemaddr_ must be cast to setelemT
    +
    +
    +  notes:
    +    *SETsizeaddr==NULL or e[*SETsizeaddr-1].p==NULL
    +*/
    +#define SETsizeaddr_(set) (&((set)->e[(set)->maxsize]))
    +
    +/*-----------------------------------------
    +
    +   SETtruncate_(set, size)
    +     truncate set to size
    +
    +   see:
    +     qh_settruncate()
    +
    +*/
    +#define SETtruncate_(set, size) {set->e[set->maxsize].i= size+1; /* maybe overwritten */ \
    +      set->e[size].p= NULL;}
    +
    +/*======= prototypes in alphabetical order ============*/
    +
    +void  qh_setaddsorted(setT **setp, void *elem);
    +void  qh_setaddnth(setT **setp, int nth, void *newelem);
    +void  qh_setappend(setT **setp, void *elem);
    +void  qh_setappend_set(setT **setp, setT *setA);
    +void  qh_setappend2ndlast(setT **setp, void *elem);
    +void  qh_setcheck(setT *set, const char *tname, unsigned id);
    +void  qh_setcompact(setT *set);
    +setT *qh_setcopy(setT *set, int extra);
    +void *qh_setdel(setT *set, void *elem);
    +void *qh_setdellast(setT *set);
    +void *qh_setdelnth(setT *set, int nth);
    +void *qh_setdelnthsorted(setT *set, int nth);
    +void *qh_setdelsorted(setT *set, void *newelem);
    +setT *qh_setduplicate( setT *set, int elemsize);
    +void **qh_setendpointer(setT *set);
    +int   qh_setequal(setT *setA, setT *setB);
    +int   qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB);
    +int   qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB);
    +void  qh_setfree(setT **set);
    +void  qh_setfree2( setT **setp, int elemsize);
    +void  qh_setfreelong(setT **set);
    +int   qh_setin(setT *set, void *setelem);
    +int   qh_setindex(setT *set, void *setelem);
    +void  qh_setlarger(setT **setp);
    +void *qh_setlast(setT *set);
    +setT *qh_setnew(int size);
    +setT *qh_setnew_delnthsorted(setT *set, int size, int nth, int prepend);
    +void  qh_setprint(FILE *fp, const char* string, setT *set);
    +void  qh_setreplace(setT *set, void *oldelem, void *newelem);
    +int   qh_setsize(setT *set);
    +setT *qh_settemp(int setsize);
    +void  qh_settempfree(setT **set);
    +void  qh_settempfree_all(void);
    +setT *qh_settemppop(void);
    +void  qh_settemppush(setT *set);
    +void  qh_settruncate(setT *set, int size);
    +int   qh_setunique(setT **set, void *elem);
    +void  qh_setzero(setT *set, int idx, int size);
    +
    +
    +#endif /* qhDEFset */
    diff --git a/xs/src/qhull/src/libqhull/random.c b/xs/src/qhull/src/libqhull/random.c
    new file mode 100644
    index 0000000000..176d697aeb
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/random.c
    @@ -0,0 +1,245 @@
    +/*
      ---------------------------------
    +
    +   random.c and utilities
    +     Park & Miller's minimimal standard random number generator
    +     argc/argv conversion
    +
    +     Used by rbox.  Do not use 'qh' 
    +*/
    +
    +#include "libqhull.h"
    +#include "random.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/*---------------------------------
    +
    + qh_argv_to_command( argc, argv, command, max_size )
    +
    +    build command from argc/argv
    +    max_size is at least
    +
    + returns:
    +    a space-delimited string of options (just as typed)
    +    returns false if max_size is too short
    +
    + notes:
    +    silently removes
    +    makes option string easy to input and output
    +    matches qh_argv_to_command_size()
    +
    +    argc may be 0
    +*/
    +int qh_argv_to_command(int argc, char *argv[], char* command, int max_size) {
    +  int i, remaining;
    +  char *s;
    +  *command= '\0';  /* max_size > 0 */
    +
    +  if (argc) {
    +    if ((s= strrchr( argv[0], '\\')) /* get filename w/o .exe extension */
    +    || (s= strrchr( argv[0], '/')))
    +        s++;
    +    else
    +        s= argv[0];
    +    if ((int)strlen(s) < max_size)   /* WARN64 */
    +        strcpy(command, s);
    +    else
    +        goto error_argv;
    +    if ((s= strstr(command, ".EXE"))
    +    ||  (s= strstr(command, ".exe")))
    +        *s= '\0';
    +  }
    +  for (i=1; i < argc; i++) {
    +    s= argv[i];
    +    remaining= max_size - (int)strlen(command) - (int)strlen(s) - 2;   /* WARN64 */
    +    if (!*s || strchr(s, ' ')) {
    +      char *t= command + strlen(command);
    +      remaining -= 2;
    +      if (remaining < 0) {
    +        goto error_argv;
    +      }
    +      *t++= ' ';
    +      *t++= '"';
    +      while (*s) {
    +        if (*s == '"') {
    +          if (--remaining < 0)
    +            goto error_argv;
    +          *t++= '\\';
    +        }
    +        *t++= *s++;
    +      }
    +      *t++= '"';
    +      *t= '\0';
    +    }else if (remaining < 0) {
    +      goto error_argv;
    +    }else
    +      strcat(command, " ");
    +      strcat(command, s);
    +  }
    +  return 1;
    +
    +error_argv:
    +  return 0;
    +} /* argv_to_command */
    +
    +/*---------------------------------
    +
    +qh_argv_to_command_size( argc, argv )
    +
    +    return size to allocate for qh_argv_to_command()
    +
    +notes:
    +    argc may be 0
    +    actual size is usually shorter
    +*/
    +int qh_argv_to_command_size(int argc, char *argv[]) {
    +    unsigned int count= 1; /* null-terminator if argc==0 */
    +    int i;
    +    char *s;
    +
    +    for (i=0; i0 && strchr(argv[i], ' ')) {
    +        count += 2;  /* quote delimiters */
    +        for (s=argv[i]; *s; s++) {
    +          if (*s == '"') {
    +            count++;
    +          }
    +        }
    +      }
    +    }
    +    return count;
    +} /* argv_to_command_size */
    +
    +/*---------------------------------
    +
    +  qh_rand()
    +  qh_srand( seed )
    +    generate pseudo-random number between 1 and 2^31 -2
    +
    +  notes:
    +    For qhull and rbox, called from qh_RANDOMint(),etc. [user.h]
    +
    +    From Park & Miller's minimal standard random number generator
    +      Communications of the ACM, 31:1192-1201, 1988.
    +    Does not use 0 or 2^31 -1
    +      this is silently enforced by qh_srand()
    +    Can make 'Rn' much faster by moving qh_rand to qh_distplane
    +*/
    +
    +/* Global variables and constants */
    +
    +int qh_last_random= 1;  /* define as global variable instead of using qh */
    +
    +#define qh_rand_a 16807
    +#define qh_rand_m 2147483647
    +#define qh_rand_q 127773  /* m div a */
    +#define qh_rand_r 2836    /* m mod a */
    +
    +int qh_rand( void) {
    +    int lo, hi, test;
    +    int seed = qh_last_random;
    +
    +    hi = seed / qh_rand_q;  /* seed div q */
    +    lo = seed % qh_rand_q;  /* seed mod q */
    +    test = qh_rand_a * lo - qh_rand_r * hi;
    +    if (test > 0)
    +        seed= test;
    +    else
    +        seed= test + qh_rand_m;
    +    qh_last_random= seed;
    +    /* seed = seed < qh_RANDOMmax/2 ? 0 : qh_RANDOMmax;  for testing */
    +    /* seed = qh_RANDOMmax;  for testing */
    +    return seed;
    +} /* rand */
    +
    +void qh_srand( int seed) {
    +    if (seed < 1)
    +        qh_last_random= 1;
    +    else if (seed >= qh_rand_m)
    +        qh_last_random= qh_rand_m - 1;
    +    else
    +        qh_last_random= seed;
    +} /* qh_srand */
    +
    +/*---------------------------------
    +
    +qh_randomfactor( scale, offset )
    +  return a random factor r * scale + offset
    +
    +notes:
    +  qh.RANDOMa/b are defined in global.c
    +*/
    +realT qh_randomfactor(realT scale, realT offset) {
    +    realT randr;
    +
    +    randr= qh_RANDOMint;
    +    return randr * scale + offset;
    +} /* randomfactor */
    +
    +/*---------------------------------
    +
    +qh_randommatrix( buffer, dim, rows )
    +  generate a random dim X dim matrix in range [-1,1]
    +  assumes buffer is [dim+1, dim]
    +
    +returns:
    +  sets buffer to random numbers
    +  sets rows to rows of buffer
    +  sets row[dim] as scratch row
    +*/
    +void qh_randommatrix(realT *buffer, int dim, realT **rows) {
    +    int i, k;
    +    realT **rowi, *coord, realr;
    +
    +    coord= buffer;
    +    rowi= rows;
    +    for (i=0; i < dim; i++) {
    +        *(rowi++)= coord;
    +        for (k=0; k < dim; k++) {
    +            realr= qh_RANDOMint;
    +            *(coord++)= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
    +        }
    +    }
    +    *rowi= coord;
    +} /* randommatrix */
    +
    +/*---------------------------------
    +
    +  qh_strtol( s, endp) qh_strtod( s, endp)
    +    internal versions of strtol() and strtod()
    +    does not skip trailing spaces
    +  notes:
    +    some implementations of strtol()/strtod() skip trailing spaces
    +*/
    +double qh_strtod(const char *s, char **endp) {
    +  double result;
    +
    +  result= strtod(s, endp);
    +  if (s < (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtod */
    +
    +int qh_strtol(const char *s, char **endp) {
    +  int result;
    +
    +  result= (int) strtol(s, endp, 10);     /* WARN64 */
    +  if (s< (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtol */
    diff --git a/xs/src/qhull/src/libqhull/random.h b/xs/src/qhull/src/libqhull/random.h
    new file mode 100644
    index 0000000000..0c6896b765
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/random.h
    @@ -0,0 +1,34 @@
    +/*
      ---------------------------------
    +
    +  random.h
    +    header file for random and utility routines
    +
    +   see qh-geom.htm and random.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/random.h#2 $$Change: 2026 $
    +   $DateTime: 2015/11/07 22:44:39 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFrandom
    +#define qhDEFrandom 1
    +
    +#include "libqhull.h"
    +
    +/*============= prototypes in alphabetical order ======= */
    +
    +
    +int     qh_argv_to_command(int argc, char *argv[], char* command, int max_size);
    +int     qh_argv_to_command_size(int argc, char *argv[]);
    +int     qh_rand( void);
    +void    qh_srand( int seed);
    +realT   qh_randomfactor(realT scale, realT offset);
    +void    qh_randommatrix(realT *buffer, int dim, realT **row);
    +int     qh_strtol(const char *s, char **endp);
    +double  qh_strtod(const char *s, char **endp);
    +
    +#endif /* qhDEFrandom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/rboxlib.c b/xs/src/qhull/src/libqhull/rboxlib.c
    new file mode 100644
    index 0000000000..f945133fa0
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/rboxlib.c
    @@ -0,0 +1,870 @@
    +/*
      ---------------------------------
    +
    +   rboxlib.c
    +     Generate input points
    +
    +   notes:
    +     For documentation, see prompt[] of rbox.c
    +     50 points generated for 'rbox D4'
    +
    +   WARNING:
    +     incorrect range if qh_RANDOMmax is defined wrong (user.h)
    +*/
    +
    +#include "libqhull.h"  /* First for user.h */
    +#include "random.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ */
    +#pragma warning( disable : 4706)  /* assignment within conditional expression. */
    +#pragma warning( disable : 4996)  /* this function (strncat,sprintf,strcpy) or variable may be unsafe. */
    +#endif
    +
    +#define MAXdim 200
    +#define PI 3.1415926535897932384
    +
    +/* ------------------------------ prototypes ----------------*/
    +int qh_roundi( double a);
    +void qh_out1( double a);
    +void qh_out2n( double a, double b);
    +void qh_out3n( double a, double b, double c);
    +void qh_outcoord(int iscdd, double *coord, int dim);
    +void qh_outcoincident(int coincidentpoints, double radius, int iscdd, double *coord, int dim);
    +
    +void    qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +int     qh_rand( void);
    +void    qh_srand( int seed);
    +
    +
    +/* ------------------------------ globals -------------------*/
    +
    +/* No state is carried between rbox requests */
    +typedef struct rboxT rboxT;
    +struct rboxT {
    +  FILE *fout;
    +  FILE *ferr;
    +  int isinteger;
    +  double out_offset;
    +  jmp_buf errexit;        /* exit label for rboxpoints, defined by setjmp(), called by qh_errexit_rbox() */
    +  char  jmpXtra[40];      /* extra bytes in case jmp_buf is defined wrong by compiler */
    +};
    +
    +
    +int rbox_inuse= 0;
    +rboxT rbox;
    +
    +/*---------------------------------
    +
    +  qh_rboxpoints( fout, ferr, rbox_command )
    +    Generate points to fout according to rbox options
    +    Report errors on ferr
    +
    +  returns:
    +    0 (qh_ERRnone) on success
    +    1 (qh_ERRinput) on input error
    +    4 (qh_ERRmem) on memory error
    +    5 (qh_ERRqhull) on internal error
    +
    +  notes:
    +    To avoid using stdio, redefine qh_malloc, qh_free, and qh_fprintf_rbox (user.c)
    +
    +  design:
    +    Straight line code (consider defining a struct and functions):
    +
    +    Parse arguments into variables
    +    Determine the number of points
    +    Generate the points
    +*/
    +int qh_rboxpoints(FILE* fout, FILE* ferr, char* rbox_command) {
    +  int i,j,k;
    +  int gendim;
    +  int coincidentcount=0, coincidenttotal=0, coincidentpoints=0;
    +  int cubesize, diamondsize, seed=0, count, apex;
    +  int dim=3, numpoints=0, totpoints, addpoints=0;
    +  int issphere=0, isaxis=0,  iscdd=0, islens=0, isregular=0, iswidth=0, addcube=0;
    +  int isgap=0, isspiral=0, NOcommand=0, adddiamond=0;
    +  int israndom=0, istime=0;
    +  int isbox=0, issimplex=0, issimplex2=0, ismesh=0;
    +  double width=0.0, gap=0.0, radius=0.0, coincidentradius=0.0;
    +  double coord[MAXdim], offset, meshm=3.0, meshn=4.0, meshr=5.0;
    +  double *coordp, *simplex= NULL, *simplexp;
    +  int nthroot, mult[MAXdim];
    +  double norm, factor, randr, rangap, lensangle=0, lensbase=1;
    +  double anglediff, angle, x, y, cube=0.0, diamond=0.0;
    +  double box= qh_DEFAULTbox; /* scale all numbers before output */
    +  double randmax= qh_RANDOMmax;
    +  char command[200], seedbuf[200];
    +  char *s= command, *t, *first_point= NULL;
    +  time_t timedata;
    +  int exitcode;
    +
    +  if (rbox_inuse) {
    +    qh_fprintf_rbox(rbox.ferr, 6188, "rbox error: rbox in use by another process.  Please lock calls to rbox.\n");
    +    return qh_ERRqhull;
    +  }
    +  rbox_inuse= True;
    +  rbox.ferr= ferr;
    +  rbox.fout= fout;
    +
    +  exitcode= setjmp(rbox.errexit);
    +  if (exitcode) {
    +    /* same code for error exit and normal return.  qh.NOerrexit is set */
    +    if (simplex)
    +        qh_free(simplex);
    +    rbox_inuse= False;
    +    return exitcode;
    +  }
    +
    +  *command= '\0';
    +  strncat(command, rbox_command, sizeof(command)-strlen(command)-1);
    +
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    if (isdigit(*s)) {
    +      numpoints= qh_strtol(s, &s);
    +      continue;
    +    }
    +    /* ============= read flags =============== */
    +    switch (*s++) {
    +    case 'c':
    +      addcube= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        cube= qh_strtod(++t, &s);
    +      break;
    +    case 'd':
    +      adddiamond= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        diamond= qh_strtod(++t, &s);
    +      break;
    +    case 'h':
    +      iscdd= 1;
    +      break;
    +    case 'l':
    +      isspiral= 1;
    +      break;
    +    case 'n':
    +      NOcommand= 1;
    +      break;
    +    case 'r':
    +      isregular= 1;
    +      break;
    +    case 's':
    +      issphere= 1;
    +      break;
    +    case 't':
    +      istime= 1;
    +      if (isdigit(*s)) {
    +        seed= qh_strtol(s, &s);
    +        israndom= 0;
    +      }else
    +        israndom= 1;
    +      break;
    +    case 'x':
    +      issimplex= 1;
    +      break;
    +    case 'y':
    +      issimplex2= 1;
    +      break;
    +    case 'z':
    +      rbox.isinteger= 1;
    +      break;
    +    case 'B':
    +      box= qh_strtod(s, &s);
    +      isbox= 1;
    +      break;
    +    case 'C':
    +      if (*s)
    +        coincidentpoints=  qh_strtol(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        coincidentradius=  qh_strtod(s, &s);
    +      }
    +      if (*s == ',') {
    +        ++s;
    +        coincidenttotal=  qh_strtol(s, &s);
    +      }
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(rbox.ferr, 7080, "rbox error: arguments for 'Cn,r,m' are not 'int', 'float', and 'int'.  Remaining string is '%s'\n", s);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      if (coincidentpoints==0){
    +        qh_fprintf_rbox(rbox.ferr, 6268, "rbox error: missing arguments for 'Cn,r,m' where n is the number of coincident points, r is the radius (default 0.0), and m is the number of points\n");
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      if (coincidentpoints<0 || coincidenttotal<0 || coincidentradius<0.0){
    +        qh_fprintf_rbox(rbox.ferr, 6269, "rbox error: negative arguments for 'Cn,m,r' where n (%d) is the number of coincident points, m (%d) is the number of points, and r (%.2g) is the radius (default 0.0)\n", coincidentpoints, coincidenttotal, coincidentradius);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      break;
    +    case 'D':
    +      dim= qh_strtol(s, &s);
    +      if (dim < 1
    +      || dim > MAXdim) {
    +        qh_fprintf_rbox(rbox.ferr, 6189, "rbox error: dimension, D%d, out of bounds (>=%d or <=0)", dim, MAXdim);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      break;
    +    case 'G':
    +      if (isdigit(*s))
    +        gap= qh_strtod(s, &s);
    +      else
    +        gap= 0.5;
    +      isgap= 1;
    +      break;
    +    case 'L':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 10;
    +      islens= 1;
    +      break;
    +    case 'M':
    +      ismesh= 1;
    +      if (*s)
    +        meshn= qh_strtod(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        meshm= qh_strtod(s, &s);
    +      }else
    +        meshm= 0.0;
    +      if (*s == ',') {
    +        ++s;
    +        meshr= qh_strtod(s, &s);
    +      }else
    +        meshr= sqrt(meshn*meshn + meshm*meshm);
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(rbox.ferr, 7069, "rbox warning: assuming 'M3,4,5' since mesh args are not integers or reals\n");
    +        meshn= 3.0, meshm=4.0, meshr=5.0;
    +      }
    +      break;
    +    case 'O':
    +      rbox.out_offset= qh_strtod(s, &s);
    +      break;
    +    case 'P':
    +      if (!first_point)
    +        first_point= s-1;
    +      addpoints++;
    +      while (*s && !isspace(*s))   /* read points later */
    +        s++;
    +      break;
    +    case 'W':
    +      width= qh_strtod(s, &s);
    +      iswidth= 1;
    +      break;
    +    case 'Z':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 1.0;
    +      isaxis= 1;
    +      break;
    +    default:
    +      qh_fprintf_rbox(rbox.ferr, 7070, "rbox error: unknown flag at %s.\nExecute 'rbox' without arguments for documentation.\n", s);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +    if (*s && !isspace(*s)) {
    +      qh_fprintf_rbox(rbox.ferr, 7071, "rbox error: missing space between flags at %s.\n", s);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +  }
    +
    +  /* ============= defaults, constants, and sizes =============== */
    +  if (rbox.isinteger && !isbox)
    +    box= qh_DEFAULTzbox;
    +  if (addcube) {
    +    cubesize= (int)floor(ldexp(1.0,dim)+0.5);
    +    if (cube == 0.0)
    +      cube= box;
    +  }else
    +    cubesize= 0;
    +  if (adddiamond) {
    +    diamondsize= 2*dim;
    +    if (diamond == 0.0)
    +      diamond= box;
    +  }else
    +    diamondsize= 0;
    +  if (islens) {
    +    if (isaxis) {
    +        qh_fprintf_rbox(rbox.ferr, 6190, "rbox error: can not combine 'Ln' with 'Zn'\n");
    +        qh_errexit_rbox(qh_ERRinput);
    +    }
    +    if (radius <= 1.0) {
    +        qh_fprintf_rbox(rbox.ferr, 6191, "rbox error: lens radius %.2g should be greater than 1.0\n",
    +               radius);
    +        qh_errexit_rbox(qh_ERRinput);
    +    }
    +    lensangle= asin(1.0/radius);
    +    lensbase= radius * cos(lensangle);
    +  }
    +
    +  if (!numpoints) {
    +    if (issimplex2)
    +        ; /* ok */
    +    else if (isregular + issimplex + islens + issphere + isaxis + isspiral + iswidth + ismesh) {
    +        qh_fprintf_rbox(rbox.ferr, 6192, "rbox error: missing count\n");
    +        qh_errexit_rbox(qh_ERRinput);
    +    }else if (adddiamond + addcube + addpoints)
    +        ; /* ok */
    +    else {
    +        numpoints= 50;  /* ./rbox D4 is the test case */
    +        issphere= 1;
    +    }
    +  }
    +  if ((issimplex + islens + isspiral + ismesh > 1)
    +  || (issimplex + issphere + isspiral + ismesh > 1)) {
    +    qh_fprintf_rbox(rbox.ferr, 6193, "rbox error: can only specify one of 'l', 's', 'x', 'Ln', or 'Mn,m,r' ('Ln s' is ok).\n");
    +    qh_errexit_rbox(qh_ERRinput);
    +  }
    +  if (coincidentpoints>0 && (numpoints == 0 || coincidenttotal > numpoints)) {
    +    qh_fprintf_rbox(rbox.ferr, 6270, "rbox error: 'Cn,r,m' requested n coincident points for each of m points.  Either there is no points or m (%d) is greater than the number of points (%d).\n", coincidenttotal, numpoints);
    +    qh_errexit_rbox(qh_ERRinput);
    +  }
    +  if (coincidenttotal == 0)
    +    coincidenttotal= numpoints;
    +
    +  /* ============= print header with total points =============== */
    +  if (issimplex || ismesh)
    +    totpoints= numpoints;
    +  else if (issimplex2)
    +    totpoints= numpoints+dim+1;
    +  else if (isregular) {
    +    totpoints= numpoints;
    +    if (dim == 2) {
    +        if (islens)
    +          totpoints += numpoints - 2;
    +    }else if (dim == 3) {
    +        if (islens)
    +          totpoints += 2 * numpoints;
    +      else if (isgap)
    +        totpoints += 1 + numpoints;
    +      else
    +        totpoints += 2;
    +    }
    +  }else
    +    totpoints= numpoints + isaxis;
    +  totpoints += cubesize + diamondsize + addpoints;
    +  totpoints += coincidentpoints*coincidenttotal;
    +
    +  /* ============= seed randoms =============== */
    +  if (istime == 0) {
    +    for (s=command; *s; s++) {
    +      if (issimplex2 && *s == 'y') /* make 'y' same seed as 'x' */
    +        i= 'x';
    +      else
    +        i= *s;
    +      seed= 11*seed + i;
    +    }
    +  }else if (israndom) {
    +    seed= (int)time(&timedata);
    +    sprintf(seedbuf, " t%d", seed);  /* appends an extra t, not worth removing */
    +    strncat(command, seedbuf, sizeof(command)-strlen(command)-1);
    +    t= strstr(command, " t ");
    +    if (t)
    +      strcpy(t+1, t+3); /* remove " t " */
    +  } /* else, seed explicitly set to n */
    +  qh_RANDOMseed_(seed);
    +
    +  /* ============= print header =============== */
    +
    +  if (iscdd)
    +      qh_fprintf_rbox(rbox.fout, 9391, "%s\nbegin\n        %d %d %s\n",
    +      NOcommand ? "" : command,
    +      totpoints, dim+1,
    +      rbox.isinteger ? "integer" : "real");
    +  else if (NOcommand)
    +      qh_fprintf_rbox(rbox.fout, 9392, "%d\n%d\n", dim, totpoints);
    +  else
    +      /* qh_fprintf_rbox special cases 9393 to append 'command' to the RboxPoints.comment() */
    +      qh_fprintf_rbox(rbox.fout, 9393, "%d %s\n%d\n", dim, command, totpoints);
    +
    +  /* ============= explicit points =============== */
    +  if ((s= first_point)) {
    +    while (s && *s) { /* 'P' */
    +      count= 0;
    +      if (iscdd)
    +        qh_out1( 1.0);
    +      while (*++s) {
    +        qh_out1( qh_strtod(s, &s));
    +        count++;
    +        if (isspace(*s) || !*s)
    +          break;
    +        if (*s != ',') {
    +          qh_fprintf_rbox(rbox.ferr, 6194, "rbox error: missing comma after coordinate in %s\n\n", s);
    +          qh_errexit_rbox(qh_ERRinput);
    +        }
    +      }
    +      if (count < dim) {
    +        for (k=dim-count; k--; )
    +          qh_out1( 0.0);
    +      }else if (count > dim) {
    +        qh_fprintf_rbox(rbox.ferr, 6195, "rbox error: %d coordinates instead of %d coordinates in %s\n\n",
    +                  count, dim, s);
    +        qh_errexit_rbox(qh_ERRinput);
    +      }
    +      qh_fprintf_rbox(rbox.fout, 9394, "\n");
    +      while ((s= strchr(s, 'P'))) {
    +        if (isspace(s[-1]))
    +          break;
    +      }
    +    }
    +  }
    +
    +  /* ============= simplex distribution =============== */
    +  if (issimplex+issimplex2) {
    +    if (!(simplex= (double*)qh_malloc( dim * (dim+1) * sizeof(double)))) {
    +      qh_fprintf_rbox(rbox.ferr, 6196, "rbox error: insufficient memory for simplex\n");
    +      qh_errexit_rbox(qh_ERRmem); /* qh_ERRmem */
    +    }
    +    simplexp= simplex;
    +    if (isregular) {
    +      for (i=0; i randmax/2)
    +          coord[dim-1]= -coord[dim-1];
    +      /* ============= project 'Wn' point toward boundary =============== */
    +      }else if (iswidth && !issphere) {
    +        j= qh_RANDOMint % gendim;
    +        if (coord[j] < 0)
    +          coord[j]= -1.0 - coord[j] * width;
    +        else
    +          coord[j]= 1.0 - coord[j] * width;
    +      }
    +      /* ============= scale point to box =============== */
    +      for (k=0; k=0; k--) {
    +        if (j & ( 1 << k))
    +          qh_out1( cube);
    +        else
    +          qh_out1( -cube);
    +      }
    +      qh_fprintf_rbox(rbox.fout, 9400, "\n");
    +    }
    +  }
    +
    +  /* ============= write diamond vertices =============== */
    +  if (adddiamond) {
    +    for (j=0; j=0; k--) {
    +        if (j/2 != k)
    +          qh_out1( 0.0);
    +        else if (j & 0x1)
    +          qh_out1( diamond);
    +        else
    +          qh_out1( -diamond);
    +      }
    +      qh_fprintf_rbox(rbox.fout, 9401, "\n");
    +    }
    +  }
    +
    +  if (iscdd)
    +    qh_fprintf_rbox(rbox.fout, 9402, "end\nhull\n");
    +
    +  /* same code for error exit and normal return */
    +  qh_free(simplex);
    +  rbox_inuse= False;
    +  return qh_ERRnone;
    +} /* rboxpoints */
    +
    +/*------------------------------------------------
    +outxxx - output functions for qh_rboxpoints
    +*/
    +int qh_roundi( double a) {
    +  if (a < 0.0) {
    +    if (a - 0.5 < INT_MIN) {
    +      qh_fprintf_rbox(rbox.ferr, 6200, "rbox input error: negative coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +    return (int)(a - 0.5);
    +  }else {
    +    if (a + 0.5 > INT_MAX) {
    +      qh_fprintf_rbox(rbox.ferr, 6201, "rbox input error: coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh_ERRinput);
    +    }
    +    return (int)(a + 0.5);
    +  }
    +} /* qh_roundi */
    +
    +void qh_out1(double a) {
    +
    +  if (rbox.isinteger)
    +    qh_fprintf_rbox(rbox.fout, 9403, "%d ", qh_roundi( a+rbox.out_offset));
    +  else
    +    qh_fprintf_rbox(rbox.fout, 9404, qh_REAL_1, a+rbox.out_offset);
    +} /* qh_out1 */
    +
    +void qh_out2n( double a, double b) {
    +
    +  if (rbox.isinteger)
    +    qh_fprintf_rbox(rbox.fout, 9405, "%d %d\n", qh_roundi(a+rbox.out_offset), qh_roundi(b+rbox.out_offset));
    +  else
    +    qh_fprintf_rbox(rbox.fout, 9406, qh_REAL_2n, a+rbox.out_offset, b+rbox.out_offset);
    +} /* qh_out2n */
    +
    +void qh_out3n( double a, double b, double c) {
    +
    +  if (rbox.isinteger)
    +    qh_fprintf_rbox(rbox.fout, 9407, "%d %d %d\n", qh_roundi(a+rbox.out_offset), qh_roundi(b+rbox.out_offset), qh_roundi(c+rbox.out_offset));
    +  else
    +    qh_fprintf_rbox(rbox.fout, 9408, qh_REAL_3n, a+rbox.out_offset, b+rbox.out_offset, c+rbox.out_offset);
    +} /* qh_out3n */
    +
    +void qh_outcoord(int iscdd, double *coord, int dim) {
    +    double *p= coord;
    +    int k;
    +
    +    if (iscdd)
    +      qh_out1( 1.0);
    +    for (k=0; k < dim; k++)
    +      qh_out1(*(p++));
    +    qh_fprintf_rbox(rbox.fout, 9396, "\n");
    +} /* qh_outcoord */
    +
    +void qh_outcoincident(int coincidentpoints, double radius, int iscdd, double *coord, int dim) {
    +  double *p;
    +  double randr, delta;
    +  int i,k;
    +  double randmax= qh_RANDOMmax;
    +
    +  for (i= 0; i
      ---------------------------------
    +
    +   stat.c
    +   contains all statistics that are collected for qhull
    +
    +   see qh-stat.htm and stat.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/stat.c#5 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_a.h"
    +
    +/*============ global data structure ==========*/
    +
    +#if qh_QHpointer
    +qhstatT *qh_qhstat=NULL;  /* global data structure */
    +#else
    +qhstatT qh_qhstat;   /* add "={0}" if this causes a compiler error */
    +#endif
    +
    +/*========== functions in alphabetic order ================*/
    +
    +/*---------------------------------
    +
    +  qh_allstatA()
    +    define statistics in groups of 20
    +
    +  notes:
    +    (otherwise, 'gcc -O2' uses too much memory)
    +    uses qhstat.next
    +*/
    +void qh_allstatA(void) {
    +
    +   /* zdef_(type,name,doc,average) */
    +  zzdef_(zdoc, Zdoc2, "precision statistics", -1);
    +  zdef_(zinc, Znewvertex, NULL, -1);
    +  zdef_(wadd, Wnewvertex, "ave. distance of a new vertex to a facet(!0s)", Znewvertex);
    +  zzdef_(wmax, Wnewvertexmax, "max. distance of a new vertex to a facet", -1);
    +  zdef_(wmax, Wvertexmax, "max. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wvertexmin, "min. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wmindenom, "min. denominator in hyperplane computation", -1);
    +
    +  qhstat precision= qhstat next;  /* call qh_precision for each of these */
    +  zzdef_(zdoc, Zdoc3, "precision problems (corrected unless 'Q0' or an error)", -1);
    +  zzdef_(zinc, Zcoplanarridges, "coplanar half ridges in output", -1);
    +  zzdef_(zinc, Zconcaveridges, "concave half ridges in output", -1);
    +  zzdef_(zinc, Zflippedfacets, "flipped facets", -1);
    +  zzdef_(zinc, Zcoplanarhorizon, "coplanar horizon facets for new vertices", -1);
    +  zzdef_(zinc, Zcoplanarpart, "coplanar points during partitioning", -1);
    +  zzdef_(zinc, Zminnorm, "degenerate hyperplanes recomputed with gaussian elimination", -1);
    +  zzdef_(zinc, Znearlysingular, "nearly singular or axis-parallel hyperplanes", -1);
    +  zzdef_(zinc, Zback0, "zero divisors during back substitute", -1);
    +  zzdef_(zinc, Zgauss0, "zero divisors during gaussian elimination", -1);
    +  zzdef_(zinc, Zmultiridge, "ridges with multiple neighbors", -1);
    +}
    +void qh_allstatB(void) {
    +  zzdef_(zdoc, Zdoc1, "summary information", -1);
    +  zdef_(zinc, Zvertices, "number of vertices in output", -1);
    +  zdef_(zinc, Znumfacets, "number of facets in output", -1);
    +  zdef_(zinc, Znonsimplicial, "number of non-simplicial facets in output", -1);
    +  zdef_(zinc, Znowsimplicial, "number of simplicial facets that were merged", -1);
    +  zdef_(zinc, Znumridges, "number of ridges in output", -1);
    +  zdef_(zadd, Znumridges, "average number of ridges per facet", Znumfacets);
    +  zdef_(zmax, Zmaxridges, "maximum number of ridges", -1);
    +  zdef_(zadd, Znumneighbors, "average number of neighbors per facet", Znumfacets);
    +  zdef_(zmax, Zmaxneighbors, "maximum number of neighbors", -1);
    +  zdef_(zadd, Znumvertices, "average number of vertices per facet", Znumfacets);
    +  zdef_(zmax, Zmaxvertices, "maximum number of vertices", -1);
    +  zdef_(zadd, Znumvneighbors, "average number of neighbors per vertex", Zvertices);
    +  zdef_(zmax, Zmaxvneighbors, "maximum number of neighbors", -1);
    +  zdef_(wadd, Wcpu, "cpu seconds for qhull after input", -1);
    +  zdef_(zinc, Ztotvertices, "vertices created altogether", -1);
    +  zzdef_(zinc, Zsetplane, "facets created altogether", -1);
    +  zdef_(zinc, Ztotridges, "ridges created altogether", -1);
    +  zdef_(zinc, Zpostfacets, "facets before post merge", -1);
    +  zdef_(zadd, Znummergetot, "average merges per facet(at most 511)", Znumfacets);
    +  zdef_(zmax, Znummergemax, "  maximum merges for a facet(at most 511)", -1);
    +  zdef_(zinc, Zangle, NULL, -1);
    +  zdef_(wadd, Wangle, "average angle(cosine) of facet normals for all ridges", Zangle);
    +  zdef_(wmax, Wanglemax, "  maximum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wmin, Wanglemin, "  minimum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wadd, Wareatot, "total area of facets", -1);
    +  zdef_(wmax, Wareamax, "  maximum facet area", -1);
    +  zdef_(wmin, Wareamin, "  minimum facet area", -1);
    +}
    +void qh_allstatC(void) {
    +  zdef_(zdoc, Zdoc9, "build hull statistics", -1);
    +  zzdef_(zinc, Zprocessed, "points processed", -1);
    +  zzdef_(zinc, Zretry, "retries due to precision problems", -1);
    +  zdef_(wmax, Wretrymax, "  max. random joggle", -1);
    +  zdef_(zmax, Zmaxvertex, "max. vertices at any one time", -1);
    +  zdef_(zinc, Ztotvisible, "ave. visible facets per iteration", Zprocessed);
    +  zdef_(zinc, Zinsidevisible, "  ave. visible facets without an horizon neighbor", Zprocessed);
    +  zdef_(zadd, Zvisfacettot,  "  ave. facets deleted per iteration", Zprocessed);
    +  zdef_(zmax, Zvisfacetmax,  "    maximum", -1);
    +  zdef_(zadd, Zvisvertextot, "ave. visible vertices per iteration", Zprocessed);
    +  zdef_(zmax, Zvisvertexmax, "    maximum", -1);
    +  zdef_(zinc, Ztothorizon, "ave. horizon facets per iteration", Zprocessed);
    +  zdef_(zadd, Znewfacettot,  "ave. new or merged facets per iteration", Zprocessed);
    +  zdef_(zmax, Znewfacetmax,  "    maximum(includes initial simplex)", -1);
    +  zdef_(wadd, Wnewbalance, "average new facet balance", Zprocessed);
    +  zdef_(wadd, Wnewbalance2, "  standard deviation", -1);
    +  zdef_(wadd, Wpbalance, "average partition balance", Zpbalance);
    +  zdef_(wadd, Wpbalance2, "  standard deviation", -1);
    +  zdef_(zinc, Zpbalance, "  number of trials", -1);
    +  zdef_(zinc, Zsearchpoints, "searches of all points for initial simplex", -1);
    +  zdef_(zinc, Zdetsimplex, "determinants computed(area & initial hull)", -1);
    +  zdef_(zinc, Znoarea, "determinants not computed because vertex too low", -1);
    +  zdef_(zinc, Znotmax, "points ignored(!above max_outside)", -1);
    +  zdef_(zinc, Znotgood, "points ignored(!above a good facet)", -1);
    +  zdef_(zinc, Znotgoodnew, "points ignored(didn't create a good new facet)", -1);
    +  zdef_(zinc, Zgoodfacet, "good facets found", -1);
    +  zzdef_(zinc, Znumvisibility, "distance tests for facet visibility", -1);
    +  zdef_(zinc, Zdistvertex, "distance tests to report minimum vertex", -1);
    +  zzdef_(zinc, Ztotcheck, "points checked for facets' outer planes", -1);
    +  zzdef_(zinc, Zcheckpart, "  ave. distance tests per check", Ztotcheck);
    +}
    +void qh_allstatD(void) {
    +  zdef_(zinc, Zvisit, "resets of visit_id", -1);
    +  zdef_(zinc, Zvvisit, "  resets of vertex_visit", -1);
    +  zdef_(zmax, Zvisit2max, "  max visit_id/2", -1);
    +  zdef_(zmax, Zvvisit2max, "  max vertex_visit/2", -1);
    +
    +  zdef_(zdoc, Zdoc4, "partitioning statistics(see previous for outer planes)", -1);
    +  zzdef_(zadd, Zdelvertextot, "total vertices deleted", -1);
    +  zdef_(zmax, Zdelvertexmax, "    maximum vertices deleted per iteration", -1);
    +  zdef_(zinc, Zfindbest, "calls to findbest", -1);
    +  zdef_(zadd, Zfindbesttot, " ave. facets tested", Zfindbest);
    +  zdef_(zmax, Zfindbestmax, " max. facets tested", -1);
    +  zdef_(zadd, Zfindcoplanar, " ave. coplanar search", Zfindbest);
    +  zdef_(zinc, Zfindnew, "calls to findbestnew", -1);
    +  zdef_(zadd, Zfindnewtot, " ave. facets tested", Zfindnew);
    +  zdef_(zmax, Zfindnewmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindnewjump, " ave. clearly better", Zfindnew);
    +  zdef_(zinc, Zfindnewsharp, " calls due to qh_sharpnewfacets", -1);
    +  zdef_(zinc, Zfindhorizon, "calls to findhorizon", -1);
    +  zdef_(zadd, Zfindhorizontot, " ave. facets tested", Zfindhorizon);
    +  zdef_(zmax, Zfindhorizonmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindjump,       " ave. clearly better", Zfindhorizon);
    +  zdef_(zinc, Zparthorizon, " horizon facets better than bestfacet", -1);
    +  zdef_(zinc, Zpartangle, "angle tests for repartitioned coplanar points", -1);
    +  zdef_(zinc, Zpartflip, "  repartitioned coplanar points for flipped orientation", -1);
    +}
    +void qh_allstatE(void) {
    +  zdef_(zinc, Zpartinside, "inside points", -1);
    +  zdef_(zinc, Zpartnear, "  inside points kept with a facet", -1);
    +  zdef_(zinc, Zcoplanarinside, "  inside points that were coplanar with a facet", -1);
    +  zdef_(zinc, Zbestlower, "calls to findbestlower", -1);
    +  zdef_(zinc, Zbestlowerv, "  with search of vertex neighbors", -1);
    +  zdef_(zinc, Zbestlowerall, "  with rare search of all facets", -1);
    +  zdef_(zmax, Zbestloweralln, "  facets per search of all facets", -1);
    +  zdef_(wadd, Wmaxout, "difference in max_outside at final check", -1);
    +  zzdef_(zinc, Zpartitionall, "distance tests for initial partition", -1);
    +  zdef_(zinc, Ztotpartition, "partitions of a point", -1);
    +  zzdef_(zinc, Zpartition, "distance tests for partitioning", -1);
    +  zzdef_(zinc, Zdistcheck, "distance tests for checking flipped facets", -1);
    +  zzdef_(zinc, Zdistconvex, "distance tests for checking convexity", -1);
    +  zdef_(zinc, Zdistgood, "distance tests for checking good point", -1);
    +  zdef_(zinc, Zdistio, "distance tests for output", -1);
    +  zdef_(zinc, Zdiststat, "distance tests for statistics", -1);
    +  zdef_(zinc, Zdistplane, "total number of distance tests", -1);
    +  zdef_(zinc, Ztotpartcoplanar, "partitions of coplanar points or deleted vertices", -1);
    +  zzdef_(zinc, Zpartcoplanar, "   distance tests for these partitions", -1);
    +  zdef_(zinc, Zcomputefurthest, "distance tests for computing furthest", -1);
    +}
    +void qh_allstatE2(void) {
    +  zdef_(zdoc, Zdoc5, "statistics for matching ridges", -1);
    +  zdef_(zinc, Zhashlookup, "total lookups for matching ridges of new facets", -1);
    +  zdef_(zinc, Zhashtests, "average number of tests to match a ridge", Zhashlookup);
    +  zdef_(zinc, Zhashridge, "total lookups of subridges(duplicates and boundary)", -1);
    +  zdef_(zinc, Zhashridgetest, "average number of tests per subridge", Zhashridge);
    +  zdef_(zinc, Zdupsame, "duplicated ridges in same merge cycle", -1);
    +  zdef_(zinc, Zdupflip, "duplicated ridges with flipped facets", -1);
    +
    +  zdef_(zdoc, Zdoc6, "statistics for determining merges", -1);
    +  zdef_(zinc, Zangletests, "angles computed for ridge convexity", -1);
    +  zdef_(zinc, Zbestcentrum, "best merges used centrum instead of vertices",-1);
    +  zzdef_(zinc, Zbestdist, "distance tests for best merge", -1);
    +  zzdef_(zinc, Zcentrumtests, "distance tests for centrum convexity", -1);
    +  zzdef_(zinc, Zdistzero, "distance tests for checking simplicial convexity", -1);
    +  zdef_(zinc, Zcoplanarangle, "coplanar angles in getmergeset", -1);
    +  zdef_(zinc, Zcoplanarcentrum, "coplanar centrums in getmergeset", -1);
    +  zdef_(zinc, Zconcaveridge, "concave ridges in getmergeset", -1);
    +}
    +void qh_allstatF(void) {
    +  zdef_(zdoc, Zdoc7, "statistics for merging", -1);
    +  zdef_(zinc, Zpremergetot, "merge iterations", -1);
    +  zdef_(zadd, Zmergeinittot, "ave. initial non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergeinitmax, "  maximum", -1);
    +  zdef_(zadd, Zmergesettot, "  ave. additional non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergesetmax, "  maximum additional in one pass", -1);
    +  zdef_(zadd, Zmergeinittot2, "initial non-convex ridges for post merging", -1);
    +  zdef_(zadd, Zmergesettot2, "  additional non-convex ridges", -1);
    +  zdef_(wmax, Wmaxoutside, "max distance of vertex or coplanar point above facet(w/roundoff)", -1);
    +  zdef_(wmin, Wminvertex, "max distance of merged vertex below facet(or roundoff)", -1);
    +  zdef_(zinc, Zwidefacet, "centrums frozen due to a wide merge", -1);
    +  zdef_(zinc, Zwidevertices, "centrums frozen due to extra vertices", -1);
    +  zzdef_(zinc, Ztotmerge, "total number of facets or cycles of facets merged", -1);
    +  zdef_(zinc, Zmergesimplex, "merged a simplex", -1);
    +  zdef_(zinc, Zonehorizon, "simplices merged into coplanar horizon", -1);
    +  zzdef_(zinc, Zcyclehorizon, "cycles of facets merged into coplanar horizon", -1);
    +  zzdef_(zadd, Zcyclefacettot, "  ave. facets per cycle", Zcyclehorizon);
    +  zdef_(zmax, Zcyclefacetmax, "  max. facets", -1);
    +  zdef_(zinc, Zmergeintohorizon, "new facets merged into horizon", -1);
    +  zdef_(zinc, Zmergenew, "new facets merged", -1);
    +  zdef_(zinc, Zmergehorizon, "horizon facets merged into new facets", -1);
    +  zdef_(zinc, Zmergevertex, "vertices deleted by merging", -1);
    +  zdef_(zinc, Zcyclevertex, "vertices deleted by merging into coplanar horizon", -1);
    +  zdef_(zinc, Zdegenvertex, "vertices deleted by degenerate facet", -1);
    +  zdef_(zinc, Zmergeflipdup, "merges due to flipped facets in duplicated ridge", -1);
    +  zdef_(zinc, Zneighbor, "merges due to redundant neighbors", -1);
    +  zdef_(zadd, Ztestvneighbor, "non-convex vertex neighbors", -1);
    +}
    +void qh_allstatG(void) {
    +  zdef_(zinc, Zacoplanar, "merges due to angle coplanar facets", -1);
    +  zdef_(wadd, Wacoplanartot, "  average merge distance", Zacoplanar);
    +  zdef_(wmax, Wacoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zcoplanar, "merges due to coplanar facets", -1);
    +  zdef_(wadd, Wcoplanartot, "  average merge distance", Zcoplanar);
    +  zdef_(wmax, Wcoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zconcave, "merges due to concave facets", -1);
    +  zdef_(wadd, Wconcavetot, "  average merge distance", Zconcave);
    +  zdef_(wmax, Wconcavemax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zavoidold, "coplanar/concave merges due to avoiding old merge", -1);
    +  zdef_(wadd, Wavoidoldtot, "  average merge distance", Zavoidold);
    +  zdef_(wmax, Wavoidoldmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zdegen, "merges due to degenerate facets", -1);
    +  zdef_(wadd, Wdegentot, "  average merge distance", Zdegen);
    +  zdef_(wmax, Wdegenmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zflipped, "merges due to removing flipped facets", -1);
    +  zdef_(wadd, Wflippedtot, "  average merge distance", Zflipped);
    +  zdef_(wmax, Wflippedmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zduplicate, "merges due to duplicated ridges", -1);
    +  zdef_(wadd, Wduplicatetot, "  average merge distance", Zduplicate);
    +  zdef_(wmax, Wduplicatemax, "  maximum merge distance", -1);
    +}
    +void qh_allstatH(void) {
    +  zdef_(zdoc, Zdoc8, "renamed vertex statistics", -1);
    +  zdef_(zinc, Zrenameshare, "renamed vertices shared by two facets", -1);
    +  zdef_(zinc, Zrenamepinch, "renamed vertices in a pinched facet", -1);
    +  zdef_(zinc, Zrenameall, "renamed vertices shared by multiple facets", -1);
    +  zdef_(zinc, Zfindfail, "rename failures due to duplicated ridges", -1);
    +  zdef_(zinc, Zdupridge, "  duplicate ridges detected", -1);
    +  zdef_(zinc, Zdelridge, "deleted ridges due to renamed vertices", -1);
    +  zdef_(zinc, Zdropneighbor, "dropped neighbors due to renamed vertices", -1);
    +  zdef_(zinc, Zdropdegen, "degenerate facets due to dropped neighbors", -1);
    +  zdef_(zinc, Zdelfacetdup, "  facets deleted because of no neighbors", -1);
    +  zdef_(zinc, Zremvertex, "vertices removed from facets due to no ridges", -1);
    +  zdef_(zinc, Zremvertexdel, "  deleted", -1);
    +  zdef_(zinc, Zintersectnum, "vertex intersections for locating redundant vertices", -1);
    +  zdef_(zinc, Zintersectfail, "intersections failed to find a redundant vertex", -1);
    +  zdef_(zinc, Zintersect, "intersections found redundant vertices", -1);
    +  zdef_(zadd, Zintersecttot, "   ave. number found per vertex", Zintersect);
    +  zdef_(zmax, Zintersectmax, "   max. found for a vertex", -1);
    +  zdef_(zinc, Zvertexridge, NULL, -1);
    +  zdef_(zadd, Zvertexridgetot, "  ave. number of ridges per tested vertex", Zvertexridge);
    +  zdef_(zmax, Zvertexridgemax, "  max. number of ridges per tested vertex", -1);
    +
    +  zdef_(zdoc, Zdoc10, "memory usage statistics(in bytes)", -1);
    +  zdef_(zadd, Zmemfacets, "for facets and their normals, neighbor and vertex sets", -1);
    +  zdef_(zadd, Zmemvertices, "for vertices and their neighbor sets", -1);
    +  zdef_(zadd, Zmempoints, "for input points and outside and coplanar sets",-1);
    +  zdef_(zadd, Zmemridges, "for ridges and their vertex sets", -1);
    +} /* allstat */
    +
    +void qh_allstatI(void) {
    +  qhstat vridges= qhstat next;
    +  zzdef_(zdoc, Zdoc11, "Voronoi ridge statistics", -1);
    +  zzdef_(zinc, Zridge, "non-simplicial Voronoi vertices for all ridges", -1);
    +  zzdef_(wadd, Wridge, "  ave. distance to ridge", Zridge);
    +  zzdef_(wmax, Wridgemax, "  max. distance to ridge", -1);
    +  zzdef_(zinc, Zridgemid, "bounded ridges", -1);
    +  zzdef_(wadd, Wridgemid, "  ave. distance of midpoint to ridge", Zridgemid);
    +  zzdef_(wmax, Wridgemidmax, "  max. distance of midpoint to ridge", -1);
    +  zzdef_(zinc, Zridgeok, "bounded ridges with ok normal", -1);
    +  zzdef_(wadd, Wridgeok, "  ave. angle to ridge", Zridgeok);
    +  zzdef_(wmax, Wridgeokmax, "  max. angle to ridge", -1);
    +  zzdef_(zinc, Zridge0, "bounded ridges with near-zero normal", -1);
    +  zzdef_(wadd, Wridge0, "  ave. angle to ridge", Zridge0);
    +  zzdef_(wmax, Wridge0max, "  max. angle to ridge", -1);
    +
    +  zdef_(zdoc, Zdoc12, "Triangulation statistics(Qt)", -1);
    +  zdef_(zinc, Ztricoplanar, "non-simplicial facets triangulated", -1);
    +  zdef_(zadd, Ztricoplanartot, "  ave. new facets created(may be deleted)", Ztricoplanar);
    +  zdef_(zmax, Ztricoplanarmax, "  max. new facets created", -1);
    +  zdef_(zinc, Ztrinull, "null new facets deleted(duplicated vertex)", -1);
    +  zdef_(zinc, Ztrimirror, "mirrored pairs of new facets deleted(same vertices)", -1);
    +  zdef_(zinc, Ztridegen, "degenerate new facets in output(same ridge)", -1);
    +} /* allstat */
    +
    +/*---------------------------------
    +
    +  qh_allstatistics()
    +    reset printed flag for all statistics
    +*/
    +void qh_allstatistics(void) {
    +  int i;
    +
    +  for(i=ZEND; i--; )
    +    qhstat printed[i]= False;
    +} /* allstatistics */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_collectstatistics()
    +    collect statistics for qh.facet_list
    +
    +*/
    +void qh_collectstatistics(void) {
    +  facetT *facet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  realT dotproduct, dist;
    +  int sizneighbors, sizridges, sizvertices, i;
    +
    +  qh old_randomdist= qh RANDOMdist;
    +  qh RANDOMdist= False;
    +  zval_(Zmempoints)= qh num_points * qh normal_size +
    +                             sizeof(qhT) + sizeof(qhstatT);
    +  zval_(Zmemfacets)= 0;
    +  zval_(Zmemridges)= 0;
    +  zval_(Zmemvertices)= 0;
    +  zval_(Zangle)= 0;
    +  wval_(Wangle)= 0.0;
    +  zval_(Znumridges)= 0;
    +  zval_(Znumfacets)= 0;
    +  zval_(Znumneighbors)= 0;
    +  zval_(Znumvertices)= 0;
    +  zval_(Znumvneighbors)= 0;
    +  zval_(Znummergetot)= 0;
    +  zval_(Znummergemax)= 0;
    +  zval_(Zvertices)= qh num_vertices - qh_setsize(qh del_vertices);
    +  if (qh MERGING || qh APPROXhull || qh JOGGLEmax < REALmax/2)
    +    wmax_(Wmaxoutside, qh max_outside);
    +  if (qh MERGING)
    +    wmin_(Wminvertex, qh min_vertex);
    +  FORALLfacets
    +    facet->seen= False;
    +  if (qh DELAUNAY) {
    +    FORALLfacets {
    +      if (facet->upperdelaunay != qh UPPERdelaunay)
    +        facet->seen= True; /* remove from angle statistics */
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->visible && qh NEWfacets)
    +      continue;
    +    sizvertices= qh_setsize(facet->vertices);
    +    sizneighbors= qh_setsize(facet->neighbors);
    +    sizridges= qh_setsize(facet->ridges);
    +    zinc_(Znumfacets);
    +    zadd_(Znumvertices, sizvertices);
    +    zmax_(Zmaxvertices, sizvertices);
    +    zadd_(Znumneighbors, sizneighbors);
    +    zmax_(Zmaxneighbors, sizneighbors);
    +    zadd_(Znummergetot, facet->nummerge);
    +    i= facet->nummerge; /* avoid warnings */
    +    zmax_(Znummergemax, i);
    +    if (!facet->simplicial) {
    +      if (sizvertices == qh hull_dim) {
    +        zinc_(Znowsimplicial);
    +      }else {
    +        zinc_(Znonsimplicial);
    +      }
    +    }
    +    if (sizridges) {
    +      zadd_(Znumridges, sizridges);
    +      zmax_(Zmaxridges, sizridges);
    +    }
    +    zadd_(Zmemfacets, sizeof(facetT) + qh normal_size + 2*sizeof(setT)
    +       + SETelemsize * (sizneighbors + sizvertices));
    +    if (facet->ridges) {
    +      zadd_(Zmemridges,
    +         sizeof(setT) + SETelemsize * sizridges + sizridges *
    +         (sizeof(ridgeT) + sizeof(setT) + SETelemsize * (qh hull_dim-1))/2);
    +    }
    +    if (facet->outsideset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(facet->outsideset));
    +    if (facet->coplanarset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(facet->coplanarset));
    +    if (facet->seen) /* Delaunay upper envelope */
    +      continue;
    +    facet->seen= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor == qh_DUPLICATEridge || neighbor == qh_MERGEridge
    +          || neighbor->seen || !facet->normal || !neighbor->normal)
    +        continue;
    +      dotproduct= qh_getangle(facet->normal, neighbor->normal);
    +      zinc_(Zangle);
    +      wadd_(Wangle, dotproduct);
    +      wmax_(Wanglemax, dotproduct)
    +      wmin_(Wanglemin, dotproduct)
    +    }
    +    if (facet->normal) {
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdiststat);
    +        qh_distplane(vertex->point, facet, &dist);
    +        wmax_(Wvertexmax, dist);
    +        wmin_(Wvertexmin, dist);
    +      }
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->deleted)
    +      continue;
    +    zadd_(Zmemvertices, sizeof(vertexT));
    +    if (vertex->neighbors) {
    +      sizneighbors= qh_setsize(vertex->neighbors);
    +      zadd_(Znumvneighbors, sizneighbors);
    +      zmax_(Zmaxvneighbors, sizneighbors);
    +      zadd_(Zmemvertices, sizeof(vertexT) + SETelemsize * sizneighbors);
    +    }
    +  }
    +  qh RANDOMdist= qh old_randomdist;
    +} /* collectstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_freestatistics(  )
    +    free memory used for statistics
    +*/
    +void qh_freestatistics(void) {
    +
    +#if qh_QHpointer
    +  qh_free(qh_qhstat);
    +  qh_qhstat= NULL;
    +#endif
    +} /* freestatistics */
    +
    +/*---------------------------------
    +
    +  qh_initstatistics(  )
    +    allocate and initialize statistics
    +
    +  notes:
    +    uses qh_malloc() instead of qh_memalloc() since mem.c not set up yet
    +    NOerrors -- qh_initstatistics can not use qh_errexit(), qh_fprintf, or qh.ferr
    +    On first call, only qhmem.ferr is defined.  qh_memalloc is not setup.
    +    Also invoked by QhullQh().
    +*/
    +void qh_initstatistics(void) {
    +  int i;
    +  realT realx;
    +  int intx;
    +
    +#if qh_QHpointer
    +  if(qh_qhstat){  /* qh_initstatistics may be called from Qhull::resetStatistics() */
    +      qh_free(qh_qhstat);
    +      qh_qhstat= 0;
    +  }
    +  if (!(qh_qhstat= (qhstatT *)qh_malloc(sizeof(qhstatT)))) {
    +    qh_fprintf_stderr(6183, "qhull error (qh_initstatistics): insufficient memory\n");
    +    qh_exit(qh_ERRmem);  /* can not use qh_errexit() */
    +  }
    +#endif
    +
    +  qhstat next= 0;
    +  qh_allstatA();
    +  qh_allstatB();
    +  qh_allstatC();
    +  qh_allstatD();
    +  qh_allstatE();
    +  qh_allstatE2();
    +  qh_allstatF();
    +  qh_allstatG();
    +  qh_allstatH();
    +  qh_allstatI();
    +  if (qhstat next > (int)sizeof(qhstat id)) {
    +    qh_fprintf(qhmem.ferr, 6184, "qhull error (qh_initstatistics): increase size of qhstat.id[].\n\
    +      qhstat.next %d should be <= sizeof(qhstat id) %d\n", qhstat next, (int)sizeof(qhstat id));
    +#if 0 /* for locating error, Znumridges should be duplicated */
    +    for(i=0; i < ZEND; i++) {
    +      int j;
    +      for(j=i+1; j < ZEND; j++) {
    +        if (qhstat id[i] == qhstat id[j]) {
    +          qh_fprintf(qhmem.ferr, 6185, "qhull error (qh_initstatistics): duplicated statistic %d at indices %d and %d\n",
    +              qhstat id[i], i, j);
    +        }
    +      }
    +    }
    +#endif
    +    qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qhstat init[zinc].i= 0;
    +  qhstat init[zadd].i= 0;
    +  qhstat init[zmin].i= INT_MAX;
    +  qhstat init[zmax].i= INT_MIN;
    +  qhstat init[wadd].r= 0;
    +  qhstat init[wmin].r= REALmax;
    +  qhstat init[wmax].r= -REALmax;
    +  for(i=0; i < ZEND; i++) {
    +    if (qhstat type[i] > ZTYPEreal) {
    +      realx= qhstat init[(unsigned char)(qhstat type[i])].r;
    +      qhstat stats[i].r= realx;
    +    }else if (qhstat type[i] != zdoc) {
    +      intx= qhstat init[(unsigned char)(qhstat type[i])].i;
    +      qhstat stats[i].i= intx;
    +    }
    +  }
    +} /* initstatistics */
    +
    +/*---------------------------------
    +
    +  qh_newstats(  )
    +    returns True if statistics for zdoc
    +
    +  returns:
    +    next zdoc
    +*/
    +boolT qh_newstats(int idx, int *nextindex) {
    +  boolT isnew= False;
    +  int start, i;
    +
    +  if (qhstat type[qhstat id[idx]] == zdoc)
    +    start= idx+1;
    +  else
    +    start= idx;
    +  for(i= start; i < qhstat next && qhstat type[qhstat id[i]] != zdoc; i++) {
    +    if (!qh_nostatistic(qhstat id[i]) && !qhstat printed[qhstat id[i]])
    +        isnew= True;
    +  }
    +  *nextindex= i;
    +  return isnew;
    +} /* newstats */
    +
    +/*---------------------------------
    +
    +  qh_nostatistic( index )
    +    true if no statistic to print
    +*/
    +boolT qh_nostatistic(int i) {
    +
    +  if ((qhstat type[i] > ZTYPEreal
    +       &&qhstat stats[i].r == qhstat init[(unsigned char)(qhstat type[i])].r)
    +      || (qhstat type[i] < ZTYPEreal
    +          &&qhstat stats[i].i == qhstat init[(unsigned char)(qhstat type[i])].i))
    +    return True;
    +  return False;
    +} /* nostatistic */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_printallstatistics( fp, string )
    +    print all statistics with header 'string'
    +*/
    +void qh_printallstatistics(FILE *fp, const char *string) {
    +
    +  qh_allstatistics();
    +  qh_collectstatistics();
    +  qh_printstatistics(fp, string);
    +  qh_memstatistics(fp);
    +}
    +
    +
    +/*---------------------------------
    +
    +  qh_printstatistics( fp, string )
    +    print statistics to a file with header 'string'
    +    skips statistics with qhstat.printed[] (reset with qh_allstatistics)
    +
    +  see:
    +    qh_printallstatistics()
    +*/
    +void qh_printstatistics(FILE *fp, const char *string) {
    +  int i, k;
    +  realT ave;
    +
    +  if (qh num_points != qh num_vertices) {
    +    wval_(Wpbalance)= 0;
    +    wval_(Wpbalance2)= 0;
    +  }else
    +    wval_(Wpbalance2)= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  wval_(Wnewbalance2)= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(fp, 9350, "\n\
    +%s\n\
    + qhull invoked by: %s | %s\n%s with options:\n%s\n", string, qh rbox_command,
    +     qh qhull_command, qh_version, qh qhull_options);
    +  qh_fprintf(fp, 9351, "\nprecision constants:\n\
    + %6.2g max. abs. coordinate in the (transformed) input('Qbd:n')\n\
    + %6.2g max. roundoff error for distance computation('En')\n\
    + %6.2g max. roundoff error for angle computations\n\
    + %6.2g min. distance for outside points ('Wn')\n\
    + %6.2g min. distance for visible facets ('Vn')\n\
    + %6.2g max. distance for coplanar facets ('Un')\n\
    + %6.2g max. facet width for recomputing centrum and area\n\
    +",
    +  qh MAXabs_coord, qh DISTround, qh ANGLEround, qh MINoutside,
    +        qh MINvisible, qh MAXcoplanar, qh WIDEfacet);
    +  if (qh KEEPnearinside)
    +    qh_fprintf(fp, 9352, "\
    + %6.2g max. distance for near-inside points\n", qh NEARinside);
    +  if (qh premerge_cos < REALmax/2) qh_fprintf(fp, 9353, "\
    + %6.2g max. cosine for pre-merge angle\n", qh premerge_cos);
    +  if (qh PREmerge) qh_fprintf(fp, 9354, "\
    + %6.2g radius of pre-merge centrum\n", qh premerge_centrum);
    +  if (qh postmerge_cos < REALmax/2) qh_fprintf(fp, 9355, "\
    + %6.2g max. cosine for post-merge angle\n", qh postmerge_cos);
    +  if (qh POSTmerge) qh_fprintf(fp, 9356, "\
    + %6.2g radius of post-merge centrum\n", qh postmerge_centrum);
    +  qh_fprintf(fp, 9357, "\
    + %6.2g max. distance for merging two simplicial facets\n\
    + %6.2g max. roundoff error for arithmetic operations\n\
    + %6.2g min. denominator for divisions\n\
    +  zero diagonal for Gauss: ", qh ONEmerge, REALepsilon, qh MINdenom);
    +  for(k=0; k < qh hull_dim; k++)
    +    qh_fprintf(fp, 9358, "%6.2e ", qh NEARzero[k]);
    +  qh_fprintf(fp, 9359, "\n\n");
    +  for(i=0 ; i < qhstat next; )
    +    qh_printstats(fp, i, &i);
    +} /* printstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_printstatlevel( fp, id )
    +    print level information for a statistic
    +
    +  notes:
    +    nop if id >= ZEND, printed, or same as initial value
    +*/
    +void qh_printstatlevel(FILE *fp, int id) {
    +#define NULLfield "       "
    +
    +  if (id >= ZEND || qhstat printed[id])
    +    return;
    +  if (qhstat type[id] == zdoc) {
    +    qh_fprintf(fp, 9360, "%s\n", qhstat doc[id]);
    +    return;
    +  }
    +  if (qh_nostatistic(id) || !qhstat doc[id])
    +    return;
    +  qhstat printed[id]= True;
    +  if (qhstat count[id] != -1
    +      && qhstat stats[(unsigned char)(qhstat count[id])].i == 0)
    +    qh_fprintf(fp, 9361, " *0 cnt*");
    +  else if (qhstat type[id] >= ZTYPEreal && qhstat count[id] == -1)
    +    qh_fprintf(fp, 9362, "%7.2g", qhstat stats[id].r);
    +  else if (qhstat type[id] >= ZTYPEreal && qhstat count[id] != -1)
    +    qh_fprintf(fp, 9363, "%7.2g", qhstat stats[id].r/ qhstat stats[(unsigned char)(qhstat count[id])].i);
    +  else if (qhstat type[id] < ZTYPEreal && qhstat count[id] == -1)
    +    qh_fprintf(fp, 9364, "%7d", qhstat stats[id].i);
    +  else if (qhstat type[id] < ZTYPEreal && qhstat count[id] != -1)
    +    qh_fprintf(fp, 9365, "%7.3g", (realT) qhstat stats[id].i / qhstat stats[(unsigned char)(qhstat count[id])].i);
    +  qh_fprintf(fp, 9366, " %s\n", qhstat doc[id]);
    +} /* printstatlevel */
    +
    +
    +/*---------------------------------
    +
    +  qh_printstats( fp, index, nextindex )
    +    print statistics for a zdoc group
    +
    +  returns:
    +    next zdoc if non-null
    +*/
    +void qh_printstats(FILE *fp, int idx, int *nextindex) {
    +  int j, nexti;
    +
    +  if (qh_newstats(idx, &nexti)) {
    +    qh_fprintf(fp, 9367, "\n");
    +    for (j=idx; j--------------------------------
    +
    +  qh_stddev( num, tot, tot2, ave )
    +    compute the standard deviation and average from statistics
    +
    +    tot2 is the sum of the squares
    +  notes:
    +    computes r.m.s.:
    +      (x-ave)^2
    +      == x^2 - 2x tot/num +   (tot/num)^2
    +      == tot2 - 2 tot tot/num + tot tot/num
    +      == tot2 - tot ave
    +*/
    +realT qh_stddev(int num, realT tot, realT tot2, realT *ave) {
    +  realT stddev;
    +
    +  *ave= tot/num;
    +  stddev= sqrt(tot2/num - *ave * *ave);
    +  return stddev;
    +} /* stddev */
    +
    +#endif /* qh_KEEPstatistics */
    +
    +#if !qh_KEEPstatistics
    +void    qh_collectstatistics(void) {}
    +void    qh_printallstatistics(FILE *fp, char *string) {};
    +void    qh_printstatistics(FILE *fp, char *string) {}
    +#endif
    +
    diff --git a/xs/src/qhull/src/libqhull/stat.h b/xs/src/qhull/src/libqhull/stat.h
    new file mode 100644
    index 0000000000..d86fc0a87a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/stat.h
    @@ -0,0 +1,543 @@
    +/*
      ---------------------------------
    +
    +   stat.h
    +     contains all statistics that are collected for qhull
    +
    +   see qh-stat.htm and stat.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull/stat.h#4 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +
    +   recompile qhull if you change this file
    +
    +   Integer statistics are Z* while real statistics are W*.
    +
    +   define maydebugx to call a routine at every statistic event
    +
    +*/
    +
    +#ifndef qhDEFstat
    +#define qhDEFstat 1
    +
    +#include "libqhull.h"
    +
    +/*---------------------------------
    +
    +  qh_KEEPstatistics
    +    0 turns off statistic gathering (except zzdef/zzinc/zzadd/zzval/wwval)
    +*/
    +#ifndef qh_KEEPstatistics
    +#define qh_KEEPstatistics 1
    +#endif
    +
    +/*---------------------------------
    +
    +  Zxxx for integers, Wxxx for reals
    +
    +  notes:
    +    be sure that all statistics are defined in stat.c
    +      otherwise initialization may core dump
    +    can pick up all statistics by:
    +      grep '[zw].*_[(][ZW]' *.c >z.x
    +    remove trailers with query">-
    +    remove leaders with  query-replace-regexp [ ^I]+  (
    +*/
    +#if qh_KEEPstatistics
    +enum qh_statistics {     /* alphabetical after Z/W */
    +    Zacoplanar,
    +    Wacoplanarmax,
    +    Wacoplanartot,
    +    Zangle,
    +    Wangle,
    +    Wanglemax,
    +    Wanglemin,
    +    Zangletests,
    +    Wareatot,
    +    Wareamax,
    +    Wareamin,
    +    Zavoidold,
    +    Wavoidoldmax,
    +    Wavoidoldtot,
    +    Zback0,
    +    Zbestcentrum,
    +    Zbestdist,
    +    Zbestlower,
    +    Zbestlowerall,
    +    Zbestloweralln,
    +    Zbestlowerv,
    +    Zcentrumtests,
    +    Zcheckpart,
    +    Zcomputefurthest,
    +    Zconcave,
    +    Wconcavemax,
    +    Wconcavetot,
    +    Zconcaveridges,
    +    Zconcaveridge,
    +    Zcoplanar,
    +    Wcoplanarmax,
    +    Wcoplanartot,
    +    Zcoplanarangle,
    +    Zcoplanarcentrum,
    +    Zcoplanarhorizon,
    +    Zcoplanarinside,
    +    Zcoplanarpart,
    +    Zcoplanarridges,
    +    Wcpu,
    +    Zcyclefacetmax,
    +    Zcyclefacettot,
    +    Zcyclehorizon,
    +    Zcyclevertex,
    +    Zdegen,
    +    Wdegenmax,
    +    Wdegentot,
    +    Zdegenvertex,
    +    Zdelfacetdup,
    +    Zdelridge,
    +    Zdelvertextot,
    +    Zdelvertexmax,
    +    Zdetsimplex,
    +    Zdistcheck,
    +    Zdistconvex,
    +    Zdistgood,
    +    Zdistio,
    +    Zdistplane,
    +    Zdiststat,
    +    Zdistvertex,
    +    Zdistzero,
    +    Zdoc1,
    +    Zdoc2,
    +    Zdoc3,
    +    Zdoc4,
    +    Zdoc5,
    +    Zdoc6,
    +    Zdoc7,
    +    Zdoc8,
    +    Zdoc9,
    +    Zdoc10,
    +    Zdoc11,
    +    Zdoc12,
    +    Zdropdegen,
    +    Zdropneighbor,
    +    Zdupflip,
    +    Zduplicate,
    +    Wduplicatemax,
    +    Wduplicatetot,
    +    Zdupridge,
    +    Zdupsame,
    +    Zflipped,
    +    Wflippedmax,
    +    Wflippedtot,
    +    Zflippedfacets,
    +    Zfindbest,
    +    Zfindbestmax,
    +    Zfindbesttot,
    +    Zfindcoplanar,
    +    Zfindfail,
    +    Zfindhorizon,
    +    Zfindhorizonmax,
    +    Zfindhorizontot,
    +    Zfindjump,
    +    Zfindnew,
    +    Zfindnewmax,
    +    Zfindnewtot,
    +    Zfindnewjump,
    +    Zfindnewsharp,
    +    Zgauss0,
    +    Zgoodfacet,
    +    Zhashlookup,
    +    Zhashridge,
    +    Zhashridgetest,
    +    Zhashtests,
    +    Zinsidevisible,
    +    Zintersect,
    +    Zintersectfail,
    +    Zintersectmax,
    +    Zintersectnum,
    +    Zintersecttot,
    +    Zmaxneighbors,
    +    Wmaxout,
    +    Wmaxoutside,
    +    Zmaxridges,
    +    Zmaxvertex,
    +    Zmaxvertices,
    +    Zmaxvneighbors,
    +    Zmemfacets,
    +    Zmempoints,
    +    Zmemridges,
    +    Zmemvertices,
    +    Zmergeflipdup,
    +    Zmergehorizon,
    +    Zmergeinittot,
    +    Zmergeinitmax,
    +    Zmergeinittot2,
    +    Zmergeintohorizon,
    +    Zmergenew,
    +    Zmergesettot,
    +    Zmergesetmax,
    +    Zmergesettot2,
    +    Zmergesimplex,
    +    Zmergevertex,
    +    Wmindenom,
    +    Wminvertex,
    +    Zminnorm,
    +    Zmultiridge,
    +    Znearlysingular,
    +    Zneighbor,
    +    Wnewbalance,
    +    Wnewbalance2,
    +    Znewfacettot,
    +    Znewfacetmax,
    +    Znewvertex,
    +    Wnewvertex,
    +    Wnewvertexmax,
    +    Znoarea,
    +    Znonsimplicial,
    +    Znowsimplicial,
    +    Znotgood,
    +    Znotgoodnew,
    +    Znotmax,
    +    Znumfacets,
    +    Znummergemax,
    +    Znummergetot,
    +    Znumneighbors,
    +    Znumridges,
    +    Znumvertices,
    +    Znumvisibility,
    +    Znumvneighbors,
    +    Zonehorizon,
    +    Zpartangle,
    +    Zpartcoplanar,
    +    Zpartflip,
    +    Zparthorizon,
    +    Zpartinside,
    +    Zpartition,
    +    Zpartitionall,
    +    Zpartnear,
    +    Zpbalance,
    +    Wpbalance,
    +    Wpbalance2,
    +    Zpostfacets,
    +    Zpremergetot,
    +    Zprocessed,
    +    Zremvertex,
    +    Zremvertexdel,
    +    Zrenameall,
    +    Zrenamepinch,
    +    Zrenameshare,
    +    Zretry,
    +    Wretrymax,
    +    Zridge,
    +    Wridge,
    +    Wridgemax,
    +    Zridge0,
    +    Wridge0,
    +    Wridge0max,
    +    Zridgemid,
    +    Wridgemid,
    +    Wridgemidmax,
    +    Zridgeok,
    +    Wridgeok,
    +    Wridgeokmax,
    +    Zsearchpoints,
    +    Zsetplane,
    +    Ztestvneighbor,
    +    Ztotcheck,
    +    Ztothorizon,
    +    Ztotmerge,
    +    Ztotpartcoplanar,
    +    Ztotpartition,
    +    Ztotridges,
    +    Ztotvertices,
    +    Ztotvisible,
    +    Ztricoplanar,
    +    Ztricoplanarmax,
    +    Ztricoplanartot,
    +    Ztridegen,
    +    Ztrimirror,
    +    Ztrinull,
    +    Wvertexmax,
    +    Wvertexmin,
    +    Zvertexridge,
    +    Zvertexridgetot,
    +    Zvertexridgemax,
    +    Zvertices,
    +    Zvisfacettot,
    +    Zvisfacetmax,
    +    Zvisit,
    +    Zvisit2max,
    +    Zvisvertextot,
    +    Zvisvertexmax,
    +    Zvvisit,
    +    Zvvisit2max,
    +    Zwidefacet,
    +    Zwidevertices,
    +    ZEND};
    +
    +/*---------------------------------
    +
    +  Zxxx/Wxxx statistics that remain defined if qh_KEEPstatistics=0
    +
    +  notes:
    +    be sure to use zzdef, zzinc, etc. with these statistics (no double checking!)
    +*/
    +#else
    +enum qh_statistics {     /* for zzdef etc. macros */
    +  Zback0,
    +  Zbestdist,
    +  Zcentrumtests,
    +  Zcheckpart,
    +  Zconcaveridges,
    +  Zcoplanarhorizon,
    +  Zcoplanarpart,
    +  Zcoplanarridges,
    +  Zcyclefacettot,
    +  Zcyclehorizon,
    +  Zdelvertextot,
    +  Zdistcheck,
    +  Zdistconvex,
    +  Zdistzero,
    +  Zdoc1,
    +  Zdoc2,
    +  Zdoc3,
    +  Zdoc11,
    +  Zflippedfacets,
    +  Zgauss0,
    +  Zminnorm,
    +  Zmultiridge,
    +  Znearlysingular,
    +  Wnewvertexmax,
    +  Znumvisibility,
    +  Zpartcoplanar,
    +  Zpartition,
    +  Zpartitionall,
    +  Zprocessed,
    +  Zretry,
    +  Zridge,
    +  Wridge,
    +  Wridgemax,
    +  Zridge0,
    +  Wridge0,
    +  Wridge0max,
    +  Zridgemid,
    +  Wridgemid,
    +  Wridgemidmax,
    +  Zridgeok,
    +  Wridgeok,
    +  Wridgeokmax,
    +  Zsetplane,
    +  Ztotcheck,
    +  Ztotmerge,
    +    ZEND};
    +#endif
    +
    +/*---------------------------------
    +
    +  ztype
    +    the type of a statistic sets its initial value.
    +
    +  notes:
    +    The type should be the same as the macro for collecting the statistic
    +*/
    +enum ztypes {zdoc,zinc,zadd,zmax,zmin,ZTYPEreal,wadd,wmax,wmin,ZTYPEend};
    +
    +/*========== macros and constants =============*/
    +
    +/*----------------------------------
    +
    +  MAYdebugx
    +    define as maydebug() to be called frequently for error trapping
    +*/
    +#define MAYdebugx
    +
    +/*----------------------------------
    +
    +  zzdef_, zdef_( type, name, doc, -1)
    +    define a statistic (assumes 'qhstat.next= 0;')
    +
    +  zdef_( type, name, doc, count)
    +    define an averaged statistic
    +    printed as name/count
    +*/
    +#define zzdef_(stype,name,string,cnt) qhstat id[qhstat next++]=name; \
    +   qhstat doc[name]= string; qhstat count[name]= cnt; qhstat type[name]= stype
    +#if qh_KEEPstatistics
    +#define zdef_(stype,name,string,cnt) qhstat id[qhstat next++]=name; \
    +   qhstat doc[name]= string; qhstat count[name]= cnt; qhstat type[name]= stype
    +#else
    +#define zdef_(type,name,doc,count)
    +#endif
    +
    +/*----------------------------------
    +
    +  zzinc_( name ), zinc_( name)
    +    increment an integer statistic
    +*/
    +#define zzinc_(id) {MAYdebugx; qhstat stats[id].i++;}
    +#if qh_KEEPstatistics
    +#define zinc_(id) {MAYdebugx; qhstat stats[id].i++;}
    +#else
    +#define zinc_(id) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzadd_( name, value ), zadd_( name, value ), wadd_( name, value )
    +    add value to an integer or real statistic
    +*/
    +#define zzadd_(id, val) {MAYdebugx; qhstat stats[id].i += (val);}
    +#define wwadd_(id, val) {MAYdebugx; qhstat stats[id].r += (val);}
    +#if qh_KEEPstatistics
    +#define zadd_(id, val) {MAYdebugx; qhstat stats[id].i += (val);}
    +#define wadd_(id, val) {MAYdebugx; qhstat stats[id].r += (val);}
    +#else
    +#define zadd_(id, val) {}
    +#define wadd_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzval_( name ), zval_( name ), wwval_( name )
    +    set or return value of a statistic
    +*/
    +#define zzval_(id) ((qhstat stats[id]).i)
    +#define wwval_(id) ((qhstat stats[id]).r)
    +#if qh_KEEPstatistics
    +#define zval_(id) ((qhstat stats[id]).i)
    +#define wval_(id) ((qhstat stats[id]).r)
    +#else
    +#define zval_(id) qhstat tempi
    +#define wval_(id) qhstat tempr
    +#endif
    +
    +/*----------------------------------
    +
    +  zmax_( id, val ), wmax_( id, value )
    +    maximize id with val
    +*/
    +#define wwmax_(id, val) {MAYdebugx; maximize_(qhstat stats[id].r,(val));}
    +#if qh_KEEPstatistics
    +#define zmax_(id, val) {MAYdebugx; maximize_(qhstat stats[id].i,(val));}
    +#define wmax_(id, val) {MAYdebugx; maximize_(qhstat stats[id].r,(val));}
    +#else
    +#define zmax_(id, val) {}
    +#define wmax_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zmin_( id, val ), wmin_( id, value )
    +    minimize id with val
    +*/
    +#if qh_KEEPstatistics
    +#define zmin_(id, val) {MAYdebugx; minimize_(qhstat stats[id].i,(val));}
    +#define wmin_(id, val) {MAYdebugx; minimize_(qhstat stats[id].r,(val));}
    +#else
    +#define zmin_(id, val) {}
    +#define wmin_(id, val) {}
    +#endif
    +
    +/*================== stat.h types ==============*/
    +
    +
    +/*----------------------------------
    +
    +  intrealT
    +    union of integer and real, used for statistics
    +*/
    +typedef union intrealT intrealT;    /* union of int and realT */
    +union intrealT {
    +    int i;
    +    realT r;
    +};
    +
    +/*----------------------------------
    +
    +  qhstat
    +    global data structure for statistics, similar to qh and qhrbox
    +
    +  notes:
    +   access to qh_qhstat is via the "qhstat" macro.  There are two choices
    +   qh_QHpointer = 1     access globals via a pointer
    +                        enables qh_saveqhull() and qh_restoreqhull()
    +                = 0     qh_qhstat is a static data structure
    +                        only one instance of qhull() can be active at a time
    +                        default value
    +   qh_QHpointer is defined in libqhull.h
    +   qh_QHpointer_dllimport and qh_dllimport define qh_qh as __declspec(dllimport) [libqhull.h]
    +
    +   allocated in stat.c using qh_malloc()
    +*/
    +#ifndef DEFqhstatT
    +#define DEFqhstatT 1
    +typedef struct qhstatT qhstatT;
    +#endif
    +
    +#if qh_QHpointer_dllimport
    +#define qhstat qh_qhstat->
    +__declspec(dllimport) extern qhstatT *qh_qhstat;
    +#elif qh_QHpointer
    +#define qhstat qh_qhstat->
    +extern qhstatT *qh_qhstat;
    +#elif qh_dllimport
    +#define qhstat qh_qhstat.
    +__declspec(dllimport) extern qhstatT qh_qhstat;
    +#else
    +#define qhstat qh_qhstat.
    +extern qhstatT qh_qhstat;
    +#endif
    +struct qhstatT {
    +  intrealT   stats[ZEND];     /* integer and real statistics */
    +  unsigned   char id[ZEND+10]; /* id's in print order */
    +  const char *doc[ZEND];       /* array of documentation strings */
    +  short int  count[ZEND];     /* -1 if none, else index of count to use */
    +  char       type[ZEND];      /* type, see ztypes above */
    +  char       printed[ZEND];   /* true, if statistic has been printed */
    +  intrealT   init[ZTYPEend];  /* initial values by types, set initstatistics */
    +
    +  int        next;            /* next index for zdef_ */
    +  int        precision;       /* index for precision problems */
    +  int        vridges;         /* index for Voronoi ridges */
    +  int        tempi;
    +  realT      tempr;
    +};
    +
    +/*========== function prototypes ===========*/
    +
    +void    qh_allstatA(void);
    +void    qh_allstatB(void);
    +void    qh_allstatC(void);
    +void    qh_allstatD(void);
    +void    qh_allstatE(void);
    +void    qh_allstatE2(void);
    +void    qh_allstatF(void);
    +void    qh_allstatG(void);
    +void    qh_allstatH(void);
    +void    qh_allstatI(void);
    +void    qh_allstatistics(void);
    +void    qh_collectstatistics(void);
    +void    qh_freestatistics(void);
    +void    qh_initstatistics(void);
    +boolT   qh_newstats(int idx, int *nextindex);
    +boolT   qh_nostatistic(int i);
    +void    qh_printallstatistics(FILE *fp, const char *string);
    +void    qh_printstatistics(FILE *fp, const char *string);
    +void    qh_printstatlevel(FILE *fp, int id);
    +void    qh_printstats(FILE *fp, int idx, int *nextindex);
    +realT   qh_stddev(int num, realT tot, realT tot2, realT *ave);
    +
    +#endif   /* qhDEFstat */
    diff --git a/xs/src/qhull/src/libqhull/user.c b/xs/src/qhull/src/libqhull/user.c
    new file mode 100644
    index 0000000000..d4726eaa31
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/user.c
    @@ -0,0 +1,538 @@
    +/*
      ---------------------------------
    +
    +   user.c
    +   user redefinable functions
    +
    +   see user2.c for qh_fprintf, qh_malloc, qh_free
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +
    +   see user_eg.c, user_eg2.c, and unix.c for examples.
    +
    +   see user.h for user-definable constants
    +
    +      use qh_NOmem in mem.h to turn off memory management
    +      use qh_NOmerge in user.h to turn off facet merging
    +      set qh_KEEPstatistics in user.h to 0 to turn off statistics
    +
    +   This is unsupported software.  You're welcome to make changes,
    +   but you're on your own if something goes wrong.  Use 'Tc' to
    +   check frequently.  Usually qhull will report an error if
    +   a data structure becomes inconsistent.  If so, it also reports
    +   the last point added to the hull, e.g., 102.  You can then trace
    +   the execution of qhull with "T4P102".
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +
    +   Qhull-template is a template for calling qhull from within your application
    +
    +   if you recompile and load this module, then user.o will not be loaded
    +   from qhull.a
    +
    +   you can add additional quick allocation sizes in qh_user_memsizes
    +
    +   if the other functions here are redefined to not use qh_print...,
    +   then io.o will not be loaded from qhull.a.  See user_eg.c for an
    +   example.  We recommend keeping io.o for the extra debugging
    +   information it supplies.
    +*/
    +
    +#include "qhull_a.h"
    +
    +#include 
    +
    +/*---------------------------------
    +
    +  Qhull-template
    +    Template for calling qhull from inside your program
    +
    +  returns:
    +    exit code(see qh_ERR... in libqhull.h)
    +    all memory freed
    +
    +  notes:
    +    This can be called any number of times.
    +*/
    +#if 0
    +{
    +  int dim;                  /* dimension of points */
    +  int numpoints;            /* number of points */
    +  coordT *points;           /* array of coordinates for each point */
    +  boolT ismalloc;           /* True if qhull should free points in qh_freeqhull() or reallocation */
    +  char flags[]= "qhull Tv"; /* option flags for qhull, see qh_opt.htm */
    +  FILE *outfile= stdout;    /* output from qh_produce_output()
    +                               use NULL to skip qh_produce_output() */
    +  FILE *errfile= stderr;    /* error messages from qhull code */
    +  int exitcode;             /* 0 if no error from qhull */
    +  facetT *facet;            /* set by FORALLfacets */
    +  int curlong, totlong;     /* memory remaining after qh_memfreeshort */
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +#if qh_QHpointer  /* see user.h */
    +  if (qh_qh){ /* should be NULL */
    +      qh_printf_stderr(6238, "Qhull link error.  The global variable qh_qh was not initialized\n\
    +              to NULL by global.c.  Please compile this program with -Dqh_QHpointer_dllimport\n\
    +              as well as -Dqh_QHpointer, or use libqhullstatic, or use a different tool chain.\n\n");
    +      exit(1);
    +  }
    +#endif
    +
    +  /* initialize dim, numpoints, points[], ismalloc here */
    +  exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh facet_list' contains the convex hull */
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf(errfile, 7068, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_new_qhull( dim, numpoints, points, ismalloc, qhull_cmd, outfile, errfile )
    +    build new qhull data structure and return exitcode (0 if no errors)
    +    if numpoints=0 and points=NULL, initializes qhull
    +
    +  notes:
    +    do not modify points until finished with results.
    +      The qhull data structure contains pointers into the points array.
    +    do not call qhull functions before qh_new_qhull().
    +      The qhull data structure is not initialized until qh_new_qhull().
    +
    +    Default errfile is stderr, outfile may be null
    +    qhull_cmd must start with "qhull "
    +    projects points to a new point array for Delaunay triangulations ('d' and 'v')
    +    transforms points into a new point array for halfspace intersection ('H')
    +
    +
    +  To allow multiple, concurrent calls to qhull()
    +    - set qh_QHpointer in user.h
    +    - use qh_save_qhull and qh_restore_qhull to swap the global data structure between calls.
    +    - use qh_freeqhull(qh_ALL) to free intermediate convex hulls
    +
    +  see:
    +      Qhull-template at the beginning of this file.
    +      An example of using qh_new_qhull is user_eg.c
    +*/
    +int qh_new_qhull(int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile) {
    +  /* gcc may issue a "might be clobbered" warning for dim, points, and ismalloc [-Wclobbered].
    +     These parameters are not referenced after a longjmp() and hence not clobbered.
    +     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
    +  int exitcode, hulldim;
    +  boolT new_ismalloc;
    +  static boolT firstcall = True;
    +  coordT *new_points;
    +  if(!errfile){
    +      errfile= stderr;
    +  }
    +  if (firstcall) {
    +    qh_meminit(errfile);
    +    firstcall= False;
    +  } else {
    +    qh_memcheck();
    +  }
    +  if (strncmp(qhull_cmd, "qhull ", (size_t)6)) {
    +    qh_fprintf(errfile, 6186, "qhull error (qh_new_qhull): start qhull_cmd argument with \"qhull \"\n");
    +    return qh_ERRinput;
    +  }
    +  qh_initqhull_start(NULL, outfile, errfile);
    +  if(numpoints==0 && points==NULL){
    +      trace1((qh ferr, 1047, "qh_new_qhull: initialize Qhull\n"));
    +      return 0;
    +  }
    +  trace1((qh ferr, 1044, "qh_new_qhull: build new Qhull for %d %d-d points with %s\n", numpoints, dim, qhull_cmd));
    +  exitcode = setjmp(qh errexit);
    +  if (!exitcode)
    +  {
    +    qh NOerrexit = False;
    +    qh_initflags(qhull_cmd);
    +    if (qh DELAUNAY)
    +      qh PROJECTdelaunay= True;
    +    if (qh HALFspace) {
    +      /* points is an array of halfspaces,
    +         the last coordinate of each halfspace is its offset */
    +      hulldim= dim-1;
    +      qh_setfeasible(hulldim);
    +      new_points= qh_sethalfspace_all(dim, numpoints, points, qh feasible_point);
    +      new_ismalloc= True;
    +      if (ismalloc)
    +        qh_free(points);
    +    }else {
    +      hulldim= dim;
    +      new_points= points;
    +      new_ismalloc= ismalloc;
    +    }
    +    qh_init_B(new_points, numpoints, hulldim, new_ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    if (outfile) {
    +      qh_produce_output();
    +    }else {
    +      qh_prepare_output();
    +    }
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +  }
    +  qh NOerrexit = True;
    +  return exitcode;
    +} /* new_qhull */
    +
    +/*---------------------------------
    +
    +  qh_errexit( exitcode, facet, ridge )
    +    report and exit from an error
    +    report facet and ridge if non-NULL
    +    reports useful information such as last point processed
    +    set qh.FORCEoutput to print neighborhood of facet
    +
    +  see:
    +    qh_errexit2() in libqhull.c for printing 2 facets
    +
    +  design:
    +    check for error within error processing
    +    compute qh.hulltime
    +    print facet and ridge (if any)
    +    report commandString, options, qh.furthest_id
    +    print summary and statistics (including precision statistics)
    +    if qh_ERRsingular
    +      print help text for singular data set
    +    exit program via long jump (if defined) or exit()
    +*/
    +void qh_errexit(int exitcode, facetT *facet, ridgeT *ridge) {
    +
    +  if (qh ERREXITcalled) {
    +    qh_fprintf(qh ferr, 8126, "\nqhull error while processing previous error.  Exit program\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh ERREXITcalled= True;
    +  if (!qh QHULLfinished)
    +    qh hulltime= qh_CPUclock - qh hulltime;
    +  qh_errprint("ERRONEOUS", facet, NULL, ridge, NULL);
    +  qh_fprintf(qh ferr, 8127, "\nWhile executing: %s | %s\n", qh rbox_command, qh qhull_command);
    +  qh_fprintf(qh ferr, 8128, "Options selected for Qhull %s:\n%s\n", qh_version, qh qhull_options);
    +  if (qh furthest_id >= 0) {
    +    qh_fprintf(qh ferr, 8129, "Last point added to hull was p%d.", qh furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh ferr, 8130, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh QHULLfinished)
    +      qh_fprintf(qh ferr, 8131, "\nQhull has finished constructing the hull.");
    +    else if (qh POSTmerging)
    +      qh_fprintf(qh ferr, 8132, "\nQhull has started post-merging.");
    +    qh_fprintf(qh ferr, 8133, "\n");
    +  }
    +  if (qh FORCEoutput && (qh QHULLfinished || (!facet && !ridge)))
    +    qh_produce_output();
    +  else if (exitcode != qh_ERRinput) {
    +    if (exitcode != qh_ERRsingular && zzval_(Zsetplane) > qh hull_dim+1) {
    +      qh_fprintf(qh ferr, 8134, "\nAt error exit:\n");
    +      qh_printsummary(qh ferr);
    +      if (qh PRINTstatistics) {
    +        qh_collectstatistics();
    +        qh_printstatistics(qh ferr, "at error exit");
    +        qh_memstatistics(qh ferr);
    +      }
    +    }
    +    if (qh PRINTprecision)
    +      qh_printstats(qh ferr, qhstat precision, NULL);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  else if (exitcode == qh_ERRsingular)
    +    qh_printhelp_singular(qh ferr);
    +  else if (exitcode == qh_ERRprec && !qh PREmerge)
    +    qh_printhelp_degenerate(qh ferr);
    +  if (qh NOerrexit) {
    +    qh_fprintf(qh ferr, 6187, "qhull error while ending program, or qh->NOerrexit not cleared after setjmp(). Exit program with error.\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh ERREXITcalled= False;
    +  qh NOerrexit= True;
    +  qh ALLOWrestart= False;  /* longjmp will undo qh_build_withrestart */
    +  longjmp(qh errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*---------------------------------
    +
    +  qh_errprint( fp, string, atfacet, otherfacet, atridge, atvertex )
    +    prints out the information of facets and ridges to fp
    +    also prints neighbors and geomview output
    +
    +  notes:
    +    except for string, any parameter may be NULL
    +*/
    +void qh_errprint(const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +  int i;
    +
    +  if (atfacet) {
    +    qh_fprintf(qh ferr, 8135, "%s FACET:\n", string);
    +    qh_printfacet(qh ferr, atfacet);
    +  }
    +  if (otherfacet) {
    +    qh_fprintf(qh ferr, 8136, "%s OTHER FACET:\n", string);
    +    qh_printfacet(qh ferr, otherfacet);
    +  }
    +  if (atridge) {
    +    qh_fprintf(qh ferr, 8137, "%s RIDGE:\n", string);
    +    qh_printridge(qh ferr, atridge);
    +    if (atridge->top && atridge->top != atfacet && atridge->top != otherfacet)
    +      qh_printfacet(qh ferr, atridge->top);
    +    if (atridge->bottom
    +        && atridge->bottom != atfacet && atridge->bottom != otherfacet)
    +      qh_printfacet(qh ferr, atridge->bottom);
    +    if (!atfacet)
    +      atfacet= atridge->top;
    +    if (!otherfacet)
    +      otherfacet= otherfacet_(atridge, atfacet);
    +  }
    +  if (atvertex) {
    +    qh_fprintf(qh ferr, 8138, "%s VERTEX:\n", string);
    +    qh_printvertex(qh ferr, atvertex);
    +  }
    +  if (qh fout && qh FORCEoutput && atfacet && !qh QHULLfinished && !qh IStracing) {
    +    qh_fprintf(qh ferr, 8139, "ERRONEOUS and NEIGHBORING FACETS to output\n");
    +    for (i=0; i < qh_PRINTEND; i++)  /* use fout for geomview output */
    +      qh_printneighborhood(qh fout, qh PRINTout[i], atfacet, otherfacet,
    +                            !qh_ALL);
    +  }
    +} /* errprint */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetlist( fp, facetlist, facets, printall )
    +    print all fields for a facet list and/or set of facets to fp
    +    if !printall,
    +      only prints good facets
    +
    +  notes:
    +    also prints all vertices
    +*/
    +void qh_printfacetlist(facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  qh_printbegin(qh ferr, qh_PRINTfacets, facetlist, facets, printall);
    +  FORALLfacet_(facetlist)
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);
    +  FOREACHfacet_(facets)
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);
    +  qh_printend(qh ferr, qh_PRINTfacets, facetlist, facets, printall);
    +} /* printfacetlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_degenerate( fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(FILE *fp) {
    +
    +  if (qh MERGEexact || qh PREmerge || qh JOGGLEmax < REALmax/2)
    +    qh_fprintf(fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh DELAUNAY && !qh SCALElast && qh MAXabs_coord > 1e4)
    +      qh_fprintf(fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh DELAUNAY && !qh ATinfinity)
    +      qh_fprintf(fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh DISTround);
    +    qh_fprintf(fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_narrowhull( minangle )
    +    Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(FILE *fp, realT minangle) {
    +
    +    qh_fprintf(fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +Is the input lower dimensional (e.g., on a plane in 3-d)?  Qhull may\n\
    +produce a wide facet.  Options 'QbB' (scale to unit box) or 'Qbb' (scale\n\
    +last coordinate) may remove this warning.  Use 'Pp' to skip this warning.\n\
    +See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/*---------------------------------
    +
    +  qh_printhelp_singular( fp )
    +    prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh hull_dim);
    +  qh_printvertexlist(fp, "", qh facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh DISTround);
    +  qh_printpointid(fp, "center point", qh hull_dim, qh interior_point, qh_IDunknown);
    +  qh_fprintf(fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9380, " p%d", qh_pointid(vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh interior_point, facet, &dist);
    +    qh_fprintf(fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh HALFspace)
    +      qh_fprintf(fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh hull_dim >= qh_INITIALmax)
    +      qh_fprintf(fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh num_points, coord= qh first_point+k; i--; coord += qh hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh DISTround);
    +#if REALfloat
    +    qh_fprintf(fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull.h).\n");
    +#endif
    +    qh_fprintf(fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +  }
    +} /* printhelp_singular */
    +
    +/*---------------------------------
    +
    +  qh_user_memsizes()
    +    allocate up to 10 additional, quick allocation sizes
    +
    +  notes:
    +    increase maximum number of allocations in qh_initqhull_mem()
    +*/
    +void qh_user_memsizes(void) {
    +
    +  /* qh_memsize(size); */
    +} /* user_memsizes */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/user.h b/xs/src/qhull/src/libqhull/user.h
    new file mode 100644
    index 0000000000..523aa7b4e8
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/user.h
    @@ -0,0 +1,909 @@
    +/*
      ---------------------------------
    +
    +   user.h
    +   user redefinable constants
    +
    +   for each source file, user.h is included first
    +   see qh-user.htm.  see COPYING for copyright information.
    +
    +   See user.c for sample code.
    +
    +   before reading any code, review libqhull.h for data structure definitions and
    +   the "qh" macro.
    +
    +Sections:
    +   ============= qhull library constants ======================
    +   ============= data types and configuration macros ==========
    +   ============= performance related constants ================
    +   ============= memory constants =============================
    +   ============= joggle constants =============================
    +   ============= conditional compilation ======================
    +   ============= -merge constants- ============================
    +
    +Code flags --
    +  NOerrors -- the code does not call qh_errexit()
    +  WARN64 -- the code may be incompatible with 64-bit pointers
    +
    +*/
    +
    +#include 
    +
    +#ifndef qhDEFuser
    +#define qhDEFuser 1
    +
    +/* Derived from Qt's corelib/global/qglobal.h */
    +#if !defined(SAG_COM) && !defined(__CYGWIN__) && (defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__))
    +#   define QHULL_OS_WIN
    +#elif defined(__MWERKS__) && defined(__INTEL__) /* Metrowerks discontinued before the release of Intel Macs */
    +#   define QHULL_OS_WIN
    +#endif
    +/*============================================================*/
    +/*============= qhull library constants ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  FILENAMElen -- max length for TI and TO filenames
    +
    +*/
    +
    +#define qh_FILENAMElen 500
    +
    +/*----------------------------------
    +
    +  msgcode -- Unique message codes for qh_fprintf
    +
    +  If add new messages, assign these values and increment in user.h and user_r.h
    +  See QhullError.h for 10000 errors.
    +
    +  def counters =  [27, 1048, 2059, 3026, 4068, 5003,
    +     6273, 7081, 8147, 9411, 10000, 11029]
    +
    +  See: qh_ERR* [libqhull.h]
    +*/
    +
    +#define MSG_TRACE0 0
    +#define MSG_TRACE1 1000
    +#define MSG_TRACE2 2000
    +#define MSG_TRACE3 3000
    +#define MSG_TRACE4 4000
    +#define MSG_TRACE5 5000
    +#define MSG_ERROR  6000   /* errors written to qh.ferr */
    +#define MSG_WARNING 7000
    +#define MSG_STDERR  8000  /* log messages Written to qh.ferr */
    +#define MSG_OUTPUT  9000
    +#define MSG_QHULL_ERROR 10000 /* errors thrown by QhullError.cpp (QHULLlastError is in QhullError.h) */
    +#define MSG_FIXUP  11000  /* FIXUP QH11... */
    +#define MSG_MAXLEN  3000 /* qh_printhelp_degenerate() in user.c */
    +
    +
    +/*----------------------------------
    +
    +  qh_OPTIONline -- max length of an option line 'FO'
    +*/
    +#define qh_OPTIONline 80
    +
    +/*============================================================*/
    +/*============= data types and configuration macros ==========*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  realT
    +    set the size of floating point numbers
    +
    +  qh_REALdigits
    +    maximimum number of significant digits
    +
    +  qh_REAL_1, qh_REAL_2n, qh_REAL_3n
    +    format strings for printf
    +
    +  qh_REALmax, qh_REALmin
    +    maximum and minimum (near zero) values
    +
    +  qh_REALepsilon
    +    machine roundoff.  Maximum roundoff error for addition and multiplication.
    +
    +  notes:
    +   Select whether to store floating point numbers in single precision (float)
    +   or double precision (double).
    +
    +   Use 'float' to save about 8% in time and 25% in space.  This is particularly
    +   helpful if high-d where convex hulls are space limited.  Using 'float' also
    +   reduces the printed size of Qhull's output since numbers have 8 digits of
    +   precision.
    +
    +   Use 'double' when greater arithmetic precision is needed.  This is needed
    +   for Delaunay triangulations and Voronoi diagrams when you are not merging
    +   facets.
    +
    +   If 'double' gives insufficient precision, your data probably includes
    +   degeneracies.  If so you should use facet merging (done by default)
    +   or exact arithmetic (see imprecision section of manual, qh-impre.htm).
    +   You may also use option 'Po' to force output despite precision errors.
    +
    +   You may use 'long double', but many format statements need to be changed
    +   and you may need a 'long double' square root routine.  S. Grundmann
    +   (sg@eeiwzb.et.tu-dresden.de) has done this.  He reports that the code runs
    +   much slower with little gain in precision.
    +
    +   WARNING: on some machines,    int f(){realT a= REALmax;return (a == REALmax);}
    +      returns False.  Use (a > REALmax/2) instead of (a == REALmax).
    +
    +   REALfloat =   1      all numbers are 'float' type
    +             =   0      all numbers are 'double' type
    +*/
    +#define REALfloat 0
    +
    +#if (REALfloat == 1)
    +#define realT float
    +#define REALmax FLT_MAX
    +#define REALmin FLT_MIN
    +#define REALepsilon FLT_EPSILON
    +#define qh_REALdigits 8   /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.8g "
    +#define qh_REAL_2n "%6.8g %6.8g\n"
    +#define qh_REAL_3n "%6.8g %6.8g %6.8g\n"
    +
    +#elif (REALfloat == 0)
    +#define realT double
    +#define REALmax DBL_MAX
    +#define REALmin DBL_MIN
    +#define REALepsilon DBL_EPSILON
    +#define qh_REALdigits 16    /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.16g "
    +#define qh_REAL_2n "%6.16g %6.16g\n"
    +#define qh_REAL_3n "%6.16g %6.16g %6.16g\n"
    +
    +#else
    +#error unknown float option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_CPUclock
    +    define the clock() function for reporting the total time spent by Qhull
    +    returns CPU ticks as a 'long int'
    +    qh_CPUclock is only used for reporting the total time spent by Qhull
    +
    +  qh_SECticks
    +    the number of clock ticks per second
    +
    +  notes:
    +    looks for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or assumes microseconds
    +    to define a custom clock, set qh_CLOCKtype to 0
    +
    +    if your system does not use clock() to return CPU ticks, replace
    +    qh_CPUclock with the corresponding function.  It is converted
    +    to 'unsigned long' to prevent wrap-around during long runs.  By default,
    +     defines clock_t as 'long'
    +
    +   Set qh_CLOCKtype to
    +
    +     1          for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or microsecond
    +                Note:  may fail if more than 1 hour elapsed time
    +
    +     2          use qh_clock() with POSIX times() (see global.c)
    +*/
    +#define qh_CLOCKtype 1  /* change to the desired number */
    +
    +#if (qh_CLOCKtype == 1)
    +
    +#if defined(CLOCKS_PER_SECOND)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SECOND
    +
    +#elif defined(CLOCKS_PER_SEC)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SEC
    +
    +#elif defined(CLK_TCK)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLK_TCK
    +
    +#else
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks 1E6
    +#endif
    +
    +#elif (qh_CLOCKtype == 2)
    +#define qh_CPUclock    qh_clock()  /* return CPU clock */
    +#define qh_SECticks 100
    +
    +#else /* qh_CLOCKtype == ? */
    +#error unknown clock option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_RANDOMtype, qh_RANDOMmax, qh_RANDOMseed
    +    define random number generator
    +
    +    qh_RANDOMint generates a random integer between 0 and qh_RANDOMmax.
    +    qh_RANDOMseed sets the random number seed for qh_RANDOMint
    +
    +  Set qh_RANDOMtype (default 5) to:
    +    1       for random() with 31 bits (UCB)
    +    2       for rand() with RAND_MAX or 15 bits (system 5)
    +    3       for rand() with 31 bits (Sun)
    +    4       for lrand48() with 31 bits (Solaris)
    +    5       for qh_rand() with 31 bits (included with Qhull)
    +
    +  notes:
    +    Random numbers are used by rbox to generate point sets.  Random
    +    numbers are used by Qhull to rotate the input ('QRn' option),
    +    simulate a randomized algorithm ('Qr' option), and to simulate
    +    roundoff errors ('Rn' option).
    +
    +    Random number generators differ between systems.  Most systems provide
    +    rand() but the period varies.  The period of rand() is not critical
    +    since qhull does not normally use random numbers.
    +
    +    The default generator is Park & Miller's minimal standard random
    +    number generator [CACM 31:1195 '88].  It is included with Qhull.
    +
    +    If qh_RANDOMmax is wrong, qhull will report a warning and Geomview
    +    output will likely be invisible.
    +*/
    +#define qh_RANDOMtype 5   /* *** change to the desired number *** */
    +
    +#if (qh_RANDOMtype == 1)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, random()/MAX */
    +#define qh_RANDOMint random()
    +#define qh_RANDOMseed_(seed) srandom(seed);
    +
    +#elif (qh_RANDOMtype == 2)
    +#ifdef RAND_MAX
    +#define qh_RANDOMmax ((realT)RAND_MAX)
    +#else
    +#define qh_RANDOMmax ((realT)32767)   /* 15 bits (System 5) */
    +#endif
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 3)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, Sun */
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 4)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, lrand38()/MAX */
    +#define qh_RANDOMint lrand48()
    +#define qh_RANDOMseed_(seed) srand48(seed);
    +
    +#elif (qh_RANDOMtype == 5)
    +#define qh_RANDOMmax ((realT)2147483646UL)  /* 31 bits, qh_rand/MAX */
    +#define qh_RANDOMint qh_rand()
    +#define qh_RANDOMseed_(seed) qh_srand(seed);
    +/* unlike rand(), never returns 0 */
    +
    +#else
    +#error: unknown random option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_ORIENTclock
    +    0 for inward pointing normals by Geomview convention
    +*/
    +#define qh_ORIENTclock 0
    +
    +
    +/*============================================================*/
    +/*============= joggle constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +qh_JOGGLEdefault
    +default qh.JOGGLEmax is qh.DISTround * qh_JOGGLEdefault
    +
    +notes:
    +rbox s r 100 | qhull QJ1e-15 QR0 generates 90% faults at distround 7e-16
    +rbox s r 100 | qhull QJ1e-14 QR0 generates 70% faults
    +rbox s r 100 | qhull QJ1e-13 QR0 generates 35% faults
    +rbox s r 100 | qhull QJ1e-12 QR0 generates 8% faults
    +rbox s r 100 | qhull QJ1e-11 QR0 generates 1% faults
    +rbox s r 100 | qhull QJ1e-10 QR0 generates 0% faults
    +rbox 1000 W0 | qhull QJ1e-12 QR0 generates 86% faults
    +rbox 1000 W0 | qhull QJ1e-11 QR0 generates 20% faults
    +rbox 1000 W0 | qhull QJ1e-10 QR0 generates 2% faults
    +the later have about 20 points per facet, each of which may interfere
    +
    +pick a value large enough to avoid retries on most inputs
    +*/
    +#define qh_JOGGLEdefault 30000.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEincrease
    +factor to increase qh.JOGGLEmax on qh_JOGGLEretry or qh_JOGGLEagain
    +*/
    +#define qh_JOGGLEincrease 10.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEretry
    +if ZZretry = qh_JOGGLEretry, increase qh.JOGGLEmax
    +
    +notes:
    +try twice at the original value in case of bad luck the first time
    +*/
    +#define qh_JOGGLEretry 2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEagain
    +every following qh_JOGGLEagain, increase qh.JOGGLEmax
    +
    +notes:
    +1 is OK since it's already failed qh_JOGGLEretry times
    +*/
    +#define qh_JOGGLEagain 1
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxincrease
    +maximum qh.JOGGLEmax due to qh_JOGGLEincrease
    +relative to qh.MAXwidth
    +
    +notes:
    +qh.joggleinput will retry at this value until qh_JOGGLEmaxretry
    +*/
    +#define qh_JOGGLEmaxincrease 1e-2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxretry
    +stop after qh_JOGGLEmaxretry attempts
    +*/
    +#define qh_JOGGLEmaxretry 100
    +
    +/*============================================================*/
    +/*============= performance related constants ================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_HASHfactor
    +    total hash slots / used hash slots.  Must be at least 1.1.
    +
    +  notes:
    +    =2 for at worst 50% occupancy for qh.hash_table and normally 25% occupancy
    +*/
    +#define qh_HASHfactor 2
    +
    +/*----------------------------------
    +
    +  qh_VERIFYdirect
    +    with 'Tv' verify all points against all facets if op count is smaller
    +
    +  notes:
    +    if greater, calls qh_check_bestdist() instead
    +*/
    +#define qh_VERIFYdirect 1000000
    +
    +/*----------------------------------
    +
    +  qh_INITIALsearch
    +     if qh_INITIALmax, search points up to this dimension
    +*/
    +#define qh_INITIALsearch 6
    +
    +/*----------------------------------
    +
    +  qh_INITIALmax
    +    if dim >= qh_INITIALmax, use min/max coordinate points for initial simplex
    +
    +  notes:
    +    from points with non-zero determinants
    +    use option 'Qs' to override (much slower)
    +*/
    +#define qh_INITIALmax 8
    +
    +/*============================================================*/
    +/*============= memory constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_MEMalign
    +    memory alignment for qh_meminitbuffers() in global.c
    +
    +  notes:
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.
    +
    +    If using gcc, best alignment is  [fmax_() is defined in geom_r.h]
    +              #define qh_MEMalign fmax_(__alignof__(realT),__alignof__(void *))
    +*/
    +#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
    +
    +/*----------------------------------
    +
    +  qh_MEMbufsize
    +    size of additional memory buffers
    +
    +  notes:
    +    used for qh_meminitbuffers() in global.c
    +*/
    +#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
    +
    +/*----------------------------------
    +
    +  qh_MEMinitbuf
    +    size of initial memory buffer
    +
    +  notes:
    +    use for qh_meminitbuffers() in global.c
    +*/
    +#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
    +
    +/*----------------------------------
    +
    +  qh_INFINITE
    +    on output, indicates Voronoi center at infinity
    +*/
    +#define qh_INFINITE  -10.101
    +
    +/*----------------------------------
    +
    +  qh_DEFAULTbox
    +    default box size (Geomview expects 0.5)
    +
    +  qh_DEFAULTbox
    +    default box size for integer coorindate (rbox only)
    +*/
    +#define qh_DEFAULTbox 0.5
    +#define qh_DEFAULTzbox 1e6
    +
    +/*============================================================*/
    +/*============= conditional compilation ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  __cplusplus
    +    defined by C++ compilers
    +
    +  __MSC_VER
    +    defined by Microsoft Visual C++
    +
    +  __MWERKS__ && __INTEL__
    +    defined by Metrowerks when compiling for Windows (not Intel-based Macintosh)
    +
    +  __MWERKS__ && __POWERPC__
    +    defined by Metrowerks when compiling for PowerPC-based Macintosh
    +  __STDC__
    +    defined for strict ANSI C
    +*/
    +
    +/*----------------------------------
    +
    +  qh_COMPUTEfurthest
    +    compute furthest distance to an outside point instead of storing it with the facet
    +    =1 to compute furthest
    +
    +  notes:
    +    computing furthest saves memory but costs time
    +      about 40% more distance tests for partitioning
    +      removes facet->furthestdist
    +*/
    +#define qh_COMPUTEfurthest 0
    +
    +/*----------------------------------
    +
    +  qh_KEEPstatistics
    +    =0 removes most of statistic gathering and reporting
    +
    +  notes:
    +    if 0, code size is reduced by about 4%.
    +*/
    +#define qh_KEEPstatistics 1
    +
    +/*----------------------------------
    +
    +  qh_MAXoutside
    +    record outer plane for each facet
    +    =1 to record facet->maxoutside
    +
    +  notes:
    +    this takes a realT per facet and slightly slows down qhull
    +    it produces better outer planes for geomview output
    +*/
    +#define qh_MAXoutside 1
    +
    +/*----------------------------------
    +
    +  qh_NOmerge
    +    disables facet merging if defined
    +
    +  notes:
    +    This saves about 10% space.
    +
    +    Unless 'Q0'
    +      qh_NOmerge sets 'QJ' to avoid precision errors
    +
    +    #define qh_NOmerge
    +
    +  see:
    +    qh_NOmem in mem.c
    +
    +    see user.c/user_eg.c for removing io.o
    +*/
    +
    +/*----------------------------------
    +
    +  qh_NOtrace
    +    no tracing if defined
    +
    +  notes:
    +    This saves about 5% space.
    +
    +    #define qh_NOtrace
    +*/
    +
    +/*----------------------------------
    +
    +  qh_QHpointer
    +    access global data with pointer or static structure
    +
    +  qh_QHpointer  = 1     access globals via a pointer to allocated memory
    +                        enables qh_saveqhull() and qh_restoreqhull()
    +                        [2010, gcc] costs about 4% in time and 4% in space
    +                        [2003, msvc] costs about 8% in time and 2% in space
    +
    +                = 0     qh_qh and qh_qhstat are static data structures
    +                        only one instance of qhull() can be active at a time
    +                        default value
    +
    +  qh_QHpointer_dllimport and qh_dllimport define qh_qh as __declspec(dllimport) [libqhull.h]
    +  It is required for msvc-2005.  It is not needed for gcc.
    +
    +  notes:
    +    [jan'16] qh_QHpointer is deprecated for Qhull.  Use libqhull_r instead.
    +    all global variables for qhull are in qh, qhmem, and qhstat
    +    qh is defined in libqhull.h
    +    qhmem is defined in mem.h
    +    qhstat is defined in stat.h
    +
    +*/
    +#ifdef qh_QHpointer
    +#if qh_dllimport
    +#error QH6207 Qhull error: Use qh_QHpointer_dllimport instead of qh_dllimport with qh_QHpointer
    +#endif
    +#else
    +#define qh_QHpointer 0
    +#if qh_QHpointer_dllimport
    +#error QH6234 Qhull error: Use qh_dllimport instead of qh_QHpointer_dllimport when qh_QHpointer is not defined
    +#endif
    +#endif
    +#if 0  /* sample code */
    +    qhT *oldqhA, *oldqhB;
    +
    +    exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +    /* use results from first call to qh_new_qhull */
    +    oldqhA= qh_save_qhull();
    +    exitcode= qh_new_qhull(dimB, numpointsB, pointsB, ismalloc,
    +                      flags, outfile, errfile);
    +    /* use results from second call to qh_new_qhull */
    +    oldqhB= qh_save_qhull();
    +    qh_restore_qhull(&oldqhA);
    +    /* use results from first call to qh_new_qhull */
    +    qh_freeqhull(qh_ALL);  /* frees all memory used by first call */
    +    qh_restore_qhull(&oldqhB);
    +    /* use results from second call to qh_new_qhull */
    +    qh_freeqhull(!qh_ALL); /* frees long memory used by second call */
    +    qh_memfreeshort(&curlong, &totlong);  /* frees short memory and memory allocator */
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_QUICKhelp
    +    =1 to use abbreviated help messages, e.g., for degenerate inputs
    +*/
    +#define qh_QUICKhelp    0
    +
    +/*============================================================*/
    +/*============= -merge constants- ============================*/
    +/*============================================================*/
    +/*
    +   These constants effect facet merging.  You probably will not need
    +   to modify them.  They effect the performance of facet merging.
    +*/
    +
    +/*----------------------------------
    +
    +  qh_DIMmergeVertex
    +    max dimension for vertex merging (it is not effective in high-d)
    +*/
    +#define qh_DIMmergeVertex 6
    +
    +/*----------------------------------
    +
    +  qh_DIMreduceBuild
    +     max dimension for vertex reduction during build (slow in high-d)
    +*/
    +#define qh_DIMreduceBuild 5
    +
    +/*----------------------------------
    +
    +  qh_BESTcentrum
    +     if > 2*dim+n vertices, qh_findbestneighbor() tests centrums (faster)
    +     else, qh_findbestneighbor() tests all vertices (much better merges)
    +
    +  qh_BESTcentrum2
    +     if qh_BESTcentrum2 * DIM3 + BESTcentrum < #vertices tests centrums
    +*/
    +#define qh_BESTcentrum 20
    +#define qh_BESTcentrum2 2
    +
    +/*----------------------------------
    +
    +  qh_BESTnonconvex
    +    if > dim+n neighbors, qh_findbestneighbor() tests nonconvex ridges.
    +
    +  notes:
    +    It is needed because qh_findbestneighbor is slow for large facets
    +*/
    +#define qh_BESTnonconvex 15
    +
    +/*----------------------------------
    +
    +  qh_MAXnewmerges
    +    if >n newmerges, qh_merge_nonconvex() calls qh_reducevertices_centrums.
    +
    +  notes:
    +    It is needed because postmerge can merge many facets at once
    +*/
    +#define qh_MAXnewmerges 2
    +
    +/*----------------------------------
    +
    +  qh_MAXnewcentrum
    +    if <= dim+n vertices (n approximates the number of merges),
    +      reset the centrum in qh_updatetested() and qh_mergecycle_facets()
    +
    +  notes:
    +    needed to reduce cost and because centrums may move too much if
    +    many vertices in high-d
    +*/
    +#define qh_MAXnewcentrum 5
    +
    +/*----------------------------------
    +
    +  qh_COPLANARratio
    +    for 3-d+ merging, qh.MINvisible is n*premerge_centrum
    +
    +  notes:
    +    for non-merging, it's DISTround
    +*/
    +#define qh_COPLANARratio 3
    +
    +/*----------------------------------
    +
    +  qh_DISToutside
    +    When is a point clearly outside of a facet?
    +    Stops search in qh_findbestnew or qh_partitionall
    +    qh_findbest uses qh.MINoutside since since it is only called if no merges.
    +
    +  notes:
    +    'Qf' always searches for best facet
    +    if !qh.MERGING, same as qh.MINoutside.
    +    if qh_USEfindbestnew, increase value since neighboring facets may be ill-behaved
    +      [Note: Zdelvertextot occurs normally with interior points]
    +            RBOX 1000 s Z1 G1e-13 t1001188774 | QHULL Tv
    +    When there is a sharp edge, need to move points to a
    +    clearly good facet; otherwise may be lost in another partitioning.
    +    if too big then O(n^2) behavior for partitioning in cone
    +    if very small then important points not processed
    +    Needed in qh_partitionall for
    +      RBOX 1000 s Z1 G1e-13 t1001032651 | QHULL Tv
    +    Needed in qh_findbestnew for many instances of
    +      RBOX 1000 s Z1 G1e-13 t | QHULL Tv
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_DISToutside ((qh_USEfindbestnew ? 2 : 1) * \
    +     fmax_((qh MERGING ? 2 : 1)*qh MINoutside, qh max_outside))
    +
    +/*----------------------------------
    +
    +  qh_RATIOnearinside
    +    ratio of qh.NEARinside to qh.ONEmerge for retaining inside points for
    +    qh_check_maxout().
    +
    +  notes:
    +    This is overkill since do not know the correct value.
    +    It effects whether 'Qc' reports all coplanar points
    +    Not used for 'd' since non-extreme points are coplanar
    +*/
    +#define qh_RATIOnearinside 5
    +
    +/*----------------------------------
    +
    +  qh_SEARCHdist
    +    When is a facet coplanar with the best facet?
    +    qh_findbesthorizon: all coplanar facets of the best facet need to be searched.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_SEARCHdist ((qh_USEfindbestnew ? 2 : 1) * \
    +      (qh max_outside + 2 * qh DISTround + fmax_( qh MINvisible, qh MAXcoplanar)));
    +
    +/*----------------------------------
    +
    +  qh_USEfindbestnew
    +     Always use qh_findbestnew for qh_partitionpoint, otherwise use
    +     qh_findbestnew if merged new facet or sharpnewfacets.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_USEfindbestnew (zzval_(Ztotmerge) > 50)
    +
    +/*----------------------------------
    +
    +  qh_WIDEcoplanar
    +    n*MAXcoplanar or n*MINvisible for a WIDEfacet
    +
    +    if vertex is further than qh.WIDEfacet from the hyperplane
    +    then its ridges are not counted in computing the area, and
    +    the facet's centrum is frozen.
    +
    +  notes:
    +   qh.WIDEfacet= max(qh.MAXoutside,qh_WIDEcoplanar*qh.MAXcoplanar,
    +      qh_WIDEcoplanar * qh.MINvisible);
    +*/
    +#define qh_WIDEcoplanar 6
    +
    +/*----------------------------------
    +
    +  qh_WIDEduplicate
    +    Merge ratio for errexit from qh_forcedmerges due to duplicate ridge
    +    Override with option Q12 no-wide-duplicate
    +
    +    Notes:
    +      Merging a duplicate ridge can lead to very wide facets.
    +      A future release of qhull will avoid duplicate ridges by removing duplicate sub-ridges from the horizon
    +*/
    +#define qh_WIDEduplicate 100
    +
    +/*----------------------------------
    +
    +  qh_MAXnarrow
    +    max. cosine in initial hull that sets qh.NARROWhull
    +
    +  notes:
    +    If qh.NARROWhull, the initial partition does not make
    +    coplanar points.  If narrow, a coplanar point can be
    +    coplanar to two facets of opposite orientations and
    +    distant from the exact convex hull.
    +
    +    Conservative estimate.  Don't actually see problems until it is -1.0
    +*/
    +#define qh_MAXnarrow -0.99999999
    +
    +/*----------------------------------
    +
    +  qh_WARNnarrow
    +    max. cosine in initial hull to warn about qh.NARROWhull
    +
    +  notes:
    +    this is a conservative estimate.
    +    Don't actually see problems until it is -1.0.  See qh-impre.htm
    +*/
    +#define qh_WARNnarrow -0.999999999999999
    +
    +/*----------------------------------
    +
    +  qh_ZEROdelaunay
    +    a zero Delaunay facet occurs for input sites coplanar with their convex hull
    +    the last normal coefficient of a zero Delaunay facet is within
    +        qh_ZEROdelaunay * qh.ANGLEround of 0
    +
    +  notes:
    +    qh_ZEROdelaunay does not allow for joggled input ('QJ').
    +
    +    You can avoid zero Delaunay facets by surrounding the input with a box.
    +
    +    Use option 'PDk:-n' to explicitly define zero Delaunay facets
    +      k= dimension of input sites (e.g., 3 for 3-d Delaunay triangulation)
    +      n= the cutoff for zero Delaunay facets (e.g., 'PD3:-1e-12')
    +*/
    +#define qh_ZEROdelaunay 2
    +
    +/*============================================================*/
    +/*============= Microsoft DevStudio ==========================*/
    +/*============================================================*/
    +
    +/*
    +   Finding Memory Leaks Using the CRT Library
    +   https://msdn.microsoft.com/en-us/library/x98tx3cf(v=vs.100).aspx
    +
    +   Reports enabled in qh_lib_check for Debug window and stderr
    +
    +   From 2005=>msvcr80d, 2010=>msvcr100d, 2012=>msvcr110d
    +
    +   Watch: {,,msvcr80d.dll}_crtBreakAlloc  Value from {n} in the leak report
    +   _CrtSetBreakAlloc(689); // qh_lib_check() [global_r.c]
    +
    +   Examples
    +     http://free-cad.sourceforge.net/SrcDocu/d2/d7f/MemDebug_8cpp_source.html
    +     https://github.com/illlust/Game/blob/master/library/MemoryLeak.cpp
    +*/
    +#if 0   /* off (0) by default for QHULL_CRTDBG */
    +#define QHULL_CRTDBG
    +#endif
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)
    +#define _CRTDBG_MAP_ALLOC
    +#include 
    +#include 
    +#endif
    +#endif /* qh_DEFuser */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/usermem.c b/xs/src/qhull/src/libqhull/usermem.c
    new file mode 100644
    index 0000000000..0e99e8f66c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/usermem.c
    @@ -0,0 +1,94 @@
    +/*
      ---------------------------------
    +
    +   usermem.c
    +   qh_exit(), qh_free(), and qh_malloc()
    +
    +   See README.txt.
    +
    +   If you redefine one of these functions you must redefine all of them.
    +   If you recompile and load this file, then usermem.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull.h for data structures, macros, and user-callable functions.
    +   See user.c for qhull-related, redefinable functions
    +   see user.h for user-definable constants
    +   See userprintf.c for qh_fprintf and userprintf_rbox.c for qh_fprintf_rbox
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull.h"
    +
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_exit( exitcode )
    +    exit program
    +
    +  notes:
    +    qh_exit() is called when qh_errexit() and longjmp() are not available.
    +
    +    This is the only use of exit() in Qhull
    +    To replace qh_exit with 'throw', see libqhullcpp/usermem_r-cpp.cpp
    +*/
    +void qh_exit(int exitcode) {
    +    exit(exitcode);
    +} /* exit */
    +
    +/*---------------------------------
    +
    +  qh_fprintf_stderr( msgcode, format, list of args )
    +    fprintf to stderr with msgcode (non-zero)
    +
    +  notes:
    +    qh_fprintf_stderr() is called when qh.ferr is not defined, usually due to an initialization error
    +    
    +    It is typically followed by qh_errexit().
    +
    +    Redefine this function to avoid using stderr
    +
    +    Use qh_fprintf [userprintf.c] for normal printing
    +*/
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +      fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +
    +/*---------------------------------
    +
    +  qh_free( mem )
    +    free memory
    +
    +  notes:
    +    same as free()
    +    No calls to qh_errexit() 
    +*/
    +void qh_free(void *mem) {
    +    free(mem);
    +} /* free */
    +
    +/*---------------------------------
    +
    +    qh_malloc( mem )
    +      allocate memory
    +
    +    notes:
    +      same as malloc()
    +*/
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +} /* malloc */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull/userprintf.c b/xs/src/qhull/src/libqhull/userprintf.c
    new file mode 100644
    index 0000000000..190d7cd79b
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/userprintf.c
    @@ -0,0 +1,66 @@
    +/*
      ---------------------------------
    +
    +   userprintf.c
    +   qh_fprintf()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull.h for data structures, macros, and user-callable functions.
    +   See user.c for qhull-related, redefinable functions
    +   see user.h for user-definable constants
    +   See usermem.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull.h"
    +#include "mem.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf(fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf via qh_errexit()
    +     may be called for errors in qh_initstatistics and qh_meminit
    +*/
    +
    +void qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        /* could use qhmem.ferr, but probably better to be cautious */
    +        qh_fprintf_stderr(6232, "Qhull internal error (userprintf.c): fp is 0.  Wrong qh_fprintf called.\n");
    +        qh_errexit(6232, NULL, NULL);
    +    }
    +    va_start(args, fmt);
    +#if qh_QHpointer
    +    if (qh_qh && qh ANNOTATEoutput) {
    +#else
    +    if (qh ANNOTATEoutput) {
    +#endif
    +      fprintf(fp, "[QH%.4d]", msgcode);
    +    }else if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR ) {
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    }
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +
    +    /* Place debugging traps here. Use with option 'Tn' */
    +
    +} /* qh_fprintf */
    +
    diff --git a/xs/src/qhull/src/libqhull/userprintf_rbox.c b/xs/src/qhull/src/libqhull/userprintf_rbox.c
    new file mode 100644
    index 0000000000..8edd2001aa
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull/userprintf_rbox.c
    @@ -0,0 +1,53 @@
    +/*
      ---------------------------------
    +
    +   userprintf_rbox.c
    +   qh_fprintf_rbox()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf_rbox.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull.h for data structures, macros, and user-callable functions.
    +   See user.c for qhull-related, redefinable functions
    +   see user.h for user-definable constants
    +   See usermem.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf_rbox(fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf_rbox via qh_errexit_rbox()
    +*/
    +
    +void qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        qh_fprintf_stderr(6231, "Qhull internal error (userprintf_rbox.c): fp is 0.  Wrong qh_fprintf_rbox called.\n");
    +        qh_errexit_rbox(6231);
    +    }
    +    if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR)
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +} /* qh_fprintf_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/Makefile b/xs/src/qhull/src/libqhull_r/Makefile
    new file mode 100644
    index 0000000000..5c40969e01
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/Makefile
    @@ -0,0 +1,240 @@
    +# Simple gcc Makefile for reentrant qhull and rbox (default gcc/g++)
    +#
    +#   make help
    +#   See README.txt and ../../Makefile
    +#       
    +# Variables
    +#   BINDIR         directory where to copy executables
    +#   DESTDIR        destination directory for 'make install'
    +#   DOCDIR         directory where to copy html documentation
    +#   INCDIR         directory where to copy headers
    +#   LIBDIR         directory where to copy libraries
    +#   MANDIR         directory where to copy manual pages
    +#   PRINTMAN       command for printing manual pages
    +#   PRINTC         command for printing C files
    +#   CC             ANSI C or C++ compiler
    +#   CC_OPTS1       options used to compile .c files
    +#   CC_OPTS2       options used to link .o files
    +#   CC_OPTS3       options to build shared libraries
    +#
    +#   LIBQHULL_OBJS  .o files for linking
    +#   LIBQHULL_HDRS  .h files for printing
    +#   CFILES         .c files for printing
    +#   DOCFILES       documentation files
    +#   FILES          miscellaneous files for printing
    +#   TFILES         .txt versions of html files
    +#   FILES          all other files
    +#   LIBQHULL_OBJS  specifies the object files of libqhullstatic_r.a
    +#
    +# Results
    +#   rbox           Generates points sets for qhull, qconvex, etc.
    +#   qhull          Computes convex hulls and related structures
    +#   qconvex, qdelaunay, qhalf, qvoronoi
    +#                  Specializations of qhull for each geometric structure
    +#   libqhullstatic_r.a Static library for reentrant qhull
    +#   testqset_r     Standalone test of reentrant qset_r.c with mem_r.c
    +#   user_eg        An example of using qhull (reentrant)
    +#   user_eg2       An example of using qhull (reentrant)
    +#
    +# Make targets
    +#   make           Build results using gcc or another compiler
    +#   make clean     Remove object files
    +#   make cleanall  Remove generated files
    +#   make doc       Print documentation
    +#   make help
    +#   make install   Copy qhull, rbox, qhull.1, rbox.1 to BINDIR, MANDIR
    +#   make new       Rebuild qhull and rbox from source
    +#   make printall  Print all files
    +#   make qtest     Quick test of qset, rbox, and qhull
    +#   make test      Quck test of qhull, qconvex, etc.
    +#
    +# Do not replace tabs with spaces.  Needed for build rules
    +# Unix line endings (\n)
    +# $Id: //main/2015/qhull/src/libqhull_r/Makefile#6 $
    +
    +DESTDIR = /usr/local
    +BINDIR	= $(DESTDIR)/bin
    +INCDIR	= $(DESTDIR)/include
    +LIBDIR	= $(DESTDIR)/lib
    +DOCDIR	= $(DESTDIR)/share/doc/qhull
    +MANDIR	= $(DESTDIR)/share/man/man1
    +
    +# if you do not have enscript, try a2ps or just use lpr.  The files are text.
    +PRINTMAN = enscript -2rl
    +PRINTC = enscript -2r
    +# PRINTMAN = lpr
    +# PRINTC = lpr
    +
    +#for Gnu's gcc compiler, -O3 for optimization, -g for debugging, -pg for profiling
    +# -fpic  needed for gcc x86_64-linux-gnu.  Not needed for mingw
    +CC        = gcc
    +CC_OPTS1  = -O3 -ansi -I../../src -fpic $(CC_WARNINGS)
    +
    +# for Sun's cc compiler, -fast or O2 for optimization, -g for debugging, -Xc for ANSI
    +#CC       = cc
    +#CC_OPTS1 = -Xc -v -fast -I../../src 
    +
    +# for Silicon Graphics cc compiler, -O2 for optimization, -g for debugging
    +#CC       = cc
    +#CC_OPTS1 = -ansi -O2 -I../../src 
    +
    +# for Next cc compiler with fat executable
    +#CC       = cc
    +#CC_OPTS1 = -ansi -O2 -I../../src -arch m68k -arch i386 -arch hppa
    +
    +# For loader, ld, 
    +CC_OPTS2 = $(CC_OPTS1)
    +
    +# Default targets for make
    +
    +all: qhull_links qhull_all qtest
    +
    +help:
    +	head -n 50 Makefile
    +
    +clean:
    +	rm -f *.o 
    +	# Delete linked files from other directories [qhull_links]
    +	rm -f qconvex_r.c unix_r.c qdelaun_r.c qhalf_r.c qvoronoi_r.c rbox_r.c
    +	rm -f user_eg_r.c user_eg2_r.c testqset_r.c
    +	
    +cleanall: clean
    +	rm -f qconvex qdelaunay qhalf qvoronoi qhull *.exe
    +	rm -f core user_eg_r user_eg2_r testqset_r libqhullstatic_r.a
    +
    +doc: 
    +	$(PRINTMAN) $(TXTFILES) $(DOCFILES)
    +
    +install:
    +	mkdir -p $(BINDIR)
    +	mkdir -p $(DOCDIR)
    +	mkdir -p $(INCDIR)/libqhull
    +	mkdir -p $(MANDIR)
    +	cp -p qconvex qdelaunay qhalf qhull qvoronoi rbox $(BINDIR)
    +	cp -p libqhullstatic_r.a $(LIBDIR)
    +	cp -p ../../html/qhull.man $(MANDIR)/qhull.1
    +	cp -p ../../html/rbox.man $(MANDIR)/rbox.1
    +	cp -p ../../html/* $(DOCDIR)
    +	cp *.h $(INCDIR)/libqhull_r
    +
    +new:	cleanall all
    +
    +printall: doc printh printc printf
    +
    +printh:
    +	$(PRINTC) $(LIBQHULL_HDRS)
    +
    +printc:
    +	$(PRINTC) $(CFILES)
    +
    +# LIBQHULL_OBJS_1 ordered by frequency of execution with small files at end.  Better locality.
    +# Same definitions as ../../Makefile
    +
    +LIBQHULLS_OBJS_1= global_r.o stat_r.o geom2_r.o poly2_r.o merge_r.o \
    +        libqhull_r.o geom_r.o poly_r.o qset_r.o mem_r.o random_r.o 
    +
    +LIBQHULLS_OBJS_2= $(LIBQHULLS_OBJS_1) usermem_r.o userprintf_r.o io_r.o user_r.o
    +
    +LIBQHULLS_OBJS= $(LIBQHULLS_OBJS_2)  rboxlib_r.o userprintf_rbox_r.o
    +
    +LIBQHULL_HDRS= user_r.h libqhull_r.h qhull_ra.h geom_r.h \
    +        io_r.h mem_r.h merge_r.h poly_r.h random_r.h \
    +        qset_r.h stat_r.h
    +
    +# CFILES ordered alphabetically after libqhull.c 
    +CFILES= ../qhull/unix_r.c libqhull_r.c geom_r.c geom2_r.c global_r.c io_r.c \
    +	mem_r.c merge_r.c poly_r.c poly2_r.c random_r.c rboxlib_r.c \
    +	qset_r.c stat_r.c user_r.c usermem_r.c userprintf_r.c \
    +	../qconvex/qconvex.c ../qdelaunay/qdelaun.c ../qhalf/qhalf.c ../qvoronoi/qvoronoi.c
    +
    +TXTFILES= ../../Announce.txt ../../REGISTER.txt ../../COPYING.txt ../../README.txt ../Changes.txt
    +DOCFILES= ../../html/rbox.txt ../../html/qhull.txt
    +
    +.c.o:
    +	$(CC) -c $(CC_OPTS1) -o $@ $<
    +
    +# Work around problems with ../ in Red Hat Linux
    +qhull_links:
    +	# On MINSYS, 'ln -s' may create a copy instead of a symbolic link
    +	[ -f qconvex_r.c ]  || ln -s ../qconvex/qconvex_r.c
    +	[ -f qdelaun_r.c ]  || ln -s ../qdelaunay/qdelaun_r.c
    +	[ -f qhalf_r.c ]    || ln -s ../qhalf/qhalf_r.c
    +	[ -f qvoronoi_r.c ] || ln -s ../qvoronoi/qvoronoi_r.c
    +	[ -f rbox_r.c ]     || ln -s ../rbox/rbox_r.c
    +	[ -f testqset_r.c ] || ln -s ../testqset_r/testqset_r.c
    +	[ -f unix_r.c ]     || ln -s ../qhull/unix_r.c
    +	[ -f user_eg_r.c ]  || ln -s ../user_eg/user_eg_r.c
    +	[ -f user_eg2_r.c ] || ln -s ../user_eg2/user_eg2_r.c
    +
    +# compile qhull without using bin/libqhullstatic_r.a
    +qhull_all: qconvex_r.o qdelaun_r.o qhalf_r.o qvoronoi_r.o unix_r.o user_eg_r.o user_eg2_r.o rbox_r.o testqset_r.o $(LIBQHULLS_OBJS)
    +	$(CC) -o qconvex $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qconvex_r.o
    +	$(CC) -o qdelaunay $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qdelaun_r.o
    +	$(CC) -o qhalf $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qhalf_r.o
    +	$(CC) -o qvoronoi $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) qvoronoi_r.o
    +	$(CC) -o qhull $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) unix_r.o 
    +	$(CC) -o rbox $(CC_OPTS2) -lm $(LIBQHULLS_OBJS) rbox_r.o
    +	$(CC) -o user_eg $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_2) user_eg_r.o 
    +	$(CC) -o user_eg2 $(CC_OPTS2) -lm $(LIBQHULLS_OBJS_1) user_eg2_r.o  usermem_r.o userprintf_r.o io_r.o
    +	$(CC) -o testqset_r $(CC_OPTS2) -lm mem_r.o qset_r.o usermem_r.o testqset_r.o
    +	-ar -rs libqhullstatic_r.a $(LIBQHULLS_OBJS)
    +	#libqhullstatic_r.a is not needed for qhull
    +	#If 'ar -rs' fails try using 'ar -s' with 'ranlib'
    +	#ranlib libqhullstatic_r.a
    +
    +qtest:
    +	@echo ============================================
    +	@echo == make qtest ==============================
    +	@echo ============================================
    +	@echo -n "== "
    +	@date
    +	@echo
    +	@echo Testing qset.c and mem.c with testqset
    +	./testqset_r 10000
    +	@echo Run the qhull smoketest
    +	./rbox D4 | ./qhull
    +	@echo ============================================
    +	@echo == To smoketest qhull programs
    +	@echo '==     make test'
    +	@echo ============================================
    +	@echo
    +	@echo ============================================
    +	@echo == For all make targets
    +	@echo '==     make help'
    +	@echo ============================================
    +	@echo
    +
    +test: qtest
    +	@echo ==============================
    +	@echo ========= qconvex ============
    +	@echo ==============================
    +	-./rbox 10 | ./qconvex Tv 
    +	@echo
    +	@echo ==============================
    +	@echo ========= qdelaunay ==========
    +	@echo ==============================
    +	-./rbox 10 | ./qdelaunay Tv 
    +	@echo
    +	@echo ==============================
    +	@echo ========= qhalf ==============
    +	@echo ==============================
    +	-./rbox 10 | ./qconvex FQ FV n Tv | ./qhalf Tv
    +	@echo
    +	@echo ==============================
    +	@echo ========= qvoronoi ===========
    +	@echo ==============================
    +	-./rbox 10 | ./qvoronoi Tv
    +	@echo
    +	@echo ==============================
    +	@echo ========= user_eg ============
    +	@echo == w/o shared library ========
    +	@echo ==============================
    +	-./user_eg
    +	@echo
    +	@echo ==============================
    +	@echo ========= user_eg2 ===========
    +	@echo ==============================
    +	-./user_eg2
    +	@echo
    +
    +# end of Makefile
    diff --git a/xs/src/qhull/src/libqhull_r/geom2_r.c b/xs/src/qhull/src/libqhull_r/geom2_r.c
    new file mode 100644
    index 0000000000..48addba1cf
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/geom2_r.c
    @@ -0,0 +1,2096 @@
    +/*
      ---------------------------------
    +
    +
    +   geom2_r.c
    +   infrequently used geometric routines of qhull
    +
    +   see qh-geom_r.htm and geom_r.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/geom2_r.c#6 $$Change: 2065 $
    +   $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +
    +   frequently used code goes into geom_r.c
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*================== functions in alphabetic order ============*/
    +
    +/*---------------------------------
    +
    +  qh_copypoints(qh, points, numpoints, dimension)
    +    return qh_malloc'd copy of points
    +  
    +  notes:
    +    qh_free the returned points to avoid a memory leak
    +*/
    +coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension) {
    +  int size;
    +  coordT *newpoints;
    +
    +  size= numpoints * dimension * (int)sizeof(coordT);
    +  if (!(newpoints= (coordT*)qh_malloc((size_t)size))) {
    +    qh_fprintf(qh, qh->ferr, 6004, "qhull error: insufficient memory to copy %d points\n",
    +        numpoints);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  memcpy((char *)newpoints, (char *)points, (size_t)size); /* newpoints!=0 by QH6004 */
    +  return newpoints;
    +} /* copypoints */
    +
    +/*---------------------------------
    +
    +  qh_crossproduct( dim, vecA, vecB, vecC )
    +    crossproduct of 2 dim vectors
    +    C= A x B
    +
    +  notes:
    +    from Glasner, Graphics Gems I, p. 639
    +    only defined for dim==3
    +*/
    +void qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]){
    +
    +  if (dim == 3) {
    +    vecC[0]=   det2_(vecA[1], vecA[2],
    +                     vecB[1], vecB[2]);
    +    vecC[1]= - det2_(vecA[0], vecA[2],
    +                     vecB[0], vecB[2]);
    +    vecC[2]=   det2_(vecA[0], vecA[1],
    +                     vecB[0], vecB[1]);
    +  }
    +} /* vcross */
    +
    +/*---------------------------------
    +
    +  qh_determinant(qh, rows, dim, nearzero )
    +    compute signed determinant of a square matrix
    +    uses qh.NEARzero to test for degenerate matrices
    +
    +  returns:
    +    determinant
    +    overwrites rows and the matrix
    +    if dim == 2 or 3
    +      nearzero iff determinant < qh->NEARzero[dim-1]
    +      (!quite correct, not critical)
    +    if dim >= 4
    +      nearzero iff diagonal[k] < qh->NEARzero[k]
    +*/
    +realT qh_determinant(qhT *qh, realT **rows, int dim, boolT *nearzero) {
    +  realT det=0;
    +  int i;
    +  boolT sign= False;
    +
    +  *nearzero= False;
    +  if (dim < 2) {
    +    qh_fprintf(qh, qh->ferr, 6005, "qhull internal error (qh_determinate): only implemented for dimension >= 2\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }else if (dim == 2) {
    +    det= det2_(rows[0][0], rows[0][1],
    +                 rows[1][0], rows[1][1]);
    +    if (fabs_(det) < 10*qh->NEARzero[1])  /* not really correct, what should this be? */
    +      *nearzero= True;
    +  }else if (dim == 3) {
    +    det= det3_(rows[0][0], rows[0][1], rows[0][2],
    +                 rows[1][0], rows[1][1], rows[1][2],
    +                 rows[2][0], rows[2][1], rows[2][2]);
    +    if (fabs_(det) < 10*qh->NEARzero[2])  /* what should this be?  det 5.5e-12 was flat for qh_maxsimplex of qdelaunay 0,0 27,27 -36,36 -9,63 */
    +      *nearzero= True;
    +  }else {
    +    qh_gausselim(qh, rows, dim, dim, &sign, nearzero);  /* if nearzero, diagonal still ok*/
    +    det= 1.0;
    +    for (i=dim; i--; )
    +      det *= (rows[i])[i];
    +    if (sign)
    +      det= -det;
    +  }
    +  return det;
    +} /* determinant */
    +
    +/*---------------------------------
    +
    +  qh_detjoggle(qh, points, numpoints, dimension )
    +    determine default max joggle for point array
    +      as qh_distround * qh_JOGGLEdefault
    +
    +  returns:
    +    initial value for JOGGLEmax from points and REALepsilon
    +
    +  notes:
    +    computes DISTround since qh_maxmin not called yet
    +    if qh->SCALElast, last dimension will be scaled later to MAXwidth
    +
    +    loop duplicated from qh_maxmin
    +*/
    +realT qh_detjoggle(qhT *qh, pointT *points, int numpoints, int dimension) {
    +  realT abscoord, distround, joggle, maxcoord, mincoord;
    +  pointT *point, *pointtemp;
    +  realT maxabs= -REALmax;
    +  realT sumabs= 0;
    +  realT maxwidth= 0;
    +  int k;
    +
    +  for (k=0; k < dimension; k++) {
    +    if (qh->SCALElast && k == dimension-1)
    +      abscoord= maxwidth;
    +    else if (qh->DELAUNAY && k == dimension-1) /* will qh_setdelaunay() */
    +      abscoord= 2 * maxabs * maxabs;  /* may be low by qh->hull_dim/2 */
    +    else {
    +      maxcoord= -REALmax;
    +      mincoord= REALmax;
    +      FORALLpoint_(qh, points, numpoints) {
    +        maximize_(maxcoord, point[k]);
    +        minimize_(mincoord, point[k]);
    +      }
    +      maximize_(maxwidth, maxcoord-mincoord);
    +      abscoord= fmax_(maxcoord, -mincoord);
    +    }
    +    sumabs += abscoord;
    +    maximize_(maxabs, abscoord);
    +  } /* for k */
    +  distround= qh_distround(qh, qh->hull_dim, maxabs, sumabs);
    +  joggle= distround * qh_JOGGLEdefault;
    +  maximize_(joggle, REALepsilon * qh_JOGGLEdefault);
    +  trace2((qh, qh->ferr, 2001, "qh_detjoggle: joggle=%2.2g maxwidth=%2.2g\n", joggle, maxwidth));
    +  return joggle;
    +} /* detjoggle */
    +
    +/*---------------------------------
    +
    +  qh_detroundoff(qh)
    +    determine maximum roundoff errors from
    +      REALepsilon, REALmax, REALmin, qh.hull_dim, qh.MAXabs_coord,
    +      qh.MAXsumcoord, qh.MAXwidth, qh.MINdenom_1
    +
    +    accounts for qh.SETroundoff, qh.RANDOMdist, qh->MERGEexact
    +      qh.premerge_cos, qh.postmerge_cos, qh.premerge_centrum,
    +      qh.postmerge_centrum, qh.MINoutside,
    +      qh_RATIOnearinside, qh_COPLANARratio, qh_WIDEcoplanar
    +
    +  returns:
    +    sets qh.DISTround, etc. (see below)
    +    appends precision constants to qh.qhull_options
    +
    +  see:
    +    qh_maxmin() for qh.NEARzero
    +
    +  design:
    +    determine qh.DISTround for distance computations
    +    determine minimum denominators for qh_divzero
    +    determine qh.ANGLEround for angle computations
    +    adjust qh.premerge_cos,... for roundoff error
    +    determine qh.ONEmerge for maximum error due to a single merge
    +    determine qh.NEARinside, qh.MAXcoplanar, qh.MINvisible,
    +      qh.MINoutside, qh.WIDEfacet
    +    initialize qh.max_vertex and qh.minvertex
    +*/
    +void qh_detroundoff(qhT *qh) {
    +
    +  qh_option(qh, "_max-width", NULL, &qh->MAXwidth);
    +  if (!qh->SETroundoff) {
    +    qh->DISTround= qh_distround(qh, qh->hull_dim, qh->MAXabs_coord, qh->MAXsumcoord);
    +    if (qh->RANDOMdist)
    +      qh->DISTround += qh->RANDOMfactor * qh->MAXabs_coord;
    +    qh_option(qh, "Error-roundoff", NULL, &qh->DISTround);
    +  }
    +  qh->MINdenom= qh->MINdenom_1 * qh->MAXabs_coord;
    +  qh->MINdenom_1_2= sqrt(qh->MINdenom_1 * qh->hull_dim) ;  /* if will be normalized */
    +  qh->MINdenom_2= qh->MINdenom_1_2 * qh->MAXabs_coord;
    +                                              /* for inner product */
    +  qh->ANGLEround= 1.01 * qh->hull_dim * REALepsilon;
    +  if (qh->RANDOMdist)
    +    qh->ANGLEround += qh->RANDOMfactor;
    +  if (qh->premerge_cos < REALmax/2) {
    +    qh->premerge_cos -= qh->ANGLEround;
    +    if (qh->RANDOMdist)
    +      qh_option(qh, "Angle-premerge-with-random", NULL, &qh->premerge_cos);
    +  }
    +  if (qh->postmerge_cos < REALmax/2) {
    +    qh->postmerge_cos -= qh->ANGLEround;
    +    if (qh->RANDOMdist)
    +      qh_option(qh, "Angle-postmerge-with-random", NULL, &qh->postmerge_cos);
    +  }
    +  qh->premerge_centrum += 2 * qh->DISTround;    /*2 for centrum and distplane()*/
    +  qh->postmerge_centrum += 2 * qh->DISTround;
    +  if (qh->RANDOMdist && (qh->MERGEexact || qh->PREmerge))
    +    qh_option(qh, "Centrum-premerge-with-random", NULL, &qh->premerge_centrum);
    +  if (qh->RANDOMdist && qh->POSTmerge)
    +    qh_option(qh, "Centrum-postmerge-with-random", NULL, &qh->postmerge_centrum);
    +  { /* compute ONEmerge, max vertex offset for merging simplicial facets */
    +    realT maxangle= 1.0, maxrho;
    +
    +    minimize_(maxangle, qh->premerge_cos);
    +    minimize_(maxangle, qh->postmerge_cos);
    +    /* max diameter * sin theta + DISTround for vertex to its hyperplane */
    +    qh->ONEmerge= sqrt((realT)qh->hull_dim) * qh->MAXwidth *
    +      sqrt(1.0 - maxangle * maxangle) + qh->DISTround;
    +    maxrho= qh->hull_dim * qh->premerge_centrum + qh->DISTround;
    +    maximize_(qh->ONEmerge, maxrho);
    +    maxrho= qh->hull_dim * qh->postmerge_centrum + qh->DISTround;
    +    maximize_(qh->ONEmerge, maxrho);
    +    if (qh->MERGING)
    +      qh_option(qh, "_one-merge", NULL, &qh->ONEmerge);
    +  }
    +  qh->NEARinside= qh->ONEmerge * qh_RATIOnearinside; /* only used if qh->KEEPnearinside */
    +  if (qh->JOGGLEmax < REALmax/2 && (qh->KEEPcoplanar || qh->KEEPinside)) {
    +    realT maxdist;             /* adjust qh.NEARinside for joggle */
    +    qh->KEEPnearinside= True;
    +    maxdist= sqrt((realT)qh->hull_dim) * qh->JOGGLEmax + qh->DISTround;
    +    maxdist= 2*maxdist;        /* vertex and coplanar point can joggle in opposite directions */
    +    maximize_(qh->NEARinside, maxdist);  /* must agree with qh_nearcoplanar() */
    +  }
    +  if (qh->KEEPnearinside)
    +    qh_option(qh, "_near-inside", NULL, &qh->NEARinside);
    +  if (qh->JOGGLEmax < qh->DISTround) {
    +    qh_fprintf(qh, qh->ferr, 6006, "qhull error: the joggle for 'QJn', %.2g, is below roundoff for distance computations, %.2g\n",
    +         qh->JOGGLEmax, qh->DISTround);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->MINvisible > REALmax/2) {
    +    if (!qh->MERGING)
    +      qh->MINvisible= qh->DISTround;
    +    else if (qh->hull_dim <= 3)
    +      qh->MINvisible= qh->premerge_centrum;
    +    else
    +      qh->MINvisible= qh_COPLANARratio * qh->premerge_centrum;
    +    if (qh->APPROXhull && qh->MINvisible > qh->MINoutside)
    +      qh->MINvisible= qh->MINoutside;
    +    qh_option(qh, "Visible-distance", NULL, &qh->MINvisible);
    +  }
    +  if (qh->MAXcoplanar > REALmax/2) {
    +    qh->MAXcoplanar= qh->MINvisible;
    +    qh_option(qh, "U-coplanar-distance", NULL, &qh->MAXcoplanar);
    +  }
    +  if (!qh->APPROXhull) {             /* user may specify qh->MINoutside */
    +    qh->MINoutside= 2 * qh->MINvisible;
    +    if (qh->premerge_cos < REALmax/2)
    +      maximize_(qh->MINoutside, (1- qh->premerge_cos) * qh->MAXabs_coord);
    +    qh_option(qh, "Width-outside", NULL, &qh->MINoutside);
    +  }
    +  qh->WIDEfacet= qh->MINoutside;
    +  maximize_(qh->WIDEfacet, qh_WIDEcoplanar * qh->MAXcoplanar);
    +  maximize_(qh->WIDEfacet, qh_WIDEcoplanar * qh->MINvisible);
    +  qh_option(qh, "_wide-facet", NULL, &qh->WIDEfacet);
    +  if (qh->MINvisible > qh->MINoutside + 3 * REALepsilon
    +  && !qh->BESToutside && !qh->FORCEoutput)
    +    qh_fprintf(qh, qh->ferr, 7001, "qhull input warning: minimum visibility V%.2g is greater than \nminimum outside W%.2g.  Flipped facets are likely.\n",
    +             qh->MINvisible, qh->MINoutside);
    +  qh->max_vertex= qh->DISTround;
    +  qh->min_vertex= -qh->DISTround;
    +  /* numeric constants reported in printsummary */
    +} /* detroundoff */
    +
    +/*---------------------------------
    +
    +  qh_detsimplex(qh, apex, points, dim, nearzero )
    +    compute determinant of a simplex with point apex and base points
    +
    +  returns:
    +     signed determinant and nearzero from qh_determinant
    +
    +  notes:
    +     uses qh.gm_matrix/qh.gm_row (assumes they're big enough)
    +
    +  design:
    +    construct qm_matrix by subtracting apex from points
    +    compute determinate
    +*/
    +realT qh_detsimplex(qhT *qh, pointT *apex, setT *points, int dim, boolT *nearzero) {
    +  pointT *coorda, *coordp, *gmcoord, *point, **pointp;
    +  coordT **rows;
    +  int k,  i=0;
    +  realT det;
    +
    +  zinc_(Zdetsimplex);
    +  gmcoord= qh->gm_matrix;
    +  rows= qh->gm_row;
    +  FOREACHpoint_(points) {
    +    if (i == dim)
    +      break;
    +    rows[i++]= gmcoord;
    +    coordp= point;
    +    coorda= apex;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *coordp++ - *coorda++;
    +  }
    +  if (i < dim) {
    +    qh_fprintf(qh, qh->ferr, 6007, "qhull internal error (qh_detsimplex): #points %d < dimension %d\n",
    +               i, dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  det= qh_determinant(qh, rows, dim, nearzero);
    +  trace2((qh, qh->ferr, 2002, "qh_detsimplex: det=%2.2g for point p%d, dim %d, nearzero? %d\n",
    +          det, qh_pointid(qh, apex), dim, *nearzero));
    +  return det;
    +} /* detsimplex */
    +
    +/*---------------------------------
    +
    +  qh_distnorm( dim, point, normal, offset )
    +    return distance from point to hyperplane at normal/offset
    +
    +  returns:
    +    dist
    +
    +  notes:
    +    dist > 0 if point is outside of hyperplane
    +
    +  see:
    +    qh_distplane in geom_r.c
    +*/
    +realT qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp) {
    +  coordT *normalp= normal, *coordp= point;
    +  realT dist;
    +  int k;
    +
    +  dist= *offsetp;
    +  for (k=dim; k--; )
    +    dist += *(coordp++) * *(normalp++);
    +  return dist;
    +} /* distnorm */
    +
    +/*---------------------------------
    +
    +  qh_distround(qh, dimension, maxabs, maxsumabs )
    +    compute maximum round-off error for a distance computation
    +      to a normalized hyperplane
    +    maxabs is the maximum absolute value of a coordinate
    +    maxsumabs is the maximum possible sum of absolute coordinate values
    +
    +  returns:
    +    max dist round for REALepsilon
    +
    +  notes:
    +    calculate roundoff error according to Golub & van Loan, 1983, Lemma 3.2-1, "Rounding Errors"
    +    use sqrt(dim) since one vector is normalized
    +      or use maxsumabs since one vector is < 1
    +*/
    +realT qh_distround(qhT *qh, int dimension, realT maxabs, realT maxsumabs) {
    +  realT maxdistsum, maxround;
    +
    +  maxdistsum= sqrt((realT)dimension) * maxabs;
    +  minimize_( maxdistsum, maxsumabs);
    +  maxround= REALepsilon * (dimension * maxdistsum * 1.01 + maxabs);
    +              /* adds maxabs for offset */
    +  trace4((qh, qh->ferr, 4008, "qh_distround: %2.2g maxabs %2.2g maxsumabs %2.2g maxdistsum %2.2g\n",
    +                 maxround, maxabs, maxsumabs, maxdistsum));
    +  return maxround;
    +} /* distround */
    +
    +/*---------------------------------
    +
    +  qh_divzero( numer, denom, mindenom1, zerodiv )
    +    divide by a number that's nearly zero
    +    mindenom1= minimum denominator for dividing into 1.0
    +
    +  returns:
    +    quotient
    +    sets zerodiv and returns 0.0 if it would overflow
    +
    +  design:
    +    if numer is nearly zero and abs(numer) < abs(denom)
    +      return numer/denom
    +    else if numer is nearly zero
    +      return 0 and zerodiv
    +    else if denom/numer non-zero
    +      return numer/denom
    +    else
    +      return 0 and zerodiv
    +*/
    +realT qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv) {
    +  realT temp, numerx, denomx;
    +
    +
    +  if (numer < mindenom1 && numer > -mindenom1) {
    +    numerx= fabs_(numer);
    +    denomx= fabs_(denom);
    +    if (numerx < denomx) {
    +      *zerodiv= False;
    +      return numer/denom;
    +    }else {
    +      *zerodiv= True;
    +      return 0.0;
    +    }
    +  }
    +  temp= denom/numer;
    +  if (temp > mindenom1 || temp < -mindenom1) {
    +    *zerodiv= False;
    +    return numer/denom;
    +  }else {
    +    *zerodiv= True;
    +    return 0.0;
    +  }
    +} /* divzero */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetarea(qh, facet )
    +    return area for a facet
    +
    +  notes:
    +    if non-simplicial,
    +      uses centrum to triangulate facet and sums the projected areas.
    +    if (qh->DELAUNAY),
    +      computes projected area instead for last coordinate
    +    assumes facet->normal exists
    +    projecting tricoplanar facets to the hyperplane does not appear to make a difference
    +
    +  design:
    +    if simplicial
    +      compute area
    +    else
    +      for each ridge
    +        compute area from centrum to ridge
    +    negate area if upper Delaunay facet
    +*/
    +realT qh_facetarea(qhT *qh, facetT *facet) {
    +  vertexT *apex;
    +  pointT *centrum;
    +  realT area= 0.0;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->simplicial) {
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    area= qh_facetarea_simplex(qh, qh->hull_dim, apex->point, facet->vertices,
    +                    apex, facet->toporient, facet->normal, &facet->offset);
    +  }else {
    +    if (qh->CENTERtype == qh_AScentrum)
    +      centrum= facet->center;
    +    else
    +      centrum= qh_getcentrum(qh, facet);
    +    FOREACHridge_(facet->ridges)
    +      area += qh_facetarea_simplex(qh, qh->hull_dim, centrum, ridge->vertices,
    +                 NULL, (boolT)(ridge->top == facet),  facet->normal, &facet->offset);
    +    if (qh->CENTERtype != qh_AScentrum)
    +      qh_memfree(qh, centrum, qh->normal_size);
    +  }
    +  if (facet->upperdelaunay && qh->DELAUNAY)
    +    area= -area;  /* the normal should be [0,...,1] */
    +  trace4((qh, qh->ferr, 4009, "qh_facetarea: f%d area %2.2g\n", facet->id, area));
    +  return area;
    +} /* facetarea */
    +
    +/*---------------------------------
    +
    +  qh_facetarea_simplex(qh, dim, apex, vertices, notvertex, toporient, normal, offset )
    +    return area for a simplex defined by
    +      an apex, a base of vertices, an orientation, and a unit normal
    +    if simplicial or tricoplanar facet,
    +      notvertex is defined and it is skipped in vertices
    +
    +  returns:
    +    computes area of simplex projected to plane [normal,offset]
    +    returns 0 if vertex too far below plane (qh->WIDEfacet)
    +      vertex can't be apex of tricoplanar facet
    +
    +  notes:
    +    if (qh->DELAUNAY),
    +      computes projected area instead for last coordinate
    +    uses qh->gm_matrix/gm_row and qh->hull_dim
    +    helper function for qh_facetarea
    +
    +  design:
    +    if Notvertex
    +      translate simplex to apex
    +    else
    +      project simplex to normal/offset
    +      translate simplex to apex
    +    if Delaunay
    +      set last row/column to 0 with -1 on diagonal
    +    else
    +      set last row to Normal
    +    compute determinate
    +    scale and flip sign for area
    +*/
    +realT qh_facetarea_simplex(qhT *qh, int dim, coordT *apex, setT *vertices,
    +        vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset) {
    +  pointT *coorda, *coordp, *gmcoord;
    +  coordT **rows, *normalp;
    +  int k,  i=0;
    +  realT area, dist;
    +  vertexT *vertex, **vertexp;
    +  boolT nearzero;
    +
    +  gmcoord= qh->gm_matrix;
    +  rows= qh->gm_row;
    +  FOREACHvertex_(vertices) {
    +    if (vertex == notvertex)
    +      continue;
    +    rows[i++]= gmcoord;
    +    coorda= apex;
    +    coordp= vertex->point;
    +    normalp= normal;
    +    if (notvertex) {
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *coordp++ - *coorda++;
    +    }else {
    +      dist= *offset;
    +      for (k=dim; k--; )
    +        dist += *coordp++ * *normalp++;
    +      if (dist < -qh->WIDEfacet) {
    +        zinc_(Znoarea);
    +        return 0.0;
    +      }
    +      coordp= vertex->point;
    +      normalp= normal;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= (*coordp++ - dist * *normalp++) - *coorda++;
    +    }
    +  }
    +  if (i != dim-1) {
    +    qh_fprintf(qh, qh->ferr, 6008, "qhull internal error (qh_facetarea_simplex): #points %d != dim %d -1\n",
    +               i, dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  rows[i]= gmcoord;
    +  if (qh->DELAUNAY) {
    +    for (i=0; i < dim-1; i++)
    +      rows[i][dim-1]= 0.0;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= 0.0;
    +    rows[dim-1][dim-1]= -1.0;
    +  }else {
    +    normalp= normal;
    +    for (k=dim; k--; )
    +      *(gmcoord++)= *normalp++;
    +  }
    +  zinc_(Zdetsimplex);
    +  area= qh_determinant(qh, rows, dim, &nearzero);
    +  if (toporient)
    +    area= -area;
    +  area *= qh->AREAfactor;
    +  trace4((qh, qh->ferr, 4010, "qh_facetarea_simplex: area=%2.2g for point p%d, toporient %d, nearzero? %d\n",
    +          area, qh_pointid(qh, apex), toporient, nearzero));
    +  return area;
    +} /* facetarea_simplex */
    +
    +/*---------------------------------
    +
    +  qh_facetcenter(qh, vertices )
    +    return Voronoi center (Voronoi vertex) for a facet's vertices
    +
    +  returns:
    +    return temporary point equal to the center
    +
    +  see:
    +    qh_voronoi_center()
    +*/
    +pointT *qh_facetcenter(qhT *qh, setT *vertices) {
    +  setT *points= qh_settemp(qh, qh_setsize(qh, vertices));
    +  vertexT *vertex, **vertexp;
    +  pointT *center;
    +
    +  FOREACHvertex_(vertices)
    +    qh_setappend(qh, &points, vertex->point);
    +  center= qh_voronoi_center(qh, qh->hull_dim-1, points);
    +  qh_settempfree(qh, &points);
    +  return center;
    +} /* facetcenter */
    +
    +/*---------------------------------
    +
    +  qh_findgooddist(qh, point, facetA, dist, facetlist )
    +    find best good facet visible for point from facetA
    +    assumes facetA is visible from point
    +
    +  returns:
    +    best facet, i.e., good facet that is furthest from point
    +      distance to best facet
    +      NULL if none
    +
    +    moves good, visible facets (and some other visible facets)
    +      to end of qh->facet_list
    +
    +  notes:
    +    uses qh->visit_id
    +
    +  design:
    +    initialize bestfacet if facetA is good
    +    move facetA to end of facetlist
    +    for each facet on facetlist
    +      for each unvisited neighbor of facet
    +        move visible neighbors to end of facetlist
    +        update best good neighbor
    +        if no good neighbors, update best facet
    +*/
    +facetT *qh_findgooddist(qhT *qh, pointT *point, facetT *facetA, realT *distp,
    +               facetT **facetlist) {
    +  realT bestdist= -REALmax, dist;
    +  facetT *neighbor, **neighborp, *bestfacet=NULL, *facet;
    +  boolT goodseen= False;
    +
    +  if (facetA->good) {
    +    zzinc_(Zcheckpart);  /* calls from check_bestdist occur after print stats */
    +    qh_distplane(qh, point, facetA, &bestdist);
    +    bestfacet= facetA;
    +    goodseen= True;
    +  }
    +  qh_removefacet(qh, facetA);
    +  qh_appendfacet(qh, facetA);
    +  *facetlist= facetA;
    +  facetA->visitid= ++qh->visit_id;
    +  FORALLfacet_(*facetlist) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh->visit_id)
    +        continue;
    +      neighbor->visitid= qh->visit_id;
    +      if (goodseen && !neighbor->good)
    +        continue;
    +      zzinc_(Zcheckpart);
    +      qh_distplane(qh, point, neighbor, &dist);
    +      if (dist > 0) {
    +        qh_removefacet(qh, neighbor);
    +        qh_appendfacet(qh, neighbor);
    +        if (neighbor->good) {
    +          goodseen= True;
    +          if (dist > bestdist) {
    +            bestdist= dist;
    +            bestfacet= neighbor;
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    *distp= bestdist;
    +    trace2((qh, qh->ferr, 2003, "qh_findgooddist: p%d is %2.2g above good facet f%d\n",
    +      qh_pointid(qh, point), bestdist, bestfacet->id));
    +    return bestfacet;
    +  }
    +  trace4((qh, qh->ferr, 4011, "qh_findgooddist: no good facet for p%d above f%d\n",
    +      qh_pointid(qh, point), facetA->id));
    +  return NULL;
    +}  /* findgooddist */
    +
    +/*---------------------------------
    +
    +  qh_getarea(qh, facetlist )
    +    set area of all facets in facetlist
    +    collect statistics
    +    nop if hasAreaVolume
    +
    +  returns:
    +    sets qh->totarea/totvol to total area and volume of convex hull
    +    for Delaunay triangulation, computes projected area of the lower or upper hull
    +      ignores upper hull if qh->ATinfinity
    +
    +  notes:
    +    could compute outer volume by expanding facet area by rays from interior
    +    the following attempt at perpendicular projection underestimated badly:
    +      qh.totoutvol += (-dist + facet->maxoutside + qh->DISTround)
    +                            * area/ qh->hull_dim;
    +  design:
    +    for each facet on facetlist
    +      compute facet->area
    +      update qh.totarea and qh.totvol
    +*/
    +void qh_getarea(qhT *qh, facetT *facetlist) {
    +  realT area;
    +  realT dist;
    +  facetT *facet;
    +
    +  if (qh->hasAreaVolume)
    +    return;
    +  if (qh->REPORTfreq)
    +    qh_fprintf(qh, qh->ferr, 8020, "computing area of each facet and volume of the convex hull\n");
    +  else
    +    trace1((qh, qh->ferr, 1001, "qh_getarea: computing volume and area for each facet\n"));
    +  qh->totarea= qh->totvol= 0.0;
    +  FORALLfacet_(facetlist) {
    +    if (!facet->normal)
    +      continue;
    +    if (facet->upperdelaunay && qh->ATinfinity)
    +      continue;
    +    if (!facet->isarea) {
    +      facet->f.area= qh_facetarea(qh, facet);
    +      facet->isarea= True;
    +    }
    +    area= facet->f.area;
    +    if (qh->DELAUNAY) {
    +      if (facet->upperdelaunay == qh->UPPERdelaunay)
    +        qh->totarea += area;
    +    }else {
    +      qh->totarea += area;
    +      qh_distplane(qh, qh->interior_point, facet, &dist);
    +      qh->totvol += -dist * area/ qh->hull_dim;
    +    }
    +    if (qh->PRINTstatistics) {
    +      wadd_(Wareatot, area);
    +      wmax_(Wareamax, area);
    +      wmin_(Wareamin, area);
    +    }
    +  }
    +  qh->hasAreaVolume= True;
    +} /* getarea */
    +
    +/*---------------------------------
    +
    +  qh_gram_schmidt(qh, dim, row )
    +    implements Gram-Schmidt orthogonalization by rows
    +
    +  returns:
    +    false if zero norm
    +    overwrites rows[dim][dim]
    +
    +  notes:
    +    see Golub & van Loan, 1983, Algorithm 6.2-2, "Modified Gram-Schmidt"
    +    overflow due to small divisors not handled
    +
    +  design:
    +    for each row
    +      compute norm for row
    +      if non-zero, normalize row
    +      for each remaining rowA
    +        compute inner product of row and rowA
    +        reduce rowA by row * inner product
    +*/
    +boolT qh_gram_schmidt(qhT *qh, int dim, realT **row) {
    +  realT *rowi, *rowj, norm;
    +  int i, j, k;
    +
    +  for (i=0; i < dim; i++) {
    +    rowi= row[i];
    +    for (norm= 0.0, k= dim; k--; rowi++)
    +      norm += *rowi * *rowi;
    +    norm= sqrt(norm);
    +    wmin_(Wmindenom, norm);
    +    if (norm == 0.0)  /* either 0 or overflow due to sqrt */
    +      return False;
    +    for (k=dim; k--; )
    +      *(--rowi) /= norm;
    +    for (j=i+1; j < dim; j++) {
    +      rowj= row[j];
    +      for (norm= 0.0, k=dim; k--; )
    +        norm += *rowi++ * *rowj++;
    +      for (k=dim; k--; )
    +        *(--rowj) -= *(--rowi) * norm;
    +    }
    +  }
    +  return True;
    +} /* gram_schmidt */
    +
    +
    +/*---------------------------------
    +
    +  qh_inthresholds(qh, normal, angle )
    +    return True if normal within qh.lower_/upper_threshold
    +
    +  returns:
    +    estimate of angle by summing of threshold diffs
    +      angle may be NULL
    +      smaller "angle" is better
    +
    +  notes:
    +    invalid if qh.SPLITthresholds
    +
    +  see:
    +    qh.lower_threshold in qh_initbuild()
    +    qh_initthresholds()
    +
    +  design:
    +    for each dimension
    +      test threshold
    +*/
    +boolT qh_inthresholds(qhT *qh, coordT *normal, realT *angle) {
    +  boolT within= True;
    +  int k;
    +  realT threshold;
    +
    +  if (angle)
    +    *angle= 0.0;
    +  for (k=0; k < qh->hull_dim; k++) {
    +    threshold= qh->lower_threshold[k];
    +    if (threshold > -REALmax/2) {
    +      if (normal[k] < threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +    if (qh->upper_threshold[k] < REALmax/2) {
    +      threshold= qh->upper_threshold[k];
    +      if (normal[k] > threshold)
    +        within= False;
    +      if (angle) {
    +        threshold -= normal[k];
    +        *angle += fabs_(threshold);
    +      }
    +    }
    +  }
    +  return within;
    +} /* inthresholds */
    +
    +
    +/*---------------------------------
    +
    +  qh_joggleinput(qh)
    +    randomly joggle input to Qhull by qh.JOGGLEmax
    +    initial input is qh.first_point/qh.num_points of qh.hull_dim
    +      repeated calls use qh.input_points/qh.num_points
    +
    +  returns:
    +    joggles points at qh.first_point/qh.num_points
    +    copies data to qh.input_points/qh.input_malloc if first time
    +    determines qh.JOGGLEmax if it was zero
    +    if qh.DELAUNAY
    +      computes the Delaunay projection of the joggled points
    +
    +  notes:
    +    if qh.DELAUNAY, unnecessarily joggles the last coordinate
    +    the initial 'QJn' may be set larger than qh_JOGGLEmaxincrease
    +
    +  design:
    +    if qh.DELAUNAY
    +      set qh.SCALElast for reduced precision errors
    +    if first call
    +      initialize qh.input_points to the original input points
    +      if qh.JOGGLEmax == 0
    +        determine default qh.JOGGLEmax
    +    else
    +      increase qh.JOGGLEmax according to qh.build_cnt
    +    joggle the input by adding a random number in [-qh.JOGGLEmax,qh.JOGGLEmax]
    +    if qh.DELAUNAY
    +      sets the Delaunay projection
    +*/
    +void qh_joggleinput(qhT *qh) {
    +  int i, seed, size;
    +  coordT *coordp, *inputp;
    +  realT randr, randa, randb;
    +
    +  if (!qh->input_points) { /* first call */
    +    qh->input_points= qh->first_point;
    +    qh->input_malloc= qh->POINTSmalloc;
    +    size= qh->num_points * qh->hull_dim * sizeof(coordT);
    +    if (!(qh->first_point=(coordT*)qh_malloc((size_t)size))) {
    +      qh_fprintf(qh, qh->ferr, 6009, "qhull error: insufficient memory to joggle %d points\n",
    +          qh->num_points);
    +      qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +    }
    +    qh->POINTSmalloc= True;
    +    if (qh->JOGGLEmax == 0.0) {
    +      qh->JOGGLEmax= qh_detjoggle(qh, qh->input_points, qh->num_points, qh->hull_dim);
    +      qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
    +    }
    +  }else {                 /* repeated call */
    +    if (!qh->RERUN && qh->build_cnt > qh_JOGGLEretry) {
    +      if (((qh->build_cnt-qh_JOGGLEretry-1) % qh_JOGGLEagain) == 0) {
    +        realT maxjoggle= qh->MAXwidth * qh_JOGGLEmaxincrease;
    +        if (qh->JOGGLEmax < maxjoggle) {
    +          qh->JOGGLEmax *= qh_JOGGLEincrease;
    +          minimize_(qh->JOGGLEmax, maxjoggle);
    +        }
    +      }
    +    }
    +    qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
    +  }
    +  if (qh->build_cnt > 1 && qh->JOGGLEmax > fmax_(qh->MAXwidth/4, 0.1)) {
    +      qh_fprintf(qh, qh->ferr, 6010, "qhull error: the current joggle for 'QJn', %.2g, is too large for the width\nof the input.  If possible, recompile Qhull with higher-precision reals.\n",
    +                qh->JOGGLEmax);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  /* for some reason, using qh->ROTATErandom and qh_RANDOMseed does not repeat the run. Use 'TRn' instead */
    +  seed= qh_RANDOMint;
    +  qh_option(qh, "_joggle-seed", &seed, NULL);
    +  trace0((qh, qh->ferr, 6, "qh_joggleinput: joggle input by %2.2g with seed %d\n",
    +    qh->JOGGLEmax, seed));
    +  inputp= qh->input_points;
    +  coordp= qh->first_point;
    +  randa= 2.0 * qh->JOGGLEmax/qh_RANDOMmax;
    +  randb= -qh->JOGGLEmax;
    +  size= qh->num_points * qh->hull_dim;
    +  for (i=size; i--; ) {
    +    randr= qh_RANDOMint;
    +    *(coordp++)= *(inputp++) + (randr * randa + randb);
    +  }
    +  if (qh->DELAUNAY) {
    +    qh->last_low= qh->last_high= qh->last_newhigh= REALmax;
    +    qh_setdelaunay(qh, qh->hull_dim, qh->num_points, qh->first_point);
    +  }
    +} /* joggleinput */
    +
    +/*---------------------------------
    +
    +  qh_maxabsval( normal, dim )
    +    return pointer to maximum absolute value of a dim vector
    +    returns NULL if dim=0
    +*/
    +realT *qh_maxabsval(realT *normal, int dim) {
    +  realT maxval= -REALmax;
    +  realT *maxp= NULL, *colp, absval;
    +  int k;
    +
    +  for (k=dim, colp= normal; k--; colp++) {
    +    absval= fabs_(*colp);
    +    if (absval > maxval) {
    +      maxval= absval;
    +      maxp= colp;
    +    }
    +  }
    +  return maxp;
    +} /* maxabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_maxmin(qh, points, numpoints, dimension )
    +    return max/min points for each dimension
    +    determine max and min coordinates
    +
    +  returns:
    +    returns a temporary set of max and min points
    +      may include duplicate points. Does not include qh.GOODpoint
    +    sets qh.NEARzero, qh.MAXabs_coord, qh.MAXsumcoord, qh.MAXwidth
    +         qh.MAXlastcoord, qh.MINlastcoord
    +    initializes qh.max_outside, qh.min_vertex, qh.WAScoplanar, qh.ZEROall_ok
    +
    +  notes:
    +    loop duplicated in qh_detjoggle()
    +
    +  design:
    +    initialize global precision variables
    +    checks definition of REAL...
    +    for each dimension
    +      for each point
    +        collect maximum and minimum point
    +      collect maximum of maximums and minimum of minimums
    +      determine qh.NEARzero for Gaussian Elimination
    +*/
    +setT *qh_maxmin(qhT *qh, pointT *points, int numpoints, int dimension) {
    +  int k;
    +  realT maxcoord, temp;
    +  pointT *minimum, *maximum, *point, *pointtemp;
    +  setT *set;
    +
    +  qh->max_outside= 0.0;
    +  qh->MAXabs_coord= 0.0;
    +  qh->MAXwidth= -REALmax;
    +  qh->MAXsumcoord= 0.0;
    +  qh->min_vertex= 0.0;
    +  qh->WAScoplanar= False;
    +  if (qh->ZEROcentrum)
    +    qh->ZEROall_ok= True;
    +  if (REALmin < REALepsilon && REALmin < REALmax && REALmin > -REALmax
    +  && REALmax > 0.0 && -REALmax < 0.0)
    +    ; /* all ok */
    +  else {
    +    qh_fprintf(qh, qh->ferr, 6011, "qhull error: floating point constants in user.h are wrong\n\
    +REALepsilon %g REALmin %g REALmax %g -REALmax %g\n",
    +             REALepsilon, REALmin, REALmax, -REALmax);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  set= qh_settemp(qh, 2*dimension);
    +  for (k=0; k < dimension; k++) {
    +    if (points == qh->GOODpointp)
    +      minimum= maximum= points + dimension;
    +    else
    +      minimum= maximum= points;
    +    FORALLpoint_(qh, points, numpoints) {
    +      if (point == qh->GOODpointp)
    +        continue;
    +      if (maximum[k] < point[k])
    +        maximum= point;
    +      else if (minimum[k] > point[k])
    +        minimum= point;
    +    }
    +    if (k == dimension-1) {
    +      qh->MINlastcoord= minimum[k];
    +      qh->MAXlastcoord= maximum[k];
    +    }
    +    if (qh->SCALElast && k == dimension-1)
    +      maxcoord= qh->MAXwidth;
    +    else {
    +      maxcoord= fmax_(maximum[k], -minimum[k]);
    +      if (qh->GOODpointp) {
    +        temp= fmax_(qh->GOODpointp[k], -qh->GOODpointp[k]);
    +        maximize_(maxcoord, temp);
    +      }
    +      temp= maximum[k] - minimum[k];
    +      maximize_(qh->MAXwidth, temp);
    +    }
    +    maximize_(qh->MAXabs_coord, maxcoord);
    +    qh->MAXsumcoord += maxcoord;
    +    qh_setappend(qh, &set, maximum);
    +    qh_setappend(qh, &set, minimum);
    +    /* calculation of qh NEARzero is based on Golub & van Loan, 1983,
    +       Eq. 4.4-13 for "Gaussian elimination with complete pivoting".
    +       Golub & van Loan say that n^3 can be ignored and 10 be used in
    +       place of rho */
    +    qh->NEARzero[k]= 80 * qh->MAXsumcoord * REALepsilon;
    +  }
    +  if (qh->IStracing >=1)
    +    qh_printpoints(qh, qh->ferr, "qh_maxmin: found the max and min points(by dim):", set);
    +  return(set);
    +} /* maxmin */
    +
    +/*---------------------------------
    +
    +  qh_maxouter(qh)
    +    return maximum distance from facet to outer plane
    +    normally this is qh.max_outside+qh.DISTround
    +    does not include qh.JOGGLEmax
    +
    +  see:
    +    qh_outerinner()
    +
    +  notes:
    +    need to add another qh.DISTround if testing actual point with computation
    +
    +  for joggle:
    +    qh_setfacetplane() updated qh.max_outer for Wnewvertexmax (max distance to vertex)
    +    need to use Wnewvertexmax since could have a coplanar point for a high
    +      facet that is replaced by a low facet
    +    need to add qh.JOGGLEmax if testing input points
    +*/
    +realT qh_maxouter(qhT *qh) {
    +  realT dist;
    +
    +  dist= fmax_(qh->max_outside, qh->DISTround);
    +  dist += qh->DISTround;
    +  trace4((qh, qh->ferr, 4012, "qh_maxouter: max distance from facet to outer plane is %2.2g max_outside is %2.2g\n", dist, qh->max_outside));
    +  return dist;
    +} /* maxouter */
    +
    +/*---------------------------------
    +
    +  qh_maxsimplex(qh, dim, maxpoints, points, numpoints, simplex )
    +    determines maximum simplex for a set of points
    +    starts from points already in simplex
    +    skips qh.GOODpointp (assumes that it isn't in maxpoints)
    +
    +  returns:
    +    simplex with dim+1 points
    +
    +  notes:
    +    assumes at least pointsneeded points in points
    +    maximizes determinate for x,y,z,w, etc.
    +    uses maxpoints as long as determinate is clearly non-zero
    +
    +  design:
    +    initialize simplex with at least two points
    +      (find points with max or min x coordinate)
    +    for each remaining dimension
    +      add point that maximizes the determinate
    +        (use points from maxpoints first)
    +*/
    +void qh_maxsimplex(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex) {
    +  pointT *point, **pointp, *pointtemp, *maxpoint, *minx=NULL, *maxx=NULL;
    +  boolT nearzero, maxnearzero= False;
    +  int k, sizinit;
    +  realT maxdet= -REALmax, det, mincoord= REALmax, maxcoord= -REALmax;
    +
    +  sizinit= qh_setsize(qh, *simplex);
    +  if (sizinit < 2) {
    +    if (qh_setsize(qh, maxpoints) >= 2) {
    +      FOREACHpoint_(maxpoints) {
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }else {
    +      FORALLpoint_(qh, points, numpoints) {
    +        if (point == qh->GOODpointp)
    +          continue;
    +        if (maxcoord < point[0]) {
    +          maxcoord= point[0];
    +          maxx= point;
    +        }
    +        if (mincoord > point[0]) {
    +          mincoord= point[0];
    +          minx= point;
    +        }
    +      }
    +    }
    +    qh_setunique(qh, simplex, minx);
    +    if (qh_setsize(qh, *simplex) < 2)
    +      qh_setunique(qh, simplex, maxx);
    +    sizinit= qh_setsize(qh, *simplex);
    +    if (sizinit < 2) {
    +      qh_precision(qh, "input has same x coordinate");
    +      if (zzval_(Zsetplane) > qh->hull_dim+1) {
    +        qh_fprintf(qh, qh->ferr, 6012, "qhull precision error (qh_maxsimplex for voronoi_center):\n%d points with the same x coordinate.\n",
    +                 qh_setsize(qh, maxpoints)+numpoints);
    +        qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +      }else {
    +        qh_fprintf(qh, qh->ferr, 6013, "qhull input error: input is less than %d-dimensional since it has the same x coordinate\n", qh->hull_dim);
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +    }
    +  }
    +  for (k=sizinit; k < dim+1; k++) {
    +    maxpoint= NULL;
    +    maxdet= -REALmax;
    +    FOREACHpoint_(maxpoints) {
    +      if (!qh_setin(*simplex, point)) {
    +        det= qh_detsimplex(qh, point, *simplex, k, &nearzero);
    +        if ((det= fabs_(det)) > maxdet) {
    +          maxdet= det;
    +          maxpoint= point;
    +          maxnearzero= nearzero;
    +        }
    +      }
    +    }
    +    if (!maxpoint || maxnearzero) {
    +      zinc_(Zsearchpoints);
    +      if (!maxpoint) {
    +        trace0((qh, qh->ferr, 7, "qh_maxsimplex: searching all points for %d-th initial vertex.\n", k+1));
    +      }else {
    +        trace0((qh, qh->ferr, 8, "qh_maxsimplex: searching all points for %d-th initial vertex, better than p%d det %2.2g\n",
    +                k+1, qh_pointid(qh, maxpoint), maxdet));
    +      }
    +      FORALLpoint_(qh, points, numpoints) {
    +        if (point == qh->GOODpointp)
    +          continue;
    +        if (!qh_setin(*simplex, point)) {
    +          det= qh_detsimplex(qh, point, *simplex, k, &nearzero);
    +          if ((det= fabs_(det)) > maxdet) {
    +            maxdet= det;
    +            maxpoint= point;
    +            maxnearzero= nearzero;
    +          }
    +        }
    +      }
    +    } /* !maxpoint */
    +    if (!maxpoint) {
    +      qh_fprintf(qh, qh->ferr, 6014, "qhull internal error (qh_maxsimplex): not enough points available\n");
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_setappend(qh, simplex, maxpoint);
    +    trace1((qh, qh->ferr, 1002, "qh_maxsimplex: selected point p%d for %d`th initial vertex, det=%2.2g\n",
    +            qh_pointid(qh, maxpoint), k+1, maxdet));
    +  } /* k */
    +} /* maxsimplex */
    +
    +/*---------------------------------
    +
    +  qh_minabsval( normal, dim )
    +    return minimum absolute value of a dim vector
    +*/
    +realT qh_minabsval(realT *normal, int dim) {
    +  realT minval= 0;
    +  realT maxval= 0;
    +  realT *colp;
    +  int k;
    +
    +  for (k=dim, colp=normal; k--; colp++) {
    +    maximize_(maxval, *colp);
    +    minimize_(minval, *colp);
    +  }
    +  return fmax_(maxval, -minval);
    +} /* minabsval */
    +
    +
    +/*---------------------------------
    +
    +  qh_mindif(qh, vecA, vecB, dim )
    +    return index of min abs. difference of two vectors
    +*/
    +int qh_mindiff(realT *vecA, realT *vecB, int dim) {
    +  realT mindiff= REALmax, diff;
    +  realT *vecAp= vecA, *vecBp= vecB;
    +  int k, mink= 0;
    +
    +  for (k=0; k < dim; k++) {
    +    diff= *vecAp++ - *vecBp++;
    +    diff= fabs_(diff);
    +    if (diff < mindiff) {
    +      mindiff= diff;
    +      mink= k;
    +    }
    +  }
    +  return mink;
    +} /* mindiff */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_orientoutside(qh, facet  )
    +    make facet outside oriented via qh.interior_point
    +
    +  returns:
    +    True if facet reversed orientation.
    +*/
    +boolT qh_orientoutside(qhT *qh, facetT *facet) {
    +  int k;
    +  realT dist;
    +
    +  qh_distplane(qh, qh->interior_point, facet, &dist);
    +  if (dist > 0) {
    +    for (k=qh->hull_dim; k--; )
    +      facet->normal[k]= -facet->normal[k];
    +    facet->offset= -facet->offset;
    +    return True;
    +  }
    +  return False;
    +} /* orientoutside */
    +
    +/*---------------------------------
    +
    +  qh_outerinner(qh, facet, outerplane, innerplane  )
    +    if facet and qh.maxoutdone (i.e., qh_check_maxout)
    +      returns outer and inner plane for facet
    +    else
    +      returns maximum outer and inner plane
    +    accounts for qh.JOGGLEmax
    +
    +  see:
    +    qh_maxouter(qh), qh_check_bestdist(), qh_check_points()
    +
    +  notes:
    +    outerplaner or innerplane may be NULL
    +    facet is const
    +    Does not error (QhullFacet)
    +
    +    includes qh.DISTround for actual points
    +    adds another qh.DISTround if testing with floating point arithmetic
    +*/
    +void qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT dist, mindist;
    +  vertexT *vertex, **vertexp;
    +
    +  if (outerplane) {
    +    if (!qh_MAXoutside || !facet || !qh->maxoutdone) {
    +      *outerplane= qh_maxouter(qh);       /* includes qh.DISTround */
    +    }else { /* qh_MAXoutside ... */
    +#if qh_MAXoutside
    +      *outerplane= facet->maxoutside + qh->DISTround;
    +#endif
    +
    +    }
    +    if (qh->JOGGLEmax < REALmax/2)
    +      *outerplane += qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +  }
    +  if (innerplane) {
    +    if (facet) {
    +      mindist= REALmax;
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(qh, vertex->point, facet, &dist);
    +        minimize_(mindist, dist);
    +      }
    +      *innerplane= mindist - qh->DISTround;
    +    }else
    +      *innerplane= qh->min_vertex - qh->DISTround;
    +    if (qh->JOGGLEmax < REALmax/2)
    +      *innerplane -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +  }
    +} /* outerinner */
    +
    +/*---------------------------------
    +
    +  qh_pointdist( point1, point2, dim )
    +    return distance between two points
    +
    +  notes:
    +    returns distance squared if 'dim' is negative
    +*/
    +coordT qh_pointdist(pointT *point1, pointT *point2, int dim) {
    +  coordT dist, diff;
    +  int k;
    +
    +  dist= 0.0;
    +  for (k= (dim > 0 ? dim : -dim); k--; ) {
    +    diff= *point1++ - *point2++;
    +    dist += diff * diff;
    +  }
    +  if (dim > 0)
    +    return(sqrt(dist));
    +  return dist;
    +} /* pointdist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printmatrix(qh, fp, string, rows, numrow, numcol )
    +    print matrix to fp given by row vectors
    +    print string as header
    +    qh may be NULL if fp is defined
    +
    +  notes:
    +    print a vector by qh_printmatrix(qh, fp, "", &vect, 1, len)
    +*/
    +void qh_printmatrix(qhT *qh, FILE *fp, const char *string, realT **rows, int numrow, int numcol) {
    +  realT *rowp;
    +  realT r; /*bug fix*/
    +  int i,k;
    +
    +  qh_fprintf(qh, fp, 9001, "%s\n", string);
    +  for (i=0; i < numrow; i++) {
    +    rowp= rows[i];
    +    for (k=0; k < numcol; k++) {
    +      r= *rowp++;
    +      qh_fprintf(qh, fp, 9002, "%6.3g ", r);
    +    }
    +    qh_fprintf(qh, fp, 9003, "\n");
    +  }
    +} /* printmatrix */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpoints(qh, fp, string, points )
    +    print pointids to fp for a set of points
    +    if string, prints string and 'p' point ids
    +*/
    +void qh_printpoints(qhT *qh, FILE *fp, const char *string, setT *points) {
    +  pointT *point, **pointp;
    +
    +  if (string) {
    +    qh_fprintf(qh, fp, 9004, "%s", string);
    +    FOREACHpoint_(points)
    +      qh_fprintf(qh, fp, 9005, " p%d", qh_pointid(qh, point));
    +    qh_fprintf(qh, fp, 9006, "\n");
    +  }else {
    +    FOREACHpoint_(points)
    +      qh_fprintf(qh, fp, 9007, " %d", qh_pointid(qh, point));
    +    qh_fprintf(qh, fp, 9008, "\n");
    +  }
    +} /* printpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectinput(qh)
    +    project input points using qh.lower_bound/upper_bound and qh->DELAUNAY
    +    if qh.lower_bound[k]=qh.upper_bound[k]= 0,
    +      removes dimension k
    +    if halfspace intersection
    +      removes dimension k from qh.feasible_point
    +    input points in qh->first_point, num_points, input_dim
    +
    +  returns:
    +    new point array in qh->first_point of qh->hull_dim coordinates
    +    sets qh->POINTSmalloc
    +    if qh->DELAUNAY
    +      projects points to paraboloid
    +      lowbound/highbound is also projected
    +    if qh->ATinfinity
    +      adds point "at-infinity"
    +    if qh->POINTSmalloc
    +      frees old point array
    +
    +  notes:
    +    checks that qh.hull_dim agrees with qh.input_dim, PROJECTinput, and DELAUNAY
    +
    +
    +  design:
    +    sets project[k] to -1 (delete), 0 (keep), 1 (add for Delaunay)
    +    determines newdim and newnum for qh->hull_dim and qh->num_points
    +    projects points to newpoints
    +    projects qh.lower_bound to itself
    +    projects qh.upper_bound to itself
    +    if qh->DELAUNAY
    +      if qh->ATINFINITY
    +        projects points to paraboloid
    +        computes "infinity" point as vertex average and 10% above all points
    +      else
    +        uses qh_setdelaunay to project points to paraboloid
    +*/
    +void qh_projectinput(qhT *qh) {
    +  int k,i;
    +  int newdim= qh->input_dim, newnum= qh->num_points;
    +  signed char *project;
    +  int projectsize= (qh->input_dim+1)*sizeof(*project);
    +  pointT *newpoints, *coord, *infinity;
    +  realT paraboloid, maxboloid= 0;
    +
    +  project= (signed char*)qh_memalloc(qh, projectsize);
    +  memset((char*)project, 0, (size_t)projectsize);
    +  for (k=0; k < qh->input_dim; k++) {   /* skip Delaunay bound */
    +    if (qh->lower_bound[k] == 0 && qh->upper_bound[k] == 0) {
    +      project[k]= -1;
    +      newdim--;
    +    }
    +  }
    +  if (qh->DELAUNAY) {
    +    project[k]= 1;
    +    newdim++;
    +    if (qh->ATinfinity)
    +      newnum++;
    +  }
    +  if (newdim != qh->hull_dim) {
    +    qh_memfree(qh, project, projectsize);
    +    qh_fprintf(qh, qh->ferr, 6015, "qhull internal error (qh_projectinput): dimension after projection %d != hull_dim %d\n", newdim, qh->hull_dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (!(newpoints= qh->temp_malloc= (coordT*)qh_malloc(newnum*newdim*sizeof(coordT)))){
    +    qh_memfree(qh, project, projectsize);
    +    qh_fprintf(qh, qh->ferr, 6016, "qhull error: insufficient memory to project %d points\n",
    +           qh->num_points);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  /* qh_projectpoints throws error if mismatched dimensions */
    +  qh_projectpoints(qh, project, qh->input_dim+1, qh->first_point,
    +                    qh->num_points, qh->input_dim, newpoints, newdim);
    +  trace1((qh, qh->ferr, 1003, "qh_projectinput: updating lower and upper_bound\n"));
    +  qh_projectpoints(qh, project, qh->input_dim+1, qh->lower_bound,
    +                    1, qh->input_dim+1, qh->lower_bound, newdim+1);
    +  qh_projectpoints(qh, project, qh->input_dim+1, qh->upper_bound,
    +                    1, qh->input_dim+1, qh->upper_bound, newdim+1);
    +  if (qh->HALFspace) {
    +    if (!qh->feasible_point) {
    +      qh_memfree(qh, project, projectsize);
    +      qh_fprintf(qh, qh->ferr, 6017, "qhull internal error (qh_projectinput): HALFspace defined without qh.feasible_point\n");
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +    qh_projectpoints(qh, project, qh->input_dim, qh->feasible_point,
    +                      1, qh->input_dim, qh->feasible_point, newdim);
    +  }
    +  qh_memfree(qh, project, projectsize);
    +  if (qh->POINTSmalloc)
    +    qh_free(qh->first_point);
    +  qh->first_point= newpoints;
    +  qh->POINTSmalloc= True;
    +  qh->temp_malloc= NULL;
    +  if (qh->DELAUNAY && qh->ATinfinity) {
    +    coord= qh->first_point;
    +    infinity= qh->first_point + qh->hull_dim * qh->num_points;
    +    for (k=qh->hull_dim-1; k--; )
    +      infinity[k]= 0.0;
    +    for (i=qh->num_points; i--; ) {
    +      paraboloid= 0.0;
    +      for (k=0; k < qh->hull_dim-1; k++) {
    +        paraboloid += *coord * *coord;
    +        infinity[k] += *coord;
    +        coord++;
    +      }
    +      *(coord++)= paraboloid;
    +      maximize_(maxboloid, paraboloid);
    +    }
    +    /* coord == infinity */
    +    for (k=qh->hull_dim-1; k--; )
    +      *(coord++) /= qh->num_points;
    +    *(coord++)= maxboloid * 1.1;
    +    qh->num_points++;
    +    trace0((qh, qh->ferr, 9, "qh_projectinput: projected points to paraboloid for Delaunay\n"));
    +  }else if (qh->DELAUNAY)  /* !qh->ATinfinity */
    +    qh_setdelaunay(qh, qh->hull_dim, qh->num_points, qh->first_point);
    +} /* projectinput */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoints(qh, project, n, points, numpoints, dim, newpoints, newdim )
    +    project points/numpoints/dim to newpoints/newdim
    +    if project[k] == -1
    +      delete dimension k
    +    if project[k] == 1
    +      add dimension k by duplicating previous column
    +    n is size of project
    +
    +  notes:
    +    newpoints may be points if only adding dimension at end
    +
    +  design:
    +    check that 'project' and 'newdim' agree
    +    for each dimension
    +      if project == -1
    +        skip dimension
    +      else
    +        determine start of column in newpoints
    +        determine start of column in points
    +          if project == +1, duplicate previous column
    +        copy dimension (column) from points to newpoints
    +*/
    +void qh_projectpoints(qhT *qh, signed char *project, int n, realT *points,
    +        int numpoints, int dim, realT *newpoints, int newdim) {
    +  int testdim= dim, oldk=0, newk=0, i,j=0,k;
    +  realT *newp, *oldp;
    +
    +  for (k=0; k < n; k++)
    +    testdim += project[k];
    +  if (testdim != newdim) {
    +    qh_fprintf(qh, qh->ferr, 6018, "qhull internal error (qh_projectpoints): newdim %d should be %d after projection\n",
    +      newdim, testdim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  for (j=0; j= dim)
    +          continue;
    +        oldp= points+oldk;
    +      }else
    +        oldp= points+oldk++;
    +      for (i=numpoints; i--; ) {
    +        *newp= *oldp;
    +        newp += newdim;
    +        oldp += dim;
    +      }
    +    }
    +    if (oldk >= dim)
    +      break;
    +  }
    +  trace1((qh, qh->ferr, 1004, "qh_projectpoints: projected %d points from dim %d to dim %d\n",
    +    numpoints, dim, newdim));
    +} /* projectpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_rotateinput(qh, rows )
    +    rotate input using row matrix
    +    input points given by qh->first_point, num_points, hull_dim
    +    assumes rows[dim] is a scratch buffer
    +    if qh->POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    rotated input
    +    sets qh->POINTSmalloc
    +
    +  design:
    +    see qh_rotatepoints
    +*/
    +void qh_rotateinput(qhT *qh, realT **rows) {
    +
    +  if (!qh->POINTSmalloc) {
    +    qh->first_point= qh_copypoints(qh, qh->first_point, qh->num_points, qh->hull_dim);
    +    qh->POINTSmalloc= True;
    +  }
    +  qh_rotatepoints(qh, qh->first_point, qh->num_points, qh->hull_dim, rows);
    +}  /* rotateinput */
    +
    +/*---------------------------------
    +
    +  qh_rotatepoints(qh, points, numpoints, dim, row )
    +    rotate numpoints points by a d-dim row matrix
    +    assumes rows[dim] is a scratch buffer
    +
    +  returns:
    +    rotated points in place
    +
    +  design:
    +    for each point
    +      for each coordinate
    +        use row[dim] to compute partial inner product
    +      for each coordinate
    +        rotate by partial inner product
    +*/
    +void qh_rotatepoints(qhT *qh, realT *points, int numpoints, int dim, realT **row) {
    +  realT *point, *rowi, *coord= NULL, sum, *newval;
    +  int i,j,k;
    +
    +  if (qh->IStracing >= 1)
    +    qh_printmatrix(qh, qh->ferr, "qh_rotatepoints: rotate points by", row, dim, dim);
    +  for (point= points, j= numpoints; j--; point += dim) {
    +    newval= row[dim];
    +    for (i=0; i < dim; i++) {
    +      rowi= row[i];
    +      coord= point;
    +      for (sum= 0.0, k= dim; k--; )
    +        sum += *rowi++ * *coord++;
    +      *(newval++)= sum;
    +    }
    +    for (k=dim; k--; )
    +      *(--coord)= *(--newval);
    +  }
    +} /* rotatepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_scaleinput(qh)
    +    scale input points using qh->low_bound/high_bound
    +    input points given by qh->first_point, num_points, hull_dim
    +    if qh->POINTSmalloc, overwrites input points, else mallocs a new array
    +
    +  returns:
    +    scales coordinates of points to low_bound[k], high_bound[k]
    +    sets qh->POINTSmalloc
    +
    +  design:
    +    see qh_scalepoints
    +*/
    +void qh_scaleinput(qhT *qh) {
    +
    +  if (!qh->POINTSmalloc) {
    +    qh->first_point= qh_copypoints(qh, qh->first_point, qh->num_points, qh->hull_dim);
    +    qh->POINTSmalloc= True;
    +  }
    +  qh_scalepoints(qh, qh->first_point, qh->num_points, qh->hull_dim,
    +       qh->lower_bound, qh->upper_bound);
    +}  /* scaleinput */
    +
    +/*---------------------------------
    +
    +  qh_scalelast(qh, points, numpoints, dim, low, high, newhigh )
    +    scale last coordinate to [0,m] for Delaunay triangulations
    +    input points given by points, numpoints, dim
    +
    +  returns:
    +    changes scale of last coordinate from [low, high] to [0, newhigh]
    +    overwrites last coordinate of each point
    +    saves low/high/newhigh in qh.last_low, etc. for qh_setdelaunay()
    +
    +  notes:
    +    when called by qh_setdelaunay, low/high may not match actual data
    +
    +  design:
    +    compute scale and shift factors
    +    apply to last coordinate of each point
    +*/
    +void qh_scalelast(qhT *qh, coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh) {
    +  realT scale, shift;
    +  coordT *coord;
    +  int i;
    +  boolT nearzero= False;
    +
    +  trace4((qh, qh->ferr, 4013, "qh_scalelast: scale last coordinate from [%2.2g, %2.2g] to [0,%2.2g]\n",
    +    low, high, newhigh));
    +  qh->last_low= low;
    +  qh->last_high= high;
    +  qh->last_newhigh= newhigh;
    +  scale= qh_divzero(newhigh, high - low,
    +                  qh->MINdenom_1, &nearzero);
    +  if (nearzero) {
    +    if (qh->DELAUNAY)
    +      qh_fprintf(qh, qh->ferr, 6019, "qhull input error: can not scale last coordinate.  Input is cocircular\n   or cospherical.   Use option 'Qz' to add a point at infinity.\n");
    +    else
    +      qh_fprintf(qh, qh->ferr, 6020, "qhull input error: can not scale last coordinate.  New bounds [0, %2.2g] are too wide for\nexisting bounds [%2.2g, %2.2g] (width %2.2g)\n",
    +                newhigh, low, high, high-low);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  shift= - low * newhigh / (high-low);
    +  coord= points + dim - 1;
    +  for (i=numpoints; i--; coord += dim)
    +    *coord= *coord * scale + shift;
    +} /* scalelast */
    +
    +/*---------------------------------
    +
    +  qh_scalepoints(qh, points, numpoints, dim, newlows, newhighs )
    +    scale points to new lowbound and highbound
    +    retains old bound when newlow= -REALmax or newhigh= +REALmax
    +
    +  returns:
    +    scaled points
    +    overwrites old points
    +
    +  design:
    +    for each coordinate
    +      compute current low and high bound
    +      compute scale and shift factors
    +      scale all points
    +      enforce new low and high bound for all points
    +*/
    +void qh_scalepoints(qhT *qh, pointT *points, int numpoints, int dim,
    +        realT *newlows, realT *newhighs) {
    +  int i,k;
    +  realT shift, scale, *coord, low, high, newlow, newhigh, mincoord, maxcoord;
    +  boolT nearzero= False;
    +
    +  for (k=0; k < dim; k++) {
    +    newhigh= newhighs[k];
    +    newlow= newlows[k];
    +    if (newhigh > REALmax/2 && newlow < -REALmax/2)
    +      continue;
    +    low= REALmax;
    +    high= -REALmax;
    +    for (i=numpoints, coord=points+k; i--; coord += dim) {
    +      minimize_(low, *coord);
    +      maximize_(high, *coord);
    +    }
    +    if (newhigh > REALmax/2)
    +      newhigh= high;
    +    if (newlow < -REALmax/2)
    +      newlow= low;
    +    if (qh->DELAUNAY && k == dim-1 && newhigh < newlow) {
    +      qh_fprintf(qh, qh->ferr, 6021, "qhull input error: 'Qb%d' or 'QB%d' inverts paraboloid since high bound %.2g < low bound %.2g\n",
    +               k, k, newhigh, newlow);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    scale= qh_divzero(newhigh - newlow, high - low,
    +                  qh->MINdenom_1, &nearzero);
    +    if (nearzero) {
    +      qh_fprintf(qh, qh->ferr, 6022, "qhull input error: %d'th dimension's new bounds [%2.2g, %2.2g] too wide for\nexisting bounds [%2.2g, %2.2g]\n",
    +              k, newlow, newhigh, low, high);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    shift= (newlow * high - low * newhigh)/(high-low);
    +    coord= points+k;
    +    for (i=numpoints; i--; coord += dim)
    +      *coord= *coord * scale + shift;
    +    coord= points+k;
    +    if (newlow < newhigh) {
    +      mincoord= newlow;
    +      maxcoord= newhigh;
    +    }else {
    +      mincoord= newhigh;
    +      maxcoord= newlow;
    +    }
    +    for (i=numpoints; i--; coord += dim) {
    +      minimize_(*coord, maxcoord);  /* because of roundoff error */
    +      maximize_(*coord, mincoord);
    +    }
    +    trace0((qh, qh->ferr, 10, "qh_scalepoints: scaled %d'th coordinate [%2.2g, %2.2g] to [%.2g, %.2g] for %d points by %2.2g and shifted %2.2g\n",
    +      k, low, high, newlow, newhigh, numpoints, scale, shift));
    +  }
    +} /* scalepoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelaunay(qh, dim, count, points )
    +    project count points to dim-d paraboloid for Delaunay triangulation
    +
    +    dim is one more than the dimension of the input set
    +    assumes dim is at least 3 (i.e., at least a 2-d Delaunay triangulation)
    +
    +    points is a dim*count realT array.  The first dim-1 coordinates
    +    are the coordinates of the first input point.  array[dim] is
    +    the first coordinate of the second input point.  array[2*dim] is
    +    the first coordinate of the third input point.
    +
    +    if qh.last_low defined (i.e., 'Qbb' called qh_scalelast)
    +      calls qh_scalelast to scale the last coordinate the same as the other points
    +
    +  returns:
    +    for each point
    +      sets point[dim-1] to sum of squares of coordinates
    +    scale points to 'Qbb' if needed
    +
    +  notes:
    +    to project one point, use
    +      qh_setdelaunay(qh, qh->hull_dim, 1, point)
    +
    +    Do not use options 'Qbk', 'QBk', or 'QbB' since they scale
    +    the coordinates after the original projection.
    +
    +*/
    +void qh_setdelaunay(qhT *qh, int dim, int count, pointT *points) {
    +  int i, k;
    +  coordT *coordp, coord;
    +  realT paraboloid;
    +
    +  trace0((qh, qh->ferr, 11, "qh_setdelaunay: project %d points to paraboloid for Delaunay triangulation\n", count));
    +  coordp= points;
    +  for (i=0; i < count; i++) {
    +    coord= *coordp++;
    +    paraboloid= coord*coord;
    +    for (k=dim-2; k--; ) {
    +      coord= *coordp++;
    +      paraboloid += coord*coord;
    +    }
    +    *coordp++ = paraboloid;
    +  }
    +  if (qh->last_low < REALmax/2)
    +    qh_scalelast(qh, points, count, dim, qh->last_low, qh->last_high, qh->last_newhigh);
    +} /* setdelaunay */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace(qh, dim, coords, nextp, normal, offset, feasible )
    +    set point to dual of halfspace relative to feasible point
    +    halfspace is normal coefficients and offset.
    +
    +  returns:
    +    false and prints error if feasible point is outside of hull
    +    overwrites coordinates for point at dim coords
    +    nextp= next point (coords)
    +    does not call qh_errexit
    +
    +  design:
    +    compute distance from feasible point to halfspace
    +    divide each normal coefficient by -dist
    +*/
    +boolT qh_sethalfspace(qhT *qh, int dim, coordT *coords, coordT **nextp,
    +         coordT *normal, coordT *offset, coordT *feasible) {
    +  coordT *normp= normal, *feasiblep= feasible, *coordp= coords;
    +  realT dist;
    +  realT r; /*bug fix*/
    +  int k;
    +  boolT zerodiv;
    +
    +  dist= *offset;
    +  for (k=dim; k--; )
    +    dist += *(normp++) * *(feasiblep++);
    +  if (dist > 0)
    +    goto LABELerroroutside;
    +  normp= normal;
    +  if (dist < -qh->MINdenom) {
    +    for (k=dim; k--; )
    +      *(coordp++)= *(normp++) / -dist;
    +  }else {
    +    for (k=dim; k--; ) {
    +      *(coordp++)= qh_divzero(*(normp++), -dist, qh->MINdenom_1, &zerodiv);
    +      if (zerodiv)
    +        goto LABELerroroutside;
    +    }
    +  }
    +  *nextp= coordp;
    +  if (qh->IStracing >= 4) {
    +    qh_fprintf(qh, qh->ferr, 8021, "qh_sethalfspace: halfspace at offset %6.2g to point: ", *offset);
    +    for (k=dim, coordp=coords; k--; ) {
    +      r= *coordp++;
    +      qh_fprintf(qh, qh->ferr, 8022, " %6.2g", r);
    +    }
    +    qh_fprintf(qh, qh->ferr, 8023, "\n");
    +  }
    +  return True;
    +LABELerroroutside:
    +  feasiblep= feasible;
    +  normp= normal;
    +  qh_fprintf(qh, qh->ferr, 6023, "qhull input error: feasible point is not clearly inside halfspace\nfeasible point: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh, qh->ferr, 8024, qh_REAL_1, r=*(feasiblep++));
    +  qh_fprintf(qh, qh->ferr, 8025, "\n     halfspace: ");
    +  for (k=dim; k--; )
    +    qh_fprintf(qh, qh->ferr, 8026, qh_REAL_1, r=*(normp++));
    +  qh_fprintf(qh, qh->ferr, 8027, "\n     at offset: ");
    +  qh_fprintf(qh, qh->ferr, 8028, qh_REAL_1, *offset);
    +  qh_fprintf(qh, qh->ferr, 8029, " and distance: ");
    +  qh_fprintf(qh, qh->ferr, 8030, qh_REAL_1, dist);
    +  qh_fprintf(qh, qh->ferr, 8031, "\n");
    +  return False;
    +} /* sethalfspace */
    +
    +/*---------------------------------
    +
    +  qh_sethalfspace_all(qh, dim, count, halfspaces, feasible )
    +    generate dual for halfspace intersection with feasible point
    +    array of count halfspaces
    +      each halfspace is normal coefficients followed by offset
    +      the origin is inside the halfspace if the offset is negative
    +    feasible is a point inside all halfspaces (http://www.qhull.org/html/qhalf.htm#notes)
    +
    +  returns:
    +    malloc'd array of count X dim-1 points
    +
    +  notes:
    +    call before qh_init_B or qh_initqhull_globals
    +    free memory when done
    +    unused/untested code: please email bradb@shore.net if this works ok for you
    +    if using option 'Fp', qh->feasible_point must be set (e.g., to 'feasible')
    +    qh->feasible_point is a malloc'd array that is freed by qh_freebuffers.
    +
    +  design:
    +    see qh_sethalfspace
    +*/
    +coordT *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible) {
    +  int i, newdim;
    +  pointT *newpoints;
    +  coordT *coordp, *normalp, *offsetp;
    +
    +  trace0((qh, qh->ferr, 12, "qh_sethalfspace_all: compute dual for halfspace intersection\n"));
    +  newdim= dim - 1;
    +  if (!(newpoints=(coordT*)qh_malloc(count*newdim*sizeof(coordT)))){
    +    qh_fprintf(qh, qh->ferr, 6024, "qhull error: insufficient memory to compute dual of %d halfspaces\n",
    +          count);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  coordp= newpoints;
    +  normalp= halfspaces;
    +  for (i=0; i < count; i++) {
    +    offsetp= normalp + newdim;
    +    if (!qh_sethalfspace(qh, newdim, coordp, &coordp, normalp, offsetp, feasible)) {
    +      qh_free(newpoints);  /* feasible is not inside halfspace as reported by qh_sethalfspace */
    +      qh_fprintf(qh, qh->ferr, 8032, "The halfspace was at index %d\n", i);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    normalp= offsetp + 1;
    +  }
    +  return newpoints;
    +} /* sethalfspace_all */
    +
    +
    +/*---------------------------------
    +
    +  qh_sharpnewfacets(qh)
    +
    +  returns:
    +    true if could be an acute angle (facets in different quadrants)
    +
    +  notes:
    +    for qh_findbest
    +
    +  design:
    +    for all facets on qh.newfacet_list
    +      if two facets are in different quadrants
    +        set issharp
    +*/
    +boolT qh_sharpnewfacets(qhT *qh) {
    +  facetT *facet;
    +  boolT issharp = False;
    +  int *quadrant, k;
    +
    +  quadrant= (int*)qh_memalloc(qh, qh->hull_dim * sizeof(int));
    +  FORALLfacet_(qh->newfacet_list) {
    +    if (facet == qh->newfacet_list) {
    +      for (k=qh->hull_dim; k--; )
    +        quadrant[ k]= (facet->normal[ k] > 0);
    +    }else {
    +      for (k=qh->hull_dim; k--; ) {
    +        if (quadrant[ k] != (facet->normal[ k] > 0)) {
    +          issharp= True;
    +          break;
    +        }
    +      }
    +    }
    +    if (issharp)
    +      break;
    +  }
    +  qh_memfree(qh, quadrant, qh->hull_dim * sizeof(int));
    +  trace3((qh, qh->ferr, 3001, "qh_sharpnewfacets: %d\n", issharp));
    +  return issharp;
    +} /* sharpnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_voronoi_center(qh, dim, points )
    +    return Voronoi center for a set of points
    +    dim is the orginal dimension of the points
    +    gh.gm_matrix/qh.gm_row are scratch buffers
    +
    +  returns:
    +    center as a temporary point (qh_memalloc)
    +    if non-simplicial,
    +      returns center for max simplex of points
    +
    +  notes:
    +    only called by qh_facetcenter
    +    from Bowyer & Woodwark, A Programmer's Geometry, 1983, p. 65
    +
    +  design:
    +    if non-simplicial
    +      determine max simplex for points
    +    translate point0 of simplex to origin
    +    compute sum of squares of diagonal
    +    compute determinate
    +    compute Voronoi center (see Bowyer & Woodwark)
    +*/
    +pointT *qh_voronoi_center(qhT *qh, int dim, setT *points) {
    +  pointT *point, **pointp, *point0;
    +  pointT *center= (pointT*)qh_memalloc(qh, qh->center_size);
    +  setT *simplex;
    +  int i, j, k, size= qh_setsize(qh, points);
    +  coordT *gmcoord;
    +  realT *diffp, sum2, *sum2row, *sum2p, det, factor;
    +  boolT nearzero, infinite;
    +
    +  if (size == dim+1)
    +    simplex= points;
    +  else if (size < dim+1) {
    +    qh_memfree(qh, center, qh->center_size);
    +    qh_fprintf(qh, qh->ferr, 6025, "qhull internal error (qh_voronoi_center):\n  need at least %d points to construct a Voronoi center\n",
    +             dim+1);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    simplex= points;  /* never executed -- avoids warning */
    +  }else {
    +    simplex= qh_settemp(qh, dim+1);
    +    qh_maxsimplex(qh, dim, points, NULL, 0, &simplex);
    +  }
    +  point0= SETfirstt_(simplex, pointT);
    +  gmcoord= qh->gm_matrix;
    +  for (k=0; k < dim; k++) {
    +    qh->gm_row[k]= gmcoord;
    +    FOREACHpoint_(simplex) {
    +      if (point != point0)
    +        *(gmcoord++)= point[k] - point0[k];
    +    }
    +  }
    +  sum2row= gmcoord;
    +  for (i=0; i < dim; i++) {
    +    sum2= 0.0;
    +    for (k=0; k < dim; k++) {
    +      diffp= qh->gm_row[k] + i;
    +      sum2 += *diffp * *diffp;
    +    }
    +    *(gmcoord++)= sum2;
    +  }
    +  det= qh_determinant(qh, qh->gm_row, dim, &nearzero);
    +  factor= qh_divzero(0.5, det, qh->MINdenom, &infinite);
    +  if (infinite) {
    +    for (k=dim; k--; )
    +      center[k]= qh_INFINITE;
    +    if (qh->IStracing)
    +      qh_printpoints(qh, qh->ferr, "qh_voronoi_center: at infinity for ", simplex);
    +  }else {
    +    for (i=0; i < dim; i++) {
    +      gmcoord= qh->gm_matrix;
    +      sum2p= sum2row;
    +      for (k=0; k < dim; k++) {
    +        qh->gm_row[k]= gmcoord;
    +        if (k == i) {
    +          for (j=dim; j--; )
    +            *(gmcoord++)= *sum2p++;
    +        }else {
    +          FOREACHpoint_(simplex) {
    +            if (point != point0)
    +              *(gmcoord++)= point[k] - point0[k];
    +          }
    +        }
    +      }
    +      center[i]= qh_determinant(qh, qh->gm_row, dim, &nearzero)*factor + point0[i];
    +    }
    +#ifndef qh_NOtrace
    +    if (qh->IStracing >= 3) {
    +      qh_fprintf(qh, qh->ferr, 8033, "qh_voronoi_center: det %2.2g factor %2.2g ", det, factor);
    +      qh_printmatrix(qh, qh->ferr, "center:", ¢er, 1, dim);
    +      if (qh->IStracing >= 5) {
    +        qh_printpoints(qh, qh->ferr, "points", simplex);
    +        FOREACHpoint_(simplex)
    +          qh_fprintf(qh, qh->ferr, 8034, "p%d dist %.2g, ", qh_pointid(qh, point),
    +                   qh_pointdist(point, center, dim));
    +        qh_fprintf(qh, qh->ferr, 8035, "\n");
    +      }
    +    }
    +#endif
    +  }
    +  if (simplex != points)
    +    qh_settempfree(qh, &simplex);
    +  return center;
    +} /* voronoi_center */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/geom_r.c b/xs/src/qhull/src/libqhull_r/geom_r.c
    new file mode 100644
    index 0000000000..8104813cad
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/geom_r.c
    @@ -0,0 +1,1234 @@
    +/*
      ---------------------------------
    +
    +   geom_r.c
    +   geometric routines of qhull
    +
    +   see qh-geom_r.htm and geom_r.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/geom_r.c#2 $$Change: 1995 $
    +   $DateTime: 2015/10/13 21:59:42 $$Author: bbarber $
    +
    +   infrequent code goes into geom2_r.c
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*---------------------------------
    +
    +  qh_distplane(qh, point, facet, dist )
    +    return distance from point to facet
    +
    +  returns:
    +    dist
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    dist > 0 if point is above facet (i.e., outside)
    +    does not error (for qh_sortfacets, qh_outerinner)
    +
    +  see:
    +    qh_distnorm in geom2_r.c
    +    qh_distplane [geom_r.c], QhullFacet::distance, and QhullHyperplane::distance are copies
    +*/
    +void qh_distplane(qhT *qh, pointT *point, facetT *facet, realT *dist) {
    +  coordT *normal= facet->normal, *coordp, randr;
    +  int k;
    +
    +  switch (qh->hull_dim){
    +  case 2:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1];
    +    break;
    +  case 3:
    +    *dist= facet->offset + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
    +    break;
    +  case 4:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
    +    break;
    +  case 5:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
    +    break;
    +  case 6:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
    +    break;
    +  case 7:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
    +    break;
    +  case 8:
    +    *dist= facet->offset+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
    +    break;
    +  default:
    +    *dist= facet->offset;
    +    coordp= point;
    +    for (k=qh->hull_dim; k--; )
    +      *dist += *coordp++ * *normal++;
    +    break;
    +  }
    +  zinc_(Zdistplane);
    +  if (!qh->RANDOMdist && qh->IStracing < 4)
    +    return;
    +  if (qh->RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    *dist += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh->RANDOMfactor * qh->MAXabs_coord;
    +  }
    +  if (qh->IStracing >= 4) {
    +    qh_fprintf(qh, qh->ferr, 8001, "qh_distplane: ");
    +    qh_fprintf(qh, qh->ferr, 8002, qh_REAL_1, *dist);
    +    qh_fprintf(qh, qh->ferr, 8003, "from p%d to f%d\n", qh_pointid(qh, point), facet->id);
    +  }
    +  return;
    +} /* distplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbest(qh, point, startfacet, bestoutside, qh_ISnewfacets, qh_NOupper, dist, isoutside, numpart )
    +    find facet that is furthest below a point
    +    for upperDelaunay facets
    +      returns facet only if !qh_NOupper and clearly above
    +
    +  input:
    +    starts search at 'startfacet' (can not be flipped)
    +    if !bestoutside(qh_ALL), stops at qh.MINoutside
    +
    +  returns:
    +    best facet (reports error if NULL)
    +    early out if isoutside defined and bestdist > qh.MINoutside
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart counts the number of distance tests
    +
    +  see also:
    +    qh_findbestnew()
    +
    +  notes:
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    after qh_distplane, this and qh_partitionpoint are the most expensive in 3-d
    +      avoid calls to distplane, function calls, and real number operations.
    +    caller traces result
    +    Optimized for outside points.   Tried recording a search set for qh_findhorizon.
    +    Made code more complicated.
    +
    +  when called by qh_partitionvisible():
    +    indicated by qh_ISnewfacets
    +    qh.newfacet_list is list of simplicial, new facets
    +    qh_findbestnew set if qh_sharpnewfacets returns True (to use qh_findbestnew)
    +    qh.bestfacet_notsharp set if qh_sharpnewfacets returns False
    +
    +  when called by qh_findfacet(), qh_partitionpoint(), qh_partitioncoplanar(),
    +                 qh_check_bestdist(), qh_addpoint()
    +    indicated by !qh_ISnewfacets
    +    returns best facet in neighborhood of given facet
    +      this is best facet overall if dist > -   qh.MAXcoplanar
    +        or hull has at least a "spherical" curvature
    +
    +  design:
    +    initialize and test for early exit
    +    repeat while there are better facets
    +      for each neighbor of facet
    +        exit if outside facet found
    +        test for better facet
    +    if point is inside and partitioning
    +      test for new facets with a "sharp" intersection
    +      if so, future calls go to qh_findbestnew()
    +    test horizon facets
    +*/
    +facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *bestfacet= NULL, *lastfacet= NULL;
    +  int oldtrace= qh->IStracing;
    +  unsigned int visitid= ++qh->visit_id;
    +  int numpartnew=0;
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  zinc_(Zfindbest);
    +  if (qh->IStracing >= 3 || (qh->TRACElevel && qh->TRACEpoint >= 0 && qh->TRACEpoint == qh_pointid(qh, point))) {
    +    if (qh->TRACElevel > qh->IStracing)
    +      qh->IStracing= qh->TRACElevel;
    +    qh_fprintf(qh, qh->ferr, 8004, "qh_findbest: point p%d starting at f%d isnewfacets? %d, unless %d exit if > %2.2g\n",
    +             qh_pointid(qh, point), startfacet->id, isnewfacets, bestoutside, qh->MINoutside);
    +    qh_fprintf(qh, qh->ferr, 8005, "  testhorizon? %d noupper? %d", testhorizon, noupper);
    +    qh_fprintf(qh, qh->ferr, 8006, "  Last point added was p%d.", qh->furthest_id);
    +    qh_fprintf(qh, qh->ferr, 8007, "  Last merge was #%d.  max_outside %2.2g\n", zzval_(Ztotmerge), qh->max_outside);
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  if (!startfacet->flipped) {  /* test startfacet */
    +    *numpart= 1;
    +    qh_distplane(qh, point, startfacet, dist);  /* this code is duplicated below */
    +    if (!bestoutside && *dist >= qh->MINoutside
    +    && (!startfacet->upperdelaunay || !noupper)) {
    +      bestfacet= startfacet;
    +      goto LABELreturn_best;
    +    }
    +    bestdist= *dist;
    +    if (!startfacet->upperdelaunay) {
    +      bestfacet= startfacet;
    +    }
    +  }else
    +    *numpart= 0;
    +  startfacet->visitid= visitid;
    +  facet= startfacet;
    +  while (facet) {
    +    trace4((qh, qh->ferr, 4001, "qh_findbest: neighbors of f%d, bestdist %2.2g f%d\n",
    +                facet->id, bestdist, getid_(bestfacet)));
    +    lastfacet= facet;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->newfacet && isnewfacets)
    +        continue;
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {  /* code duplicated above */
    +        (*numpart)++;
    +        qh_distplane(qh, point, neighbor, dist);
    +        if (*dist > bestdist) {
    +          if (!bestoutside && *dist >= qh->MINoutside
    +          && (!neighbor->upperdelaunay || !noupper)) {
    +            bestfacet= neighbor;
    +            goto LABELreturn_best;
    +          }
    +          if (!neighbor->upperdelaunay) {
    +            bestfacet= neighbor;
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }else if (!bestfacet) {
    +            bestdist= *dist;
    +            break; /* switch to neighbor */
    +          }
    +        } /* end of *dist>bestdist */
    +      } /* end of !flipped */
    +    } /* end of FOREACHneighbor */
    +    facet= neighbor;  /* non-NULL only if *dist>bestdist */
    +  } /* end of while facet (directed search) */
    +  if (isnewfacets) {
    +    if (!bestfacet) {
    +      bestdist= -REALmax/2;
    +      bestfacet= qh_findbestnew(qh, point, startfacet->next, &bestdist, bestoutside, isoutside, &numpartnew);
    +      testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +    }else if (!qh->findbest_notsharp && bestdist < - qh->DISTround) {
    +      if (qh_sharpnewfacets(qh)) {
    +        /* seldom used, qh_findbestnew will retest all facets */
    +        zinc_(Zfindnewsharp);
    +        bestfacet= qh_findbestnew(qh, point, bestfacet, &bestdist, bestoutside, isoutside, &numpartnew);
    +        testhorizon= False; /* qh_findbestnew calls qh_findbesthorizon */
    +        qh->findbestnew= True;
    +      }else
    +        qh->findbest_notsharp= True;
    +    }
    +  }
    +  if (!bestfacet)
    +    bestfacet= qh_findbestlower(qh, lastfacet, point, &bestdist, numpart);
    +  if (testhorizon)
    +    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, bestfacet, noupper, &bestdist, &numpartnew);
    +  *dist= bestdist;
    +  if (isoutside && bestdist < qh->MINoutside)
    +    *isoutside= False;
    +LABELreturn_best:
    +  zadd_(Zfindbesttot, *numpart);
    +  zmax_(Zfindbestmax, *numpart);
    +  (*numpart) += numpartnew;
    +  qh->IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbest */
    +
    +
    +/*---------------------------------
    +
    +  qh_findbesthorizon(qh, qh_IScheckmax, point, startfacet, qh_NOupper, &bestdist, &numpart )
    +    search coplanar and better horizon facets from startfacet/bestdist
    +    ischeckmax turns off statistics and minsearch update
    +    all arguments must be initialized
    +  returns(ischeckmax):
    +    best facet
    +  returns(!ischeckmax):
    +    best facet that is not upperdelaunay
    +    allows upperdelaunay that is clearly outside
    +  returns:
    +    bestdist is distance to bestfacet
    +    numpart -- updates number of distance tests
    +
    +  notes:
    +    no early out -- use qh_findbest() or qh_findbestnew()
    +    Searches coplanar or better horizon facets
    +
    +  when called by qh_check_maxout() (qh_IScheckmax)
    +    startfacet must be closest to the point
    +      Otherwise, if point is beyond and below startfacet, startfacet may be a local minimum
    +      even though other facets are below the point.
    +    updates facet->maxoutside for good, visited facets
    +    may return NULL
    +
    +    searchdist is qh.max_outside + 2 * DISTround
    +      + max( MINvisible('Vn'), MAXcoplanar('Un'));
    +    This setting is a guess.  It must be at least max_outside + 2*DISTround
    +    because a facet may have a geometric neighbor across a vertex
    +
    +  design:
    +    for each horizon facet of coplanar best facets
    +      continue if clearly inside
    +      unless upperdelaunay or clearly outside
    +         update best facet
    +*/
    +facetT *qh_findbesthorizon(qhT *qh, boolT ischeckmax, pointT* point, facetT *startfacet, boolT noupper, realT *bestdist, int *numpart) {
    +  facetT *bestfacet= startfacet;
    +  realT dist;
    +  facetT *neighbor, **neighborp, *facet;
    +  facetT *nextfacet= NULL; /* optimize last facet of coplanarfacetset */
    +  int numpartinit= *numpart, coplanarfacetset_size;
    +  unsigned int visitid= ++qh->visit_id;
    +  boolT newbest= False; /* for tracing */
    +  realT minsearch, searchdist;  /* skip facets that are too far from point */
    +
    +  if (!ischeckmax) {
    +    zinc_(Zfindhorizon);
    +  }else {
    +#if qh_MAXoutside
    +    if ((!qh->ONLYgood || startfacet->good) && *bestdist > startfacet->maxoutside)
    +      startfacet->maxoutside= *bestdist;
    +#endif
    +  }
    +  searchdist= qh_SEARCHdist; /* multiple of qh.max_outside and precision constants */
    +  minsearch= *bestdist - searchdist;
    +  if (ischeckmax) {
    +    /* Always check coplanar facets.  Needed for RBOX 1000 s Z1 G1e-13 t996564279 | QHULL Tv */
    +    minimize_(minsearch, -searchdist);
    +  }
    +  coplanarfacetset_size= 0;
    +  facet= startfacet;
    +  while (True) {
    +    trace4((qh, qh->ferr, 4002, "qh_findbesthorizon: neighbors of f%d bestdist %2.2g f%d ischeckmax? %d noupper? %d minsearch %2.2g searchdist %2.2g\n",
    +                facet->id, *bestdist, getid_(bestfacet), ischeckmax, noupper,
    +                minsearch, searchdist));
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == visitid)
    +        continue;
    +      neighbor->visitid= visitid;
    +      if (!neighbor->flipped) {
    +        qh_distplane(qh, point, neighbor, &dist);
    +        (*numpart)++;
    +        if (dist > *bestdist) {
    +          if (!neighbor->upperdelaunay || ischeckmax || (!noupper && dist >= qh->MINoutside)) {
    +            bestfacet= neighbor;
    +            *bestdist= dist;
    +            newbest= True;
    +            if (!ischeckmax) {
    +              minsearch= dist - searchdist;
    +              if (dist > *bestdist + searchdist) {
    +                zinc_(Zfindjump);  /* everything in qh.coplanarfacetset at least searchdist below */
    +                coplanarfacetset_size= 0;
    +              }
    +            }
    +          }
    +        }else if (dist < minsearch)
    +          continue;  /* if ischeckmax, dist can't be positive */
    +#if qh_MAXoutside
    +        if (ischeckmax && dist > neighbor->maxoutside)
    +          neighbor->maxoutside= dist;
    +#endif
    +      } /* end of !flipped */
    +      if (nextfacet) {
    +        if (!coplanarfacetset_size++) {
    +          SETfirst_(qh->coplanarfacetset)= nextfacet;
    +          SETtruncate_(qh->coplanarfacetset, 1);
    +        }else
    +          qh_setappend(qh, &qh->coplanarfacetset, nextfacet); /* Was needed for RBOX 1000 s W1e-13 P0 t996547055 | QHULL d Qbb Qc Tv
    +                                                 and RBOX 1000 s Z1 G1e-13 t996564279 | qhull Tv  */
    +      }
    +      nextfacet= neighbor;
    +    } /* end of EACHneighbor */
    +    facet= nextfacet;
    +    if (facet)
    +      nextfacet= NULL;
    +    else if (!coplanarfacetset_size)
    +      break;
    +    else if (!--coplanarfacetset_size) {
    +      facet= SETfirstt_(qh->coplanarfacetset, facetT);
    +      SETtruncate_(qh->coplanarfacetset, 0);
    +    }else
    +      facet= (facetT*)qh_setdellast(qh->coplanarfacetset);
    +  } /* while True, for each facet in qh.coplanarfacetset */
    +  if (!ischeckmax) {
    +    zadd_(Zfindhorizontot, *numpart - numpartinit);
    +    zmax_(Zfindhorizonmax, *numpart - numpartinit);
    +    if (newbest)
    +      zinc_(Zparthorizon);
    +  }
    +  trace4((qh, qh->ferr, 4003, "qh_findbesthorizon: newbest? %d bestfacet f%d bestdist %2.2g\n", newbest, getid_(bestfacet), *bestdist));
    +  return bestfacet;
    +}  /* findbesthorizon */
    +
    +/*---------------------------------
    +
    +  qh_findbestnew(qh, point, startfacet, dist, isoutside, numpart )
    +    find best newfacet for point
    +    searches all of qh.newfacet_list starting at startfacet
    +    searches horizon facets of coplanar best newfacets
    +    searches all facets if startfacet == qh.facet_list
    +  returns:
    +    best new or horizon facet that is not upperdelaunay
    +    early out if isoutside and not 'Qf'
    +    dist is distance to facet
    +    isoutside is true if point is outside of facet
    +    numpart is number of distance tests
    +
    +  notes:
    +    Always used for merged new facets (see qh_USEfindbestnew)
    +    Avoids upperdelaunay facet unless (isoutside and outside)
    +
    +    Uses qh.visit_id, qh.coplanarfacetset.
    +    If share visit_id with qh_findbest, coplanarfacetset is incorrect.
    +
    +    If merging (testhorizon), searches horizon facets of coplanar best facets because
    +    a point maybe coplanar to the bestfacet, below its horizon facet,
    +    and above a horizon facet of a coplanar newfacet.  For example,
    +      rbox 1000 s Z1 G1e-13 | qhull
    +      rbox 1000 s W1e-13 P0 t992110337 | QHULL d Qbb Qc
    +
    +    qh_findbestnew() used if
    +       qh_sharpnewfacets -- newfacets contains a sharp angle
    +       if many merges, qh_premerge found a merge, or 'Qf' (qh.findbestnew)
    +
    +  see also:
    +    qh_partitionall() and qh_findbest()
    +
    +  design:
    +    for each new facet starting from startfacet
    +      test distance from point to facet
    +      return facet if clearly outside
    +      unless upperdelaunay and a lowerdelaunay exists
    +         update best facet
    +    test horizon facets
    +*/
    +facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet,
    +           realT *dist, boolT bestoutside, boolT *isoutside, int *numpart) {
    +  realT bestdist= -REALmax/2;
    +  facetT *bestfacet= NULL, *facet;
    +  int oldtrace= qh->IStracing, i;
    +  unsigned int visitid= ++qh->visit_id;
    +  realT distoutside= 0.0;
    +  boolT isdistoutside; /* True if distoutside is defined */
    +  boolT testhorizon = True; /* needed if precise, e.g., rbox c D6 | qhull Q0 Tv */
    +
    +  if (!startfacet) {
    +    if (qh->MERGING)
    +      qh_fprintf(qh, qh->ferr, 6001, "qhull precision error (qh_findbestnew): merging has formed and deleted a cone of new facets.  Can not continue.\n");
    +    else
    +      qh_fprintf(qh, qh->ferr, 6002, "qhull internal error (qh_findbestnew): no new facets for point p%d\n",
    +              qh->furthest_id);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  zinc_(Zfindnew);
    +  if (qh->BESToutside || bestoutside)
    +    isdistoutside= False;
    +  else {
    +    isdistoutside= True;
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +  }
    +  if (isoutside)
    +    *isoutside= True;
    +  *numpart= 0;
    +  if (qh->IStracing >= 3 || (qh->TRACElevel && qh->TRACEpoint >= 0 && qh->TRACEpoint == qh_pointid(qh, point))) {
    +    if (qh->TRACElevel > qh->IStracing)
    +      qh->IStracing= qh->TRACElevel;
    +    qh_fprintf(qh, qh->ferr, 8008, "qh_findbestnew: point p%d facet f%d. Stop? %d if dist > %2.2g\n",
    +             qh_pointid(qh, point), startfacet->id, isdistoutside, distoutside);
    +    qh_fprintf(qh, qh->ferr, 8009, "  Last point added p%d visitid %d.",  qh->furthest_id, visitid);
    +    qh_fprintf(qh, qh->ferr, 8010, "  Last merge was #%d.\n", zzval_(Ztotmerge));
    +  }
    +  /* visit all new facets starting with startfacet, maybe qh->facet_list */
    +  for (i=0, facet=startfacet; i < 2; i++, facet= qh->newfacet_list) {
    +    FORALLfacet_(facet) {
    +      if (facet == startfacet && i)
    +        break;
    +      facet->visitid= visitid;
    +      if (!facet->flipped) {
    +        qh_distplane(qh, point, facet, dist);
    +        (*numpart)++;
    +        if (*dist > bestdist) {
    +          if (!facet->upperdelaunay || *dist >= qh->MINoutside) {
    +            bestfacet= facet;
    +            if (isdistoutside && *dist >= distoutside)
    +              goto LABELreturn_bestnew;
    +            bestdist= *dist;
    +          }
    +        }
    +      } /* end of !flipped */
    +    } /* FORALLfacet from startfacet or qh->newfacet_list */
    +  }
    +  if (testhorizon || !bestfacet) /* testhorizon is always True.  Keep the same code as qh_findbest */
    +    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, bestfacet ? bestfacet : startfacet,
    +                                        !qh_NOupper, &bestdist, numpart);
    +  *dist= bestdist;
    +  if (isoutside && *dist < qh->MINoutside)
    +    *isoutside= False;
    +LABELreturn_bestnew:
    +  zadd_(Zfindnewtot, *numpart);
    +  zmax_(Zfindnewmax, *numpart);
    +  trace4((qh, qh->ferr, 4004, "qh_findbestnew: bestfacet f%d bestdist %2.2g\n", getid_(bestfacet), *dist));
    +  qh->IStracing= oldtrace;
    +  return bestfacet;
    +}  /* findbestnew */
    +
    +/* ============ hyperplane functions -- keep code together [?] ============ */
    +
    +/*---------------------------------
    +
    +  qh_backnormal(qh, rows, numrow, numcol, sign, normal, nearzero )
    +    given an upper-triangular rows array and a sign,
    +    solve for normal equation x using back substitution over rows U
    +
    +  returns:
    +     normal= x
    +
    +     if will not be able to divzero() when normalized(qh.MINdenom_2 and qh.MINdenom_1_2),
    +       if fails on last row
    +         this means that the hyperplane intersects [0,..,1]
    +         sets last coordinate of normal to sign
    +       otherwise
    +         sets tail of normal to [...,sign,0,...], i.e., solves for b= [0...0]
    +         sets nearzero
    +
    +  notes:
    +     assumes numrow == numcol-1
    +
    +     see Golub & van Loan, 1983, Eq. 4.4-9 for "Gaussian elimination with complete pivoting"
    +
    +     solves Ux=b where Ax=b and PA=LU
    +     b= [0,...,0,sign or 0]  (sign is either -1 or +1)
    +     last row of A= [0,...,0,1]
    +
    +     1) Ly=Pb == y=b since P only permutes the 0's of   b
    +
    +  design:
    +    for each row from end
    +      perform back substitution
    +      if near zero
    +        use qh_divzero for division
    +        if zero divide and not last row
    +          set tail of normal to 0
    +*/
    +void qh_backnormal(qhT *qh, realT **rows, int numrow, int numcol, boolT sign,
    +        coordT *normal, boolT *nearzero) {
    +  int i, j;
    +  coordT *normalp, *normal_tail, *ai, *ak;
    +  realT diagonal;
    +  boolT waszero;
    +  int zerocol= -1;
    +
    +  normalp= normal + numcol - 1;
    +  *normalp--= (sign ? -1.0 : 1.0);
    +  for (i=numrow; i--; ) {
    +    *normalp= 0.0;
    +    ai= rows[i] + i + 1;
    +    ak= normalp+1;
    +    for (j=i+1; j < numcol; j++)
    +      *normalp -= *ai++ * *ak++;
    +    diagonal= (rows[i])[i];
    +    if (fabs_(diagonal) > qh->MINdenom_2)
    +      *(normalp--) /= diagonal;
    +    else {
    +      waszero= False;
    +      *normalp= qh_divzero(*normalp, diagonal, qh->MINdenom_1_2, &waszero);
    +      if (waszero) {
    +        zerocol= i;
    +        *(normalp--)= (sign ? -1.0 : 1.0);
    +        for (normal_tail= normalp+2; normal_tail < normal + numcol; normal_tail++)
    +          *normal_tail= 0.0;
    +      }else
    +        normalp--;
    +    }
    +  }
    +  if (zerocol != -1) {
    +    zzinc_(Zback0);
    +    *nearzero= True;
    +    trace4((qh, qh->ferr, 4005, "qh_backnormal: zero diagonal at column %d.\n", i));
    +    qh_precision(qh, "zero diagonal on back substitution");
    +  }
    +} /* backnormal */
    +
    +/*---------------------------------
    +
    +  qh_gausselim(qh, rows, numrow, numcol, sign )
    +    Gaussian elimination with partial pivoting
    +
    +  returns:
    +    rows is upper triangular (includes row exchanges)
    +    flips sign for each row exchange
    +    sets nearzero if pivot[k] < qh.NEARzero[k], else clears it
    +
    +  notes:
    +    if nearzero, the determinant's sign may be incorrect.
    +    assumes numrow <= numcol
    +
    +  design:
    +    for each row
    +      determine pivot and exchange rows if necessary
    +      test for near zero
    +      perform gaussian elimination step
    +*/
    +void qh_gausselim(qhT *qh, realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero) {
    +  realT *ai, *ak, *rowp, *pivotrow;
    +  realT n, pivot, pivot_abs= 0.0, temp;
    +  int i, j, k, pivoti, flip=0;
    +
    +  *nearzero= False;
    +  for (k=0; k < numrow; k++) {
    +    pivot_abs= fabs_((rows[k])[k]);
    +    pivoti= k;
    +    for (i=k+1; i < numrow; i++) {
    +      if ((temp= fabs_((rows[i])[k])) > pivot_abs) {
    +        pivot_abs= temp;
    +        pivoti= i;
    +      }
    +    }
    +    if (pivoti != k) {
    +      rowp= rows[pivoti];
    +      rows[pivoti]= rows[k];
    +      rows[k]= rowp;
    +      *sign ^= 1;
    +      flip ^= 1;
    +    }
    +    if (pivot_abs <= qh->NEARzero[k]) {
    +      *nearzero= True;
    +      if (pivot_abs == 0.0) {   /* remainder of column == 0 */
    +        if (qh->IStracing >= 4) {
    +          qh_fprintf(qh, qh->ferr, 8011, "qh_gausselim: 0 pivot at column %d. (%2.2g < %2.2g)\n", k, pivot_abs, qh->DISTround);
    +          qh_printmatrix(qh, qh->ferr, "Matrix:", rows, numrow, numcol);
    +        }
    +        zzinc_(Zgauss0);
    +        qh_precision(qh, "zero pivot for Gaussian elimination");
    +        goto LABELnextcol;
    +      }
    +    }
    +    pivotrow= rows[k] + k;
    +    pivot= *pivotrow++;  /* signed value of pivot, and remainder of row */
    +    for (i=k+1; i < numrow; i++) {
    +      ai= rows[i] + k;
    +      ak= pivotrow;
    +      n= (*ai++)/pivot;   /* divzero() not needed since |pivot| >= |*ai| */
    +      for (j= numcol - (k+1); j--; )
    +        *ai++ -= n * *ak++;
    +    }
    +  LABELnextcol:
    +    ;
    +  }
    +  wmin_(Wmindenom, pivot_abs);  /* last pivot element */
    +  if (qh->IStracing >= 5)
    +    qh_printmatrix(qh, qh->ferr, "qh_gausselem: result", rows, numrow, numcol);
    +} /* gausselim */
    +
    +
    +/*---------------------------------
    +
    +  qh_getangle(qh, vect1, vect2 )
    +    returns the dot product of two vectors
    +    if qh.RANDOMdist, joggles result
    +
    +  notes:
    +    the angle may be > 1.0 or < -1.0 because of roundoff errors
    +
    +*/
    +realT qh_getangle(qhT *qh, pointT *vect1, pointT *vect2) {
    +  realT angle= 0, randr;
    +  int k;
    +
    +  for (k=qh->hull_dim; k--; )
    +    angle += *vect1++ * *vect2++;
    +  if (qh->RANDOMdist) {
    +    randr= qh_RANDOMint;
    +    angle += (2.0 * randr / qh_RANDOMmax - 1.0) *
    +      qh->RANDOMfactor;
    +  }
    +  trace4((qh, qh->ferr, 4006, "qh_getangle: %2.2g\n", angle));
    +  return(angle);
    +} /* getangle */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcenter(qh, vertices )
    +    returns arithmetic center of a set of vertices as a new point
    +
    +  notes:
    +    allocates point array for center
    +*/
    +pointT *qh_getcenter(qhT *qh, setT *vertices) {
    +  int k;
    +  pointT *center, *coord;
    +  vertexT *vertex, **vertexp;
    +  int count= qh_setsize(qh, vertices);
    +
    +  if (count < 2) {
    +    qh_fprintf(qh, qh->ferr, 6003, "qhull internal error (qh_getcenter): not defined for %d points\n", count);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  center= (pointT *)qh_memalloc(qh, qh->normal_size);
    +  for (k=0; k < qh->hull_dim; k++) {
    +    coord= center+k;
    +    *coord= 0.0;
    +    FOREACHvertex_(vertices)
    +      *coord += vertex->point[k];
    +    *coord /= count;  /* count>=2 by QH6003 */
    +  }
    +  return(center);
    +} /* getcenter */
    +
    +
    +/*---------------------------------
    +
    +  qh_getcentrum(qh, facet )
    +    returns the centrum for a facet as a new point
    +
    +  notes:
    +    allocates the centrum
    +*/
    +pointT *qh_getcentrum(qhT *qh, facetT *facet) {
    +  realT dist;
    +  pointT *centrum, *point;
    +
    +  point= qh_getcenter(qh, facet->vertices);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(qh, point, facet, &dist);
    +  centrum= qh_projectpoint(qh, point, facet, dist);
    +  qh_memfree(qh, point, qh->normal_size);
    +  trace4((qh, qh->ferr, 4007, "qh_getcentrum: for f%d, %d vertices dist= %2.2g\n",
    +          facet->id, qh_setsize(qh, facet->vertices), dist));
    +  return centrum;
    +} /* getcentrum */
    +
    +
    +/*---------------------------------
    +
    +  qh_getdistance(qh, facet, neighbor, mindist, maxdist )
    +    returns the maxdist and mindist distance of any vertex from neighbor
    +
    +  returns:
    +    the max absolute value
    +
    +  design:
    +    for each vertex of facet that is not in neighbor
    +      test the distance from vertex to neighbor
    +*/
    +realT qh_getdistance(qhT *qh, facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist) {
    +  vertexT *vertex, **vertexp;
    +  realT dist, maxd, mind;
    +
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHvertex_(neighbor->vertices)
    +    vertex->seen= True;
    +  mind= 0.0;
    +  maxd= 0.0;
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      zzinc_(Zbestdist);
    +      qh_distplane(qh, vertex->point, neighbor, &dist);
    +      if (dist < mind)
    +        mind= dist;
    +      else if (dist > maxd)
    +        maxd= dist;
    +    }
    +  }
    +  *mindist= mind;
    +  *maxdist= maxd;
    +  mind= -mind;
    +  if (maxd > mind)
    +    return maxd;
    +  else
    +    return mind;
    +} /* getdistance */
    +
    +
    +/*---------------------------------
    +
    +  qh_normalize(qh, normal, dim, toporient )
    +    normalize a vector and report if too small
    +    does not use min norm
    +
    +  see:
    +    qh_normalize2
    +*/
    +void qh_normalize(qhT *qh, coordT *normal, int dim, boolT toporient) {
    +  qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +} /* normalize */
    +
    +/*---------------------------------
    +
    +  qh_normalize2(qh, normal, dim, toporient, minnorm, ismin )
    +    normalize a vector and report if too small
    +    qh.MINdenom/MINdenom1 are the upper limits for divide overflow
    +
    +  returns:
    +    normalized vector
    +    flips sign if !toporient
    +    if minnorm non-NULL,
    +      sets ismin if normal < minnorm
    +
    +  notes:
    +    if zero norm
    +       sets all elements to sqrt(1.0/dim)
    +    if divide by zero (divzero())
    +       sets largest element to   +/-1
    +       bumps Znearlysingular
    +
    +  design:
    +    computes norm
    +    test for minnorm
    +    if not near zero
    +      normalizes normal
    +    else if zero norm
    +      sets normal to standard value
    +    else
    +      uses qh_divzero to normalize
    +      if nearzero
    +        sets norm to direction of maximum value
    +*/
    +void qh_normalize2(qhT *qh, coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin) {
    +  int k;
    +  realT *colp, *maxp, norm= 0, temp, *norm1, *norm2, *norm3;
    +  boolT zerodiv;
    +
    +  norm1= normal+1;
    +  norm2= normal+2;
    +  norm3= normal+3;
    +  if (dim == 2)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1));
    +  else if (dim == 3)
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2));
    +  else if (dim == 4) {
    +    norm= sqrt((*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3));
    +  }else if (dim > 4) {
    +    norm= (*normal)*(*normal) + (*norm1)*(*norm1) + (*norm2)*(*norm2)
    +               + (*norm3)*(*norm3);
    +    for (k=dim-4, colp=normal+4; k--; colp++)
    +      norm += (*colp) * (*colp);
    +    norm= sqrt(norm);
    +  }
    +  if (minnorm) {
    +    if (norm < *minnorm)
    +      *ismin= True;
    +    else
    +      *ismin= False;
    +  }
    +  wmin_(Wmindenom, norm);
    +  if (norm > qh->MINdenom) {
    +    if (!toporient)
    +      norm= -norm;
    +    *normal /= norm;
    +    *norm1 /= norm;
    +    if (dim == 2)
    +      ; /* all done */
    +    else if (dim == 3)
    +      *norm2 /= norm;
    +    else if (dim == 4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +    }else if (dim >4) {
    +      *norm2 /= norm;
    +      *norm3 /= norm;
    +      for (k=dim-4, colp=normal+4; k--; )
    +        *colp++ /= norm;
    +    }
    +  }else if (norm == 0.0) {
    +    temp= sqrt(1.0/dim);
    +    for (k=dim, colp=normal; k--; )
    +      *colp++ = temp;
    +  }else {
    +    if (!toporient)
    +      norm= -norm;
    +    for (k=dim, colp=normal; k--; colp++) { /* k used below */
    +      temp= qh_divzero(*colp, norm, qh->MINdenom_1, &zerodiv);
    +      if (!zerodiv)
    +        *colp= temp;
    +      else {
    +        maxp= qh_maxabsval(normal, dim);
    +        temp= ((*maxp * norm >= 0.0) ? 1.0 : -1.0);
    +        for (k=dim, colp=normal; k--; colp++)
    +          *colp= 0.0;
    +        *maxp= temp;
    +        zzinc_(Znearlysingular);
    +        trace0((qh, qh->ferr, 1, "qh_normalize: norm=%2.2g too small during p%d\n",
    +               norm, qh->furthest_id));
    +        return;
    +      }
    +    }
    +  }
    +} /* normalize */
    +
    +
    +/*---------------------------------
    +
    +  qh_projectpoint(qh, point, facet, dist )
    +    project point onto a facet by dist
    +
    +  returns:
    +    returns a new point
    +
    +  notes:
    +    if dist= distplane(point,facet)
    +      this projects point to hyperplane
    +    assumes qh_memfree_() is valid for normal_size
    +*/
    +pointT *qh_projectpoint(qhT *qh, pointT *point, facetT *facet, realT dist) {
    +  pointT *newpoint, *np, *normal;
    +  int normsize= qh->normal_size;
    +  int k;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(qh, normsize, freelistp, newpoint, pointT);
    +  np= newpoint;
    +  normal= facet->normal;
    +  for (k=qh->hull_dim; k--; )
    +    *(np++)= *point++ - dist * *normal++;
    +  return(newpoint);
    +} /* projectpoint */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfacetplane(qh, facet )
    +    sets the hyperplane for a facet
    +    if qh.RANDOMdist, joggles hyperplane
    +
    +  notes:
    +    uses global buffers qh.gm_matrix and qh.gm_row
    +    overwrites facet->normal if already defined
    +    updates Wnewvertex if PRINTstatistics
    +    sets facet->upperdelaunay if upper envelope of Delaunay triangulation
    +
    +  design:
    +    copy vertex coordinates to qh.gm_matrix/gm_row
    +    compute determinate
    +    if nearzero
    +      recompute determinate with gaussian elimination
    +      if nearzero
    +        force outside orientation by testing interior point
    +*/
    +void qh_setfacetplane(qhT *qh, facetT *facet) {
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int normsize= qh->normal_size;
    +  int k,i, oldtrace= 0;
    +  realT dist;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +  coordT *coord, *gmcoord;
    +  pointT *point0= SETfirstt_(facet->vertices, vertexT)->point;
    +  boolT nearzero= False;
    +
    +  zzinc_(Zsetplane);
    +  if (!facet->normal)
    +    qh_memalloc_(qh, normsize, freelistp, facet->normal, coordT);
    +  if (facet == qh->tracefacet) {
    +    oldtrace= qh->IStracing;
    +    qh->IStracing= 5;
    +    qh_fprintf(qh, qh->ferr, 8012, "qh_setfacetplane: facet f%d created.\n", facet->id);
    +    qh_fprintf(qh, qh->ferr, 8013, "  Last point added to hull was p%d.", qh->furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh, qh->ferr, 8014, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    qh_fprintf(qh, qh->ferr, 8015, "\n\nCurrent summary is:\n");
    +      qh_printsummary(qh, qh->ferr);
    +  }
    +  if (qh->hull_dim <= 4) {
    +    i= 0;
    +    if (qh->RANDOMdist) {
    +      gmcoord= qh->gm_matrix;
    +      FOREACHvertex_(facet->vertices) {
    +        qh->gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        for (k=qh->hull_dim; k--; )
    +          *(gmcoord++)= *coord++ * qh_randomfactor(qh, qh->RANDOMa, qh->RANDOMb);
    +      }
    +    }else {
    +      FOREACHvertex_(facet->vertices)
    +       qh->gm_row[i++]= vertex->point;
    +    }
    +    qh_sethyperplane_det(qh, qh->hull_dim, qh->gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +  }
    +  if (qh->hull_dim > 4 || nearzero) {
    +    i= 0;
    +    gmcoord= qh->gm_matrix;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        qh->gm_row[i++]= gmcoord;
    +        coord= vertex->point;
    +        point= point0;
    +        for (k=qh->hull_dim; k--; )
    +          *(gmcoord++)= *coord++ - *point++;
    +      }
    +    }
    +    qh->gm_row[i]= gmcoord;  /* for areasimplex */
    +    if (qh->RANDOMdist) {
    +      gmcoord= qh->gm_matrix;
    +      for (i=qh->hull_dim-1; i--; ) {
    +        for (k=qh->hull_dim; k--; )
    +          *(gmcoord++) *= qh_randomfactor(qh, qh->RANDOMa, qh->RANDOMb);
    +      }
    +    }
    +    qh_sethyperplane_gauss(qh, qh->hull_dim, qh->gm_row, point0, facet->toporient,
    +                facet->normal, &facet->offset, &nearzero);
    +    if (nearzero) {
    +      if (qh_orientoutside(qh, facet)) {
    +        trace0((qh, qh->ferr, 2, "qh_setfacetplane: flipped orientation after testing interior_point during p%d\n", qh->furthest_id));
    +      /* this is part of using Gaussian Elimination.  For example in 5-d
    +           1 1 1 1 0
    +           1 1 1 1 1
    +           0 0 0 1 0
    +           0 1 0 0 0
    +           1 0 0 0 0
    +           norm= 0.38 0.38 -0.76 0.38 0
    +         has a determinate of 1, but g.e. after subtracting pt. 0 has
    +         0's in the diagonal, even with full pivoting.  It does work
    +         if you subtract pt. 4 instead. */
    +      }
    +    }
    +  }
    +  facet->upperdelaunay= False;
    +  if (qh->DELAUNAY) {
    +    if (qh->UPPERdelaunay) {     /* matches qh_triangulate_facet and qh.lower_threshold in qh_initbuild */
    +      if (facet->normal[qh->hull_dim -1] >= qh->ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }else {
    +      if (facet->normal[qh->hull_dim -1] > -qh->ANGLEround * qh_ZEROdelaunay)
    +        facet->upperdelaunay= True;
    +    }
    +  }
    +  if (qh->PRINTstatistics || qh->IStracing || qh->TRACElevel || qh->JOGGLEmax < REALmax) {
    +    qh->old_randomdist= qh->RANDOMdist;
    +    qh->RANDOMdist= False;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->point != point0) {
    +        boolT istrace= False;
    +        zinc_(Zdiststat);
    +        qh_distplane(qh, vertex->point, facet, &dist);
    +        dist= fabs_(dist);
    +        zinc_(Znewvertex);
    +        wadd_(Wnewvertex, dist);
    +        if (dist > wwval_(Wnewvertexmax)) {
    +          wwval_(Wnewvertexmax)= dist;
    +          if (dist > qh->max_outside) {
    +            qh->max_outside= dist;  /* used by qh_maxouter(qh) */
    +            if (dist > qh->TRACEdist)
    +              istrace= True;
    +          }
    +        }else if (-dist > qh->TRACEdist)
    +          istrace= True;
    +        if (istrace) {
    +          qh_fprintf(qh, qh->ferr, 8016, "qh_setfacetplane: ====== vertex p%d(v%d) increases max_outside to %2.2g for new facet f%d last p%d\n",
    +                qh_pointid(qh, vertex->point), vertex->id, dist, facet->id, qh->furthest_id);
    +          qh_errprint(qh, "DISTANT", facet, NULL, NULL, NULL);
    +        }
    +      }
    +    }
    +    qh->RANDOMdist= qh->old_randomdist;
    +  }
    +  if (qh->IStracing >= 3) {
    +    qh_fprintf(qh, qh->ferr, 8017, "qh_setfacetplane: f%d offset %2.2g normal: ",
    +             facet->id, facet->offset);
    +    for (k=0; k < qh->hull_dim; k++)
    +      qh_fprintf(qh, qh->ferr, 8018, "%2.2g ", facet->normal[k]);
    +    qh_fprintf(qh, qh->ferr, 8019, "\n");
    +  }
    +  if (facet == qh->tracefacet)
    +    qh->IStracing= oldtrace;
    +} /* setfacetplane */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_det(qh, dim, rows, point0, toporient, normal, offset, nearzero )
    +    given dim X dim array indexed by rows[], one row per point,
    +        toporient(flips all signs),
    +        and point0 (any row)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +    sets nearzero if hyperplane not through points
    +
    +  notes:
    +    only defined for dim == 2..4
    +    rows[] is not modified
    +    solves det(P-V_0, V_n-V_0, ..., V_1-V_0)=0, i.e. every point is on hyperplane
    +    see Bower & Woodworth, A programmer's geometry, Butterworths 1983.
    +
    +  derivation of 3-d minnorm
    +    Goal: all vertices V_i within qh.one_merge of hyperplane
    +    Plan: exactly translate the facet so that V_0 is the origin
    +          exactly rotate the facet so that V_1 is on the x-axis and y_2=0.
    +          exactly rotate the effective perturbation to only effect n_0
    +             this introduces a factor of sqrt(3)
    +    n_0 = ((y_2-y_0)*(z_1-z_0) - (z_2-z_0)*(y_1-y_0)) / norm
    +    Let M_d be the max coordinate difference
    +    Let M_a be the greater of M_d and the max abs. coordinate
    +    Let u be machine roundoff and distround be max error for distance computation
    +    The max error for n_0 is sqrt(3) u M_a M_d / norm.  n_1 is approx. 1 and n_2 is approx. 0
    +    The max error for distance of V_1 is sqrt(3) u M_a M_d M_d / norm.  Offset=0 at origin
    +    Then minnorm = 1.8 u M_a M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 45.5 u M_a and norm is usually about M_d M_d
    +
    +  derivation of 4-d minnorm
    +    same as above except rotate the facet so that V_1 on x-axis and w_2, y_3, w_3=0
    +     [if two vertices fixed on x-axis, can rotate the other two in yzw.]
    +    n_0 = det3_(...) = y_2 det2_(z_1, w_1, z_3, w_3) = - y_2 w_1 z_3
    +     [all other terms contain at least two factors nearly zero.]
    +    The max error for n_0 is sqrt(4) u M_a M_d M_d / norm
    +    Then minnorm = 2 u M_a M_d M_d M_d / qh.ONEmerge
    +    Note that qh.one_merge is approx. 82 u M_a and norm is usually about M_d M_d M_d
    +*/
    +void qh_sethyperplane_det(qhT *qh, int dim, coordT **rows, coordT *point0,
    +          boolT toporient, coordT *normal, realT *offset, boolT *nearzero) {
    +  realT maxround, dist;
    +  int i;
    +  pointT *point;
    +
    +
    +  if (dim == 2) {
    +    normal[0]= dY(1,0);
    +    normal[1]= dX(0,1);
    +    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0]+point0[1]*normal[1]);
    +    *nearzero= False;  /* since nearzero norm => incident points */
    +  }else if (dim == 3) {
    +    normal[0]= det2_(dY(2,0), dZ(2,0),
    +                     dY(1,0), dZ(1,0));
    +    normal[1]= det2_(dX(1,0), dZ(1,0),
    +                     dX(2,0), dZ(2,0));
    +    normal[2]= det2_(dX(2,0), dY(2,0),
    +                     dX(1,0), dY(1,0));
    +    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2]);
    +    maxround= qh->DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }else if (dim == 4) {
    +    normal[0]= - det3_(dY(2,0), dZ(2,0), dW(2,0),
    +                        dY(1,0), dZ(1,0), dW(1,0),
    +                        dY(3,0), dZ(3,0), dW(3,0));
    +    normal[1]=   det3_(dX(2,0), dZ(2,0), dW(2,0),
    +                        dX(1,0), dZ(1,0), dW(1,0),
    +                        dX(3,0), dZ(3,0), dW(3,0));
    +    normal[2]= - det3_(dX(2,0), dY(2,0), dW(2,0),
    +                        dX(1,0), dY(1,0), dW(1,0),
    +                        dX(3,0), dY(3,0), dW(3,0));
    +    normal[3]=   det3_(dX(2,0), dY(2,0), dZ(2,0),
    +                        dX(1,0), dY(1,0), dZ(1,0),
    +                        dX(3,0), dY(3,0), dZ(3,0));
    +    qh_normalize2(qh, normal, dim, toporient, NULL, NULL);
    +    *offset= -(point0[0]*normal[0] + point0[1]*normal[1]
    +               + point0[2]*normal[2] + point0[3]*normal[3]);
    +    maxround= qh->DISTround;
    +    for (i=dim; i--; ) {
    +      point= rows[i];
    +      if (point != point0) {
    +        dist= *offset + (point[0]*normal[0] + point[1]*normal[1]
    +               + point[2]*normal[2] + point[3]*normal[3]);
    +        if (dist > maxround || dist < -maxround) {
    +          *nearzero= True;
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  if (*nearzero) {
    +    zzinc_(Zminnorm);
    +    trace0((qh, qh->ferr, 3, "qh_sethyperplane_det: degenerate norm during p%d.\n", qh->furthest_id));
    +    zzinc_(Znearlysingular);
    +  }
    +} /* sethyperplane_det */
    +
    +
    +/*---------------------------------
    +
    +  qh_sethyperplane_gauss(qh, dim, rows, point0, toporient, normal, offset, nearzero )
    +    given(dim-1) X dim array of rows[i]= V_{i+1} - V_0 (point0)
    +    set normalized hyperplane equation from oriented simplex
    +
    +  returns:
    +    normal (normalized)
    +    offset (places point0 on the hyperplane)
    +
    +  notes:
    +    if nearzero
    +      orientation may be incorrect because of incorrect sign flips in gausselim
    +    solves [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0 .. 0 1]
    +        or [V_n-V_0,...,V_1-V_0, 0 .. 0 1] * N == [0]
    +    i.e., N is normal to the hyperplane, and the unnormalized
    +        distance to [0 .. 1] is either 1 or   0
    +
    +  design:
    +    perform gaussian elimination
    +    flip sign for negative values
    +    perform back substitution
    +    normalize result
    +    compute offset
    +*/
    +void qh_sethyperplane_gauss(qhT *qh, int dim, coordT **rows, pointT *point0,
    +                boolT toporient, coordT *normal, coordT *offset, boolT *nearzero) {
    +  coordT *pointcoord, *normalcoef;
    +  int k;
    +  boolT sign= toporient, nearzero2= False;
    +
    +  qh_gausselim(qh, rows, dim-1, dim, &sign, nearzero);
    +  for (k=dim-1; k--; ) {
    +    if ((rows[k])[k] < 0)
    +      sign ^= 1;
    +  }
    +  if (*nearzero) {
    +    zzinc_(Znearlysingular);
    +    trace0((qh, qh->ferr, 4, "qh_sethyperplane_gauss: nearly singular or axis parallel hyperplane during p%d.\n", qh->furthest_id));
    +    qh_backnormal(qh, rows, dim-1, dim, sign, normal, &nearzero2);
    +  }else {
    +    qh_backnormal(qh, rows, dim-1, dim, sign, normal, &nearzero2);
    +    if (nearzero2) {
    +      zzinc_(Znearlysingular);
    +      trace0((qh, qh->ferr, 5, "qh_sethyperplane_gauss: singular or axis parallel hyperplane at normalization during p%d.\n", qh->furthest_id));
    +    }
    +  }
    +  if (nearzero2)
    +    *nearzero= True;
    +  qh_normalize2(qh, normal, dim, True, NULL, NULL);
    +  pointcoord= point0;
    +  normalcoef= normal;
    +  *offset= -(*pointcoord++ * *normalcoef++);
    +  for (k=dim-1; k--; )
    +    *offset -= *pointcoord++ * *normalcoef++;
    +} /* sethyperplane_gauss */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/geom_r.h b/xs/src/qhull/src/libqhull_r/geom_r.h
    new file mode 100644
    index 0000000000..d73e953453
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/geom_r.h
    @@ -0,0 +1,184 @@
    +/*
      ---------------------------------
    +
    +  geom_r.h
    +    header file for geometric routines
    +
    +   see qh-geom_r.htm and geom_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/geom_r.h#3 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFgeom
    +#define qhDEFgeom 1
    +
    +#include "libqhull_r.h"
    +
    +/* ============ -macros- ======================== */
    +
    +/*----------------------------------
    +
    +  fabs_(a)
    +    returns the absolute value of a
    +*/
    +#define fabs_( a ) ((( a ) < 0 ) ? -( a ):( a ))
    +
    +/*----------------------------------
    +
    +  fmax_(a,b)
    +    returns the maximum value of a and b
    +*/
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  fmin_(a,b)
    +    returns the minimum value of a and b
    +*/
    +#define fmin_( a,b )  ( ( a ) > ( b ) ? ( b ) : ( a ) )
    +
    +/*----------------------------------
    +
    +  maximize_(maxval, val)
    +    set maxval to val if val is greater than maxval
    +*/
    +#define maximize_( maxval, val ) { if (( maxval ) < ( val )) ( maxval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  minimize_(minval, val)
    +    set minval to val if val is less than minval
    +*/
    +#define minimize_( minval, val ) { if (( minval ) > ( val )) ( minval )= ( val ); }
    +
    +/*----------------------------------
    +
    +  det2_(a1, a2,
    +        b1, b2)
    +
    +    compute a 2-d determinate
    +*/
    +#define det2_( a1,a2,b1,b2 ) (( a1 )*( b2 ) - ( a2 )*( b1 ))
    +
    +/*----------------------------------
    +
    +  det3_(a1, a2, a3,
    +       b1, b2, b3,
    +       c1, c2, c3)
    +
    +    compute a 3-d determinate
    +*/
    +#define det3_( a1,a2,a3,b1,b2,b3,c1,c2,c3 ) ( ( a1 )*det2_( b2,b3,c2,c3 ) \
    +                - ( b1 )*det2_( a2,a3,c2,c3 ) + ( c1 )*det2_( a2,a3,b2,b3 ) )
    +
    +/*----------------------------------
    +
    +  dX( p1, p2 )
    +  dY( p1, p2 )
    +  dZ( p1, p2 )
    +
    +    given two indices into rows[],
    +
    +    compute the difference between X, Y, or Z coordinates
    +*/
    +#define dX( p1,p2 )  ( *( rows[p1] ) - *( rows[p2] ))
    +#define dY( p1,p2 )  ( *( rows[p1]+1 ) - *( rows[p2]+1 ))
    +#define dZ( p1,p2 )  ( *( rows[p1]+2 ) - *( rows[p2]+2 ))
    +#define dW( p1,p2 )  ( *( rows[p1]+3 ) - *( rows[p2]+3 ))
    +
    +/*============= prototypes in alphabetical order, infrequent at end ======= */
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_backnormal(qhT *qh, realT **rows, int numrow, int numcol, boolT sign, coordT *normal, boolT *nearzero);
    +void    qh_distplane(qhT *qh, pointT *point, facetT *facet, realT *dist);
    +facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT isnewfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbesthorizon(qhT *qh, boolT ischeckmax, pointT *point,
    +                     facetT *startfacet, boolT noupper, realT *bestdist, int *numpart);
    +facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet, realT *dist,
    +                     boolT bestoutside, boolT *isoutside, int *numpart);
    +void    qh_gausselim(qhT *qh, realT **rows, int numrow, int numcol, boolT *sign, boolT *nearzero);
    +realT   qh_getangle(qhT *qh, pointT *vect1, pointT *vect2);
    +pointT *qh_getcenter(qhT *qh, setT *vertices);
    +pointT *qh_getcentrum(qhT *qh, facetT *facet);
    +realT   qh_getdistance(qhT *qh, facetT *facet, facetT *neighbor, realT *mindist, realT *maxdist);
    +void    qh_normalize(qhT *qh, coordT *normal, int dim, boolT toporient);
    +void    qh_normalize2(qhT *qh, coordT *normal, int dim, boolT toporient,
    +            realT *minnorm, boolT *ismin);
    +pointT *qh_projectpoint(qhT *qh, pointT *point, facetT *facet, realT dist);
    +
    +void    qh_setfacetplane(qhT *qh, facetT *newfacets);
    +void    qh_sethyperplane_det(qhT *qh, int dim, coordT **rows, coordT *point0,
    +              boolT toporient, coordT *normal, realT *offset, boolT *nearzero);
    +void    qh_sethyperplane_gauss(qhT *qh, int dim, coordT **rows, pointT *point0,
    +             boolT toporient, coordT *normal, coordT *offset, boolT *nearzero);
    +boolT   qh_sharpnewfacets(qhT *qh);
    +
    +/*========= infrequently used code in geom2_r.c =============*/
    +
    +coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension);
    +void    qh_crossproduct(int dim, realT vecA[3], realT vecB[3], realT vecC[3]);
    +realT   qh_determinant(qhT *qh, realT **rows, int dim, boolT *nearzero);
    +realT   qh_detjoggle(qhT *qh, pointT *points, int numpoints, int dimension);
    +void    qh_detroundoff(qhT *qh);
    +realT   qh_detsimplex(qhT *qh, pointT *apex, setT *points, int dim, boolT *nearzero);
    +realT   qh_distnorm(int dim, pointT *point, pointT *normal, realT *offsetp);
    +realT   qh_distround(qhT *qh, int dimension, realT maxabs, realT maxsumabs);
    +realT   qh_divzero(realT numer, realT denom, realT mindenom1, boolT *zerodiv);
    +realT   qh_facetarea(qhT *qh, facetT *facet);
    +realT   qh_facetarea_simplex(qhT *qh, int dim, coordT *apex, setT *vertices,
    +          vertexT *notvertex,  boolT toporient, coordT *normal, realT *offset);
    +pointT *qh_facetcenter(qhT *qh, setT *vertices);
    +facetT *qh_findgooddist(qhT *qh, pointT *point, facetT *facetA, realT *distp, facetT **facetlist);
    +void    qh_getarea(qhT *qh, facetT *facetlist);
    +boolT   qh_gram_schmidt(qhT *qh, int dim, realT **rows);
    +boolT   qh_inthresholds(qhT *qh, coordT *normal, realT *angle);
    +void    qh_joggleinput(qhT *qh);
    +realT  *qh_maxabsval(realT *normal, int dim);
    +setT   *qh_maxmin(qhT *qh, pointT *points, int numpoints, int dimension);
    +realT   qh_maxouter(qhT *qh);
    +void    qh_maxsimplex(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints, setT **simplex);
    +realT   qh_minabsval(realT *normal, int dim);
    +int     qh_mindiff(realT *vecA, realT *vecB, int dim);
    +boolT   qh_orientoutside(qhT *qh, facetT *facet);
    +void    qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
    +coordT  qh_pointdist(pointT *point1, pointT *point2, int dim);
    +void    qh_printmatrix(qhT *qh, FILE *fp, const char *string, realT **rows, int numrow, int numcol);
    +void    qh_printpoints(qhT *qh, FILE *fp, const char *string, setT *points);
    +void    qh_projectinput(qhT *qh);
    +void    qh_projectpoints(qhT *qh, signed char *project, int n, realT *points,
    +             int numpoints, int dim, realT *newpoints, int newdim);
    +void    qh_rotateinput(qhT *qh, realT **rows);
    +void    qh_rotatepoints(qhT *qh, realT *points, int numpoints, int dim, realT **rows);
    +void    qh_scaleinput(qhT *qh);
    +void    qh_scalelast(qhT *qh, coordT *points, int numpoints, int dim, coordT low,
    +                   coordT high, coordT newhigh);
    +void    qh_scalepoints(qhT *qh, pointT *points, int numpoints, int dim,
    +                realT *newlows, realT *newhighs);
    +boolT   qh_sethalfspace(qhT *qh, int dim, coordT *coords, coordT **nextp,
    +              coordT *normal, coordT *offset, coordT *feasible);
    +coordT *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible);
    +pointT *qh_voronoi_center(qhT *qh, int dim, setT *points);
    +
    +#ifdef __cplusplus
    +} /* extern "C"*/
    +#endif
    +
    +#endif /* qhDEFgeom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/global_r.c b/xs/src/qhull/src/libqhull_r/global_r.c
    new file mode 100644
    index 0000000000..eef465ca14
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/global_r.c
    @@ -0,0 +1,2100 @@
    +
    +/*
      ---------------------------------
    +
    +   global_r.c
    +   initializes all the globals of the qhull application
    +
    +   see README
    +
    +   see libqhull_r.h for qh.globals and function prototypes
    +
    +   see qhull_ra.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/global_r.c#16 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    + */
    +
    +#include "qhull_ra.h"
    +
    +/*========= qh->definition -- globals defined in libqhull_r.h =======================*/
    +
    +/*----------------------------------
    +
    +  qh_version
    +    version string by year and date
    +    qh_version2 for Unix users and -V
    +
    +    the revision increases on code changes only
    +
    +  notes:
    +    change date:    Changes.txt, Announce.txt, index.htm, README.txt,
    +                    qhull-news.html, Eudora signatures, CMakeLists.txt
    +    change version: README.txt, qh-get.htm, File_id.diz, Makefile.txt, CMakeLists.txt
    +    check that CmakeLists @version is the same as qh_version2
    +    change year:    Copying.txt
    +    check download size
    +    recompile user_eg_r.c, rbox_r.c, libqhull_r.c, qconvex_r.c, qdelaun_r.c qvoronoi_r.c, qhalf_r.c, testqset_r.c
    +*/
    +
    +const char qh_version[]= "2015.2.r 2016/01/18";
    +const char qh_version2[]= "qhull_r 7.2.0 (2015.2.r 2016/01/18)";
    +
    +/*---------------------------------
    +
    +  qh_appendprint(qh, printFormat )
    +    append printFormat to qh.PRINTout unless already defined
    +*/
    +void qh_appendprint(qhT *qh, qh_PRINT format) {
    +  int i;
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    if (qh->PRINTout[i] == format && format != qh_PRINTqhull)
    +      break;
    +    if (!qh->PRINTout[i]) {
    +      qh->PRINTout[i]= format;
    +      break;
    +    }
    +  }
    +} /* appendprint */
    +
    +/*---------------------------------
    +
    +  qh_checkflags(qh, commandStr, hiddenFlags )
    +    errors if commandStr contains hiddenFlags
    +    hiddenFlags starts and ends with a space and is space delimited (checked)
    +
    +  notes:
    +    ignores first word (e.g., "qconvex i")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initflags() initializes Qhull according to commandStr
    +*/
    +void qh_checkflags(qhT *qh, char *command, char *hiddenflags) {
    +  char *s= command, *t, *chkerr; /* qh_skipfilename is non-const */
    +  char key, opt, prevopt;
    +  char chkkey[]= "   ";
    +  char chkopt[]=  "    ";
    +  char chkopt2[]= "     ";
    +  boolT waserr= False;
    +
    +  if (*hiddenflags != ' ' || hiddenflags[strlen(hiddenflags)-1] != ' ') {
    +    qh_fprintf(qh, qh->ferr, 6026, "qhull error (qh_checkflags): hiddenflags must start and end with a space: \"%s\"", hiddenflags);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (strpbrk(hiddenflags, ",\n\r\t")) {
    +    qh_fprintf(qh, qh->ferr, 6027, "qhull error (qh_checkflags): hiddenflags contains commas, newlines, or tabs: \"%s\"", hiddenflags);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    key = *s++;
    +    chkerr = NULL;
    +    if (key == 'T' && (*s == 'I' || *s == 'O')) {  /* TI or TO 'file name' */
    +      s= qh_skipfilename(qh, ++s);
    +      continue;
    +    }
    +    chkkey[1]= key;
    +    if (strstr(hiddenflags, chkkey)) {
    +      chkerr= chkkey;
    +    }else if (isupper(key)) {
    +      opt= ' ';
    +      prevopt= ' ';
    +      chkopt[1]= key;
    +      chkopt2[1]= key;
    +      while (!chkerr && *s && !isspace(*s)) {
    +        opt= *s++;
    +        if (isalpha(opt)) {
    +          chkopt[2]= opt;
    +          if (strstr(hiddenflags, chkopt))
    +            chkerr= chkopt;
    +          if (prevopt != ' ') {
    +            chkopt2[2]= prevopt;
    +            chkopt2[3]= opt;
    +            if (strstr(hiddenflags, chkopt2))
    +              chkerr= chkopt2;
    +          }
    +        }else if (key == 'Q' && isdigit(opt) && prevopt != 'b'
    +              && (prevopt == ' ' || islower(prevopt))) {
    +            chkopt[2]= opt;
    +            if (strstr(hiddenflags, chkopt))
    +              chkerr= chkopt;
    +        }else {
    +          qh_strtod(s-1, &t);
    +          if (s < t)
    +            s= t;
    +        }
    +        prevopt= opt;
    +      }
    +    }
    +    if (chkerr) {
    +      *chkerr= '\'';
    +      chkerr[strlen(chkerr)-1]=  '\'';
    +      qh_fprintf(qh, qh->ferr, 6029, "qhull error: option %s is not used with this program.\n             It may be used with qhull.\n", chkerr);
    +      waserr= True;
    +    }
    +  }
    +  if (waserr)
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +} /* checkflags */
    +
    +/*---------------------------------
    +
    +  qh_clear_outputflags(qh)
    +    Clear output flags for QhullPoints
    +*/
    +void qh_clear_outputflags(qhT *qh) {
    +  int i,k;
    +
    +  qh->ANNOTATEoutput= False;
    +  qh->DOintersections= False;
    +  qh->DROPdim= -1;
    +  qh->FORCEoutput= False;
    +  qh->GETarea= False;
    +  qh->GOODpoint= 0;
    +  qh->GOODpointp= NULL;
    +  qh->GOODthreshold= False;
    +  qh->GOODvertex= 0;
    +  qh->GOODvertexp= NULL;
    +  qh->IStracing= 0;
    +  qh->KEEParea= False;
    +  qh->KEEPmerge= False;
    +  qh->KEEPminArea= REALmax;
    +  qh->PRINTcentrums= False;
    +  qh->PRINTcoplanar= False;
    +  qh->PRINTdots= False;
    +  qh->PRINTgood= False;
    +  qh->PRINTinner= False;
    +  qh->PRINTneighbors= False;
    +  qh->PRINTnoplanes= False;
    +  qh->PRINToptions1st= False;
    +  qh->PRINTouter= False;
    +  qh->PRINTprecision= True;
    +  qh->PRINTridges= False;
    +  qh->PRINTspheres= False;
    +  qh->PRINTstatistics= False;
    +  qh->PRINTsummary= False;
    +  qh->PRINTtransparent= False;
    +  qh->SPLITthresholds= False;
    +  qh->TRACElevel= 0;
    +  qh->TRInormals= False;
    +  qh->USEstdout= False;
    +  qh->VERIFYoutput= False;
    +  for (k=qh->input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh->lower_threshold[k]= -REALmax;
    +    qh->upper_threshold[k]= REALmax;
    +    qh->lower_bound[k]= -REALmax;
    +    qh->upper_bound[k]= REALmax;
    +  }
    +
    +  for (i=0; i < qh_PRINTEND; i++) {
    +    qh->PRINTout[i]= qh_PRINTnone;
    +  }
    +
    +  if (!qh->qhull_commandsiz2)
    +      qh->qhull_commandsiz2= (int)strlen(qh->qhull_command); /* WARN64 */
    +  else {
    +      qh->qhull_command[qh->qhull_commandsiz2]= '\0';
    +  }
    +  if (!qh->qhull_optionsiz2)
    +    qh->qhull_optionsiz2= (int)strlen(qh->qhull_options);  /* WARN64 */
    +  else {
    +    qh->qhull_options[qh->qhull_optionsiz2]= '\0';
    +    qh->qhull_optionlen= qh_OPTIONline;  /* start a new line */
    +  }
    +} /* clear_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_clock()
    +    return user CPU time in 100ths (qh_SECtick)
    +    only defined for qh_CLOCKtype == 2
    +
    +  notes:
    +    use first value to determine time 0
    +    from Stevens '92 8.15
    +*/
    +unsigned long qh_clock(qhT *qh) {
    +
    +#if (qh_CLOCKtype == 2)
    +  struct tms time;
    +  static long clktck;  /* initialized first call and never updated */
    +  double ratio, cpu;
    +  unsigned long ticks;
    +
    +  if (!clktck) {
    +    if ((clktck= sysconf(_SC_CLK_TCK)) < 0) {
    +      qh_fprintf(qh, qh->ferr, 6030, "qhull internal error (qh_clock): sysconf() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  if (times(&time) == -1) {
    +    qh_fprintf(qh, qh->ferr, 6031, "qhull internal error (qh_clock): times() failed.  Use qh_CLOCKtype 1 in user.h\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  ratio= qh_SECticks / (double)clktck;
    +  ticks= time.tms_utime * ratio;
    +  return ticks;
    +#else
    +  qh_fprintf(qh, qh->ferr, 6032, "qhull internal error (qh_clock): use qh_CLOCKtype 2 in user.h\n");
    +  qh_errexit(qh, qh_ERRqhull, NULL, NULL); /* never returns */
    +  return 0;
    +#endif
    +} /* clock */
    +
    +/*---------------------------------
    +
    +  qh_freebuffers()
    +    free up global memory buffers
    +
    +  notes:
    +    must match qh_initbuffers()
    +*/
    +void qh_freebuffers(qhT *qh) {
    +
    +  trace5((qh, qh->ferr, 5001, "qh_freebuffers: freeing up global memory buffers\n"));
    +  /* allocated by qh_initqhull_buffers */
    +  qh_memfree(qh, qh->NEARzero, qh->hull_dim * sizeof(realT));
    +  qh_memfree(qh, qh->lower_threshold, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->upper_threshold, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->lower_bound, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->upper_bound, (qh->input_dim+1) * sizeof(realT));
    +  qh_memfree(qh, qh->gm_matrix, (qh->hull_dim+1) * qh->hull_dim * sizeof(coordT));
    +  qh_memfree(qh, qh->gm_row, (qh->hull_dim+1) * sizeof(coordT *));
    +  qh->NEARzero= qh->lower_threshold= qh->upper_threshold= NULL;
    +  qh->lower_bound= qh->upper_bound= NULL;
    +  qh->gm_matrix= NULL;
    +  qh->gm_row= NULL;
    +  qh_setfree(qh, &qh->other_points);
    +  qh_setfree(qh, &qh->del_vertices);
    +  qh_setfree(qh, &qh->coplanarfacetset);
    +  if (qh->line)                /* allocated by qh_readinput, freed if no error */
    +    qh_free(qh->line);
    +  if (qh->half_space)
    +    qh_free(qh->half_space);
    +  if (qh->temp_malloc)
    +    qh_free(qh->temp_malloc);
    +  if (qh->feasible_point)      /* allocated by qh_readfeasible */
    +    qh_free(qh->feasible_point);
    +  if (qh->feasible_string)     /* allocated by qh_initflags */
    +    qh_free(qh->feasible_string);
    +  qh->line= qh->feasible_string= NULL;
    +  qh->half_space= qh->feasible_point= qh->temp_malloc= NULL;
    +  /* usually allocated by qh_readinput */
    +  if (qh->first_point && qh->POINTSmalloc) {
    +    qh_free(qh->first_point);
    +    qh->first_point= NULL;
    +  }
    +  if (qh->input_points && qh->input_malloc) { /* set by qh_joggleinput */
    +    qh_free(qh->input_points);
    +    qh->input_points= NULL;
    +  }
    +  trace5((qh, qh->ferr, 5002, "qh_freebuffers: finished\n"));
    +} /* freebuffers */
    +
    +
    +/*---------------------------------
    +
    +  qh_freebuild(qh, allmem )
    +    free global memory used by qh_initbuild and qh_buildhull
    +    if !allmem,
    +      does not free short memory (e.g., facetT, freed by qh_memfreeshort)
    +
    +  design:
    +    free centrums
    +    free each vertex
    +    mark unattached ridges
    +    for each facet
    +      free ridges
    +      free outside set, coplanar set, neighbor set, ridge set, vertex set
    +      free facet
    +    free hash table
    +    free interior point
    +    free merge set
    +    free temporary sets
    +*/
    +void qh_freebuild(qhT *qh, boolT allmem) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  ridgeT *ridge, **ridgep;
    +  mergeT *merge, **mergep;
    +
    +  trace1((qh, qh->ferr, 1005, "qh_freebuild: free memory from qh_inithull and qh_buildhull\n"));
    +  if (qh->del_vertices)
    +    qh_settruncate(qh, qh->del_vertices, 0);
    +  if (allmem) {
    +    while ((vertex= qh->vertex_list)) {
    +      if (vertex->next)
    +        qh_delvertex(qh, vertex);
    +      else {
    +        qh_memfree(qh, vertex, (int)sizeof(vertexT));
    +        qh->newvertex_list= qh->vertex_list= NULL;
    +      }
    +    }
    +  }else if (qh->VERTEXneighbors) {
    +    FORALLvertices
    +      qh_setfreelong(qh, &(vertex->neighbors));
    +  }
    +  qh->VERTEXneighbors= False;
    +  qh->GOODclosest= NULL;
    +  if (allmem) {
    +    FORALLfacets {
    +      FOREACHridge_(facet->ridges)
    +        ridge->seen= False;
    +    }
    +    FORALLfacets {
    +      if (facet->visible) {
    +        FOREACHridge_(facet->ridges) {
    +          if (!otherfacet_(ridge, facet)->visible)
    +            ridge->seen= True;  /* an unattached ridge */
    +        }
    +      }
    +    }
    +    while ((facet= qh->facet_list)) {
    +      FOREACHridge_(facet->ridges) {
    +        if (ridge->seen) {
    +          qh_setfree(qh, &(ridge->vertices));
    +          qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +        }else
    +          ridge->seen= True;
    +      }
    +      qh_setfree(qh, &(facet->outsideset));
    +      qh_setfree(qh, &(facet->coplanarset));
    +      qh_setfree(qh, &(facet->neighbors));
    +      qh_setfree(qh, &(facet->ridges));
    +      qh_setfree(qh, &(facet->vertices));
    +      if (facet->next)
    +        qh_delfacet(qh, facet);
    +      else {
    +        qh_memfree(qh, facet, (int)sizeof(facetT));
    +        qh->visible_list= qh->newfacet_list= qh->facet_list= NULL;
    +      }
    +    }
    +  }else {
    +    FORALLfacets {
    +      qh_setfreelong(qh, &(facet->outsideset));
    +      qh_setfreelong(qh, &(facet->coplanarset));
    +      if (!facet->simplicial) {
    +        qh_setfreelong(qh, &(facet->neighbors));
    +        qh_setfreelong(qh, &(facet->ridges));
    +        qh_setfreelong(qh, &(facet->vertices));
    +      }
    +    }
    +  }
    +  qh_setfree(qh, &(qh->hash_table));
    +  qh_memfree(qh, qh->interior_point, qh->normal_size);
    +  qh->interior_point= NULL;
    +  FOREACHmerge_(qh->facet_mergeset)  /* usually empty */
    +    qh_memfree(qh, merge, (int)sizeof(mergeT));
    +  qh->facet_mergeset= NULL;  /* temp set */
    +  qh->degen_mergeset= NULL;  /* temp set */
    +  qh_settempfree_all(qh);
    +} /* freebuild */
    +
    +/*---------------------------------
    +
    +  qh_freeqhull(qh, allmem )
    +
    +  free global memory and set qhT to 0
    +  if !allmem,
    +    does not free short memory (freed by qh_memfreeshort unless qh_NOmem)
    +
    +notes:
    +  sets qh.NOerrexit in case caller forgets to
    +  Does not throw errors
    +
    +see:
    +  see qh_initqhull_start2()
    +  For libqhull_r, qhstatT is part of qhT
    +
    +design:
    +  free global and temporary memory from qh_initbuild and qh_buildhull
    +  free buffers
    +*/
    +void qh_freeqhull(qhT *qh, boolT allmem) {
    +
    +  qh->NOerrexit= True;  /* no more setjmp since called at exit and ~QhullQh */
    +  trace1((qh, qh->ferr, 1006, "qh_freeqhull: free global memory\n"));
    +  qh_freebuild(qh, allmem);
    +  qh_freebuffers(qh);
    +  /* memset is the same in qh_freeqhull() and qh_initqhull_start2() */
    +  memset((char *)qh, 0, sizeof(qhT)-sizeof(qhmemT)-sizeof(qhstatT));
    +  qh->NOerrexit= True;
    +} /* freeqhull2 */
    +
    +/*---------------------------------
    +
    +  qh_init_A(qh, infile, outfile, errfile, argc, argv )
    +    initialize memory and stdio files
    +    convert input options to option string (qh.qhull_command)
    +
    +  notes:
    +    infile may be NULL if qh_readpoints() is not called
    +
    +    errfile should always be defined.  It is used for reporting
    +    errors.  outfile is used for output and format options.
    +
    +    argc/argv may be 0/NULL
    +
    +    called before error handling initialized
    +    qh_errexit() may not be used
    +*/
    +void qh_init_A(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]) {
    +  qh_meminit(qh, errfile);
    +  qh_initqhull_start(qh, infile, outfile, errfile);
    +  qh_init_qhull_command(qh, argc, argv);
    +} /* init_A */
    +
    +/*---------------------------------
    +
    +  qh_init_B(qh, points, numpoints, dim, ismalloc )
    +    initialize globals for points array
    +
    +    points has numpoints dim-dimensional points
    +      points[0] is the first coordinate of the first point
    +      points[1] is the second coordinate of the first point
    +      points[dim] is the first coordinate of the second point
    +
    +    ismalloc=True
    +      Qhull will call qh_free(points) on exit or input transformation
    +    ismalloc=False
    +      Qhull will allocate a new point array if needed for input transformation
    +
    +    qh.qhull_command
    +      is the option string.
    +      It is defined by qh_init_B(), qh_qhull_command(), or qh_initflags
    +
    +  returns:
    +    if qh.PROJECTinput or (qh.DELAUNAY and qh.PROJECTdelaunay)
    +      projects the input to a new point array
    +
    +        if qh.DELAUNAY,
    +          qh.hull_dim is increased by one
    +        if qh.ATinfinity,
    +          qh_projectinput adds point-at-infinity for Delaunay tri.
    +
    +    if qh.SCALEinput
    +      changes the upper and lower bounds of the input, see qh_scaleinput(qh)
    +
    +    if qh.ROTATEinput
    +      rotates the input by a random rotation, see qh_rotateinput()
    +      if qh.DELAUNAY
    +        rotates about the last coordinate
    +
    +  notes:
    +    called after points are defined
    +    qh_errexit() may be used
    +*/
    +void qh_init_B(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  qh_initqhull_globals(qh, points, numpoints, dim, ismalloc);
    +  if (qh->qhmem.LASTsize == 0)
    +    qh_initqhull_mem(qh);
    +  /* mem_r.c and qset_r.c are initialized */
    +  qh_initqhull_buffers(qh);
    +  qh_initthresholds(qh, qh->qhull_command);
    +  if (qh->PROJECTinput || (qh->DELAUNAY && qh->PROJECTdelaunay))
    +    qh_projectinput(qh);
    +  if (qh->SCALEinput)
    +    qh_scaleinput(qh);
    +  if (qh->ROTATErandom >= 0) {
    +    qh_randommatrix(qh, qh->gm_matrix, qh->hull_dim, qh->gm_row);
    +    if (qh->DELAUNAY) {
    +      int k, lastk= qh->hull_dim-1;
    +      for (k=0; k < lastk; k++) {
    +        qh->gm_row[k][lastk]= 0.0;
    +        qh->gm_row[lastk][k]= 0.0;
    +      }
    +      qh->gm_row[lastk][lastk]= 1.0;
    +    }
    +    qh_gram_schmidt(qh, qh->hull_dim, qh->gm_row);
    +    qh_rotateinput(qh, qh->gm_row);
    +  }
    +} /* init_B */
    +
    +/*---------------------------------
    +
    +  qh_init_qhull_command(qh, argc, argv )
    +    build qh.qhull_command from argc/argv
    +    Calls qh_exit if qhull_command is too short
    +
    +  returns:
    +    a space-delimited string of options (just as typed)
    +
    +  notes:
    +    makes option string easy to input and output
    +
    +    argc/argv may be 0/NULL
    +*/
    +void qh_init_qhull_command(qhT *qh, int argc, char *argv[]) {
    +
    +  if (!qh_argv_to_command(argc, argv, qh->qhull_command, (int)sizeof(qh->qhull_command))){
    +    /* Assumes qh.ferr is defined. */
    +    qh_fprintf(qh, qh->ferr, 6033, "qhull input error: more than %d characters in command line.\n",
    +          (int)sizeof(qh->qhull_command));
    +    qh_exit(qh_ERRinput);  /* error reported, can not use qh_errexit */
    +  }
    +} /* init_qhull_command */
    +
    +/*---------------------------------
    +
    +  qh_initflags(qh, commandStr )
    +    set flags and initialized constants from commandStr
    +    calls qh_exit() if qh->NOerrexit
    +
    +  returns:
    +    sets qh.qhull_command to command if needed
    +
    +  notes:
    +    ignores first word (e.g., "qhull d")
    +    use qh_strtol/strtod since strtol/strtod may or may not skip trailing spaces
    +
    +  see:
    +    qh_initthresholds() continues processing of 'Pdn' and 'PDn'
    +    'prompt' in unix_r.c for documentation
    +
    +  design:
    +    for each space-delimited option group
    +      if top-level option
    +        check syntax
    +        append appropriate option to option string
    +        set appropriate global variable or append printFormat to print options
    +      else
    +        for each sub-option
    +          check syntax
    +          append appropriate option to option string
    +          set appropriate global variable or append printFormat to print options
    +*/
    +void qh_initflags(qhT *qh, char *command) {
    +  int k, i, lastproject;
    +  char *s= command, *t, *prev_s, *start, key;
    +  boolT isgeom= False, wasproject;
    +  realT r;
    +
    +  if(qh->NOerrexit){
    +    qh_fprintf(qh, qh->ferr, 6245, "qhull initflags error: qh.NOerrexit was not cleared before calling qh_initflags().  It should be cleared after setjmp().  Exit qhull.");
    +    qh_exit(6245);
    +  }
    +  if (command <= &qh->qhull_command[0] || command > &qh->qhull_command[0] + sizeof(qh->qhull_command)) {
    +    if (command != &qh->qhull_command[0]) {
    +      *qh->qhull_command= '\0';
    +      strncat(qh->qhull_command, command, sizeof(qh->qhull_command)-strlen(qh->qhull_command)-1);
    +    }
    +    while (*s && !isspace(*s))  /* skip program name */
    +      s++;
    +  }
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    prev_s= s;
    +    switch (*s++) {
    +    case 'd':
    +      qh_option(qh, "delaunay", NULL, NULL);
    +      qh->DELAUNAY= True;
    +      break;
    +    case 'f':
    +      qh_option(qh, "facets", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTfacets);
    +      break;
    +    case 'i':
    +      qh_option(qh, "incidence", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTincidences);
    +      break;
    +    case 'm':
    +      qh_option(qh, "mathematica", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTmathematica);
    +      break;
    +    case 'n':
    +      qh_option(qh, "normals", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTnormals);
    +      break;
    +    case 'o':
    +      qh_option(qh, "offFile", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINToff);
    +      break;
    +    case 'p':
    +      qh_option(qh, "points", NULL, NULL);
    +      qh_appendprint(qh, qh_PRINTpoints);
    +      break;
    +    case 's':
    +      qh_option(qh, "summary", NULL, NULL);
    +      qh->PRINTsummary= True;
    +      break;
    +    case 'v':
    +      qh_option(qh, "voronoi", NULL, NULL);
    +      qh->VORONOI= True;
    +      qh->DELAUNAY= True;
    +      break;
    +    case 'A':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7002, "qhull warning: no maximum cosine angle given for option 'An'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh->premerge_cos= -qh_strtod(s, &s);
    +          qh_option(qh, "Angle-premerge-", NULL, &qh->premerge_cos);
    +          qh->PREmerge= True;
    +        }else {
    +          qh->postmerge_cos= qh_strtod(s, &s);
    +          qh_option(qh, "Angle-postmerge", NULL, &qh->postmerge_cos);
    +          qh->POSTmerge= True;
    +        }
    +        qh->MERGING= True;
    +      }
    +      break;
    +    case 'C':
    +      if (!isdigit(*s) && *s != '.' && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7003, "qhull warning: no centrum radius given for option 'Cn'.  Ignored.\n");
    +      else {
    +        if (*s == '-') {
    +          qh->premerge_centrum= -qh_strtod(s, &s);
    +          qh_option(qh, "Centrum-premerge-", NULL, &qh->premerge_centrum);
    +          qh->PREmerge= True;
    +        }else {
    +          qh->postmerge_centrum= qh_strtod(s, &s);
    +          qh_option(qh, "Centrum-postmerge", NULL, &qh->postmerge_centrum);
    +          qh->POSTmerge= True;
    +        }
    +        qh->MERGING= True;
    +      }
    +      break;
    +    case 'E':
    +      if (*s == '-')
    +        qh_fprintf(qh, qh->ferr, 7004, "qhull warning: negative maximum roundoff given for option 'An'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh, qh->ferr, 7005, "qhull warning: no maximum roundoff given for option 'En'.  Ignored.\n");
    +      else {
    +        qh->DISTround= qh_strtod(s, &s);
    +        qh_option(qh, "Distance-roundoff", NULL, &qh->DISTround);
    +        qh->SETroundoff= True;
    +      }
    +      break;
    +    case 'H':
    +      start= s;
    +      qh->HALFspace= True;
    +      qh_strtod(s, &t);
    +      while (t > s)  {
    +        if (*t && !isspace(*t)) {
    +          if (*t == ',')
    +            t++;
    +          else
    +            qh_fprintf(qh, qh->ferr, 7006, "qhull warning: origin for Halfspace intersection should be 'Hn,n,n,...'\n");
    +        }
    +        s= t;
    +        qh_strtod(s, &t);
    +      }
    +      if (start < t) {
    +        if (!(qh->feasible_string= (char*)calloc((size_t)(t-start+1), (size_t)1))) {
    +          qh_fprintf(qh, qh->ferr, 6034, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +          qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +        }
    +        strncpy(qh->feasible_string, start, (size_t)(t-start));
    +        qh_option(qh, "Halfspace-about", NULL, NULL);
    +        qh_option(qh, qh->feasible_string, NULL, NULL);
    +      }else
    +        qh_option(qh, "Halfspace", NULL, NULL);
    +      break;
    +    case 'R':
    +      if (!isdigit(*s))
    +        qh_fprintf(qh, qh->ferr, 7007, "qhull warning: missing random perturbation for option 'Rn'.  Ignored\n");
    +      else {
    +        qh->RANDOMfactor= qh_strtod(s, &s);
    +        qh_option(qh, "Random_perturb", NULL, &qh->RANDOMfactor);
    +        qh->RANDOMdist= True;
    +      }
    +      break;
    +    case 'V':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7008, "qhull warning: missing visible distance for option 'Vn'.  Ignored\n");
    +      else {
    +        qh->MINvisible= qh_strtod(s, &s);
    +        qh_option(qh, "Visible", NULL, &qh->MINvisible);
    +      }
    +      break;
    +    case 'U':
    +      if (!isdigit(*s) && *s != '-')
    +        qh_fprintf(qh, qh->ferr, 7009, "qhull warning: missing coplanar distance for option 'Un'.  Ignored\n");
    +      else {
    +        qh->MAXcoplanar= qh_strtod(s, &s);
    +        qh_option(qh, "U-coplanar", NULL, &qh->MAXcoplanar);
    +      }
    +      break;
    +    case 'W':
    +      if (*s == '-')
    +        qh_fprintf(qh, qh->ferr, 7010, "qhull warning: negative outside width for option 'Wn'.  Ignored.\n");
    +      else if (!isdigit(*s))
    +        qh_fprintf(qh, qh->ferr, 7011, "qhull warning: missing outside width for option 'Wn'.  Ignored\n");
    +      else {
    +        qh->MINoutside= qh_strtod(s, &s);
    +        qh_option(qh, "W-outside", NULL, &qh->MINoutside);
    +        qh->APPROXhull= True;
    +      }
    +      break;
    +    /************  sub menus ***************/
    +    case 'F':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option(qh, "Farea", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTarea);
    +          qh->GETarea= True;
    +          break;
    +        case 'A':
    +          qh_option(qh, "FArea-total", NULL, NULL);
    +          qh->GETarea= True;
    +          break;
    +        case 'c':
    +          qh_option(qh, "Fcoplanars", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTcoplanars);
    +          break;
    +        case 'C':
    +          qh_option(qh, "FCentrums", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTcentrums);
    +          break;
    +        case 'd':
    +          qh_option(qh, "Fd-cdd-in", NULL, NULL);
    +          qh->CDDinput= True;
    +          break;
    +        case 'D':
    +          qh_option(qh, "FD-cdd-out", NULL, NULL);
    +          qh->CDDoutput= True;
    +          break;
    +        case 'F':
    +          qh_option(qh, "FFacets-xridge", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTfacets_xridge);
    +          break;
    +        case 'i':
    +          qh_option(qh, "Finner", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTinner);
    +          break;
    +        case 'I':
    +          qh_option(qh, "FIDs", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTids);
    +          break;
    +        case 'm':
    +          qh_option(qh, "Fmerges", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTmerges);
    +          break;
    +        case 'M':
    +          qh_option(qh, "FMaple", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTmaple);
    +          break;
    +        case 'n':
    +          qh_option(qh, "Fneighbors", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTneighbors);
    +          break;
    +        case 'N':
    +          qh_option(qh, "FNeighbors-vertex", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTvneighbors);
    +          break;
    +        case 'o':
    +          qh_option(qh, "Fouter", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTouter);
    +          break;
    +        case 'O':
    +          if (qh->PRINToptions1st) {
    +            qh_option(qh, "FOptions", NULL, NULL);
    +            qh_appendprint(qh, qh_PRINToptions);
    +          }else
    +            qh->PRINToptions1st= True;
    +          break;
    +        case 'p':
    +          qh_option(qh, "Fpoint-intersect", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTpointintersect);
    +          break;
    +        case 'P':
    +          qh_option(qh, "FPoint-nearest", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTpointnearest);
    +          break;
    +        case 'Q':
    +          qh_option(qh, "FQhull", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTqhull);
    +          break;
    +        case 's':
    +          qh_option(qh, "Fsummary", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTsummary);
    +          break;
    +        case 'S':
    +          qh_option(qh, "FSize", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTsize);
    +          qh->GETarea= True;
    +          break;
    +        case 't':
    +          qh_option(qh, "Ftriangles", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTtriangles);
    +          break;
    +        case 'v':
    +          /* option set in qh_initqhull_globals */
    +          qh_appendprint(qh, qh_PRINTvertices);
    +          break;
    +        case 'V':
    +          qh_option(qh, "FVertex-average", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTaverage);
    +          break;
    +        case 'x':
    +          qh_option(qh, "Fxtremes", NULL, NULL);
    +          qh_appendprint(qh, qh_PRINTextremes);
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7012, "qhull warning: unknown 'F' output option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'G':
    +      isgeom= True;
    +      qh_appendprint(qh, qh_PRINTgeom);
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'a':
    +          qh_option(qh, "Gall-points", NULL, NULL);
    +          qh->PRINTdots= True;
    +          break;
    +        case 'c':
    +          qh_option(qh, "Gcentrums", NULL, NULL);
    +          qh->PRINTcentrums= True;
    +          break;
    +        case 'h':
    +          qh_option(qh, "Gintersections", NULL, NULL);
    +          qh->DOintersections= True;
    +          break;
    +        case 'i':
    +          qh_option(qh, "Ginner", NULL, NULL);
    +          qh->PRINTinner= True;
    +          break;
    +        case 'n':
    +          qh_option(qh, "Gno-planes", NULL, NULL);
    +          qh->PRINTnoplanes= True;
    +          break;
    +        case 'o':
    +          qh_option(qh, "Gouter", NULL, NULL);
    +          qh->PRINTouter= True;
    +          break;
    +        case 'p':
    +          qh_option(qh, "Gpoints", NULL, NULL);
    +          qh->PRINTcoplanar= True;
    +          break;
    +        case 'r':
    +          qh_option(qh, "Gridges", NULL, NULL);
    +          qh->PRINTridges= True;
    +          break;
    +        case 't':
    +          qh_option(qh, "Gtransparent", NULL, NULL);
    +          qh->PRINTtransparent= True;
    +          break;
    +        case 'v':
    +          qh_option(qh, "Gvertices", NULL, NULL);
    +          qh->PRINTspheres= True;
    +          break;
    +        case 'D':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6035, "qhull input error: missing dimension for option 'GDn'\n");
    +          else {
    +            if (qh->DROPdim >= 0)
    +              qh_fprintf(qh, qh->ferr, 7013, "qhull warning: can only drop one dimension.  Previous 'GD%d' ignored\n",
    +                   qh->DROPdim);
    +            qh->DROPdim= qh_strtol(s, &s);
    +            qh_option(qh, "GDrop-dim", &qh->DROPdim, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7014, "qhull warning: unknown 'G' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'P':
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'd': case 'D':  /* see qh_initthresholds() */
    +          key= s[-1];
    +          i= qh_strtol(s, &s);
    +          r= 0;
    +          if (*s == ':') {
    +            s++;
    +            r= qh_strtod(s, &s);
    +          }
    +          if (key == 'd')
    +            qh_option(qh, "Pdrop-facets-dim-less", &i, &r);
    +          else
    +            qh_option(qh, "PDrop-facets-dim-more", &i, &r);
    +          break;
    +        case 'g':
    +          qh_option(qh, "Pgood-facets", NULL, NULL);
    +          qh->PRINTgood= True;
    +          break;
    +        case 'G':
    +          qh_option(qh, "PGood-facet-neighbors", NULL, NULL);
    +          qh->PRINTneighbors= True;
    +          break;
    +        case 'o':
    +          qh_option(qh, "Poutput-forced", NULL, NULL);
    +          qh->FORCEoutput= True;
    +          break;
    +        case 'p':
    +          qh_option(qh, "Pprecision-ignore", NULL, NULL);
    +          qh->PRINTprecision= False;
    +          break;
    +        case 'A':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6036, "qhull input error: missing facet count for keep area option 'PAn'\n");
    +          else {
    +            qh->KEEParea= qh_strtol(s, &s);
    +            qh_option(qh, "PArea-keep", &qh->KEEParea, NULL);
    +            qh->GETarea= True;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6037, "qhull input error: missing facet area for option 'PFn'\n");
    +          else {
    +            qh->KEEPminArea= qh_strtod(s, &s);
    +            qh_option(qh, "PFacet-area-keep", NULL, &qh->KEEPminArea);
    +            qh->GETarea= True;
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6038, "qhull input error: missing merge count for option 'PMn'\n");
    +          else {
    +            qh->KEEPmerge= qh_strtol(s, &s);
    +            qh_option(qh, "PMerge-keep", &qh->KEEPmerge, NULL);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7015, "qhull warning: unknown 'P' print option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'Q':
    +      lastproject= -1;
    +      while (*s && !isspace(*s)) {
    +        switch (*s++) {
    +        case 'b': case 'B':  /* handled by qh_initthresholds */
    +          key= s[-1];
    +          if (key == 'b' && *s == 'B') {
    +            s++;
    +            r= qh_DEFAULTbox;
    +            qh->SCALEinput= True;
    +            qh_option(qh, "QbBound-unit-box", NULL, &r);
    +            break;
    +          }
    +          if (key == 'b' && *s == 'b') {
    +            s++;
    +            qh->SCALElast= True;
    +            qh_option(qh, "Qbbound-last", NULL, NULL);
    +            break;
    +          }
    +          k= qh_strtol(s, &s);
    +          r= 0.0;
    +          wasproject= False;
    +          if (*s == ':') {
    +            s++;
    +            if ((r= qh_strtod(s, &s)) == 0.0) {
    +              t= s;            /* need true dimension for memory allocation */
    +              while (*t && !isspace(*t)) {
    +                if (toupper(*t++) == 'B'
    +                 && k == qh_strtol(t, &t)
    +                 && *t++ == ':'
    +                 && qh_strtod(t, &t) == 0.0) {
    +                  qh->PROJECTinput++;
    +                  trace2((qh, qh->ferr, 2004, "qh_initflags: project dimension %d\n", k));
    +                  qh_option(qh, "Qb-project-dim", &k, NULL);
    +                  wasproject= True;
    +                  lastproject= k;
    +                  break;
    +                }
    +              }
    +            }
    +          }
    +          if (!wasproject) {
    +            if (lastproject == k && r == 0.0)
    +              lastproject= -1;  /* doesn't catch all possible sequences */
    +            else if (key == 'b') {
    +              qh->SCALEinput= True;
    +              if (r == 0.0)
    +                r= -qh_DEFAULTbox;
    +              qh_option(qh, "Qbound-dim-low", &k, &r);
    +            }else {
    +              qh->SCALEinput= True;
    +              if (r == 0.0)
    +                r= qh_DEFAULTbox;
    +              qh_option(qh, "QBound-dim-high", &k, &r);
    +            }
    +          }
    +          break;
    +        case 'c':
    +          qh_option(qh, "Qcoplanar-keep", NULL, NULL);
    +          qh->KEEPcoplanar= True;
    +          break;
    +        case 'f':
    +          qh_option(qh, "Qfurthest-outside", NULL, NULL);
    +          qh->BESToutside= True;
    +          break;
    +        case 'g':
    +          qh_option(qh, "Qgood-facets-only", NULL, NULL);
    +          qh->ONLYgood= True;
    +          break;
    +        case 'i':
    +          qh_option(qh, "Qinterior-keep", NULL, NULL);
    +          qh->KEEPinside= True;
    +          break;
    +        case 'm':
    +          qh_option(qh, "Qmax-outside-only", NULL, NULL);
    +          qh->ONLYmax= True;
    +          break;
    +        case 'r':
    +          qh_option(qh, "Qrandom-outside", NULL, NULL);
    +          qh->RANDOMoutside= True;
    +          break;
    +        case 's':
    +          qh_option(qh, "Qsearch-initial-simplex", NULL, NULL);
    +          qh->ALLpoints= True;
    +          break;
    +        case 't':
    +          qh_option(qh, "Qtriangulate", NULL, NULL);
    +          qh->TRIangulate= True;
    +          break;
    +        case 'T':
    +          qh_option(qh, "QTestPoints", NULL, NULL);
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 6039, "qhull input error: missing number of test points for option 'QTn'\n");
    +          else {
    +            qh->TESTpoints= qh_strtol(s, &s);
    +            qh_option(qh, "QTestPoints", &qh->TESTpoints, NULL);
    +          }
    +          break;
    +        case 'u':
    +          qh_option(qh, "QupperDelaunay", NULL, NULL);
    +          qh->UPPERdelaunay= True;
    +          break;
    +        case 'v':
    +          qh_option(qh, "Qvertex-neighbors-convex", NULL, NULL);
    +          qh->TESTvneighbors= True;
    +          break;
    +        case 'x':
    +          qh_option(qh, "Qxact-merge", NULL, NULL);
    +          qh->MERGEexact= True;
    +          break;
    +        case 'z':
    +          qh_option(qh, "Qz-infinity-point", NULL, NULL);
    +          qh->ATinfinity= True;
    +          break;
    +        case '0':
    +          qh_option(qh, "Q0-no-premerge", NULL, NULL);
    +          qh->NOpremerge= True;
    +          break;
    +        case '1':
    +          if (!isdigit(*s)) {
    +            qh_option(qh, "Q1-no-angle-sort", NULL, NULL);
    +            qh->ANGLEmerge= False;
    +            break;
    +          }
    +          switch (*s++) {
    +          case '0':
    +            qh_option(qh, "Q10-no-narrow", NULL, NULL);
    +            qh->NOnarrow= True;
    +            break;
    +          case '1':
    +            qh_option(qh, "Q11-trinormals Qtriangulate", NULL, NULL);
    +            qh->TRInormals= True;
    +            qh->TRIangulate= True;
    +            break;
    +          case '2':
    +              qh_option(qh, "Q12-no-wide-dup", NULL, NULL);
    +              qh->NOwide= True;
    +            break;
    +          default:
    +            s--;
    +            qh_fprintf(qh, qh->ferr, 7016, "qhull warning: unknown 'Q' qhull option 1%c, rest ignored\n", (int)s[0]);
    +            while (*++s && !isspace(*s));
    +            break;
    +          }
    +          break;
    +        case '2':
    +          qh_option(qh, "Q2-no-merge-independent", NULL, NULL);
    +          qh->MERGEindependent= False;
    +          goto LABELcheckdigit;
    +          break; /* no warnings */
    +        case '3':
    +          qh_option(qh, "Q3-no-merge-vertices", NULL, NULL);
    +          qh->MERGEvertices= False;
    +        LABELcheckdigit:
    +          if (isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7017, "qhull warning: can not follow '1', '2', or '3' with a digit.  '%c' skipped.\n",
    +                     *s++);
    +          break;
    +        case '4':
    +          qh_option(qh, "Q4-avoid-old-into-new", NULL, NULL);
    +          qh->AVOIDold= True;
    +          break;
    +        case '5':
    +          qh_option(qh, "Q5-no-check-outer", NULL, NULL);
    +          qh->SKIPcheckmax= True;
    +          break;
    +        case '6':
    +          qh_option(qh, "Q6-no-concave-merge", NULL, NULL);
    +          qh->SKIPconvex= True;
    +          break;
    +        case '7':
    +          qh_option(qh, "Q7-no-breadth-first", NULL, NULL);
    +          qh->VIRTUALmemory= True;
    +          break;
    +        case '8':
    +          qh_option(qh, "Q8-no-near-inside", NULL, NULL);
    +          qh->NOnearinside= True;
    +          break;
    +        case '9':
    +          qh_option(qh, "Q9-pick-furthest", NULL, NULL);
    +          qh->PICKfurthest= True;
    +          break;
    +        case 'G':
    +          i= qh_strtol(s, &t);
    +          if (qh->GOODpoint)
    +            qh_fprintf(qh, qh->ferr, 7018, "qhull warning: good point already defined for option 'QGn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh, qh->ferr, 7019, "qhull warning: missing good point id for option 'QGn'.  Ignored\n");
    +          else if (i < 0 || *s == '-') {
    +            qh->GOODpoint= i-1;
    +            qh_option(qh, "QGood-if-dont-see-point", &i, NULL);
    +          }else {
    +            qh->GOODpoint= i+1;
    +            qh_option(qh, "QGood-if-see-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'J':
    +          if (!isdigit(*s) && *s != '-')
    +            qh->JOGGLEmax= 0.0;
    +          else {
    +            qh->JOGGLEmax= (realT) qh_strtod(s, &s);
    +            qh_option(qh, "QJoggle", NULL, &qh->JOGGLEmax);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s) && *s != '-')
    +            qh_fprintf(qh, qh->ferr, 7020, "qhull warning: missing random seed for option 'QRn'.  Ignored\n");
    +          else {
    +            qh->ROTATErandom= i= qh_strtol(s, &s);
    +            if (i > 0)
    +              qh_option(qh, "QRotate-id", &i, NULL );
    +            else if (i < -1)
    +              qh_option(qh, "QRandom-seed", &i, NULL );
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (qh->GOODvertex)
    +            qh_fprintf(qh, qh->ferr, 7021, "qhull warning: good vertex already defined for option 'QVn'.  Ignored\n");
    +          else if (s == t)
    +            qh_fprintf(qh, qh->ferr, 7022, "qhull warning: no good point id given for option 'QVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh->GOODvertex= i - 1;
    +            qh_option(qh, "QV-good-facets-not-point", &i, NULL);
    +          }else {
    +            qh_option(qh, "QV-good-facets-point", &i, NULL);
    +            qh->GOODvertex= i + 1;
    +          }
    +          s= t;
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7023, "qhull warning: unknown 'Q' qhull option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    case 'T':
    +      while (*s && !isspace(*s)) {
    +        if (isdigit(*s) || *s == '-')
    +          qh->IStracing= qh_strtol(s, &s);
    +        else switch (*s++) {
    +        case 'a':
    +          qh_option(qh, "Tannotate-output", NULL, NULL);
    +          qh->ANNOTATEoutput= True;
    +          break;
    +        case 'c':
    +          qh_option(qh, "Tcheck-frequently", NULL, NULL);
    +          qh->CHECKfrequently= True;
    +          break;
    +        case 's':
    +          qh_option(qh, "Tstatistics", NULL, NULL);
    +          qh->PRINTstatistics= True;
    +          break;
    +        case 'v':
    +          qh_option(qh, "Tverify", NULL, NULL);
    +          qh->VERIFYoutput= True;
    +          break;
    +        case 'z':
    +          if (qh->ferr == qh_FILEstderr) {
    +            /* The C++ interface captures the output in qh_fprint_qhull() */
    +            qh_option(qh, "Tz-stdout", NULL, NULL);
    +            qh->USEstdout= True;
    +          }else if (!qh->fout)
    +            qh_fprintf(qh, qh->ferr, 7024, "qhull warning: output file undefined(stdout).  Option 'Tz' ignored.\n");
    +          else {
    +            qh_option(qh, "Tz-stdout", NULL, NULL);
    +            qh->USEstdout= True;
    +            qh->ferr= qh->fout;
    +            qh->qhmem.ferr= qh->fout;
    +          }
    +          break;
    +        case 'C':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7025, "qhull warning: missing point id for cone for trace option 'TCn'.  Ignored\n");
    +          else {
    +            i= qh_strtol(s, &s);
    +            qh_option(qh, "TCone-stop", &i, NULL);
    +            qh->STOPcone= i + 1;
    +          }
    +          break;
    +        case 'F':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7026, "qhull warning: missing frequency count for trace option 'TFn'.  Ignored\n");
    +          else {
    +            qh->REPORTfreq= qh_strtol(s, &s);
    +            qh_option(qh, "TFacet-log", &qh->REPORTfreq, NULL);
    +            qh->REPORTfreq2= qh->REPORTfreq/2;  /* for tracemerging() */
    +          }
    +          break;
    +        case 'I':
    +          if (!isspace(*s))
    +            qh_fprintf(qh, qh->ferr, 7027, "qhull warning: missing space between 'TI' and filename, %s\n", s);
    +          while (isspace(*s))
    +            s++;
    +          t= qh_skipfilename(qh, s);
    +          {
    +            char filename[qh_FILENAMElen];
    +
    +            qh_copyfilename(qh, filename, (int)sizeof(filename), s, (int)(t-s));   /* WARN64 */
    +            s= t;
    +            if (!freopen(filename, "r", stdin)) {
    +              qh_fprintf(qh, qh->ferr, 6041, "qhull error: could not open file \"%s\".", filename);
    +              qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +            }else {
    +              qh_option(qh, "TInput-file", NULL, NULL);
    +              qh_option(qh, filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'O':
    +            if (!isspace(*s))
    +                qh_fprintf(qh, qh->ferr, 7028, "qhull warning: missing space between 'TO' and filename, %s\n", s);
    +            while (isspace(*s))
    +                s++;
    +            t= qh_skipfilename(qh, s);
    +            {
    +              char filename[qh_FILENAMElen];
    +
    +              qh_copyfilename(qh, filename, (int)sizeof(filename), s, (int)(t-s));  /* WARN64 */
    +              s= t;
    +              if (!qh->fout) {
    +                qh_fprintf(qh, qh->ferr, 6266, "qhull input warning: qh.fout was not set by caller.  Cannot use option 'TO' to redirect output.  Ignoring option 'TO'\n");
    +              }else if (!freopen(filename, "w", qh->fout)) {
    +                qh_fprintf(qh, qh->ferr, 6044, "qhull error: could not open file \"%s\".", filename);
    +                qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +              }else {
    +                qh_option(qh, "TOutput-file", NULL, NULL);
    +              qh_option(qh, filename, NULL, NULL);
    +            }
    +          }
    +          break;
    +        case 'P':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7029, "qhull warning: missing point id for trace option 'TPn'.  Ignored\n");
    +          else {
    +            qh->TRACEpoint= qh_strtol(s, &s);
    +            qh_option(qh, "Trace-point", &qh->TRACEpoint, NULL);
    +          }
    +          break;
    +        case 'M':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7030, "qhull warning: missing merge id for trace option 'TMn'.  Ignored\n");
    +          else {
    +            qh->TRACEmerge= qh_strtol(s, &s);
    +            qh_option(qh, "Trace-merge", &qh->TRACEmerge, NULL);
    +          }
    +          break;
    +        case 'R':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7031, "qhull warning: missing rerun count for trace option 'TRn'.  Ignored\n");
    +          else {
    +            qh->RERUN= qh_strtol(s, &s);
    +            qh_option(qh, "TRerun", &qh->RERUN, NULL);
    +          }
    +          break;
    +        case 'V':
    +          i= qh_strtol(s, &t);
    +          if (s == t)
    +            qh_fprintf(qh, qh->ferr, 7032, "qhull warning: missing furthest point id for trace option 'TVn'.  Ignored\n");
    +          else if (i < 0) {
    +            qh->STOPpoint= i - 1;
    +            qh_option(qh, "TV-stop-before-point", &i, NULL);
    +          }else {
    +            qh->STOPpoint= i + 1;
    +            qh_option(qh, "TV-stop-after-point", &i, NULL);
    +          }
    +          s= t;
    +          break;
    +        case 'W':
    +          if (!isdigit(*s))
    +            qh_fprintf(qh, qh->ferr, 7033, "qhull warning: missing max width for trace option 'TWn'.  Ignored\n");
    +          else {
    +            qh->TRACEdist= (realT) qh_strtod(s, &s);
    +            qh_option(qh, "TWide-trace", NULL, &qh->TRACEdist);
    +          }
    +          break;
    +        default:
    +          s--;
    +          qh_fprintf(qh, qh->ferr, 7034, "qhull warning: unknown 'T' trace option %c, rest ignored\n", (int)s[0]);
    +          while (*++s && !isspace(*s));
    +          break;
    +        }
    +      }
    +      break;
    +    default:
    +      qh_fprintf(qh, qh->ferr, 7035, "qhull warning: unknown flag %c(%x)\n", (int)s[-1],
    +               (int)s[-1]);
    +      break;
    +    }
    +    if (s-1 == prev_s && *s && !isspace(*s)) {
    +      qh_fprintf(qh, qh->ferr, 7036, "qhull warning: missing space after flag %c(%x); reserved for menu. Skipped.\n",
    +               (int)*prev_s, (int)*prev_s);
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +  }
    +  if (qh->STOPcone && qh->JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh, qh->ferr, 7078, "qhull warning: 'TCn' (stopCone) ignored when used with 'QJn' (joggle)\n");
    +  if (isgeom && !qh->FORCEoutput && qh->PRINTout[1])
    +    qh_fprintf(qh, qh->ferr, 7037, "qhull warning: additional output formats are not compatible with Geomview\n");
    +  /* set derived values in qh_initqhull_globals */
    +} /* initflags */
    +
    +
    +/*---------------------------------
    +
    +  qh_initqhull_buffers(qh)
    +    initialize global memory buffers
    +
    +  notes:
    +    must match qh_freebuffers()
    +*/
    +void qh_initqhull_buffers(qhT *qh) {
    +  int k;
    +
    +  qh->TEMPsize= (qh->qhmem.LASTsize - sizeof(setT))/SETelemsize;
    +  if (qh->TEMPsize <= 0 || qh->TEMPsize > qh->qhmem.LASTsize)
    +    qh->TEMPsize= 8;  /* e.g., if qh_NOmem */
    +  qh->other_points= qh_setnew(qh, qh->TEMPsize);
    +  qh->del_vertices= qh_setnew(qh, qh->TEMPsize);
    +  qh->coplanarfacetset= qh_setnew(qh, qh->TEMPsize);
    +  qh->NEARzero= (realT *)qh_memalloc(qh, qh->hull_dim * sizeof(realT));
    +  qh->lower_threshold= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  qh->upper_threshold= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  qh->lower_bound= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  qh->upper_bound= (realT *)qh_memalloc(qh, (qh->input_dim+1) * sizeof(realT));
    +  for (k=qh->input_dim+1; k--; ) {  /* duplicated in qh_initqhull_buffers and qh_clear_outputflags */
    +    qh->lower_threshold[k]= -REALmax;
    +    qh->upper_threshold[k]= REALmax;
    +    qh->lower_bound[k]= -REALmax;
    +    qh->upper_bound[k]= REALmax;
    +  }
    +  qh->gm_matrix= (coordT *)qh_memalloc(qh, (qh->hull_dim+1) * qh->hull_dim * sizeof(coordT));
    +  qh->gm_row= (coordT **)qh_memalloc(qh, (qh->hull_dim+1) * sizeof(coordT *));
    +} /* initqhull_buffers */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_globals(qh, points, numpoints, dim, ismalloc )
    +    initialize globals
    +    if ismalloc
    +      points were malloc'd and qhull should free at end
    +
    +  returns:
    +    sets qh.first_point, num_points, input_dim, hull_dim and others
    +    seeds random number generator (seed=1 if tracing)
    +    modifies qh.hull_dim if ((qh.DELAUNAY and qh.PROJECTdelaunay) or qh.PROJECTinput)
    +    adjust user flags as needed
    +    also checks DIM3 dependencies and constants
    +
    +  notes:
    +    do not use qh_point() since an input transformation may move them elsewhere
    +
    +  see:
    +    qh_initqhull_start() sets default values for non-zero globals
    +
    +  design:
    +    initialize points array from input arguments
    +    test for qh.ZEROcentrum
    +      (i.e., use opposite vertex instead of cetrum for convexity testing)
    +    initialize qh.CENTERtype, qh.normal_size,
    +      qh.center_size, qh.TRACEpoint/level,
    +    initialize and test random numbers
    +    qh_initqhull_outputflags() -- adjust and test output flags
    +*/
    +void qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc) {
    +  int seed, pointsneeded, extra= 0, i, randi, k;
    +  realT randr;
    +  realT factorial;
    +
    +  time_t timedata;
    +
    +  trace0((qh, qh->ferr, 13, "qh_initqhull_globals: for %s | %s\n", qh->rbox_command,
    +      qh->qhull_command));
    +  qh->POINTSmalloc= ismalloc;
    +  qh->first_point= points;
    +  qh->num_points= numpoints;
    +  qh->hull_dim= qh->input_dim= dim;
    +  if (!qh->NOpremerge && !qh->MERGEexact && !qh->PREmerge && qh->JOGGLEmax > REALmax/2) {
    +    qh->MERGING= True;
    +    if (qh->hull_dim <= 4) {
    +      qh->PREmerge= True;
    +      qh_option(qh, "_pre-merge", NULL, NULL);
    +    }else {
    +      qh->MERGEexact= True;
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +    }
    +  }else if (qh->MERGEexact)
    +    qh->MERGING= True;
    +  if (!qh->NOpremerge && qh->JOGGLEmax > REALmax/2) {
    +#ifdef qh_NOmerge
    +    qh->JOGGLEmax= 0.0;
    +#endif
    +  }
    +  if (qh->TRIangulate && qh->JOGGLEmax < REALmax/2 && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 7038, "qhull warning: joggle('QJ') always produces simplicial output.  Triangulated output('Qt') does nothing.\n");
    +  if (qh->JOGGLEmax < REALmax/2 && qh->DELAUNAY && !qh->SCALEinput && !qh->SCALElast) {
    +    qh->SCALElast= True;
    +    qh_option(qh, "Qbbound-last-qj", NULL, NULL);
    +  }
    +  if (qh->MERGING && !qh->POSTmerge && qh->premerge_cos > REALmax/2
    +  && qh->premerge_centrum == 0) {
    +    qh->ZEROcentrum= True;
    +    qh->ZEROall_ok= True;
    +    qh_option(qh, "_zero-centrum", NULL, NULL);
    +  }
    +  if (qh->JOGGLEmax < REALmax/2 && REALepsilon > 2e-8 && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 7039, "qhull warning: real epsilon, %2.2g, is probably too large for joggle('QJn')\nRecompile with double precision reals(see user.h).\n",
    +          REALepsilon);
    +#ifdef qh_NOmerge
    +  if (qh->MERGING) {
    +    qh_fprintf(qh, qh->ferr, 6045, "qhull input error: merging not installed(qh_NOmerge + 'Qx', 'Cn' or 'An')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +#endif
    +  if (qh->DELAUNAY && qh->KEEPcoplanar && !qh->KEEPinside) {
    +    qh->KEEPinside= True;
    +    qh_option(qh, "Qinterior-keep", NULL, NULL);
    +  }
    +  if (qh->DELAUNAY && qh->HALFspace) {
    +    qh_fprintf(qh, qh->ferr, 6046, "qhull input error: can not use Delaunay('d') or Voronoi('v') with halfspace intersection('H')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (!qh->DELAUNAY && (qh->UPPERdelaunay || qh->ATinfinity)) {
    +    qh_fprintf(qh, qh->ferr, 6047, "qhull input error: use upper-Delaunay('Qu') or infinity-point('Qz') with Delaunay('d') or Voronoi('v')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->UPPERdelaunay && qh->ATinfinity) {
    +    qh_fprintf(qh, qh->ferr, 6048, "qhull input error: can not use infinity-point('Qz') with upper-Delaunay('Qu')\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->SCALElast && !qh->DELAUNAY && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 7040, "qhull input warning: option 'Qbb' (scale-last-coordinate) is normally used with 'd' or 'v'\n");
    +  qh->DOcheckmax= (!qh->SKIPcheckmax && qh->MERGING );
    +  qh->KEEPnearinside= (qh->DOcheckmax && !(qh->KEEPinside && qh->KEEPcoplanar)
    +                          && !qh->NOnearinside);
    +  if (qh->MERGING)
    +    qh->CENTERtype= qh_AScentrum;
    +  else if (qh->VORONOI)
    +    qh->CENTERtype= qh_ASvoronoi;
    +  if (qh->TESTvneighbors && !qh->MERGING) {
    +    qh_fprintf(qh, qh->ferr, 6049, "qhull input error: test vertex neighbors('Qv') needs a merge option\n");
    +    qh_errexit(qh, qh_ERRinput, NULL ,NULL);
    +  }
    +  if (qh->PROJECTinput || (qh->DELAUNAY && qh->PROJECTdelaunay)) {
    +    qh->hull_dim -= qh->PROJECTinput;
    +    if (qh->DELAUNAY) {
    +      qh->hull_dim++;
    +      if (qh->ATinfinity)
    +        extra= 1;
    +    }
    +  }
    +  if (qh->hull_dim <= 1) {
    +    qh_fprintf(qh, qh->ferr, 6050, "qhull error: dimension %d must be > 1\n", qh->hull_dim);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  for (k=2, factorial=1.0; k < qh->hull_dim; k++)
    +    factorial *= k;
    +  qh->AREAfactor= 1.0 / factorial;
    +  trace2((qh, qh->ferr, 2005, "qh_initqhull_globals: initialize globals.  dim %d numpoints %d malloc? %d projected %d to hull_dim %d\n",
    +        dim, numpoints, ismalloc, qh->PROJECTinput, qh->hull_dim));
    +  qh->normal_size= qh->hull_dim * sizeof(coordT);
    +  qh->center_size= qh->normal_size - sizeof(coordT);
    +  pointsneeded= qh->hull_dim+1;
    +  if (qh->hull_dim > qh_DIMmergeVertex) {
    +    qh->MERGEvertices= False;
    +    qh_option(qh, "Q3-no-merge-vertices-dim-high", NULL, NULL);
    +  }
    +  if (qh->GOODpoint)
    +    pointsneeded++;
    +#ifdef qh_NOtrace
    +  if (qh->IStracing) {
    +    qh_fprintf(qh, qh->ferr, 6051, "qhull input error: tracing is not installed(qh_NOtrace in user.h)");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +#endif
    +  if (qh->RERUN > 1) {
    +    qh->TRACElastrun= qh->IStracing; /* qh_build_withrestart duplicates next conditional */
    +    if (qh->IStracing != -1)
    +      qh->IStracing= 0;
    +  }else if (qh->TRACEpoint != qh_IDunknown || qh->TRACEdist < REALmax/2 || qh->TRACEmerge) {
    +    qh->TRACElevel= (qh->IStracing? qh->IStracing : 3);
    +    qh->IStracing= 0;
    +  }
    +  if (qh->ROTATErandom == 0 || qh->ROTATErandom == -1) {
    +    seed= (int)time(&timedata);
    +    if (qh->ROTATErandom  == -1) {
    +      seed= -seed;
    +      qh_option(qh, "QRandom-seed", &seed, NULL );
    +    }else
    +      qh_option(qh, "QRotate-random", &seed, NULL);
    +    qh->ROTATErandom= seed;
    +  }
    +  seed= qh->ROTATErandom;
    +  if (seed == INT_MIN)    /* default value */
    +    seed= 1;
    +  else if (seed < 0)
    +    seed= -seed;
    +  qh_RANDOMseed_(qh, seed);
    +  randr= 0.0;
    +  for (i=1000; i--; ) {
    +    randi= qh_RANDOMint;
    +    randr += randi;
    +    if (randi > qh_RANDOMmax) {
    +      qh_fprintf(qh, qh->ferr, 8036, "\
    +qhull configuration error (qh_RANDOMmax in user.h):\n\
    +   random integer %d > qh_RANDOMmax(qh, %.8g)\n",
    +               randi, qh_RANDOMmax);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  qh_RANDOMseed_(qh, seed);
    +  randr = randr/1000;
    +  if (randr < qh_RANDOMmax * 0.1
    +  || randr > qh_RANDOMmax * 0.9)
    +    qh_fprintf(qh, qh->ferr, 8037, "\
    +qhull configuration warning (qh_RANDOMmax in user.h):\n\
    +   average of 1000 random integers (%.2g) is much different than expected (%.2g).\n\
    +   Is qh_RANDOMmax (%.2g) wrong?\n",
    +             randr, qh_RANDOMmax * 0.5, qh_RANDOMmax);
    +  qh->RANDOMa= 2.0 * qh->RANDOMfactor/qh_RANDOMmax;
    +  qh->RANDOMb= 1.0 - qh->RANDOMfactor;
    +  if (qh_HASHfactor < 1.1) {
    +    qh_fprintf(qh, qh->ferr, 6052, "qhull internal error (qh_initqhull_globals): qh_HASHfactor %d must be at least 1.1.  Qhull uses linear hash probing\n",
    +      qh_HASHfactor);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (numpoints+extra < pointsneeded) {
    +    qh_fprintf(qh, qh->ferr, 6214, "qhull input error: not enough points(%d) to construct initial simplex (need %d)\n",
    +            numpoints, pointsneeded);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  qh_initqhull_outputflags(qh);
    +} /* initqhull_globals */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_mem(qh, )
    +    initialize mem_r.c for qhull
    +    qh.hull_dim and qh.normal_size determine some of the allocation sizes
    +    if qh.MERGING,
    +      includes ridgeT
    +    calls qh_user_memsizes(qh) to add up to 10 additional sizes for quick allocation
    +      (see numsizes below)
    +
    +  returns:
    +    mem_r.c already for qh_memalloc/qh_memfree (errors if called beforehand)
    +
    +  notes:
    +    qh_produceoutput() prints memsizes
    +
    +*/
    +void qh_initqhull_mem(qhT *qh) {
    +  int numsizes;
    +  int i;
    +
    +  numsizes= 8+10;
    +  qh_meminitbuffers(qh, qh->IStracing, qh_MEMalign, numsizes,
    +                     qh_MEMbufsize, qh_MEMinitbuf);
    +  qh_memsize(qh, (int)sizeof(vertexT));
    +  if (qh->MERGING) {
    +    qh_memsize(qh, (int)sizeof(ridgeT));
    +    qh_memsize(qh, (int)sizeof(mergeT));
    +  }
    +  qh_memsize(qh, (int)sizeof(facetT));
    +  i= sizeof(setT) + (qh->hull_dim - 1) * SETelemsize;  /* ridge.vertices */
    +  qh_memsize(qh, i);
    +  qh_memsize(qh, qh->normal_size);        /* normal */
    +  i += SETelemsize;                 /* facet.vertices, .ridges, .neighbors */
    +  qh_memsize(qh, i);
    +  qh_user_memsizes(qh);
    +  qh_memsetup(qh);
    +} /* initqhull_mem */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_outputflags
    +    initialize flags concerned with output
    +
    +  returns:
    +    adjust user flags as needed
    +
    +  see:
    +    qh_clear_outputflags() resets the flags
    +
    +  design:
    +    test for qh.PRINTgood (i.e., only print 'good' facets)
    +    check for conflicting print output options
    +*/
    +void qh_initqhull_outputflags(qhT *qh) {
    +  boolT printgeom= False, printmath= False, printcoplanar= False;
    +  int i;
    +
    +  trace3((qh, qh->ferr, 3024, "qh_initqhull_outputflags: %s\n", qh->qhull_command));
    +  if (!(qh->PRINTgood || qh->PRINTneighbors)) {
    +    if (qh->KEEParea || qh->KEEPminArea < REALmax/2 || qh->KEEPmerge || qh->DELAUNAY
    +        || (!qh->ONLYgood && (qh->GOODvertex || qh->GOODpoint))) {
    +      qh->PRINTgood= True;
    +      qh_option(qh, "Pgood", NULL, NULL);
    +    }
    +  }
    +  if (qh->PRINTtransparent) {
    +    if (qh->hull_dim != 4 || !qh->DELAUNAY || qh->VORONOI || qh->DROPdim >= 0) {
    +      qh_fprintf(qh, qh->ferr, 6215, "qhull input error: transparent Delaunay('Gt') needs 3-d Delaunay('d') w/o 'GDn'\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    qh->DROPdim = 3;
    +    qh->PRINTridges = True;
    +  }
    +  for (i=qh_PRINTEND; i--; ) {
    +    if (qh->PRINTout[i] == qh_PRINTgeom)
    +      printgeom= True;
    +    else if (qh->PRINTout[i] == qh_PRINTmathematica || qh->PRINTout[i] == qh_PRINTmaple)
    +      printmath= True;
    +    else if (qh->PRINTout[i] == qh_PRINTcoplanars)
    +      printcoplanar= True;
    +    else if (qh->PRINTout[i] == qh_PRINTpointnearest)
    +      printcoplanar= True;
    +    else if (qh->PRINTout[i] == qh_PRINTpointintersect && !qh->HALFspace) {
    +      qh_fprintf(qh, qh->ferr, 6053, "qhull input error: option 'Fp' is only used for \nhalfspace intersection('Hn,n,n').\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }else if (qh->PRINTout[i] == qh_PRINTtriangles && (qh->HALFspace || qh->VORONOI)) {
    +      qh_fprintf(qh, qh->ferr, 6054, "qhull input error: option 'Ft' is not available for Voronoi vertices or halfspace intersection\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }else if (qh->PRINTout[i] == qh_PRINTcentrums && qh->VORONOI) {
    +      qh_fprintf(qh, qh->ferr, 6055, "qhull input error: option 'FC' is not available for Voronoi vertices('v')\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }else if (qh->PRINTout[i] == qh_PRINTvertices) {
    +      if (qh->VORONOI)
    +        qh_option(qh, "Fvoronoi", NULL, NULL);
    +      else
    +        qh_option(qh, "Fvertices", NULL, NULL);
    +    }
    +  }
    +  if (printcoplanar && qh->DELAUNAY && qh->JOGGLEmax < REALmax/2) {
    +    if (qh->PRINTprecision)
    +      qh_fprintf(qh, qh->ferr, 7041, "qhull input warning: 'QJ' (joggle) will usually prevent coincident input sites for options 'Fc' and 'FP'\n");
    +  }
    +  if (printmath && (qh->hull_dim > 3 || qh->VORONOI)) {
    +    qh_fprintf(qh, qh->ferr, 6056, "qhull input error: Mathematica and Maple output is only available for 2-d and 3-d convex hulls and 2-d Delaunay triangulations\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (printgeom) {
    +    if (qh->hull_dim > 4) {
    +      qh_fprintf(qh, qh->ferr, 6057, "qhull input error: Geomview output is only available for 2-d, 3-d and 4-d\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh->PRINTnoplanes && !(qh->PRINTcoplanar + qh->PRINTcentrums
    +     + qh->PRINTdots + qh->PRINTspheres + qh->DOintersections + qh->PRINTridges)) {
    +      qh_fprintf(qh, qh->ferr, 6058, "qhull input error: no output specified for Geomview\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh->VORONOI && (qh->hull_dim > 3 || qh->DROPdim >= 0)) {
    +      qh_fprintf(qh, qh->ferr, 6059, "qhull input error: Geomview output for Voronoi diagrams only for 2-d\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    /* can not warn about furthest-site Geomview output: no lower_threshold */
    +    if (qh->hull_dim == 4 && qh->DROPdim == -1 &&
    +        (qh->PRINTcoplanar || qh->PRINTspheres || qh->PRINTcentrums)) {
    +      qh_fprintf(qh, qh->ferr, 7042, "qhull input warning: coplanars, vertices, and centrums output not\n\
    +available for 4-d output(ignored).  Could use 'GDn' instead.\n");
    +      qh->PRINTcoplanar= qh->PRINTspheres= qh->PRINTcentrums= False;
    +    }
    +  }
    +  if (!qh->KEEPcoplanar && !qh->KEEPinside && !qh->ONLYgood) {
    +    if ((qh->PRINTcoplanar && qh->PRINTspheres) || printcoplanar) {
    +      if (qh->QHULLfinished) {
    +        qh_fprintf(qh, qh->ferr, 7072, "qhull output warning: ignoring coplanar points, option 'Qc' was not set for the first run of qhull.\n");
    +      }else {
    +        qh->KEEPcoplanar = True;
    +        qh_option(qh, "Qcoplanar", NULL, NULL);
    +      }
    +    }
    +  }
    +  qh->PRINTdim= qh->hull_dim;
    +  if (qh->DROPdim >=0) {    /* after Geomview checks */
    +    if (qh->DROPdim < qh->hull_dim) {
    +      qh->PRINTdim--;
    +      if (!printgeom || qh->hull_dim < 3)
    +        qh_fprintf(qh, qh->ferr, 7043, "qhull input warning: drop dimension 'GD%d' is only available for 3-d/4-d Geomview\n", qh->DROPdim);
    +    }else
    +      qh->DROPdim= -1;
    +  }else if (qh->VORONOI) {
    +    qh->DROPdim= qh->hull_dim-1;
    +    qh->PRINTdim= qh->hull_dim-1;
    +  }
    +} /* qh_initqhull_outputflags */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start(qh, infile, outfile, errfile )
    +    allocate memory if needed and call qh_initqhull_start2()
    +*/
    +void qh_initqhull_start(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile) {
    +
    +  qh_initstatistics(qh);
    +  qh_initqhull_start2(qh, infile, outfile, errfile);
    +} /* initqhull_start */
    +
    +/*---------------------------------
    +
    +  qh_initqhull_start2(qh, infile, outfile, errfile )
    +    start initialization of qhull
    +    initialize statistics, stdio, default values for global variables
    +    assumes qh is allocated
    +  notes:
    +    report errors elsewhere, error handling and g_qhull_output [Qhull.cpp, QhullQh()] not in initialized
    +  see:
    +    qh_maxmin() determines the precision constants
    +    qh_freeqhull()
    +*/
    +void qh_initqhull_start2(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile) {
    +  time_t timedata;
    +  int seed;
    +
    +  qh_CPUclock; /* start the clock(for qh_clock).  One-shot. */
    +  /* memset is the same in qh_freeqhull() and qh_initqhull_start2() */
    +  memset((char *)qh, 0, sizeof(qhT)-sizeof(qhmemT)-sizeof(qhstatT));   /* every field is 0, FALSE, NULL */
    +  qh->NOerrexit= True;
    +  qh->ANGLEmerge= True;
    +  qh->DROPdim= -1;
    +  qh->ferr= errfile;
    +  qh->fin= infile;
    +  qh->fout= outfile;
    +  qh->furthest_id= qh_IDunknown;
    +  qh->JOGGLEmax= REALmax;
    +  qh->KEEPminArea = REALmax;
    +  qh->last_low= REALmax;
    +  qh->last_high= REALmax;
    +  qh->last_newhigh= REALmax;
    +  qh->last_random= 1;
    +  qh->max_outside= 0.0;
    +  qh->max_vertex= 0.0;
    +  qh->MAXabs_coord= 0.0;
    +  qh->MAXsumcoord= 0.0;
    +  qh->MAXwidth= -REALmax;
    +  qh->MERGEindependent= True;
    +  qh->MINdenom_1= fmax_(1.0/REALmax, REALmin); /* used by qh_scalepoints */
    +  qh->MINoutside= 0.0;
    +  qh->MINvisible= REALmax;
    +  qh->MAXcoplanar= REALmax;
    +  qh->outside_err= REALmax;
    +  qh->premerge_centrum= 0.0;
    +  qh->premerge_cos= REALmax;
    +  qh->PRINTprecision= True;
    +  qh->PRINTradius= 0.0;
    +  qh->postmerge_cos= REALmax;
    +  qh->postmerge_centrum= 0.0;
    +  qh->ROTATErandom= INT_MIN;
    +  qh->MERGEvertices= True;
    +  qh->totarea= 0.0;
    +  qh->totvol= 0.0;
    +  qh->TRACEdist= REALmax;
    +  qh->TRACEpoint= qh_IDunknown; /* recompile or use 'TPn' */
    +  qh->tracefacet_id= UINT_MAX;  /* recompile to trace a facet */
    +  qh->tracevertex_id= UINT_MAX; /* recompile to trace a vertex */
    +  seed= (int)time(&timedata);
    +  qh_RANDOMseed_(qh, seed);
    +  qh->run_id= qh_RANDOMint;
    +  if(!qh->run_id)
    +      qh->run_id++;  /* guarantee non-zero */
    +  qh_option(qh, "run-id", &qh->run_id, NULL);
    +  strcat(qh->qhull, "qhull");
    +} /* initqhull_start2 */
    +
    +/*---------------------------------
    +
    +  qh_initthresholds(qh, commandString )
    +    set thresholds for printing and scaling from commandString
    +
    +  returns:
    +    sets qh.GOODthreshold or qh.SPLITthreshold if 'Pd0D1' used
    +
    +  see:
    +    qh_initflags(), 'Qbk' 'QBk' 'Pdk' and 'PDk'
    +    qh_inthresholds()
    +
    +  design:
    +    for each 'Pdn' or 'PDn' option
    +      check syntax
    +      set qh.lower_threshold or qh.upper_threshold
    +    set qh.GOODthreshold if an unbounded threshold is used
    +    set qh.SPLITthreshold if a bounded threshold is used
    +*/
    +void qh_initthresholds(qhT *qh, char *command) {
    +  realT value;
    +  int idx, maxdim, k;
    +  char *s= command; /* non-const due to strtol */
    +  char key;
    +
    +  maxdim= qh->input_dim;
    +  if (qh->DELAUNAY && (qh->PROJECTdelaunay || qh->PROJECTinput))
    +    maxdim++;
    +  while (*s) {
    +    if (*s == '-')
    +      s++;
    +    if (*s == 'P') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'd' || key == 'D') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh, qh->ferr, 7044, "qhull warning: no dimension given for Print option '%c' at: %s.  Ignored\n",
    +                    key, s-1);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= qh->hull_dim) {
    +            qh_fprintf(qh, qh->ferr, 7045, "qhull warning: dimension %d for Print option '%c' is >= %d.  Ignored\n",
    +                idx, key, qh->hull_dim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +            if (fabs((double)value) > 1.0) {
    +              qh_fprintf(qh, qh->ferr, 7046, "qhull warning: value %2.4g for Print option %c is > +1 or < -1.  Ignored\n",
    +                      value, key);
    +              continue;
    +            }
    +          }else
    +            value= 0.0;
    +          if (key == 'd')
    +            qh->lower_threshold[idx]= value;
    +          else
    +            qh->upper_threshold[idx]= value;
    +        }
    +      }
    +    }else if (*s == 'Q') {
    +      s++;
    +      while (*s && !isspace(key= *s++)) {
    +        if (key == 'b' && *s == 'B') {
    +          s++;
    +          for (k=maxdim; k--; ) {
    +            qh->lower_bound[k]= -qh_DEFAULTbox;
    +            qh->upper_bound[k]= qh_DEFAULTbox;
    +          }
    +        }else if (key == 'b' && *s == 'b')
    +          s++;
    +        else if (key == 'b' || key == 'B') {
    +          if (!isdigit(*s)) {
    +            qh_fprintf(qh, qh->ferr, 7047, "qhull warning: no dimension given for Qhull option %c.  Ignored\n",
    +                    key);
    +            continue;
    +          }
    +          idx= qh_strtol(s, &s);
    +          if (idx >= maxdim) {
    +            qh_fprintf(qh, qh->ferr, 7048, "qhull warning: dimension %d for Qhull option %c is >= %d.  Ignored\n",
    +                idx, key, maxdim);
    +            continue;
    +          }
    +          if (*s == ':') {
    +            s++;
    +            value= qh_strtod(s, &s);
    +          }else if (key == 'b')
    +            value= -qh_DEFAULTbox;
    +          else
    +            value= qh_DEFAULTbox;
    +          if (key == 'b')
    +            qh->lower_bound[idx]= value;
    +          else
    +            qh->upper_bound[idx]= value;
    +        }
    +      }
    +    }else {
    +      while (*s && !isspace(*s))
    +        s++;
    +    }
    +    while (isspace(*s))
    +      s++;
    +  }
    +  for (k=qh->hull_dim; k--; ) {
    +    if (qh->lower_threshold[k] > -REALmax/2) {
    +      qh->GOODthreshold= True;
    +      if (qh->upper_threshold[k] < REALmax/2) {
    +        qh->SPLITthresholds= True;
    +        qh->GOODthreshold= False;
    +        break;
    +      }
    +    }else if (qh->upper_threshold[k] < REALmax/2)
    +      qh->GOODthreshold= True;
    +  }
    +} /* initthresholds */
    +
    +/*---------------------------------
    +
    +  qh_lib_check( qhullLibraryType, qhTsize, vertexTsize, ridgeTsize, facetTsize, setTsize, qhmemTsize )
    +    Report error if library does not agree with caller
    +
    +  notes:
    +    NOerrors -- qh_lib_check can not call qh_errexit()
    +*/
    +void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize) {
    +    boolT iserror= False;
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
    +    // _CrtSetBreakAlloc(744);  /* Break at memalloc {744}, or 'watch' _crtBreakAlloc */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR );
    +    _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    if (qhullLibraryType==QHULL_NON_REENTRANT) { /* 0 */
    +        qh_fprintf_stderr(6257, "qh_lib_check: Incorrect qhull library called.  Caller uses non-reentrant Qhull with a static qhT.  Library is reentrant.\n");
    +        iserror= True;
    +    }else if (qhullLibraryType==QHULL_QH_POINTER) { /* 1 */
    +        qh_fprintf_stderr(6258, "qh_lib_check: Incorrect qhull library called.  Caller uses non-reentrant Qhull with a dynamic qhT via qh_QHpointer.  Library is reentrant.\n");
    +        iserror= True;
    +    }else if (qhullLibraryType!=QHULL_REENTRANT) { /* 2 */
    +        qh_fprintf_stderr(6262, "qh_lib_check: Expecting qhullLibraryType QHULL_NON_REENTRANT(0), QHULL_QH_POINTER(1), or QHULL_REENTRANT(2).  Got %d\n", qhullLibraryType);
    +        iserror= True;
    +    }
    +    if (qhTsize != sizeof(qhT)) {
    +        qh_fprintf_stderr(6249, "qh_lib_check: Incorrect qhull library called.  Size of qhT for caller is %d, but for library is %d.\n", qhTsize, sizeof(qhT));
    +        iserror= True;
    +    }
    +    if (vertexTsize != sizeof(vertexT)) {
    +        qh_fprintf_stderr(6250, "qh_lib_check: Incorrect qhull library called.  Size of vertexT for caller is %d, but for library is %d.\n", vertexTsize, sizeof(vertexT));
    +        iserror= True;
    +    }
    +    if (ridgeTsize != sizeof(ridgeT)) {
    +        qh_fprintf_stderr(6251, "qh_lib_check: Incorrect qhull library called.  Size of ridgeT for caller is %d, but for library is %d.\n", ridgeTsize, sizeof(ridgeT));
    +        iserror= True;
    +    }
    +    if (facetTsize != sizeof(facetT)) {
    +        qh_fprintf_stderr(6252, "qh_lib_check: Incorrect qhull library called.  Size of facetT for caller is %d, but for library is %d.\n", facetTsize, sizeof(facetT));
    +        iserror= True;
    +    }
    +    if (setTsize && setTsize != sizeof(setT)) {
    +        qh_fprintf_stderr(6253, "qh_lib_check: Incorrect qhull library called.  Size of setT for caller is %d, but for library is %d.\n", setTsize, sizeof(setT));
    +        iserror= True;
    +    }
    +    if (qhmemTsize && qhmemTsize != sizeof(qhmemT)) {
    +        qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called.  Size of qhmemT for caller is %d, but for library is %d.\n", qhmemTsize, sizeof(qhmemT));
    +        iserror= True;
    +    }
    +    if (iserror) {
    +        qh_fprintf_stderr(6259, "qh_lib_check: Cannot continue.  Library '%s' is reentrant (e.g., qhull_r.so)\n", qh_version2);
    +        qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +    }
    +} /* lib_check */
    +
    +/*---------------------------------
    +
    +  qh_option(qh, option, intVal, realVal )
    +    add an option description to qh.qhull_options
    +
    +  notes:
    +    NOerrors -- qh_option can not call qh_errexit() [qh_initqhull_start2]
    +    will be printed with statistics ('Ts') and errors
    +    strlen(option) < 40
    +*/
    +void qh_option(qhT *qh, const char *option, int *i, realT *r) {
    +  char buf[200];
    +  int len, maxlen;
    +
    +  sprintf(buf, "  %s", option);
    +  if (i)
    +    sprintf(buf+strlen(buf), " %d", *i);
    +  if (r)
    +    sprintf(buf+strlen(buf), " %2.2g", *r);
    +  len= (int)strlen(buf);  /* WARN64 */
    +  qh->qhull_optionlen += len;
    +  maxlen= sizeof(qh->qhull_options) - len -1;
    +  maximize_(maxlen, 0);
    +  if (qh->qhull_optionlen >= qh_OPTIONline && maxlen > 0) {
    +    qh->qhull_optionlen= len;
    +    strncat(qh->qhull_options, "\n", (size_t)(maxlen--));
    +  }
    +  strncat(qh->qhull_options, buf, (size_t)maxlen);
    +} /* option */
    +
    +/*---------------------------------
    +
    +  qh_zero( qh, errfile )
    +    Initialize and zero Qhull's memory for qh_new_qhull()
    +
    +  notes:
    +    Not needed in global.c because static variables are initialized to zero
    +*/
    +void qh_zero(qhT *qh, FILE *errfile) {
    +    memset((char *)qh, 0, sizeof(qhT));   /* every field is 0, FALSE, NULL */
    +    qh->NOerrexit= True;
    +    qh_meminit(qh, errfile);
    +} /* zero */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/index.htm b/xs/src/qhull/src/libqhull_r/index.htm
    new file mode 100644
    index 0000000000..c62030e06b
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/index.htm
    @@ -0,0 +1,266 @@
    +
    +
    +
    +
    +Reentrant Qhull functions, macros, and data structures
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code
    +To: Qhull files
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser + +


    + + +

    Reentrant Qhull functions, macros, and data structures

    +
    +

    The following sections provide an overview and index to +reentrant Qhull's functions, macros, and data structures. +Each section starts with an introduction. +See also Calling +Qhull from C programs and Calling Qhull from C++ programs.

    + +

    Qhull uses the following conventions:

    +
    + +
      +
    • in code, global variables start with "qh " +
    • in documentation, global variables start with 'qh.' +
    • constants start with an upper case word +
    • important globals include an '_' +
    • functions, macros, and constants start with "qh_"
    • +
    • data types end in "T"
    • +
    • macros with arguments end in "_"
    • +
    • iterators are macros that use local variables
    • +
    • iterators for sets start with "FOREACH"
    • +
    • iterators for lists start with "FORALL"
    • +
    • qhull options are in single quotes (e.g., 'Pdn')
    • +
    • lists are sorted alphabetically
    • +
    • preprocessor directives on left margin for older compilers
    • +
    +
    +

    +Reentrant Qhull is nearly the same as non-reentrant Qhull. In reentrant +Qhull, the qhT data structure is the first parameter to most functions. Qhull accesses +this data structure with 'qh->...'. +In non-reentrant Qhull, the global data structure is either a struct (qh_QHpointer==0) +or a pointer (qh_QHpointer==1). The non-reentrant code looks different because this data +structure is accessed via the 'qh' macro. This macro expands to 'qh_qh.' or 'qh_qh->' (resp.). +

    +When reading code with an editor, a search for +'"function' +will locate the header of qh_function. A search for '* function' +will locate the tail of qh_function. + +

    A useful starting point is libqhull_r.h. It defines most +of Qhull data structures and top-level functions. Search for 'PFn' to +determine the corresponding constant in Qhull. Search for 'Fp' to +determine the corresponding qh_PRINT... constant. +Search io_r.c to learn how the print function is implemented.

    + +

    If your web browser is configured for .c and .h files, the function, macro, and data type links +go to the corresponding source location. To configure your web browser for .c and .h files. +

      +
    • In the Download Preferences or Options panel, add file extensions 'c' and 'h' to mime type 'text/html'. +
    • Opera 12.10 +
        +
      1. In Tools > Preferences > Advanced > Downloads +
      2. Uncheck 'Hide file types opened with Opera' +
      3. Quick find 'html' +
      4. Select 'text/html' > Edit +
      5. Add File extensions 'c,h,' +
      6. Click 'OK' +
      +
    • Internet Explorer -- Mime types are not available from 'Internet Options'. Is there a registry key for these settings? +
    • Firefox -- Mime types are not available from 'Preferences'. Is there an add-on to change the file extensions for a mime type? +
    • Chrome -- Can Chrome be configured? +
    + +

    +Please report documentation and link errors +to qhull-bug@qhull.org. +

    + +

    Copyright © 1997-2015 C.B. Barber

    + +
    + +

    »Qhull files

    +
    + +

    This sections lists the .c and .h files for Qhull. Please +refer to these files for detailed information.

    +
    + +
    +
    Makefile, CMakeLists.txt
    +
    Makefile is preconfigured for gcc. CMakeLists.txt supports multiple +platforms with CMake. +Qhull includes project files for Visual Studio and Qt. +
    + +
     
    +
    libqhull_r.h
    +
    Include file for the Qhull library (libqhull.so, qhull.dll, libqhullstatic.a). +Data structures are documented under Poly. +Global variables are documented under Global. +Other data structures and variables are documented under +Qhull or Geom.
    + +
     
    +
    Geom, +geom_r.h, +geom_r.c, +geom2_r.c, +random_r.c, +random_r.h
    +
    Geometric routines. These routines implement mathematical +functions such as Gaussian elimination and geometric +routines needed for Qhull. Frequently used routines are +in geom_r.c while infrequent ones are in geom2_r.c. +
    + +
     
    +
    Global, +global_r.c, +libqhull_r.h
    +
    Global routines. Qhull uses a global data structure, qh, +to store globally defined constants, lists, sets, and +variables. +global_r.c initializes and frees these +structures.
    + +
     
    +
    Io, io_r.h, +io_r.c
    +
    Input and output routines. Qhull provides a wide range of +input and output options.
    + +
     
    +
    Mem, +mem_r.h, +mem_r.c
    +
    Memory routines. Qhull provides memory allocation and +deallocation. It uses quick-fit allocation.
    + +
     
    +
    Merge, +merge_r.h, +merge_r.c
    +
    Merge routines. Qhull handles precision problems by +merged facets or joggled input. These routines merge simplicial facets, +merge non-simplicial facets, merge cycles of facets, and +rename redundant vertices.
    + +
     
    +
    Poly, +poly_r.h, +poly_r.c, +poly2_r.c, +libqhull_r.h
    +
    Polyhedral routines. Qhull produces a polyhedron as a +list of facets with vertices, neighbors, ridges, and +geometric information. libqhull_r.h defines the main +data structures. Frequently used routines are in poly_r.c +while infrequent ones are in poly2_r.c.
    + +
     
    +
    Qhull, +libqhull_r.c, +libqhull_r.h, +qhull_ra.h, +unix_r.c , +qconvex_r.c , +qdelaun_r.c , +qhalf_r.c , +qvoronoi_r.c
    +
    Top-level routines. The Quickhull algorithm is +implemented by libqhull_r.c. qhull_ra.h +includes all header files.
    + +
     
    +
    Set, +qset_r.h, +qset_r.c
    +
    Set routines. Qhull implements its data structures as +sets. A set is an array of pointers that is expanded as +needed. This is a separate package that may be used in +other applications.
    + +
     
    +
    Stat, +stat_r.h, +stat_r.c
    +
    Statistical routines. Qhull maintains statistics about +its implementation.
    + +
     
    +
    User, +user_r.h, +user_r.c, +user_eg_r.c, +user_eg2_r.c, +user_eg3_r.cpp, +
    +
    User-defined routines. Qhull allows the user to configure +the code with defined constants and specialized routines. +
    +
    +
    + +
    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull files
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/io_r.c b/xs/src/qhull/src/libqhull_r/io_r.c new file mode 100644 index 0000000000..9721a000dd --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/io_r.c @@ -0,0 +1,4062 @@ +/*
      ---------------------------------
    +
    +   io_r.c
    +   Input/Output routines of qhull application
    +
    +   see qh-io_r.htm and io_r.h
    +
    +   see user_r.c for qh_errprint and qh_printfacetlist
    +
    +   unix_r.c calls qh_readpoints and qh_produce_output
    +
    +   unix_r.c and user_r.c are the only callers of io_r.c functions
    +   This allows the user to avoid loading io_r.o from qhull.a
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/io_r.c#4 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*========= -functions in alphabetical order after qh_produce_output(qh)  =====*/
    +
    +/*---------------------------------
    +
    +  qh_produce_output(qh)
    +  qh_produce_output2(qh)
    +    prints out the result of qhull in desired format
    +    qh_produce_output2(qh) does not call qh_prepare_output(qh)
    +    if qh.GETarea
    +      computes and prints area and volume
    +    qh.PRINTout[] is an array of output formats
    +
    +  notes:
    +    prints output in qh.PRINTout order
    +*/
    +void qh_produce_output(qhT *qh) {
    +    int tempsize= qh_setsize(qh, qh->qhmem.tempstack);
    +
    +    qh_prepare_output(qh);
    +    qh_produce_output2(qh);
    +    if (qh_setsize(qh, qh->qhmem.tempstack) != tempsize) {
    +        qh_fprintf(qh, qh->ferr, 6206, "qhull internal error (qh_produce_output): temporary sets not empty(%d)\n",
    +            qh_setsize(qh, qh->qhmem.tempstack));
    +        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +} /* produce_output */
    +
    +
    +void qh_produce_output2(qhT *qh) {
    +  int i, tempsize= qh_setsize(qh, qh->qhmem.tempstack), d_1;
    +
    +  if (qh->PRINTsummary)
    +    qh_printsummary(qh, qh->ferr);
    +  else if (qh->PRINTout[0] == qh_PRINTnone)
    +    qh_printsummary(qh, qh->fout);
    +  for (i=0; i < qh_PRINTEND; i++)
    +    qh_printfacets(qh, qh->fout, qh->PRINTout[i], qh->facet_list, NULL, !qh_ALL);
    +  qh_allstatistics(qh);
    +  if (qh->PRINTprecision && !qh->MERGING && (qh->JOGGLEmax > REALmax/2 || qh->RERUN))
    +    qh_printstats(qh, qh->ferr, qh->qhstat.precision, NULL);
    +  if (qh->VERIFYoutput && (zzval_(Zridge) > 0 || zzval_(Zridgemid) > 0))
    +    qh_printstats(qh, qh->ferr, qh->qhstat.vridges, NULL);
    +  if (qh->PRINTstatistics) {
    +    qh_printstatistics(qh, qh->ferr, "");
    +    qh_memstatistics(qh, qh->ferr);
    +    d_1= sizeof(setT) + (qh->hull_dim - 1) * SETelemsize;
    +    qh_fprintf(qh, qh->ferr, 8040, "\
    +    size in bytes: merge %d ridge %d vertex %d facet %d\n\
    +         normal %d ridge vertices %d facet vertices or neighbors %d\n",
    +            (int)sizeof(mergeT), (int)sizeof(ridgeT),
    +            (int)sizeof(vertexT), (int)sizeof(facetT),
    +            qh->normal_size, d_1, d_1 + SETelemsize);
    +  }
    +  if (qh_setsize(qh, qh->qhmem.tempstack) != tempsize) {
    +    qh_fprintf(qh, qh->ferr, 6065, "qhull internal error (qh_produce_output2): temporary sets not empty(%d)\n",
    +             qh_setsize(qh, qh->qhmem.tempstack));
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +} /* produce_output2 */
    +
    +/*---------------------------------
    +
    +  qh_dfacet(qh, id )
    +    print facet by id, for debugging
    +
    +*/
    +void qh_dfacet(qhT *qh, unsigned id) {
    +  facetT *facet;
    +
    +  FORALLfacets {
    +    if (facet->id == id) {
    +      qh_printfacet(qh, qh->fout, facet);
    +      break;
    +    }
    +  }
    +} /* dfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_dvertex(qh, id )
    +    print vertex by id, for debugging
    +*/
    +void qh_dvertex(qhT *qh, unsigned id) {
    +  vertexT *vertex;
    +
    +  FORALLvertices {
    +    if (vertex->id == id) {
    +      qh_printvertex(qh, qh->fout, vertex);
    +      break;
    +    }
    +  }
    +} /* dvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_compare_facetarea(p1, p2 )
    +    used by qsort() to order facets by area
    +*/
    +int qh_compare_facetarea(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  if (!a->isarea)
    +    return -1;
    +  if (!b->isarea)
    +    return 1;
    +  if (a->f.area > b->f.area)
    +    return 1;
    +  else if (a->f.area == b->f.area)
    +    return 0;
    +  return -1;
    +} /* compare_facetarea */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetmerge(p1, p2 )
    +    used by qsort() to order facets by number of merges
    +*/
    +int qh_compare_facetmerge(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +
    +  return(a->nummerge - b->nummerge);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_facetvisit(p1, p2 )
    +    used by qsort() to order facets by visit id or id
    +*/
    +int qh_compare_facetvisit(const void *p1, const void *p2) {
    +  const facetT *a= *((facetT *const*)p1), *b= *((facetT *const*)p2);
    +  int i,j;
    +
    +  if (!(i= a->visitid))
    +    i= 0 - a->id; /* do not convert to int, sign distinguishes id from visitid */
    +  if (!(j= b->visitid))
    +    j= 0 - b->id;
    +  return(i - j);
    +} /* compare_facetvisit */
    +
    +/*---------------------------------
    +
    +  qh_compare_vertexpoint( p1, p2 )
    +    used by qsort() to order vertices by point id
    +
    +  Not usable in qhulllib_r since qh_pointid depends on qh
    +
    +  int qh_compare_vertexpoint(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return((qh_pointid(qh, a->point) > qh_pointid(qh, b->point)?1:-1));
    +}*/
    +
    +/*---------------------------------
    +
    +  qh_copyfilename(qh, dest, size, source, length )
    +    copy filename identified by qh_skipfilename()
    +
    +  notes:
    +    see qh_skipfilename() for syntax
    +*/
    +void qh_copyfilename(qhT *qh, char *filename, int size, const char* source, int length) {
    +  char c= *source;
    +
    +  if (length > size + 1) {
    +      qh_fprintf(qh, qh->ferr, 6040, "qhull error: filename is more than %d characters, %s\n",  size-1, source);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  strncpy(filename, source, length);
    +  filename[length]= '\0';
    +  if (c == '\'' || c == '"') {
    +    char *s= filename + 1;
    +    char *t= filename;
    +    while (*s) {
    +      if (*s == c) {
    +          if (s[-1] == '\\')
    +              t[-1]= c;
    +      }else
    +          *t++= *s;
    +      s++;
    +    }
    +    *t= '\0';
    +  }
    +} /* copyfilename */
    +
    +/*---------------------------------
    +
    +  qh_countfacets(qh, facetlist, facets, printall,
    +          numfacets, numsimplicial, totneighbors, numridges, numcoplanar, numtricoplanars  )
    +    count good facets for printing and set visitid
    +    if allfacets, ignores qh_skipfacet()
    +
    +  notes:
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  returns:
    +    numfacets, numsimplicial, total neighbors, numridges, coplanars
    +    each facet with ->visitid indicating 1-relative position
    +      ->visitid==0 indicates not good
    +
    +  notes
    +    numfacets >= numsimplicial
    +    if qh.NEWfacets,
    +      does not count visible facets (matches qh_printafacet)
    +
    +  design:
    +    for all facets on facetlist and in facets set
    +      unless facet is skipped or visible (i.e., will be deleted)
    +        mark facet->visitid
    +        update counts
    +*/
    +void qh_countfacets(qhT *qh, facetT *facetlist, setT *facets, boolT printall,
    +    int *numfacetsp, int *numsimplicialp, int *totneighborsp, int *numridgesp, int *numcoplanarsp, int *numtricoplanarsp) {
    +  facetT *facet, **facetp;
    +  int numfacets= 0, numsimplicial= 0, numridges= 0, totneighbors= 0, numcoplanars= 0, numtricoplanars= 0;
    +
    +  FORALLfacet_(facetlist) {
    +    if ((facet->visible && qh->NEWfacets)
    +    || (!printall && qh_skipfacet(qh, facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(qh, facet->neighbors);
    +      if (facet->simplicial) {
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(qh, facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(qh, facet->coplanarset);
    +    }
    +  }
    +
    +  FOREACHfacet_(facets) {
    +    if ((facet->visible && qh->NEWfacets)
    +    || (!printall && qh_skipfacet(qh, facet)))
    +      facet->visitid= 0;
    +    else {
    +      facet->visitid= ++numfacets;
    +      totneighbors += qh_setsize(qh, facet->neighbors);
    +      if (facet->simplicial){
    +        numsimplicial++;
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else
    +        numridges += qh_setsize(qh, facet->ridges);
    +      if (facet->coplanarset)
    +        numcoplanars += qh_setsize(qh, facet->coplanarset);
    +    }
    +  }
    +  qh->visit_id += numfacets+1;
    +  *numfacetsp= numfacets;
    +  *numsimplicialp= numsimplicial;
    +  *totneighborsp= totneighbors;
    +  *numridgesp= numridges;
    +  *numcoplanarsp= numcoplanars;
    +  *numtricoplanarsp= numtricoplanars;
    +} /* countfacets */
    +
    +/*---------------------------------
    +
    +  qh_detvnorm(qh, vertex, vertexA, centers, offset )
    +    compute separating plane of the Voronoi diagram for a pair of input sites
    +    centers= set of facets (i.e., Voronoi vertices)
    +      facet->visitid= 0 iff vertex-at-infinity (i.e., unbounded)
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  returns:
    +    norm
    +      a pointer into qh.gm_matrix to qh.hull_dim-1 reals
    +      copy the data before reusing qh.gm_matrix
    +    offset
    +      if 'QVn'
    +        sign adjusted so that qh.GOODvertexp is inside
    +      else
    +        sign adjusted so that vertex is inside
    +
    +    qh.gm_matrix= simplex of points from centers relative to first center
    +
    +  notes:
    +    in io_r.c so that code for 'v Tv' can be removed by removing io_r.c
    +    returns pointer into qh.gm_matrix to avoid tracking of temporary memory
    +
    +  design:
    +    determine midpoint of input sites
    +    build points as the set of Voronoi vertices
    +    select a simplex from points (if necessary)
    +      include midpoint if the Voronoi region is unbounded
    +    relocate the first vertex of the simplex to the origin
    +    compute the normalized hyperplane through the simplex
    +    orient the hyperplane toward 'QVn' or 'vertex'
    +    if 'Tv' or 'Ts'
    +      if bounded
    +        test that hyperplane is the perpendicular bisector of the input sites
    +      test that Voronoi vertices not in the simplex are still on the hyperplane
    +    free up temporary memory
    +*/
    +pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp) {
    +  facetT *facet, **facetp;
    +  int  i, k, pointid, pointidA, point_i, point_n;
    +  setT *simplex= NULL;
    +  pointT *point, **pointp, *point0, *midpoint, *normal, *inpoint;
    +  coordT *coord, *gmcoord, *normalp;
    +  setT *points= qh_settemp(qh, qh->TEMPsize);
    +  boolT nearzero= False;
    +  boolT unbounded= False;
    +  int numcenters= 0;
    +  int dim= qh->hull_dim - 1;
    +  realT dist, offset, angle, zero= 0.0;
    +
    +  midpoint= qh->gm_matrix + qh->hull_dim * qh->hull_dim;  /* last row */
    +  for (k=0; k < dim; k++)
    +    midpoint[k]= (vertex->point[k] + vertexA->point[k])/2;
    +  FOREACHfacet_(centers) {
    +    numcenters++;
    +    if (!facet->visitid)
    +      unbounded= True;
    +    else {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(qh, facet->vertices);
    +      qh_setappend(qh, &points, facet->center);
    +    }
    +  }
    +  if (numcenters > dim) {
    +    simplex= qh_settemp(qh, qh->TEMPsize);
    +    qh_setappend(qh, &simplex, vertex->point);
    +    if (unbounded)
    +      qh_setappend(qh, &simplex, midpoint);
    +    qh_maxsimplex(qh, dim, points, NULL, 0, &simplex);
    +    qh_setdelnth(qh, simplex, 0);
    +  }else if (numcenters == dim) {
    +    if (unbounded)
    +      qh_setappend(qh, &points, midpoint);
    +    simplex= points;
    +  }else {
    +    qh_fprintf(qh, qh->ferr, 6216, "qhull internal error (qh_detvnorm): too few points(%d) to compute separating plane\n", numcenters);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  i= 0;
    +  gmcoord= qh->gm_matrix;
    +  point0= SETfirstt_(simplex, pointT);
    +  FOREACHpoint_(simplex) {
    +    if (qh->IStracing >= 4)
    +      qh_printmatrix(qh, qh->ferr, "qh_detvnorm: Voronoi vertex or midpoint",
    +                              &point, 1, dim);
    +    if (point != point0) {
    +      qh->gm_row[i++]= gmcoord;
    +      coord= point0;
    +      for (k=dim; k--; )
    +        *(gmcoord++)= *point++ - *coord++;
    +    }
    +  }
    +  qh->gm_row[i]= gmcoord;  /* does not overlap midpoint, may be used later for qh_areasimplex */
    +  normal= gmcoord;
    +  qh_sethyperplane_gauss(qh, dim, qh->gm_row, point0, True,
    +                normal, &offset, &nearzero);
    +  if (qh->GOODvertexp == vertexA->point)
    +    inpoint= vertexA->point;
    +  else
    +    inpoint= vertex->point;
    +  zinc_(Zdistio);
    +  dist= qh_distnorm(dim, inpoint, normal, &offset);
    +  if (dist > 0) {
    +    offset= -offset;
    +    normalp= normal;
    +    for (k=dim; k--; ) {
    +      *normalp= -(*normalp);
    +      normalp++;
    +    }
    +  }
    +  if (qh->VERIFYoutput || qh->PRINTstatistics) {
    +    pointid= qh_pointid(qh, vertex->point);
    +    pointidA= qh_pointid(qh, vertexA->point);
    +    if (!unbounded) {
    +      zinc_(Zdiststat);
    +      dist= qh_distnorm(dim, midpoint, normal, &offset);
    +      if (dist < 0)
    +        dist= -dist;
    +      zzinc_(Zridgemid);
    +      wwmax_(Wridgemidmax, dist);
    +      wwadd_(Wridgemid, dist);
    +      trace4((qh, qh->ferr, 4014, "qh_detvnorm: points %d %d midpoint dist %2.2g\n",
    +                 pointid, pointidA, dist));
    +      for (k=0; k < dim; k++)
    +        midpoint[k]= vertexA->point[k] - vertex->point[k];  /* overwrites midpoint! */
    +      qh_normalize(qh, midpoint, dim, False);
    +      angle= qh_distnorm(dim, midpoint, normal, &zero); /* qh_detangle uses dim+1 */
    +      if (angle < 0.0)
    +        angle= angle + 1.0;
    +      else
    +        angle= angle - 1.0;
    +      if (angle < 0.0)
    +        angle -= angle;
    +      trace4((qh, qh->ferr, 4015, "qh_detvnorm: points %d %d angle %2.2g nearzero %d\n",
    +                 pointid, pointidA, angle, nearzero));
    +      if (nearzero) {
    +        zzinc_(Zridge0);
    +        wwmax_(Wridge0max, angle);
    +        wwadd_(Wridge0, angle);
    +      }else {
    +        zzinc_(Zridgeok)
    +        wwmax_(Wridgeokmax, angle);
    +        wwadd_(Wridgeok, angle);
    +      }
    +    }
    +    if (simplex != points) {
    +      FOREACHpoint_i_(qh, points) {
    +        if (!qh_setin(simplex, point)) {
    +          facet= SETelemt_(centers, point_i, facetT);
    +          zinc_(Zdiststat);
    +          dist= qh_distnorm(dim, point, normal, &offset);
    +          if (dist < 0)
    +            dist= -dist;
    +          zzinc_(Zridge);
    +          wwmax_(Wridgemax, dist);
    +          wwadd_(Wridge, dist);
    +          trace4((qh, qh->ferr, 4016, "qh_detvnorm: points %d %d Voronoi vertex %d dist %2.2g\n",
    +                             pointid, pointidA, facet->visitid, dist));
    +        }
    +      }
    +    }
    +  }
    +  *offsetp= offset;
    +  if (simplex != points)
    +    qh_settempfree(qh, &simplex);
    +  qh_settempfree(qh, &points);
    +  return normal;
    +} /* detvnorm */
    +
    +/*---------------------------------
    +
    +  qh_detvridge(qh, vertexA )
    +    determine Voronoi ridge from 'seen' neighbors of vertexA
    +    include one vertex-at-infinite if an !neighbor->visitid
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    sorted by center id
    +*/
    +setT *qh_detvridge(qhT *qh, vertexT *vertex) {
    +  setT *centers= qh_settemp(qh, qh->TEMPsize);
    +  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
    +  facetT *neighbor, **neighborp;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->seen) {
    +      if (neighbor->visitid) {
    +        if (!neighbor->tricoplanar || qh_setunique(qh, &tricenters, neighbor->center))
    +          qh_setappend(qh, ¢ers, neighbor);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(qh, ¢ers, neighbor);
    +      }
    +    }
    +  }
    +  qsort(SETaddr_(centers, facetT), (size_t)qh_setsize(qh, centers),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +  qh_settempfree(qh, &tricenters);
    +  return centers;
    +} /* detvridge */
    +
    +/*---------------------------------
    +
    +  qh_detvridge3(qh, atvertex, vertex )
    +    determine 3-d Voronoi ridge from 'seen' neighbors of atvertex and vertex
    +    include one vertex-at-infinite for !neighbor->visitid
    +    assumes all facet->seen2= True
    +
    +  returns:
    +    temporary set of centers (facets, i.e., Voronoi vertices)
    +    listed in adjacency order (!oriented)
    +    all facet->seen2= True
    +
    +  design:
    +    mark all neighbors of atvertex
    +    for each adjacent neighbor of both atvertex and vertex
    +      if neighbor selected
    +        add neighbor to set of Voronoi vertices
    +*/
    +setT *qh_detvridge3(qhT *qh, vertexT *atvertex, vertexT *vertex) {
    +  setT *centers= qh_settemp(qh, qh->TEMPsize);
    +  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
    +  facetT *neighbor, **neighborp, *facet= NULL;
    +  boolT firstinf= True;
    +
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= False;
    +  FOREACHneighbor_(vertex) {
    +    if (!neighbor->seen2) {
    +      facet= neighbor;
    +      break;
    +    }
    +  }
    +  while (facet) {
    +    facet->seen2= True;
    +    if (neighbor->seen) {
    +      if (facet->visitid) {
    +        if (!facet->tricoplanar || qh_setunique(qh, &tricenters, facet->center))
    +          qh_setappend(qh, ¢ers, facet);
    +      }else if (firstinf) {
    +        firstinf= False;
    +        qh_setappend(qh, ¢ers, facet);
    +      }
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen2) {
    +        if (qh_setin(vertex->neighbors, neighbor))
    +          break;
    +        else
    +          neighbor->seen2= True;
    +      }
    +    }
    +    facet= neighbor;
    +  }
    +  if (qh->CHECKfrequently) {
    +    FOREACHneighbor_(vertex) {
    +      if (!neighbor->seen2) {
    +          qh_fprintf(qh, qh->ferr, 6217, "qhull internal error (qh_detvridge3): neighbors of vertex p%d are not connected at facet %d\n",
    +                 qh_pointid(qh, vertex->point), neighbor->id);
    +        qh_errexit(qh, qh_ERRqhull, neighbor, NULL);
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen2= True;
    +  qh_settempfree(qh, &tricenters);
    +  return centers;
    +} /* detvridge3 */
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi(qh, fp, printvridge, vertex, visitall, innerouter, inorder )
    +    if visitall,
    +      visit all Voronoi ridges for vertex (i.e., an input site)
    +    else
    +      visit all unvisited Voronoi ridges for vertex
    +      all vertex->seen= False if unvisited
    +    assumes
    +      all facet->seen= False
    +      all facet->seen2= True (for qh_detvridge3)
    +      all facet->visitid == 0 if vertex_at_infinity
    +                         == index of Voronoi vertex
    +                         >= qh.num_facets if ignored
    +    innerouter:
    +      qh_RIDGEall--  both inner (bounded) and outer(unbounded) ridges
    +      qh_RIDGEinner- only inner
    +      qh_RIDGEouter- only outer
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns:
    +    number of visited ridges (does not include previously visited ridges)
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +        fp== any pointer (assumes FILE*)
    +        vertex,vertexA= pair of input sites that define a Voronoi ridge
    +        centers= set of facets (i.e., Voronoi vertices)
    +                 ->visitid == index or 0 if vertex_at_infinity
    +                 ordered for 3-d Voronoi diagram
    +  notes:
    +    uses qh.vertex_visit
    +
    +  see:
    +    qh_eachvoronoi_all()
    +
    +  design:
    +    mark selected neighbors of atvertex
    +    for each selected neighbor (either Voronoi vertex or vertex-at-infinity)
    +      for each unvisited vertex
    +        if atvertex and vertex share more than d-1 neighbors
    +          bump totalcount
    +          if printvridge defined
    +            build the set of shared neighbors (i.e., Voronoi vertices)
    +            call printvridge
    +*/
    +int qh_eachvoronoi(qhT *qh, FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder) {
    +  boolT unbounded;
    +  int count;
    +  facetT *neighbor, **neighborp, *neighborA, **neighborAp;
    +  setT *centers;
    +  setT *tricenters= qh_settemp(qh, qh->TEMPsize);
    +
    +  vertexT *vertex, **vertexp;
    +  boolT firstinf;
    +  unsigned int numfacets= (unsigned int)qh->num_facets;
    +  int totridges= 0;
    +
    +  qh->vertex_visit++;
    +  atvertex->seen= True;
    +  if (visitall) {
    +    FORALLvertices
    +      vertex->seen= False;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->visitid < numfacets)
    +      neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(atvertex) {
    +    if (neighbor->seen) {
    +      FOREACHvertex_(neighbor->vertices) {
    +        if (vertex->visitid != qh->vertex_visit && !vertex->seen) {
    +          vertex->visitid= qh->vertex_visit;
    +          count= 0;
    +          firstinf= True;
    +          qh_settruncate(qh, tricenters, 0);
    +          FOREACHneighborA_(vertex) {
    +            if (neighborA->seen) {
    +              if (neighborA->visitid) {
    +                if (!neighborA->tricoplanar || qh_setunique(qh, &tricenters, neighborA->center))
    +                  count++;
    +              }else if (firstinf) {
    +                count++;
    +                firstinf= False;
    +              }
    +            }
    +          }
    +          if (count >= qh->hull_dim - 1) {  /* e.g., 3 for 3-d Voronoi */
    +            if (firstinf) {
    +              if (innerouter == qh_RIDGEouter)
    +                continue;
    +              unbounded= False;
    +            }else {
    +              if (innerouter == qh_RIDGEinner)
    +                continue;
    +              unbounded= True;
    +            }
    +            totridges++;
    +            trace4((qh, qh->ferr, 4017, "qh_eachvoronoi: Voronoi ridge of %d vertices between sites %d and %d\n",
    +                  count, qh_pointid(qh, atvertex->point), qh_pointid(qh, vertex->point)));
    +            if (printvridge && fp) {
    +              if (inorder && qh->hull_dim == 3+1) /* 3-d Voronoi diagram */
    +                centers= qh_detvridge3(qh, atvertex, vertex);
    +              else
    +                centers= qh_detvridge(qh, vertex);
    +              (*printvridge)(qh, fp, atvertex, vertex, centers, unbounded);
    +              qh_settempfree(qh, ¢ers);
    +            }
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(atvertex)
    +    neighbor->seen= False;
    +  qh_settempfree(qh, &tricenters);
    +  return totridges;
    +} /* eachvoronoi */
    +
    +
    +/*---------------------------------
    +
    +  qh_eachvoronoi_all(qh, fp, printvridge, isUpper, innerouter, inorder )
    +    visit all Voronoi ridges
    +
    +    innerouter:
    +      see qh_eachvoronoi()
    +
    +    if inorder
    +      orders vertices for 3-d Voronoi diagrams
    +
    +  returns
    +    total number of ridges
    +
    +    if isUpper == facet->upperdelaunay  (i.e., a Vornoi vertex)
    +      facet->visitid= Voronoi vertex index(same as 'o' format)
    +    else
    +      facet->visitid= 0
    +
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers)
    +      [see qh_eachvoronoi]
    +
    +  notes:
    +    Not used for qhull.exe
    +    same effect as qh_printvdiagram but ridges not sorted by point id
    +*/
    +int qh_eachvoronoi_all(qhT *qh, FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int numcenters= 1;  /* vertex 0 is vertex-at-infinity */
    +  int totridges= 0;
    +
    +  qh_clearcenters(qh, qh_ASvoronoi);
    +  qh_vertexneighbors(qh);
    +  maximize_(qh->visit_id, (unsigned) qh->num_facets);
    +  FORALLfacets {
    +    facet->visitid= 0;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  FORALLfacets {
    +    if (facet->upperdelaunay == isUpper)
    +      facet->visitid= numcenters++;
    +  }
    +  FORALLvertices
    +    vertex->seen= False;
    +  FORALLvertices {
    +    if (qh->GOODvertex > 0 && qh_pointid(qh, vertex->point)+1 != qh->GOODvertex)
    +      continue;
    +    totridges += qh_eachvoronoi(qh, fp, printvridge, vertex,
    +                   !qh_ALL, innerouter, inorder);
    +  }
    +  return totridges;
    +} /* eachvoronoi_all */
    +
    +/*---------------------------------
    +
    +  qh_facet2point(qh, facet, point0, point1, mindist )
    +    return two projected temporary vertices for a 2-d facet
    +    may be non-simplicial
    +
    +  returns:
    +    point0 and point1 oriented and projected to the facet
    +    returns mindist (maximum distance below plane)
    +*/
    +void qh_facet2point(qhT *qh, facetT *facet, pointT **point0, pointT **point1, realT *mindist) {
    +  vertexT *vertex0, *vertex1;
    +  realT dist;
    +
    +  if (facet->toporient ^ qh_ORIENTclock) {
    +    vertex0= SETfirstt_(facet->vertices, vertexT);
    +    vertex1= SETsecondt_(facet->vertices, vertexT);
    +  }else {
    +    vertex1= SETfirstt_(facet->vertices, vertexT);
    +    vertex0= SETsecondt_(facet->vertices, vertexT);
    +  }
    +  zadd_(Zdistio, 2);
    +  qh_distplane(qh, vertex0->point, facet, &dist);
    +  *mindist= dist;
    +  *point0= qh_projectpoint(qh, vertex0->point, facet, dist);
    +  qh_distplane(qh, vertex1->point, facet, &dist);
    +  minimize_(*mindist, dist);
    +  *point1= qh_projectpoint(qh, vertex1->point, facet, dist);
    +} /* facet2point */
    +
    +
    +/*---------------------------------
    +
    +  qh_facetvertices(qh, facetlist, facets, allfacets )
    +    returns temporary set of vertices in a set and/or list of facets
    +    if allfacets, ignores qh_skipfacet()
    +
    +  returns:
    +    vertices with qh.vertex_visit
    +
    +  notes:
    +    optimized for allfacets of facet_list
    +
    +  design:
    +    if allfacets of facet_list
    +      create vertex set from vertex_list
    +    else
    +      for each selected facet in facets or facetlist
    +        append unvisited vertices to vertex set
    +*/
    +setT *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets) {
    +  setT *vertices;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +
    +  qh->vertex_visit++;
    +  if (facetlist == qh->facet_list && allfacets && !facets) {
    +    vertices= qh_settemp(qh, qh->num_vertices);
    +    FORALLvertices {
    +      vertex->visitid= qh->vertex_visit;
    +      qh_setappend(qh, &vertices, vertex);
    +    }
    +  }else {
    +    vertices= qh_settemp(qh, qh->TEMPsize);
    +    FORALLfacet_(facetlist) {
    +      if (!allfacets && qh_skipfacet(qh, facet))
    +        continue;
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex->visitid != qh->vertex_visit) {
    +          vertex->visitid= qh->vertex_visit;
    +          qh_setappend(qh, &vertices, vertex);
    +        }
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (!allfacets && qh_skipfacet(qh, facet))
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        vertex->visitid= qh->vertex_visit;
    +        qh_setappend(qh, &vertices, vertex);
    +      }
    +    }
    +  }
    +  return vertices;
    +} /* facetvertices */
    +
    +/*---------------------------------
    +
    +  qh_geomplanes(qh, facet, outerplane, innerplane )
    +    return outer and inner planes for Geomview
    +    qh.PRINTradius is size of vertices and points (includes qh.JOGGLEmax)
    +
    +  notes:
    +    assume precise calculations in io.c with roundoff covered by qh_GEOMepsilon
    +*/
    +void qh_geomplanes(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane) {
    +  realT radius;
    +
    +  if (qh->MERGING || qh->JOGGLEmax < REALmax/2) {
    +    qh_outerinner(qh, facet, outerplane, innerplane);
    +    radius= qh->PRINTradius;
    +    if (qh->JOGGLEmax < REALmax/2)
    +      radius -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);  /* already accounted for in qh_outerinner() */
    +    *outerplane += radius;
    +    *innerplane -= radius;
    +    if (qh->PRINTcoplanar || qh->PRINTspheres) {
    +      *outerplane += qh->MAXabs_coord * qh_GEOMepsilon;
    +      *innerplane -= qh->MAXabs_coord * qh_GEOMepsilon;
    +    }
    +  }else
    +    *innerplane= *outerplane= 0;
    +} /* geomplanes */
    +
    +
    +/*---------------------------------
    +
    +  qh_markkeep(qh, facetlist )
    +    mark good facets that meet qh.KEEParea, qh.KEEPmerge, and qh.KEEPminArea
    +    ignores visible facets (!part of convex hull)
    +
    +  returns:
    +    may clear facet->good
    +    recomputes qh.num_good
    +
    +  design:
    +    get set of good facets
    +    if qh.KEEParea
    +      sort facets by area
    +      clear facet->good for all but n largest facets
    +    if qh.KEEPmerge
    +      sort facets by merge count
    +      clear facet->good for all but n most merged facets
    +    if qh.KEEPminarea
    +      clear facet->good if area too small
    +    update qh.num_good
    +*/
    +void qh_markkeep(qhT *qh, facetT *facetlist) {
    +  facetT *facet, **facetp;
    +  setT *facets= qh_settemp(qh, qh->num_facets);
    +  int size, count;
    +
    +  trace2((qh, qh->ferr, 2006, "qh_markkeep: only keep %d largest and/or %d most merged facets and/or min area %.2g\n",
    +          qh->KEEParea, qh->KEEPmerge, qh->KEEPminArea));
    +  FORALLfacet_(facetlist) {
    +    if (!facet->visible && facet->good)
    +      qh_setappend(qh, &facets, facet);
    +  }
    +  size= qh_setsize(qh, facets);
    +  if (qh->KEEParea) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetarea);
    +    if ((count= size - qh->KEEParea) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh->KEEPmerge) {
    +    qsort(SETaddr_(facets, facetT), (size_t)size,
    +             sizeof(facetT *), qh_compare_facetmerge);
    +    if ((count= size - qh->KEEPmerge) > 0) {
    +      FOREACHfacet_(facets) {
    +        facet->good= False;
    +        if (--count == 0)
    +          break;
    +      }
    +    }
    +  }
    +  if (qh->KEEPminArea < REALmax/2) {
    +    FOREACHfacet_(facets) {
    +      if (!facet->isarea || facet->f.area < qh->KEEPminArea)
    +        facet->good= False;
    +    }
    +  }
    +  qh_settempfree(qh, &facets);
    +  count= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      count++;
    +  }
    +  qh->num_good= count;
    +} /* markkeep */
    +
    +
    +/*---------------------------------
    +
    +  qh_markvoronoi(qh, facetlist, facets, printall, isLower, numcenters )
    +    mark voronoi vertices for printing by site pairs
    +
    +  returns:
    +    temporary set of vertices indexed by pointid
    +    isLower set if printing lower hull (i.e., at least one facet is lower hull)
    +    numcenters= total number of Voronoi vertices
    +    bumps qh.printoutnum for vertex-at-infinity
    +    clears all facet->seen and sets facet->seen2
    +
    +    if selected
    +      facet->visitid= Voronoi vertex id
    +    else if upper hull (or 'Qu' and lower hull)
    +      facet->visitid= 0
    +    else
    +      facet->visitid >= qh->num_facets
    +
    +  notes:
    +    ignores qh.ATinfinity, if defined
    +*/
    +setT *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp) {
    +  int numcenters=0;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  boolT isLower= False;
    +
    +  qh->printoutnum++;
    +  qh_clearcenters(qh, qh_ASvoronoi);  /* in case, qh_printvdiagram2 called by user */
    +  qh_vertexneighbors(qh);
    +  vertices= qh_pointvertex(qh);
    +  if (qh->ATinfinity)
    +    SETelem_(vertices, qh->num_points-1)= NULL;
    +  qh->visit_id++;
    +  maximize_(qh->visit_id, (unsigned) qh->num_facets);
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(qh, facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(qh, facet)) {
    +      if (!facet->upperdelaunay) {
    +        isLower= True;
    +        break;
    +      }
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->normal && (facet->upperdelaunay == isLower))
    +      facet->visitid= 0;  /* facetlist or facets may overwrite */
    +    else
    +      facet->visitid= qh->visit_id;
    +    facet->seen= False;
    +    facet->seen2= True;
    +  }
    +  numcenters++;  /* qh_INFINITE */
    +  FORALLfacet_(facetlist) {
    +    if (printall || !qh_skipfacet(qh, facet))
    +      facet->visitid= numcenters++;
    +  }
    +  FOREACHfacet_(facets) {
    +    if (printall || !qh_skipfacet(qh, facet))
    +      facet->visitid= numcenters++;
    +  }
    +  *isLowerp= isLower;
    +  *numcentersp= numcenters;
    +  trace2((qh, qh->ferr, 2007, "qh_markvoronoi: isLower %d numcenters %d\n", isLower, numcenters));
    +  return vertices;
    +} /* markvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_order_vertexneighbors(qh, vertex )
    +    order facet neighbors of a 2-d or 3-d vertex by adjacency
    +
    +  notes:
    +    does not orient the neighbors
    +
    +  design:
    +    initialize a new neighbor set with the first facet in vertex->neighbors
    +    while vertex->neighbors non-empty
    +      select next neighbor in the previous facet's neighbor set
    +    set vertex->neighbors to the new neighbor set
    +*/
    +void qh_order_vertexneighbors(qhT *qh, vertexT *vertex) {
    +  setT *newset;
    +  facetT *facet, *neighbor, **neighborp;
    +
    +  trace4((qh, qh->ferr, 4018, "qh_order_vertexneighbors: order neighbors of v%d for 3-d\n", vertex->id));
    +  newset= qh_settemp(qh, qh_setsize(qh, vertex->neighbors));
    +  facet= (facetT*)qh_setdellast(vertex->neighbors);
    +  qh_setappend(qh, &newset, facet);
    +  while (qh_setsize(qh, vertex->neighbors)) {
    +    FOREACHneighbor_(vertex) {
    +      if (qh_setin(facet->neighbors, neighbor)) {
    +        qh_setdel(vertex->neighbors, neighbor);
    +        qh_setappend(qh, &newset, neighbor);
    +        facet= neighbor;
    +        break;
    +      }
    +    }
    +    if (!neighbor) {
    +      qh_fprintf(qh, qh->ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n",
    +        vertex->id, facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  qh_setfree(qh, &vertex->neighbors);
    +  qh_settemppop(qh);
    +  vertex->neighbors= newset;
    +} /* order_vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_prepare_output(qh, )
    +    prepare for qh_produce_output2(qh) according to
    +      qh.KEEPminArea, KEEParea, KEEPmerge, GOODvertex, GOODthreshold, GOODpoint, ONLYgood, SPLITthresholds
    +    does not reset facet->good
    +
    +  notes
    +    except for PRINTstatistics, no-op if previously called with same options
    +*/
    +void qh_prepare_output(qhT *qh) {
    +  if (qh->VORONOI) {
    +    qh_clearcenters(qh, qh_ASvoronoi);  /* must be before qh_triangulate */
    +    qh_vertexneighbors(qh);
    +  }
    +  if (qh->TRIangulate && !qh->hasTriangulation) {
    +    qh_triangulate(qh);
    +    if (qh->VERIFYoutput && !qh->CHECKfrequently)
    +      qh_checkpolygon(qh, qh->facet_list);
    +  }
    +  qh_findgood_all(qh, qh->facet_list);
    +  if (qh->GETarea)
    +    qh_getarea(qh, qh->facet_list);
    +  if (qh->KEEParea || qh->KEEPmerge || qh->KEEPminArea < REALmax/2)
    +    qh_markkeep(qh, qh->facet_list);
    +  if (qh->PRINTstatistics)
    +    qh_collectstatistics(qh);
    +}
    +
    +/*---------------------------------
    +
    +  qh_printafacet(qh, fp, format, facet, printall )
    +    print facet to fp in given output format (see qh.PRINTout)
    +
    +  returns:
    +    nop if !printall and qh_skipfacet()
    +    nop if visible facet and NEWfacets and format != PRINTfacets
    +    must match qh_countfacets
    +
    +  notes
    +    preserves qh.visit_id
    +    facet->normal may be null if PREmerge/MERGEexact and STOPcone before merge
    +
    +  see
    +    qh_printbegin() and qh_printend()
    +
    +  design:
    +    test for printing facet
    +    call appropriate routine for format
    +    or output results directly
    +*/
    +void qh_printafacet(qhT *qh, FILE *fp, qh_PRINT format, facetT *facet, boolT printall) {
    +  realT color[4], offset, dist, outerplane, innerplane;
    +  boolT zerodiv;
    +  coordT *point, *normp, *coordp, **pointp, *feasiblep;
    +  int k;
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  if (!printall && qh_skipfacet(qh, facet))
    +    return;
    +  if (facet->visible && qh->NEWfacets && format != qh_PRINTfacets)
    +    return;
    +  qh->printoutnum++;
    +  switch (format) {
    +  case qh_PRINTarea:
    +    if (facet->isarea) {
    +      qh_fprintf(qh, fp, 9009, qh_REAL_1, facet->f.area);
    +      qh_fprintf(qh, fp, 9010, "\n");
    +    }else
    +      qh_fprintf(qh, fp, 9011, "0\n");
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(qh, fp, 9012, "%d", qh_setsize(qh, facet->coplanarset));
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_fprintf(qh, fp, 9013, " %d", qh_pointid(qh, point));
    +    qh_fprintf(qh, fp, 9014, "\n");
    +    break;
    +  case qh_PRINTcentrums:
    +    qh_printcenter(qh, fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTfacets:
    +    qh_printfacet(qh, fp, facet);
    +    break;
    +  case qh_PRINTfacets_xridge:
    +    qh_printfacetheader(qh, fp, facet);
    +    break;
    +  case qh_PRINTgeom:  /* either 2 , 3, or 4-d by qh_printbegin */
    +    if (!facet->normal)
    +      break;
    +    for (k=qh->hull_dim; k--; ) {
    +      color[k]= (facet->normal[k]+1.0)/2.0;
    +      maximize_(color[k], -1.0);
    +      minimize_(color[k], +1.0);
    +    }
    +    qh_projectdim3(qh, color, color);
    +    if (qh->PRINTdim != qh->hull_dim)
    +      qh_normalize2(qh, color, 3, True, NULL, NULL);
    +    if (qh->hull_dim <= 2)
    +      qh_printfacet2geom(qh, fp, facet, color);
    +    else if (qh->hull_dim == 3) {
    +      if (facet->simplicial)
    +        qh_printfacet3geom_simplicial(qh, fp, facet, color);
    +      else
    +        qh_printfacet3geom_nonsimplicial(qh, fp, facet, color);
    +    }else {
    +      if (facet->simplicial)
    +        qh_printfacet4geom_simplicial(qh, fp, facet, color);
    +      else
    +        qh_printfacet4geom_nonsimplicial(qh, fp, facet, color);
    +    }
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(qh, fp, 9015, "%d\n", facet->id);
    +    break;
    +  case qh_PRINTincidences:
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh->hull_dim == 3 && format != qh_PRINTtriangles)
    +      qh_printfacet3vertex(qh, fp, facet, format);
    +    else if (facet->simplicial || qh->hull_dim == 2 || format == qh_PRINToff)
    +      qh_printfacetNvertex_simplicial(qh, fp, facet, format);
    +    else
    +      qh_printfacetNvertex_nonsimplicial(qh, fp, facet, qh->printoutvar++, format);
    +    break;
    +  case qh_PRINTinner:
    +    qh_outerinner(qh, facet, NULL, &innerplane);
    +    offset= facet->offset - innerplane;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTmerges:
    +    qh_fprintf(qh, fp, 9016, "%d\n", facet->nummerge);
    +    break;
    +  case qh_PRINTnormals:
    +    offset= facet->offset;
    +    goto LABELprintnorm;
    +    break; /* prevent warning */
    +  case qh_PRINTouter:
    +    qh_outerinner(qh, facet, &outerplane, NULL);
    +    offset= facet->offset - outerplane;
    +  LABELprintnorm:
    +    if (!facet->normal) {
    +      qh_fprintf(qh, fp, 9017, "no normal for facet f%d\n", facet->id);
    +      break;
    +    }
    +    if (qh->CDDoutput) {
    +      qh_fprintf(qh, fp, 9018, qh_REAL_1, -offset);
    +      for (k=0; k < qh->hull_dim; k++)
    +        qh_fprintf(qh, fp, 9019, qh_REAL_1, -facet->normal[k]);
    +    }else {
    +      for (k=0; k < qh->hull_dim; k++)
    +        qh_fprintf(qh, fp, 9020, qh_REAL_1, facet->normal[k]);
    +      qh_fprintf(qh, fp, 9021, qh_REAL_1, offset);
    +    }
    +    qh_fprintf(qh, fp, 9022, "\n");
    +    break;
    +  case qh_PRINTmathematica:  /* either 2 or 3-d by qh_printbegin */
    +  case qh_PRINTmaple:
    +    if (qh->hull_dim == 2)
    +      qh_printfacet2math(qh, fp, facet, format, qh->printoutvar++);
    +    else
    +      qh_printfacet3math(qh, fp, facet, format, qh->printoutvar++);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(qh, fp, 9023, "%d", qh_setsize(qh, facet->neighbors));
    +    FOREACHneighbor_(facet)
    +      qh_fprintf(qh, fp, 9024, " %d",
    +               neighbor->visitid ? neighbor->visitid - 1: 0 - neighbor->id);
    +    qh_fprintf(qh, fp, 9025, "\n");
    +    break;
    +  case qh_PRINTpointintersect:
    +    if (!qh->feasible_point) {
    +      qh_fprintf(qh, qh->ferr, 6067, "qhull input error (qh_printafacet): option 'Fp' needs qh->feasible_point\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (facet->offset > 0)
    +      goto LABELprintinfinite;
    +    point= coordp= (coordT*)qh_memalloc(qh, qh->normal_size);
    +    normp= facet->normal;
    +    feasiblep= qh->feasible_point;
    +    if (facet->offset < -qh->MINdenom) {
    +      for (k=qh->hull_dim; k--; )
    +        *(coordp++)= (*(normp++) / - facet->offset) + *(feasiblep++);
    +    }else {
    +      for (k=qh->hull_dim; k--; ) {
    +        *(coordp++)= qh_divzero(*(normp++), facet->offset, qh->MINdenom_1,
    +                                 &zerodiv) + *(feasiblep++);
    +        if (zerodiv) {
    +          qh_memfree(qh, point, qh->normal_size);
    +          goto LABELprintinfinite;
    +        }
    +      }
    +    }
    +    qh_printpoint(qh, fp, NULL, point);
    +    qh_memfree(qh, point, qh->normal_size);
    +    break;
    +  LABELprintinfinite:
    +    for (k=qh->hull_dim; k--; )
    +      qh_fprintf(qh, fp, 9026, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(qh, fp, 9027, "\n");
    +    break;
    +  case qh_PRINTpointnearest:
    +    FOREACHpoint_(facet->coplanarset) {
    +      int id, id2;
    +      vertex= qh_nearvertex(qh, facet, point, &dist);
    +      id= qh_pointid(qh, vertex->point);
    +      id2= qh_pointid(qh, point);
    +      qh_fprintf(qh, fp, 9028, "%d %d %d " qh_REAL_1 "\n", id, id2, facet->id, dist);
    +    }
    +    break;
    +  case qh_PRINTpoints:  /* VORONOI only by qh_printbegin */
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9029, "1 ");
    +    qh_printcenter(qh, fp, format, NULL, facet);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(qh, fp, 9030, "%d", qh_setsize(qh, facet->vertices));
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9031, " %d", qh_pointid(qh, vertex->point));
    +    qh_fprintf(qh, fp, 9032, "\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printafacet */
    +
    +/*---------------------------------
    +
    +  qh_printbegin(qh, )
    +    prints header for all output formats
    +
    +  returns:
    +    checks for valid format
    +
    +  notes:
    +    uses qh.visit_id for 3/4off
    +    changes qh.interior_point if printing centrums
    +    qh_countfacets clears facet->visitid for non-good facets
    +
    +  see
    +    qh_printend() and qh_printafacet()
    +
    +  design:
    +    count facets and related statistics
    +    print header for format
    +*/
    +void qh_printbegin(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  int i, num;
    +  facetT *facet, **facetp;
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +  pointT *point, **pointp, *pointtemp;
    +
    +  qh->printoutnum= 0;
    +  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +  switch (format) {
    +  case qh_PRINTnone:
    +    break;
    +  case qh_PRINTarea:
    +    qh_fprintf(qh, fp, 9033, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcoplanars:
    +    qh_fprintf(qh, fp, 9034, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTcentrums:
    +    if (qh->CENTERtype == qh_ASnone)
    +      qh_clearcenters(qh, qh_AScentrum);
    +    qh_fprintf(qh, fp, 9035, "%d\n%d\n", qh->hull_dim, numfacets);
    +    break;
    +  case qh_PRINTfacets:
    +  case qh_PRINTfacets_xridge:
    +    if (facetlist)
    +      qh_printvertexlist(qh, fp, "Vertices and facets:\n", facetlist, facets, printall);
    +    break;
    +  case qh_PRINTgeom:
    +    if (qh->hull_dim > 4)  /* qh_initqhull_globals also checks */
    +      goto LABELnoformat;
    +    if (qh->VORONOI && qh->hull_dim > 3)  /* PRINTdim == DROPdim == hull_dim-1 */
    +      goto LABELnoformat;
    +    if (qh->hull_dim == 2 && (qh->PRINTridges || qh->DOintersections))
    +      qh_fprintf(qh, qh->ferr, 7049, "qhull warning: output for ridges and intersections not implemented in 2-d\n");
    +    if (qh->hull_dim == 4 && (qh->PRINTinner || qh->PRINTouter ||
    +                             (qh->PRINTdim == 4 && qh->PRINTcentrums)))
    +      qh_fprintf(qh, qh->ferr, 7050, "qhull warning: output for outer/inner planes and centrums not implemented in 4-d\n");
    +    if (qh->PRINTdim == 4 && (qh->PRINTspheres))
    +      qh_fprintf(qh, qh->ferr, 7051, "qhull warning: output for vertices not implemented in 4-d\n");
    +    if (qh->PRINTdim == 4 && qh->DOintersections && qh->PRINTnoplanes)
    +      qh_fprintf(qh, qh->ferr, 7052, "qhull warning: 'Gnh' generates no output in 4-d\n");
    +    if (qh->PRINTdim == 2) {
    +      qh_fprintf(qh, fp, 9036, "{appearance {linewidth 3} LIST # %s | %s\n",
    +              qh->rbox_command, qh->qhull_command);
    +    }else if (qh->PRINTdim == 3) {
    +      qh_fprintf(qh, fp, 9037, "{appearance {+edge -evert linewidth 2} LIST # %s | %s\n",
    +              qh->rbox_command, qh->qhull_command);
    +    }else if (qh->PRINTdim == 4) {
    +      qh->visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)    /* get number of ridges to be printed */
    +        qh_printend4geom(qh, NULL, facet, &num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(qh, NULL, facet, &num, printall);
    +      qh->ridgeoutnum= num;
    +      qh->printoutvar= 0;  /* counts number of ridges in output */
    +      qh_fprintf(qh, fp, 9038, "LIST # %s | %s\n", qh->rbox_command, qh->qhull_command);
    +    }
    +
    +    if (qh->PRINTdots) {
    +      qh->printoutnum++;
    +      num= qh->num_points + qh_setsize(qh, qh->other_points);
    +      if (qh->DELAUNAY && qh->ATinfinity)
    +        num--;
    +      if (qh->PRINTdim == 4)
    +        qh_fprintf(qh, fp, 9039, "4VECT %d %d 1\n", num, num);
    +      else
    +        qh_fprintf(qh, fp, 9040, "VECT %d %d 1\n", num, num);
    +
    +      for (i=num; i--; ) {
    +        if (i % 20 == 0)
    +          qh_fprintf(qh, fp, 9041, "\n");
    +        qh_fprintf(qh, fp, 9042, "1 ");
    +      }
    +      qh_fprintf(qh, fp, 9043, "# 1 point per line\n1 ");
    +      for (i=num-1; i--; ) { /* num at least 3 for D2 */
    +        if (i % 20 == 0)
    +          qh_fprintf(qh, fp, 9044, "\n");
    +        qh_fprintf(qh, fp, 9045, "0 ");
    +      }
    +      qh_fprintf(qh, fp, 9046, "# 1 color for all\n");
    +      FORALLpoints {
    +        if (!qh->DELAUNAY || !qh->ATinfinity || qh_pointid(qh, point) != qh->num_points-1) {
    +          if (qh->PRINTdim == 4)
    +            qh_printpoint(qh, fp, NULL, point);
    +            else
    +              qh_printpoint3(qh, fp, point);
    +        }
    +      }
    +      FOREACHpoint_(qh->other_points) {
    +        if (qh->PRINTdim == 4)
    +          qh_printpoint(qh, fp, NULL, point);
    +        else
    +          qh_printpoint3(qh, fp, point);
    +      }
    +      qh_fprintf(qh, fp, 9047, "0 1 1 1  # color of points\n");
    +    }
    +
    +    if (qh->PRINTdim == 4  && !qh->PRINTnoplanes)
    +      /* 4dview loads up multiple 4OFF objects slowly */
    +      qh_fprintf(qh, fp, 9048, "4OFF %d %d 1\n", 3*qh->ridgeoutnum, qh->ridgeoutnum);
    +    qh->PRINTcradius= 2 * qh->DISTround;  /* include test DISTround */
    +    if (qh->PREmerge) {
    +      maximize_(qh->PRINTcradius, qh->premerge_centrum + qh->DISTround);
    +    }else if (qh->POSTmerge)
    +      maximize_(qh->PRINTcradius, qh->postmerge_centrum + qh->DISTround);
    +    qh->PRINTradius= qh->PRINTcradius;
    +    if (qh->PRINTspheres + qh->PRINTcoplanar)
    +      maximize_(qh->PRINTradius, qh->MAXabs_coord * qh_MINradius);
    +    if (qh->premerge_cos < REALmax/2) {
    +      maximize_(qh->PRINTradius, (1- qh->premerge_cos) * qh->MAXabs_coord);
    +    }else if (!qh->PREmerge && qh->POSTmerge && qh->postmerge_cos < REALmax/2) {
    +      maximize_(qh->PRINTradius, (1- qh->postmerge_cos) * qh->MAXabs_coord);
    +    }
    +    maximize_(qh->PRINTradius, qh->MINvisible);
    +    if (qh->JOGGLEmax < REALmax/2)
    +      qh->PRINTradius += qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +    if (qh->PRINTdim != 4 &&
    +        (qh->PRINTcoplanar || qh->PRINTspheres || qh->PRINTcentrums)) {
    +      vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +      if (qh->PRINTspheres && qh->PRINTdim <= 3)
    +        qh_printspheres(qh, fp, vertices, qh->PRINTradius);
    +      if (qh->PRINTcoplanar || qh->PRINTcentrums) {
    +        qh->firstcentrum= True;
    +        if (qh->PRINTcoplanar&& !qh->PRINTspheres) {
    +          FOREACHvertex_(vertices)
    +            qh_printpointvect2(qh, fp, vertex->point, NULL, qh->interior_point, qh->PRINTradius);
    +        }
    +        FORALLfacet_(facetlist) {
    +          if (!printall && qh_skipfacet(qh, facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh->PRINTcentrums && qh->PRINTdim <= 3)
    +            qh_printcentrum(qh, fp, facet, qh->PRINTcradius);
    +          if (!qh->PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +        }
    +        FOREACHfacet_(facets) {
    +          if (!printall && qh_skipfacet(qh, facet))
    +            continue;
    +          if (!facet->normal)
    +            continue;
    +          if (qh->PRINTcentrums && qh->PRINTdim <= 3)
    +            qh_printcentrum(qh, fp, facet, qh->PRINTcradius);
    +          if (!qh->PRINTcoplanar)
    +            continue;
    +          FOREACHpoint_(facet->coplanarset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +          FOREACHpoint_(facet->outsideset)
    +            qh_printpointvect2(qh, fp, point, facet->normal, NULL, qh->PRINTradius);
    +        }
    +      }
    +      qh_settempfree(qh, &vertices);
    +    }
    +    qh->visit_id++; /* for printing hyperplane intersections */
    +    break;
    +  case qh_PRINTids:
    +    qh_fprintf(qh, fp, 9049, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTincidences:
    +    if (qh->VORONOI && qh->PRINTprecision)
    +      qh_fprintf(qh, qh->ferr, 7053, "qhull warning: writing Delaunay.  Use 'p' or 'o' for Voronoi centers\n");
    +    qh->printoutvar= qh->vertex_id;  /* centrum id for non-simplicial facets */
    +    if (qh->hull_dim <= 3)
    +      qh_fprintf(qh, fp, 9050, "%d\n", numfacets);
    +    else
    +      qh_fprintf(qh, fp, 9051, "%d\n", numsimplicial+numridges);
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9052, "%s | %s\nbegin\n    %d %d real\n", qh->rbox_command,
    +            qh->qhull_command, numfacets, qh->hull_dim+1);
    +    else
    +      qh_fprintf(qh, fp, 9053, "%d\n%d\n", qh->hull_dim+1, numfacets);
    +    break;
    +  case qh_PRINTmathematica:
    +  case qh_PRINTmaple:
    +    if (qh->hull_dim > 3)  /* qh_initbuffers also checks */
    +      goto LABELnoformat;
    +    if (qh->VORONOI)
    +      qh_fprintf(qh, qh->ferr, 7054, "qhull warning: output is the Delaunay triangulation\n");
    +    if (format == qh_PRINTmaple) {
    +      if (qh->hull_dim == 2)
    +        qh_fprintf(qh, fp, 9054, "PLOT(CURVES(\n");
    +      else
    +        qh_fprintf(qh, fp, 9055, "PLOT3D(POLYGONS(\n");
    +    }else
    +      qh_fprintf(qh, fp, 9056, "{\n");
    +    qh->printoutvar= 0;   /* counts number of facets for notfirst */
    +    break;
    +  case qh_PRINTmerges:
    +    qh_fprintf(qh, fp, 9057, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTpointintersect:
    +    qh_fprintf(qh, fp, 9058, "%d\n%d\n", qh->hull_dim, numfacets);
    +    break;
    +  case qh_PRINTneighbors:
    +    qh_fprintf(qh, fp, 9059, "%d\n", numfacets);
    +    break;
    +  case qh_PRINToff:
    +  case qh_PRINTtriangles:
    +    if (qh->VORONOI)
    +      goto LABELnoformat;
    +    num = qh->hull_dim;
    +    if (format == qh_PRINToff || qh->hull_dim == 2)
    +      qh_fprintf(qh, fp, 9060, "%d\n%d %d %d\n", num,
    +        qh->num_points+qh_setsize(qh, qh->other_points), numfacets, totneighbors/2);
    +    else { /* qh_PRINTtriangles */
    +      qh->printoutvar= qh->num_points+qh_setsize(qh, qh->other_points); /* first centrum */
    +      if (qh->DELAUNAY)
    +        num--;  /* drop last dimension */
    +      qh_fprintf(qh, fp, 9061, "%d\n%d %d %d\n", num, qh->printoutvar
    +        + numfacets - numsimplicial, numsimplicial + numridges, totneighbors/2);
    +    }
    +    FORALLpoints
    +      qh_printpointid(qh, qh->fout, NULL, num, point, qh_IDunknown);
    +    FOREACHpoint_(qh->other_points)
    +      qh_printpointid(qh, qh->fout, NULL, num, point, qh_IDunknown);
    +    if (format == qh_PRINTtriangles && qh->hull_dim > 2) {
    +      FORALLfacets {
    +        if (!facet->simplicial && facet->visitid)
    +          qh_printcenter(qh, qh->fout, format, NULL, facet);
    +      }
    +    }
    +    break;
    +  case qh_PRINTpointnearest:
    +    qh_fprintf(qh, fp, 9062, "%d\n", numcoplanars);
    +    break;
    +  case qh_PRINTpoints:
    +    if (!qh->VORONOI)
    +      goto LABELnoformat;
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9063, "%s | %s\nbegin\n%d %d real\n", qh->rbox_command,
    +           qh->qhull_command, numfacets, qh->hull_dim);
    +    else
    +      qh_fprintf(qh, fp, 9064, "%d\n%d\n", qh->hull_dim-1, numfacets);
    +    break;
    +  case qh_PRINTvertices:
    +    qh_fprintf(qh, fp, 9065, "%d\n", numfacets);
    +    break;
    +  case qh_PRINTsummary:
    +  default:
    +  LABELnoformat:
    +    qh_fprintf(qh, qh->ferr, 6068, "qhull internal error (qh_printbegin): can not use this format for dimension %d\n",
    +         qh->hull_dim);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +} /* printbegin */
    +
    +/*---------------------------------
    +
    +  qh_printcenter(qh, fp, string, facet )
    +    print facet->center as centrum or Voronoi center
    +    string may be NULL.  Don't include '%' codes.
    +    nop if qh->CENTERtype neither CENTERvoronoi nor CENTERcentrum
    +    if upper envelope of Delaunay triangulation and point at-infinity
    +      prints qh_INFINITE instead;
    +
    +  notes:
    +    defines facet->center if needed
    +    if format=PRINTgeom, adds a 0 if would otherwise be 2-d
    +    Same as QhullFacet::printCenter
    +*/
    +void qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, facetT *facet) {
    +  int k, num;
    +
    +  if (qh->CENTERtype != qh_ASvoronoi && qh->CENTERtype != qh_AScentrum)
    +    return;
    +  if (string)
    +    qh_fprintf(qh, fp, 9066, string);
    +  if (qh->CENTERtype == qh_ASvoronoi) {
    +    num= qh->hull_dim-1;
    +    if (!facet->normal || !facet->upperdelaunay || !qh->ATinfinity) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(qh, facet->vertices);
    +      for (k=0; k < num; k++)
    +        qh_fprintf(qh, fp, 9067, qh_REAL_1, facet->center[k]);
    +    }else {
    +      for (k=0; k < num; k++)
    +        qh_fprintf(qh, fp, 9068, qh_REAL_1, qh_INFINITE);
    +    }
    +  }else /* qh->CENTERtype == qh_AScentrum */ {
    +    num= qh->hull_dim;
    +    if (format == qh_PRINTtriangles && qh->DELAUNAY)
    +      num--;
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(qh, facet);
    +    for (k=0; k < num; k++)
    +      qh_fprintf(qh, fp, 9069, qh_REAL_1, facet->center[k]);
    +  }
    +  if (format == qh_PRINTgeom && num == 2)
    +    qh_fprintf(qh, fp, 9070, " 0\n");
    +  else
    +    qh_fprintf(qh, fp, 9071, "\n");
    +} /* printcenter */
    +
    +/*---------------------------------
    +
    +  qh_printcentrum(qh, fp, facet, radius )
    +    print centrum for a facet in OOGL format
    +    radius defines size of centrum
    +    2-d or 3-d only
    +
    +  returns:
    +    defines facet->center if needed
    +*/
    +void qh_printcentrum(qhT *qh, FILE *fp, facetT *facet, realT radius) {
    +  pointT *centrum, *projpt;
    +  boolT tempcentrum= False;
    +  realT xaxis[4], yaxis[4], normal[4], dist;
    +  realT green[3]={0, 1, 0};
    +  vertexT *apex;
    +  int k;
    +
    +  if (qh->CENTERtype == qh_AScentrum) {
    +    if (!facet->center)
    +      facet->center= qh_getcentrum(qh, facet);
    +    centrum= facet->center;
    +  }else {
    +    centrum= qh_getcentrum(qh, facet);
    +    tempcentrum= True;
    +  }
    +  qh_fprintf(qh, fp, 9072, "{appearance {-normal -edge normscale 0} ");
    +  if (qh->firstcentrum) {
    +    qh->firstcentrum= False;
    +    qh_fprintf(qh, fp, 9073, "{INST geom { define centrum CQUAD  # f%d\n\
    +-0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3 -0.3 0.0001     0 0 1 1\n\
    + 0.3  0.3 0.0001     0 0 1 1\n\
    +-0.3  0.3 0.0001     0 0 1 1 } transform { \n", facet->id);
    +  }else
    +    qh_fprintf(qh, fp, 9074, "{INST geom { : centrum } transform { # f%d\n", facet->id);
    +  apex= SETfirstt_(facet->vertices, vertexT);
    +  qh_distplane(qh, apex->point, facet, &dist);
    +  projpt= qh_projectpoint(qh, apex->point, facet, dist);
    +  for (k=qh->hull_dim; k--; ) {
    +    xaxis[k]= projpt[k] - centrum[k];
    +    normal[k]= facet->normal[k];
    +  }
    +  if (qh->hull_dim == 2) {
    +    xaxis[2]= 0;
    +    normal[2]= 0;
    +  }else if (qh->hull_dim == 4) {
    +    qh_projectdim3(qh, xaxis, xaxis);
    +    qh_projectdim3(qh, normal, normal);
    +    qh_normalize2(qh, normal, qh->PRINTdim, True, NULL, NULL);
    +  }
    +  qh_crossproduct(3, xaxis, normal, yaxis);
    +  qh_fprintf(qh, fp, 9075, "%8.4g %8.4g %8.4g 0\n", xaxis[0], xaxis[1], xaxis[2]);
    +  qh_fprintf(qh, fp, 9076, "%8.4g %8.4g %8.4g 0\n", yaxis[0], yaxis[1], yaxis[2]);
    +  qh_fprintf(qh, fp, 9077, "%8.4g %8.4g %8.4g 0\n", normal[0], normal[1], normal[2]);
    +  qh_printpoint3(qh, fp, centrum);
    +  qh_fprintf(qh, fp, 9078, "1 }}}\n");
    +  qh_memfree(qh, projpt, qh->normal_size);
    +  qh_printpointvect(qh, fp, centrum, facet->normal, NULL, radius, green);
    +  if (tempcentrum)
    +    qh_memfree(qh, centrum, qh->normal_size);
    +} /* printcentrum */
    +
    +/*---------------------------------
    +
    +  qh_printend(qh, fp, format )
    +    prints trailer for all output formats
    +
    +  see:
    +    qh_printbegin() and qh_printafacet()
    +
    +*/
    +void qh_printend(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int num;
    +  facetT *facet, **facetp;
    +
    +  if (!qh->printoutnum)
    +    qh_fprintf(qh, qh->ferr, 7055, "qhull warning: no facets printed\n");
    +  switch (format) {
    +  case qh_PRINTgeom:
    +    if (qh->hull_dim == 4 && qh->DROPdim < 0  && !qh->PRINTnoplanes) {
    +      qh->visit_id++;
    +      num= 0;
    +      FORALLfacet_(facetlist)
    +        qh_printend4geom(qh, fp, facet,&num, printall);
    +      FOREACHfacet_(facets)
    +        qh_printend4geom(qh, fp, facet, &num, printall);
    +      if (num != qh->ridgeoutnum || qh->printoutvar != qh->ridgeoutnum) {
    +        qh_fprintf(qh, qh->ferr, 6069, "qhull internal error (qh_printend): number of ridges %d != number printed %d and at end %d\n", qh->ridgeoutnum, qh->printoutvar, num);
    +        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +      }
    +    }else
    +      qh_fprintf(qh, fp, 9079, "}\n");
    +    break;
    +  case qh_PRINTinner:
    +  case qh_PRINTnormals:
    +  case qh_PRINTouter:
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9080, "end\n");
    +    break;
    +  case qh_PRINTmaple:
    +    qh_fprintf(qh, fp, 9081, "));\n");
    +    break;
    +  case qh_PRINTmathematica:
    +    qh_fprintf(qh, fp, 9082, "}\n");
    +    break;
    +  case qh_PRINTpoints:
    +    if (qh->CDDoutput)
    +      qh_fprintf(qh, fp, 9083, "end\n");
    +    break;
    +  default:
    +    break;
    +  }
    +} /* printend */
    +
    +/*---------------------------------
    +
    +  qh_printend4geom(qh, fp, facet, numridges, printall )
    +    helper function for qh_printbegin/printend
    +
    +  returns:
    +    number of printed ridges
    +
    +  notes:
    +    just counts printed ridges if fp=NULL
    +    uses facet->visitid
    +    must agree with qh_printfacet4geom...
    +
    +  design:
    +    computes color for facet from its normal
    +    prints each ridge of facet
    +*/
    +void qh_printend4geom(qhT *qh, FILE *fp, facetT *facet, int *nump, boolT printall) {
    +  realT color[3];
    +  int i, num= *nump;
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (!printall && qh_skipfacet(qh, facet))
    +    return;
    +  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
    +    return;
    +  if (!facet->normal)
    +    return;
    +  if (fp) {
    +    for (i=0; i < 3; i++) {
    +      color[i]= (facet->normal[i]+1.0)/2.0;
    +      maximize_(color[i], -1.0);
    +      minimize_(color[i], +1.0);
    +    }
    +  }
    +  facet->visitid= qh->visit_id;
    +  if (facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (fp)
    +          qh_fprintf(qh, fp, 9084, "3 %d %d %d %8.4g %8.4g %8.4g 1 # f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }else {
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (fp)
    +          qh_fprintf(qh, fp, 9085, "3 %d %d %d %8.4g %8.4g %8.4g 1 #r%d f%d f%d\n",
    +                 3*num, 3*num+1, 3*num+2, color[0], color[1], color[2],
    +                 ridge->id, facet->id, neighbor->id);
    +        num++;
    +      }
    +    }
    +  }
    +  *nump= num;
    +} /* printend4geom */
    +
    +/*---------------------------------
    +
    +  qh_printextremes(qh, fp, facetlist, facets, printall )
    +    print extreme points for convex hulls or halfspace intersections
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    sorted by id
    +    same order as qh_printpoints_out if no coplanar/interior points
    +*/
    +void qh_printextremes(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices, *points;
    +  pointT *point;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +  int numpoints=0, point_i, point_n;
    +  int allpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +
    +  points= qh_settemp(qh, allpoints);
    +  qh_setzero(qh, points, 0, allpoints);
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(qh, vertex->point);
    +    if (id >= 0) {
    +      SETelem_(points, id)= vertex->point;
    +      numpoints++;
    +    }
    +  }
    +  qh_settempfree(qh, &vertices);
    +  qh_fprintf(qh, fp, 9086, "%d\n", numpoints);
    +  FOREACHpoint_i_(qh, points) {
    +    if (point)
    +      qh_fprintf(qh, fp, 9087, "%d\n", point_i);
    +  }
    +  qh_settempfree(qh, &points);
    +} /* printextremes */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_2d(qh, fp, facetlist, facets, printall )
    +    prints point ids for facets in qh_ORIENTclock order
    +
    +  notes:
    +    #points, followed by ids, one per line
    +    if facetlist/facets are disjoint than the output includes skips
    +    errors if facets form a loop
    +    does not print coplanar points
    +*/
    +void qh_printextremes_2d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numridges, totneighbors, numcoplanars, numsimplicial, numtricoplanars;
    +  setT *vertices;
    +  facetT *facet, *startfacet, *nextfacet;
    +  vertexT *vertexA, *vertexB;
    +
    +  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars); /* marks qh->visit_id */
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  qh_fprintf(qh, fp, 9088, "%d\n", qh_setsize(qh, vertices));
    +  qh_settempfree(qh, &vertices);
    +  if (!numfacets)
    +    return;
    +  facet= startfacet= facetlist ? facetlist : SETfirstt_(facets, facetT);
    +  qh->vertex_visit++;
    +  qh->visit_id++;
    +  do {
    +    if (facet->toporient ^ qh_ORIENTclock) {
    +      vertexA= SETfirstt_(facet->vertices, vertexT);
    +      vertexB= SETsecondt_(facet->vertices, vertexT);
    +      nextfacet= SETfirstt_(facet->neighbors, facetT);
    +    }else {
    +      vertexA= SETsecondt_(facet->vertices, vertexT);
    +      vertexB= SETfirstt_(facet->vertices, vertexT);
    +      nextfacet= SETsecondt_(facet->neighbors, facetT);
    +    }
    +    if (facet->visitid == qh->visit_id) {
    +      qh_fprintf(qh, qh->ferr, 6218, "Qhull internal error (qh_printextremes_2d): loop in facet list.  facet %d nextfacet %d\n",
    +                 facet->id, nextfacet->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet, nextfacet);
    +    }
    +    if (facet->visitid) {
    +      if (vertexA->visitid != qh->vertex_visit) {
    +        vertexA->visitid= qh->vertex_visit;
    +        qh_fprintf(qh, fp, 9089, "%d\n", qh_pointid(qh, vertexA->point));
    +      }
    +      if (vertexB->visitid != qh->vertex_visit) {
    +        vertexB->visitid= qh->vertex_visit;
    +        qh_fprintf(qh, fp, 9090, "%d\n", qh_pointid(qh, vertexB->point));
    +      }
    +    }
    +    facet->visitid= qh->visit_id;
    +    facet= nextfacet;
    +  }while (facet && facet != startfacet);
    +} /* printextremes_2d */
    +
    +/*---------------------------------
    +
    +  qh_printextremes_d(qh, fp, facetlist, facets, printall )
    +    print extreme points of input sites for Delaunay triangulations
    +
    +  notes:
    +    #points, followed by ids, one per line
    +
    +    unordered
    +*/
    +void qh_printextremes_d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  vertexT *vertex, **vertexp;
    +  boolT upperseen, lowerseen;
    +  facetT *neighbor, **neighborp;
    +  int numpoints=0;
    +
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  qh_vertexneighbors(qh);
    +  FOREACHvertex_(vertices) {
    +    upperseen= lowerseen= False;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay)
    +        upperseen= True;
    +      else
    +        lowerseen= True;
    +    }
    +    if (upperseen && lowerseen) {
    +      vertex->seen= True;
    +      numpoints++;
    +    }else
    +      vertex->seen= False;
    +  }
    +  qh_fprintf(qh, fp, 9091, "%d\n", numpoints);
    +  FOREACHvertex_(vertices) {
    +    if (vertex->seen)
    +      qh_fprintf(qh, fp, 9092, "%d\n", qh_pointid(qh, vertex->point));
    +  }
    +  qh_settempfree(qh, &vertices);
    +} /* printextremes_d */
    +
    +/*---------------------------------
    +
    +  qh_printfacet(qh, fp, facet )
    +    prints all fields of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +*/
    +void qh_printfacet(qhT *qh, FILE *fp, facetT *facet) {
    +
    +  qh_printfacetheader(qh, fp, facet);
    +  if (facet->ridges)
    +    qh_printfacetridges(qh, fp, facet);
    +} /* printfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom(qh, fp, facet, color )
    +    print facet as part of a 2-d VECT for Geomview
    +
    +    notes:
    +      assume precise calculations in io_r.c with roundoff covered by qh_GEOMepsilon
    +      mindist is calculated within io_r.c.  maxoutside is calculated elsewhere
    +      so a DISTround error may have occurred.
    +*/
    +void qh_printfacet2geom(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  pointT *point0, *point1;
    +  realT mindist, innerplane, outerplane;
    +  int k;
    +
    +  qh_facet2point(qh, facet, &point0, &point1, &mindist);
    +  qh_geomplanes(qh, facet, &outerplane, &innerplane);
    +  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
    +    qh_printfacet2geom_points(qh, fp, point0, point1, facet, outerplane, color);
    +  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
    +                outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet2geom_points(qh, fp, point0, point1, facet, innerplane, color);
    +  }
    +  qh_memfree(qh, point1, qh->normal_size);
    +  qh_memfree(qh, point0, qh->normal_size);
    +} /* printfacet2geom */
    +
    +/*---------------------------------
    +
    +  qh_printfacet2geom_points(qh, fp, point1, point2, facet, offset, color )
    +    prints a 2-d facet as a VECT with 2 points at some offset.
    +    The points are on the facet's plane.
    +*/
    +void qh_printfacet2geom_points(qhT *qh, FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]) {
    +  pointT *p1= point1, *p2= point2;
    +
    +  qh_fprintf(qh, fp, 9093, "VECT 1 2 1 2 1 # f%d\n", facet->id);
    +  if (offset != 0.0) {
    +    p1= qh_projectpoint(qh, p1, facet, -offset);
    +    p2= qh_projectpoint(qh, p2, facet, -offset);
    +  }
    +  qh_fprintf(qh, fp, 9094, "%8.4g %8.4g %8.4g\n%8.4g %8.4g %8.4g\n",
    +           p1[0], p1[1], 0.0, p2[0], p2[1], 0.0);
    +  if (offset != 0.0) {
    +    qh_memfree(qh, p1, qh->normal_size);
    +    qh_memfree(qh, p2, qh->normal_size);
    +  }
    +  qh_fprintf(qh, fp, 9095, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printfacet2geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet2math(qh, fp, facet, format, notfirst )
    +    print 2-d Maple or Mathematica output for a facet
    +    may be non-simplicial
    +
    +  notes:
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet3math
    +*/
    +void qh_printfacet2math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  pointT *point0, *point1;
    +  realT mindist;
    +  const char *pointfmt;
    +
    +  qh_facet2point(qh, facet, &point0, &point1, &mindist);
    +  if (notfirst)
    +    qh_fprintf(qh, fp, 9096, ",");
    +  if (format == qh_PRINTmaple)
    +    pointfmt= "[[%16.8f, %16.8f], [%16.8f, %16.8f]]\n";
    +  else
    +    pointfmt= "Line[{{%16.8f, %16.8f}, {%16.8f, %16.8f}}]\n";
    +  qh_fprintf(qh, fp, 9097, pointfmt, point0[0], point0[1], point1[0], point1[1]);
    +  qh_memfree(qh, point1, qh->normal_size);
    +  qh_memfree(qh, point0, qh->normal_size);
    +} /* printfacet2math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_nonsimplicial(qh, fp, facet, color )
    +    print Geomview OFF for a 3-d nonsimplicial facet.
    +    if DOintersections, prints ridges to unvisited neighbors(qh->visit_id)
    +
    +  notes
    +    uses facet->visitid for intersections and ridges
    +*/
    +void qh_printfacet3geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  ridgeT *ridge, **ridgep;
    +  setT *projectedpoints, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  pointT *projpt, *point, **pointp;
    +  facetT *neighbor;
    +  realT dist, outerplane, innerplane;
    +  int cntvertices, k;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +
    +  qh_geomplanes(qh, facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(qh, facet); /* oriented */
    +  cntvertices= qh_setsize(qh, vertices);
    +  projectedpoints= qh_settemp(qh, cntvertices);
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(qh, vertex->point, facet, &dist);
    +    projpt= qh_projectpoint(qh, vertex->point, facet, dist);
    +    qh_setappend(qh, &projectedpoints, projpt);
    +  }
    +  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
    +    qh_printfacet3geom_points(qh, fp, projectedpoints, facet, outerplane, color);
    +  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
    +                outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(qh, fp, projectedpoints, facet, innerplane, color);
    +  }
    +  FOREACHpoint_(projectedpoints)
    +    qh_memfree(qh, point, qh->normal_size);
    +  qh_settempfree(qh, &projectedpoints);
    +  qh_settempfree(qh, &vertices);
    +  if ((qh->DOintersections || qh->PRINTridges)
    +  && (!facet->visible || !qh->NEWfacets)) {
    +    facet->visitid= qh->visit_id;
    +    FOREACHridge_(facet->ridges) {
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (qh->DOintersections)
    +          qh_printhyperplaneintersection(qh, fp, facet, neighbor, ridge->vertices, black);
    +        if (qh->PRINTridges) {
    +          vertexA= SETfirstt_(ridge->vertices, vertexT);
    +          vertexB= SETsecondt_(ridge->vertices, vertexT);
    +          qh_printline3geom(qh, fp, vertexA->point, vertexB->point, green);
    +        }
    +      }
    +    }
    +  }
    +} /* printfacet3geom_nonsimplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_points(qh, fp, points, facet, offset )
    +    prints a 3-d facet as OFF Geomview object.
    +    offset is relative to the facet's hyperplane
    +    Facet is determined as a list of points
    +*/
    +void qh_printfacet3geom_points(qhT *qh, FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]) {
    +  int k, n= qh_setsize(qh, points), i;
    +  pointT *point, **pointp;
    +  setT *printpoints;
    +
    +  qh_fprintf(qh, fp, 9098, "{ OFF %d 1 1 # f%d\n", n, facet->id);
    +  if (offset != 0.0) {
    +    printpoints= qh_settemp(qh, n);
    +    FOREACHpoint_(points)
    +      qh_setappend(qh, &printpoints, qh_projectpoint(qh, point, facet, -offset));
    +  }else
    +    printpoints= points;
    +  FOREACHpoint_(printpoints) {
    +    for (k=0; k < qh->hull_dim; k++) {
    +      if (k == qh->DROPdim)
    +        qh_fprintf(qh, fp, 9099, "0 ");
    +      else
    +        qh_fprintf(qh, fp, 9100, "%8.4g ", point[k]);
    +    }
    +    if (printpoints != points)
    +      qh_memfree(qh, point, qh->normal_size);
    +    qh_fprintf(qh, fp, 9101, "\n");
    +  }
    +  if (printpoints != points)
    +    qh_settempfree(qh, &printpoints);
    +  qh_fprintf(qh, fp, 9102, "%d ", n);
    +  for (i=0; i < n; i++)
    +    qh_fprintf(qh, fp, 9103, "%d ", i);
    +  qh_fprintf(qh, fp, 9104, "%8.4g %8.4g %8.4g 1.0 }\n", color[0], color[1], color[2]);
    +} /* printfacet3geom_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3geom_simplicial(qh, )
    +    print Geomview OFF for a 3-d simplicial facet.
    +
    +  notes:
    +    may flip color
    +    uses facet->visitid for intersections and ridges
    +
    +    assume precise calculations in io_r.c with roundoff covered by qh_GEOMepsilon
    +    innerplane may be off by qh->DISTround.  Maxoutside is calculated elsewhere
    +    so a DISTround error may have occurred.
    +*/
    +void qh_printfacet3geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  setT *points, *vertices;
    +  vertexT *vertex, **vertexp, *vertexA, *vertexB;
    +  facetT *neighbor, **neighborp;
    +  realT outerplane, innerplane;
    +  realT black[3]={0, 0, 0}, green[3]={0, 1, 0};
    +  int k;
    +
    +  qh_geomplanes(qh, facet, &outerplane, &innerplane);
    +  vertices= qh_facet3vertex(qh, facet);
    +  points= qh_settemp(qh, qh->TEMPsize);
    +  FOREACHvertex_(vertices)
    +    qh_setappend(qh, &points, vertex->point);
    +  if (qh->PRINTouter || (!qh->PRINTnoplanes && !qh->PRINTinner))
    +    qh_printfacet3geom_points(qh, fp, points, facet, outerplane, color);
    +  if (qh->PRINTinner || (!qh->PRINTnoplanes && !qh->PRINTouter &&
    +              outerplane - innerplane > 2 * qh->MAXabs_coord * qh_GEOMepsilon)) {
    +    for (k=3; k--; )
    +      color[k]= 1.0 - color[k];
    +    qh_printfacet3geom_points(qh, fp, points, facet, innerplane, color);
    +  }
    +  qh_settempfree(qh, &points);
    +  qh_settempfree(qh, &vertices);
    +  if ((qh->DOintersections || qh->PRINTridges)
    +  && (!facet->visible || !qh->NEWfacets)) {
    +    facet->visitid= qh->visit_id;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +        if (qh->DOintersections)
    +           qh_printhyperplaneintersection(qh, fp, facet, neighbor, vertices, black);
    +        if (qh->PRINTridges) {
    +          vertexA= SETfirstt_(vertices, vertexT);
    +          vertexB= SETsecondt_(vertices, vertexT);
    +          qh_printline3geom(qh, fp, vertexA->point, vertexB->point, green);
    +        }
    +        qh_setfree(qh, &vertices);
    +      }
    +    }
    +  }
    +} /* printfacet3geom_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_printfacet3math(qh, fp, facet, notfirst )
    +    print 3-d Maple or Mathematica output for a facet
    +
    +  notes:
    +    may be non-simplicial
    +    use %16.8f since Mathematica 2.2 does not handle exponential format
    +    see qh_printfacet2math
    +*/
    +void qh_printfacet3math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst) {
    +  vertexT *vertex, **vertexp;
    +  setT *points, *vertices;
    +  pointT *point, **pointp;
    +  boolT firstpoint= True;
    +  realT dist;
    +  const char *pointfmt, *endfmt;
    +
    +  if (notfirst)
    +    qh_fprintf(qh, fp, 9105, ",\n");
    +  vertices= qh_facet3vertex(qh, facet);
    +  points= qh_settemp(qh, qh_setsize(qh, vertices));
    +  FOREACHvertex_(vertices) {
    +    zinc_(Zdistio);
    +    qh_distplane(qh, vertex->point, facet, &dist);
    +    point= qh_projectpoint(qh, vertex->point, facet, dist);
    +    qh_setappend(qh, &points, point);
    +  }
    +  if (format == qh_PRINTmaple) {
    +    qh_fprintf(qh, fp, 9106, "[");
    +    pointfmt= "[%16.8f, %16.8f, %16.8f]";
    +    endfmt= "]";
    +  }else {
    +    qh_fprintf(qh, fp, 9107, "Polygon[{");
    +    pointfmt= "{%16.8f, %16.8f, %16.8f}";
    +    endfmt= "}]";
    +  }
    +  FOREACHpoint_(points) {
    +    if (firstpoint)
    +      firstpoint= False;
    +    else
    +      qh_fprintf(qh, fp, 9108, ",\n");
    +    qh_fprintf(qh, fp, 9109, pointfmt, point[0], point[1], point[2]);
    +  }
    +  FOREACHpoint_(points)
    +    qh_memfree(qh, point, qh->normal_size);
    +  qh_settempfree(qh, &points);
    +  qh_settempfree(qh, &vertices);
    +  qh_fprintf(qh, fp, 9110, "%s", endfmt);
    +} /* printfacet3math */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet3vertex(qh, fp, facet, format )
    +    print vertices in a 3-d facet as point ids
    +
    +  notes:
    +    prints number of vertices first if format == qh_PRINToff
    +    the facet may be non-simplicial
    +*/
    +void qh_printfacet3vertex(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facet3vertex(qh, facet);
    +  if (format == qh_PRINToff)
    +    qh_fprintf(qh, fp, 9111, "%d ", qh_setsize(qh, vertices));
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(qh, fp, 9112, "%d ", qh_pointid(qh, vertex->point));
    +  qh_fprintf(qh, fp, 9113, "\n");
    +  qh_settempfree(qh, &vertices);
    +} /* printfacet3vertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_nonsimplicial(qh, )
    +    print Geomview 4OFF file for a 4d nonsimplicial facet
    +    prints all ridges to unvisited neighbors (qh.visit_id)
    +    if qh.DROPdim
    +      prints in OFF format
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  facetT *neighbor;
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  pointT *point;
    +  int k;
    +  realT dist;
    +
    +  facet->visitid= qh->visit_id;
    +  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh->visit_id)
    +      continue;
    +    if (qh->PRINTtransparent && !neighbor->good)
    +      continue;
    +    if (qh->DOintersections)
    +      qh_printhyperplaneintersection(qh, fp, facet, neighbor, ridge->vertices, color);
    +    else {
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9114, "OFF 3 1 1 # f%d\n", facet->id);
    +      else {
    +        qh->printoutvar++;
    +        qh_fprintf(qh, fp, 9115, "# r%d between f%d f%d\n", ridge->id, facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(ridge->vertices) {
    +        zinc_(Zdistio);
    +        qh_distplane(qh, vertex->point,facet, &dist);
    +        point=qh_projectpoint(qh, vertex->point,facet, dist);
    +        for (k=0; k < qh->hull_dim; k++) {
    +          if (k != qh->DROPdim)
    +            qh_fprintf(qh, fp, 9116, "%8.4g ", point[k]);
    +        }
    +        qh_fprintf(qh, fp, 9117, "\n");
    +        qh_memfree(qh, point, qh->normal_size);
    +      }
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9118, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +  }
    +} /* printfacet4geom_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacet4geom_simplicial(qh, fp, facet, color )
    +    print Geomview 4OFF file for a 4d simplicial facet
    +    prints triangles for unvisited neighbors (qh.visit_id)
    +
    +  notes:
    +    must agree with printend4geom()
    +*/
    +void qh_printfacet4geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]) {
    +  setT *vertices;
    +  facetT *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int k;
    +
    +  facet->visitid= qh->visit_id;
    +  if (qh->PRINTnoplanes || (facet->visible && qh->NEWfacets))
    +    return;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid == qh->visit_id)
    +      continue;
    +    if (qh->PRINTtransparent && !neighbor->good)
    +      continue;
    +    vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
    +                          SETindex_(facet->neighbors, neighbor), 0);
    +    if (qh->DOintersections)
    +      qh_printhyperplaneintersection(qh, fp, facet, neighbor, vertices, color);
    +    else {
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9119, "OFF 3 1 1 # ridge between f%d f%d\n",
    +                facet->id, neighbor->id);
    +      else {
    +        qh->printoutvar++;
    +        qh_fprintf(qh, fp, 9120, "# ridge between f%d f%d\n", facet->id, neighbor->id);
    +      }
    +      FOREACHvertex_(vertices) {
    +        for (k=0; k < qh->hull_dim; k++) {
    +          if (k != qh->DROPdim)
    +            qh_fprintf(qh, fp, 9121, "%8.4g ", vertex->point[k]);
    +        }
    +        qh_fprintf(qh, fp, 9122, "\n");
    +      }
    +      if (qh->DROPdim >= 0)
    +        qh_fprintf(qh, fp, 9123, "3 0 1 2 %8.4g %8.4g %8.4g\n", color[0], color[1], color[2]);
    +    }
    +    qh_setfree(qh, &vertices);
    +  }
    +} /* printfacet4geom_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_nonsimplicial(qh, fp, facet, id, format )
    +    print vertices for an N-d non-simplicial facet
    +    triangulates each ridge to the id
    +*/
    +void qh_printfacetNvertex_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, int id, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +  ridgeT *ridge, **ridgep;
    +
    +  if (facet->visible && qh->NEWfacets)
    +    return;
    +  FOREACHridge_(facet->ridges) {
    +    if (format == qh_PRINTtriangles)
    +      qh_fprintf(qh, fp, 9124, "%d ", qh->hull_dim);
    +    qh_fprintf(qh, fp, 9125, "%d ", id);
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      FOREACHvertex_(ridge->vertices)
    +        qh_fprintf(qh, fp, 9126, "%d ", qh_pointid(qh, vertex->point));
    +    }else {
    +      FOREACHvertexreverse12_(ridge->vertices)
    +        qh_fprintf(qh, fp, 9127, "%d ", qh_pointid(qh, vertex->point));
    +    }
    +    qh_fprintf(qh, fp, 9128, "\n");
    +  }
    +} /* printfacetNvertex_nonsimplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetNvertex_simplicial(qh, fp, facet, format )
    +    print vertices for an N-d simplicial facet
    +    prints vertices for non-simplicial facets
    +      2-d facets (orientation preserved by qh_mergefacet2d)
    +      PRINToff ('o') for 4-d and higher
    +*/
    +void qh_printfacetNvertex_simplicial(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format) {
    +  vertexT *vertex, **vertexp;
    +
    +  if (format == qh_PRINToff || format == qh_PRINTtriangles)
    +    qh_fprintf(qh, fp, 9129, "%d ", qh_setsize(qh, facet->vertices));
    +  if ((facet->toporient ^ qh_ORIENTclock)
    +  || (qh->hull_dim > 2 && !facet->simplicial)) {
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9130, "%d ", qh_pointid(qh, vertex->point));
    +  }else {
    +    FOREACHvertexreverse12_(facet->vertices)
    +      qh_fprintf(qh, fp, 9131, "%d ", qh_pointid(qh, vertex->point));
    +  }
    +  qh_fprintf(qh, fp, 9132, "\n");
    +} /* printfacetNvertex_simplicial */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetheader(qh, fp, facet )
    +    prints header fields of a facet to fp
    +
    +  notes:
    +    for 'f' output and debugging
    +    Same as QhullFacet::printHeader()
    +*/
    +void qh_printfacetheader(qhT *qh, FILE *fp, facetT *facet) {
    +  pointT *point, **pointp, *furthest;
    +  facetT *neighbor, **neighborp;
    +  realT dist;
    +
    +  if (facet == qh_MERGEridge) {
    +    qh_fprintf(qh, fp, 9133, " MERGEridge\n");
    +    return;
    +  }else if (facet == qh_DUPLICATEridge) {
    +    qh_fprintf(qh, fp, 9134, " DUPLICATEridge\n");
    +    return;
    +  }else if (!facet) {
    +    qh_fprintf(qh, fp, 9135, " NULLfacet\n");
    +    return;
    +  }
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  qh_fprintf(qh, fp, 9136, "- f%d\n", facet->id);
    +  qh_fprintf(qh, fp, 9137, "    - flags:");
    +  if (facet->toporient)
    +    qh_fprintf(qh, fp, 9138, " top");
    +  else
    +    qh_fprintf(qh, fp, 9139, " bottom");
    +  if (facet->simplicial)
    +    qh_fprintf(qh, fp, 9140, " simplicial");
    +  if (facet->tricoplanar)
    +    qh_fprintf(qh, fp, 9141, " tricoplanar");
    +  if (facet->upperdelaunay)
    +    qh_fprintf(qh, fp, 9142, " upperDelaunay");
    +  if (facet->visible)
    +    qh_fprintf(qh, fp, 9143, " visible");
    +  if (facet->newfacet)
    +    qh_fprintf(qh, fp, 9144, " new");
    +  if (facet->tested)
    +    qh_fprintf(qh, fp, 9145, " tested");
    +  if (!facet->good)
    +    qh_fprintf(qh, fp, 9146, " notG");
    +  if (facet->seen)
    +    qh_fprintf(qh, fp, 9147, " seen");
    +  if (facet->coplanar)
    +    qh_fprintf(qh, fp, 9148, " coplanar");
    +  if (facet->mergehorizon)
    +    qh_fprintf(qh, fp, 9149, " mergehorizon");
    +  if (facet->keepcentrum)
    +    qh_fprintf(qh, fp, 9150, " keepcentrum");
    +  if (facet->dupridge)
    +    qh_fprintf(qh, fp, 9151, " dupridge");
    +  if (facet->mergeridge && !facet->mergeridge2)
    +    qh_fprintf(qh, fp, 9152, " mergeridge1");
    +  if (facet->mergeridge2)
    +    qh_fprintf(qh, fp, 9153, " mergeridge2");
    +  if (facet->newmerge)
    +    qh_fprintf(qh, fp, 9154, " newmerge");
    +  if (facet->flipped)
    +    qh_fprintf(qh, fp, 9155, " flipped");
    +  if (facet->notfurthest)
    +    qh_fprintf(qh, fp, 9156, " notfurthest");
    +  if (facet->degenerate)
    +    qh_fprintf(qh, fp, 9157, " degenerate");
    +  if (facet->redundant)
    +    qh_fprintf(qh, fp, 9158, " redundant");
    +  qh_fprintf(qh, fp, 9159, "\n");
    +  if (facet->isarea)
    +    qh_fprintf(qh, fp, 9160, "    - area: %2.2g\n", facet->f.area);
    +  else if (qh->NEWfacets && facet->visible && facet->f.replace)
    +    qh_fprintf(qh, fp, 9161, "    - replacement: f%d\n", facet->f.replace->id);
    +  else if (facet->newfacet) {
    +    if (facet->f.samecycle && facet->f.samecycle != facet)
    +      qh_fprintf(qh, fp, 9162, "    - shares same visible/horizon as f%d\n", facet->f.samecycle->id);
    +  }else if (facet->tricoplanar /* !isarea */) {
    +    if (facet->f.triowner)
    +      qh_fprintf(qh, fp, 9163, "    - owner of normal & centrum is facet f%d\n", facet->f.triowner->id);
    +  }else if (facet->f.newcycle)
    +    qh_fprintf(qh, fp, 9164, "    - was horizon to f%d\n", facet->f.newcycle->id);
    +  if (facet->nummerge)
    +    qh_fprintf(qh, fp, 9165, "    - merges: %d\n", facet->nummerge);
    +  qh_printpointid(qh, fp, "    - normal: ", qh->hull_dim, facet->normal, qh_IDunknown);
    +  qh_fprintf(qh, fp, 9166, "    - offset: %10.7g\n", facet->offset);
    +  if (qh->CENTERtype == qh_ASvoronoi || facet->center)
    +    qh_printcenter(qh, fp, qh_PRINTfacets, "    - center: ", facet);
    +#if qh_MAXoutside
    +  if (facet->maxoutside > qh->DISTround)
    +    qh_fprintf(qh, fp, 9167, "    - maxoutside: %10.7g\n", facet->maxoutside);
    +#endif
    +  if (!SETempty_(facet->outsideset)) {
    +    furthest= (pointT*)qh_setlast(facet->outsideset);
    +    if (qh_setsize(qh, facet->outsideset) < 6) {
    +      qh_fprintf(qh, fp, 9168, "    - outside set(furthest p%d):\n", qh_pointid(qh, furthest));
    +      FOREACHpoint_(facet->outsideset)
    +        qh_printpoint(qh, fp, "     ", point);
    +    }else if (qh_setsize(qh, facet->outsideset) < 21) {
    +      qh_printpoints(qh, fp, "    - outside set:", facet->outsideset);
    +    }else {
    +      qh_fprintf(qh, fp, 9169, "    - outside set:  %d points.", qh_setsize(qh, facet->outsideset));
    +      qh_printpoint(qh, fp, "  Furthest", furthest);
    +    }
    +#if !qh_COMPUTEfurthest
    +    qh_fprintf(qh, fp, 9170, "    - furthest distance= %2.2g\n", facet->furthestdist);
    +#endif
    +  }
    +  if (!SETempty_(facet->coplanarset)) {
    +    furthest= (pointT*)qh_setlast(facet->coplanarset);
    +    if (qh_setsize(qh, facet->coplanarset) < 6) {
    +      qh_fprintf(qh, fp, 9171, "    - coplanar set(furthest p%d):\n", qh_pointid(qh, furthest));
    +      FOREACHpoint_(facet->coplanarset)
    +        qh_printpoint(qh, fp, "     ", point);
    +    }else if (qh_setsize(qh, facet->coplanarset) < 21) {
    +      qh_printpoints(qh, fp, "    - coplanar set:", facet->coplanarset);
    +    }else {
    +      qh_fprintf(qh, fp, 9172, "    - coplanar set:  %d points.", qh_setsize(qh, facet->coplanarset));
    +      qh_printpoint(qh, fp, "  Furthest", furthest);
    +    }
    +    zinc_(Zdistio);
    +    qh_distplane(qh, furthest, facet, &dist);
    +    qh_fprintf(qh, fp, 9173, "      furthest distance= %2.2g\n", dist);
    +  }
    +  qh_printvertices(qh, fp, "    - vertices:", facet->vertices);
    +  qh_fprintf(qh, fp, 9174, "    - neighboring facets:");
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      qh_fprintf(qh, fp, 9175, " MERGE");
    +    else if (neighbor == qh_DUPLICATEridge)
    +      qh_fprintf(qh, fp, 9176, " DUP");
    +    else
    +      qh_fprintf(qh, fp, 9177, " f%d", neighbor->id);
    +  }
    +  qh_fprintf(qh, fp, 9178, "\n");
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* printfacetheader */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetridges(qh, fp, facet )
    +    prints ridges of a facet to fp
    +
    +  notes:
    +    ridges printed in neighbor order
    +    assumes the ridges exist
    +    for 'f' output
    +    same as QhullFacet::printRidges
    +*/
    +void qh_printfacetridges(qhT *qh, FILE *fp, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int numridges= 0;
    +
    +
    +  if (facet->visible && qh->NEWfacets) {
    +    qh_fprintf(qh, fp, 9179, "    - ridges(ids may be garbage):");
    +    FOREACHridge_(facet->ridges)
    +      qh_fprintf(qh, fp, 9180, " r%d", ridge->id);
    +    qh_fprintf(qh, fp, 9181, "\n");
    +  }else {
    +    qh_fprintf(qh, fp, 9182, "    - ridges:\n");
    +    FOREACHridge_(facet->ridges)
    +      ridge->seen= False;
    +    if (qh->hull_dim == 3) {
    +      ridge= SETfirstt_(facet->ridges, ridgeT);
    +      while (ridge && !ridge->seen) {
    +        ridge->seen= True;
    +        qh_printridge(qh, fp, ridge);
    +        numridges++;
    +        ridge= qh_nextridge3d(ridge, facet, NULL);
    +        }
    +    }else {
    +      FOREACHneighbor_(facet) {
    +        FOREACHridge_(facet->ridges) {
    +          if (otherfacet_(ridge,facet) == neighbor) {
    +            ridge->seen= True;
    +            qh_printridge(qh, fp, ridge);
    +            numridges++;
    +          }
    +        }
    +      }
    +    }
    +    if (numridges != qh_setsize(qh, facet->ridges)) {
    +      qh_fprintf(qh, fp, 9183, "     - all ridges:");
    +      FOREACHridge_(facet->ridges)
    +        qh_fprintf(qh, fp, 9184, " r%d", ridge->id);
    +        qh_fprintf(qh, fp, 9185, "\n");
    +    }
    +    FOREACHridge_(facet->ridges) {
    +      if (!ridge->seen)
    +        qh_printridge(qh, fp, ridge);
    +    }
    +  }
    +} /* printfacetridges */
    +
    +/*---------------------------------
    +
    +  qh_printfacets(qh, fp, format, facetlist, facets, printall )
    +    prints facetlist and/or facet set in output format
    +
    +  notes:
    +    also used for specialized formats ('FO' and summary)
    +    turns off 'Rn' option since want actual numbers
    +*/
    +void qh_printfacets(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numcoplanars, numtricoplanars;
    +  facetT *facet, **facetp;
    +  setT *vertices;
    +  coordT *center;
    +  realT outerplane, innerplane;
    +
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  if (qh->CDDoutput && (format == qh_PRINTcentrums || format == qh_PRINTpointintersect || format == qh_PRINToff))
    +    qh_fprintf(qh, qh->ferr, 7056, "qhull warning: CDD format is not available for centrums, halfspace\nintersections, and OFF file format.\n");
    +  if (format == qh_PRINTnone)
    +    ; /* print nothing */
    +  else if (format == qh_PRINTaverage) {
    +    vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +    center= qh_getcenter(qh, vertices);
    +    qh_fprintf(qh, fp, 9186, "%d 1\n", qh->hull_dim);
    +    qh_printpointid(qh, fp, NULL, qh->hull_dim, center, qh_IDunknown);
    +    qh_memfree(qh, center, qh->normal_size);
    +    qh_settempfree(qh, &vertices);
    +  }else if (format == qh_PRINTextremes) {
    +    if (qh->DELAUNAY)
    +      qh_printextremes_d(qh, fp, facetlist, facets, printall);
    +    else if (qh->hull_dim == 2)
    +      qh_printextremes_2d(qh, fp, facetlist, facets, printall);
    +    else
    +      qh_printextremes(qh, fp, facetlist, facets, printall);
    +  }else if (format == qh_PRINToptions)
    +    qh_fprintf(qh, fp, 9187, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
    +  else if (format == qh_PRINTpoints && !qh->VORONOI)
    +    qh_printpoints_out(qh, fp, facetlist, facets, printall);
    +  else if (format == qh_PRINTqhull)
    +    qh_fprintf(qh, fp, 9188, "%s | %s\n", qh->rbox_command, qh->qhull_command);
    +  else if (format == qh_PRINTsize) {
    +    qh_fprintf(qh, fp, 9189, "0\n2 ");
    +    qh_fprintf(qh, fp, 9190, qh_REAL_1, qh->totarea);
    +    qh_fprintf(qh, fp, 9191, qh_REAL_1, qh->totvol);
    +    qh_fprintf(qh, fp, 9192, "\n");
    +  }else if (format == qh_PRINTsummary) {
    +    qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);
    +    vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +    qh_fprintf(qh, fp, 9193, "10 %d %d %d %d %d %d %d %d %d %d\n2 ", qh->hull_dim,
    +                qh->num_points + qh_setsize(qh, qh->other_points),
    +                qh->num_vertices, qh->num_facets - qh->num_visible,
    +                qh_setsize(qh, vertices), numfacets, numcoplanars,
    +                numfacets - numsimplicial, zzval_(Zdelvertextot),
    +                numtricoplanars);
    +    qh_settempfree(qh, &vertices);
    +    qh_outerinner(qh, NULL, &outerplane, &innerplane);
    +    qh_fprintf(qh, fp, 9194, qh_REAL_2n, outerplane, innerplane);
    +  }else if (format == qh_PRINTvneighbors)
    +    qh_printvneighbors(qh, fp, facetlist, facets, printall);
    +  else if (qh->VORONOI && format == qh_PRINToff)
    +    qh_printvoronoi(qh, fp, format, facetlist, facets, printall);
    +  else if (qh->VORONOI && format == qh_PRINTgeom) {
    +    qh_printbegin(qh, fp, format, facetlist, facets, printall);
    +    qh_printvoronoi(qh, fp, format, facetlist, facets, printall);
    +    qh_printend(qh, fp, format, facetlist, facets, printall);
    +  }else if (qh->VORONOI
    +  && (format == qh_PRINTvertices || format == qh_PRINTinner || format == qh_PRINTouter))
    +    qh_printvdiagram(qh, fp, format, facetlist, facets, printall);
    +  else {
    +    qh_printbegin(qh, fp, format, facetlist, facets, printall);
    +    FORALLfacet_(facetlist)
    +      qh_printafacet(qh, fp, format, facet, printall);
    +    FOREACHfacet_(facets)
    +      qh_printafacet(qh, fp, format, facet, printall);
    +    qh_printend(qh, fp, format, facetlist, facets, printall);
    +  }
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* printfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhyperplaneintersection(qh, fp, facet1, facet2, vertices, color )
    +    print Geomview OFF or 4OFF for the intersection of two hyperplanes in 3-d or 4-d
    +*/
    +void qh_printhyperplaneintersection(qhT *qh, FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]) {
    +  realT costheta, denominator, dist1, dist2, s, t, mindenom, p[4];
    +  vertexT *vertex, **vertexp;
    +  int i, k;
    +  boolT nearzero1, nearzero2;
    +
    +  costheta= qh_getangle(qh, facet1->normal, facet2->normal);
    +  denominator= 1 - costheta * costheta;
    +  i= qh_setsize(qh, vertices);
    +  if (qh->hull_dim == 3)
    +    qh_fprintf(qh, fp, 9195, "VECT 1 %d 1 %d 1 ", i, i);
    +  else if (qh->hull_dim == 4 && qh->DROPdim >= 0)
    +    qh_fprintf(qh, fp, 9196, "OFF 3 1 1 ");
    +  else
    +    qh->printoutvar++;
    +  qh_fprintf(qh, fp, 9197, "# intersect f%d f%d\n", facet1->id, facet2->id);
    +  mindenom= 1 / (10.0 * qh->MAXabs_coord);
    +  FOREACHvertex_(vertices) {
    +    zadd_(Zdistio, 2);
    +    qh_distplane(qh, vertex->point, facet1, &dist1);
    +    qh_distplane(qh, vertex->point, facet2, &dist2);
    +    s= qh_divzero(-dist1 + costheta * dist2, denominator,mindenom,&nearzero1);
    +    t= qh_divzero(-dist2 + costheta * dist1, denominator,mindenom,&nearzero2);
    +    if (nearzero1 || nearzero2)
    +      s= t= 0.0;
    +    for (k=qh->hull_dim; k--; )
    +      p[k]= vertex->point[k] + facet1->normal[k] * s + facet2->normal[k] * t;
    +    if (qh->PRINTdim <= 3) {
    +      qh_projectdim3(qh, p, p);
    +      qh_fprintf(qh, fp, 9198, "%8.4g %8.4g %8.4g # ", p[0], p[1], p[2]);
    +    }else
    +      qh_fprintf(qh, fp, 9199, "%8.4g %8.4g %8.4g %8.4g # ", p[0], p[1], p[2], p[3]);
    +    if (nearzero1+nearzero2)
    +      qh_fprintf(qh, fp, 9200, "p%d(coplanar facets)\n", qh_pointid(qh, vertex->point));
    +    else
    +      qh_fprintf(qh, fp, 9201, "projected p%d\n", qh_pointid(qh, vertex->point));
    +  }
    +  if (qh->hull_dim == 3)
    +    qh_fprintf(qh, fp, 9202, "%8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +  else if (qh->hull_dim == 4 && qh->DROPdim >= 0)
    +    qh_fprintf(qh, fp, 9203, "3 0 1 2 %8.4g %8.4g %8.4g 1.0\n", color[0], color[1], color[2]);
    +} /* printhyperplaneintersection */
    +
    +/*---------------------------------
    +
    +  qh_printline3geom(qh, fp, pointA, pointB, color )
    +    prints a line as a VECT
    +    prints 0's for qh.DROPdim
    +
    +  notes:
    +    if pointA == pointB,
    +      it's a 1 point VECT
    +*/
    +void qh_printline3geom(qhT *qh, FILE *fp, pointT *pointA, pointT *pointB, realT color[3]) {
    +  int k;
    +  realT pA[4], pB[4];
    +
    +  qh_projectdim3(qh, pointA, pA);
    +  qh_projectdim3(qh, pointB, pB);
    +  if ((fabs(pA[0] - pB[0]) > 1e-3) ||
    +      (fabs(pA[1] - pB[1]) > 1e-3) ||
    +      (fabs(pA[2] - pB[2]) > 1e-3)) {
    +    qh_fprintf(qh, fp, 9204, "VECT 1 2 1 2 1\n");
    +    for (k=0; k < 3; k++)
    +       qh_fprintf(qh, fp, 9205, "%8.4g ", pB[k]);
    +    qh_fprintf(qh, fp, 9206, " # p%d\n", qh_pointid(qh, pointB));
    +  }else
    +    qh_fprintf(qh, fp, 9207, "VECT 1 1 1 1 1\n");
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(qh, fp, 9208, "%8.4g ", pA[k]);
    +  qh_fprintf(qh, fp, 9209, " # p%d\n", qh_pointid(qh, pointA));
    +  qh_fprintf(qh, fp, 9210, "%8.4g %8.4g %8.4g 1\n", color[0], color[1], color[2]);
    +}
    +
    +/*---------------------------------
    +
    +  qh_printneighborhood(qh, fp, format, facetA, facetB, printall )
    +    print neighborhood of one or two facets
    +
    +  notes:
    +    calls qh_findgood_all()
    +    bumps qh.visit_id
    +*/
    +void qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall) {
    +  facetT *neighbor, **neighborp, *facet;
    +  setT *facets;
    +
    +  if (format == qh_PRINTnone)
    +    return;
    +  qh_findgood_all(qh, qh->facet_list);
    +  if (facetA == facetB)
    +    facetB= NULL;
    +  facets= qh_settemp(qh, 2*(qh_setsize(qh, facetA->neighbors)+1));
    +  qh->visit_id++;
    +  for (facet= facetA; facet; facet= ((facet == facetA) ? facetB : NULL)) {
    +    if (facet->visitid != qh->visit_id) {
    +      facet->visitid= qh->visit_id;
    +      qh_setappend(qh, &facets, facet);
    +    }
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid == qh->visit_id)
    +        continue;
    +      neighbor->visitid= qh->visit_id;
    +      if (printall || !qh_skipfacet(qh, neighbor))
    +        qh_setappend(qh, &facets, neighbor);
    +    }
    +  }
    +  qh_printfacets(qh, fp, format, NULL, facets, printall);
    +  qh_settempfree(qh, &facets);
    +} /* printneighborhood */
    +
    +/*---------------------------------
    +
    +  qh_printpoint(qh, fp, string, point )
    +  qh_printpointid(qh, fp, string, dim, point, id )
    +    prints the coordinates of a point
    +
    +  returns:
    +    if string is defined
    +      prints 'string p%d'.  Skips p%d if id=qh_IDunknown(-1) or qh_IDnone(-3)
    +
    +  notes:
    +    nop if point is NULL
    +    Same as QhullPoint's printPoint
    +*/
    +void qh_printpoint(qhT *qh, FILE *fp, const char *string, pointT *point) {
    +  int id= qh_pointid(qh, point);
    +
    +  qh_printpointid(qh, fp, string, qh->hull_dim, point, id);
    +} /* printpoint */
    +
    +void qh_printpointid(qhT *qh, FILE *fp, const char *string, int dim, pointT *point, int id) {
    +  int k;
    +  realT r; /*bug fix*/
    +
    +  if (!point)
    +    return;
    +  if (string) {
    +    qh_fprintf(qh, fp, 9211, "%s", string);
    +    if (id != qh_IDunknown && id != qh_IDnone)
    +      qh_fprintf(qh, fp, 9212, " p%d: ", id);
    +  }
    +  for (k=dim; k--; ) {
    +    r= *point++;
    +    if (string)
    +      qh_fprintf(qh, fp, 9213, " %8.4g", r);
    +    else
    +      qh_fprintf(qh, fp, 9214, qh_REAL_1, r);
    +  }
    +  qh_fprintf(qh, fp, 9215, "\n");
    +} /* printpointid */
    +
    +/*---------------------------------
    +
    +  qh_printpoint3(qh, fp, point )
    +    prints 2-d, 3-d, or 4-d point as Geomview 3-d coordinates
    +*/
    +void qh_printpoint3(qhT *qh, FILE *fp, pointT *point) {
    +  int k;
    +  realT p[4];
    +
    +  qh_projectdim3(qh, point, p);
    +  for (k=0; k < 3; k++)
    +    qh_fprintf(qh, fp, 9216, "%8.4g ", p[k]);
    +  qh_fprintf(qh, fp, 9217, " # p%d\n", qh_pointid(qh, point));
    +} /* printpoint3 */
    +
    +/*----------------------------------------
    +-printpoints- print pointids for a set of points starting at index
    +   see geom_r.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printpoints_out(qh, fp, facetlist, facets, printall )
    +    prints vertices, coplanar/inside points, for facets by their point coordinates
    +    allows qh.CDDoutput
    +
    +  notes:
    +    same format as qhull input
    +    if no coplanar/interior points,
    +      same order as qh_printextremes
    +*/
    +void qh_printpoints_out(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall) {
    +  int allpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  int numpoints=0, point_i, point_n;
    +  setT *vertices, *points;
    +  facetT *facet, **facetp;
    +  pointT *point, **pointp;
    +  vertexT *vertex, **vertexp;
    +  int id;
    +
    +  points= qh_settemp(qh, allpoints);
    +  qh_setzero(qh, points, 0, allpoints);
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  FOREACHvertex_(vertices) {
    +    id= qh_pointid(qh, vertex->point);
    +    if (id >= 0)
    +      SETelem_(points, id)= vertex->point;
    +  }
    +  if (qh->KEEPinside || qh->KEEPcoplanar || qh->KEEPnearinside) {
    +    FORALLfacet_(facetlist) {
    +      if (!printall && qh_skipfacet(qh, facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(qh, point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +    FOREACHfacet_(facets) {
    +      if (!printall && qh_skipfacet(qh, facet))
    +        continue;
    +      FOREACHpoint_(facet->coplanarset) {
    +        id= qh_pointid(qh, point);
    +        if (id >= 0)
    +          SETelem_(points, id)= point;
    +      }
    +    }
    +  }
    +  qh_settempfree(qh, &vertices);
    +  FOREACHpoint_i_(qh, points) {
    +    if (point)
    +      numpoints++;
    +  }
    +  if (qh->CDDoutput)
    +    qh_fprintf(qh, fp, 9218, "%s | %s\nbegin\n%d %d real\n", qh->rbox_command,
    +             qh->qhull_command, numpoints, qh->hull_dim + 1);
    +  else
    +    qh_fprintf(qh, fp, 9219, "%d\n%d\n", qh->hull_dim, numpoints);
    +  FOREACHpoint_i_(qh, points) {
    +    if (point) {
    +      if (qh->CDDoutput)
    +        qh_fprintf(qh, fp, 9220, "1 ");
    +      qh_printpoint(qh, fp, NULL, point);
    +    }
    +  }
    +  if (qh->CDDoutput)
    +    qh_fprintf(qh, fp, 9221, "end\n");
    +  qh_settempfree(qh, &points);
    +} /* printpoints_out */
    +
    +
    +/*---------------------------------
    +
    +  qh_printpointvect(qh, fp, point, normal, center, radius, color )
    +    prints a 2-d, 3-d, or 4-d point as 3-d VECT's relative to normal or to center point
    +*/
    +void qh_printpointvect(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]) {
    +  realT diff[4], pointA[4];
    +  int k;
    +
    +  for (k=qh->hull_dim; k--; ) {
    +    if (center)
    +      diff[k]= point[k]-center[k];
    +    else if (normal)
    +      diff[k]= normal[k];
    +    else
    +      diff[k]= 0;
    +  }
    +  if (center)
    +    qh_normalize2(qh, diff, qh->hull_dim, True, NULL, NULL);
    +  for (k=qh->hull_dim; k--; )
    +    pointA[k]= point[k]+diff[k] * radius;
    +  qh_printline3geom(qh, fp, point, pointA, color);
    +} /* printpointvect */
    +
    +/*---------------------------------
    +
    +  qh_printpointvect2(qh, fp, point, normal, center, radius )
    +    prints a 2-d, 3-d, or 4-d point as 2 3-d VECT's for an imprecise point
    +*/
    +void qh_printpointvect2(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius) {
    +  realT red[3]={1, 0, 0}, yellow[3]={1, 1, 0};
    +
    +  qh_printpointvect(qh, fp, point, normal, center, radius, red);
    +  qh_printpointvect(qh, fp, point, normal, center, -radius, yellow);
    +} /* printpointvect2 */
    +
    +/*---------------------------------
    +
    +  qh_printridge(qh, fp, ridge )
    +    prints the information in a ridge
    +
    +  notes:
    +    for qh_printfacetridges()
    +    same as operator<< [QhullRidge.cpp]
    +*/
    +void qh_printridge(qhT *qh, FILE *fp, ridgeT *ridge) {
    +
    +  qh_fprintf(qh, fp, 9222, "     - r%d", ridge->id);
    +  if (ridge->tested)
    +    qh_fprintf(qh, fp, 9223, " tested");
    +  if (ridge->nonconvex)
    +    qh_fprintf(qh, fp, 9224, " nonconvex");
    +  qh_fprintf(qh, fp, 9225, "\n");
    +  qh_printvertices(qh, fp, "           vertices:", ridge->vertices);
    +  if (ridge->top && ridge->bottom)
    +    qh_fprintf(qh, fp, 9226, "           between f%d and f%d\n",
    +            ridge->top->id, ridge->bottom->id);
    +} /* printridge */
    +
    +/*---------------------------------
    +
    +  qh_printspheres(qh, fp, vertices, radius )
    +    prints 3-d vertices as OFF spheres
    +
    +  notes:
    +    inflated octahedron from Stuart Levy earth/mksphere2
    +*/
    +void qh_printspheres(qhT *qh, FILE *fp, setT *vertices, realT radius) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh->printoutnum++;
    +  qh_fprintf(qh, fp, 9227, "{appearance {-edge -normal normscale 0} {\n\
    +INST geom {define vsphere OFF\n\
    +18 32 48\n\
    +\n\
    +0 0 1\n\
    +1 0 0\n\
    +0 1 0\n\
    +-1 0 0\n\
    +0 -1 0\n\
    +0 0 -1\n\
    +0.707107 0 0.707107\n\
    +0 -0.707107 0.707107\n\
    +0.707107 -0.707107 0\n\
    +-0.707107 0 0.707107\n\
    +-0.707107 -0.707107 0\n\
    +0 0.707107 0.707107\n\
    +-0.707107 0.707107 0\n\
    +0.707107 0.707107 0\n\
    +0.707107 0 -0.707107\n\
    +0 0.707107 -0.707107\n\
    +-0.707107 0 -0.707107\n\
    +0 -0.707107 -0.707107\n\
    +\n\
    +3 0 6 11\n\
    +3 0 7 6 \n\
    +3 0 9 7 \n\
    +3 0 11 9\n\
    +3 1 6 8 \n\
    +3 1 8 14\n\
    +3 1 13 6\n\
    +3 1 14 13\n\
    +3 2 11 13\n\
    +3 2 12 11\n\
    +3 2 13 15\n\
    +3 2 15 12\n\
    +3 3 9 12\n\
    +3 3 10 9\n\
    +3 3 12 16\n\
    +3 3 16 10\n\
    +3 4 7 10\n\
    +3 4 8 7\n\
    +3 4 10 17\n\
    +3 4 17 8\n\
    +3 5 14 17\n\
    +3 5 15 14\n\
    +3 5 16 15\n\
    +3 5 17 16\n\
    +3 6 13 11\n\
    +3 7 8 6\n\
    +3 9 10 7\n\
    +3 11 12 9\n\
    +3 14 8 17\n\
    +3 15 13 14\n\
    +3 16 12 15\n\
    +3 17 10 16\n} transforms { TLIST\n");
    +  FOREACHvertex_(vertices) {
    +    qh_fprintf(qh, fp, 9228, "%8.4g 0 0 0 # v%d\n 0 %8.4g 0 0\n0 0 %8.4g 0\n",
    +      radius, vertex->id, radius, radius);
    +    qh_printpoint3(qh, fp, vertex->point);
    +    qh_fprintf(qh, fp, 9229, "1\n");
    +  }
    +  qh_fprintf(qh, fp, 9230, "}}}\n");
    +} /* printspheres */
    +
    +
    +/*----------------------------------------------
    +-printsummary-
    +                see libqhull_r.c
    +*/
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram(qh, fp, format, facetlist, facets, printall )
    +    print voronoi diagram
    +      # of pairs of input sites
    +      #indices site1 site2 vertex1 ...
    +
    +    sites indexed by input point id
    +      point 0 is the first input point
    +    vertices indexed by 'o' and 'p' order
    +      vertex 0 is the 'vertex-at-infinity'
    +      vertex 1 is the first Voronoi vertex
    +
    +  see:
    +    qh_printvoronoi()
    +    qh_eachvoronoi_all()
    +
    +  notes:
    +    if all facets are upperdelaunay,
    +      prints upper hull (furthest-site Voronoi diagram)
    +*/
    +void qh_printvdiagram(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  setT *vertices;
    +  int totcount, numcenters;
    +  boolT isLower;
    +  qh_RIDGE innerouter= qh_RIDGEall;
    +  printvridgeT printvridge= NULL;
    +
    +  if (format == qh_PRINTvertices) {
    +    innerouter= qh_RIDGEall;
    +    printvridge= qh_printvridge;
    +  }else if (format == qh_PRINTinner) {
    +    innerouter= qh_RIDGEinner;
    +    printvridge= qh_printvnorm;
    +  }else if (format == qh_PRINTouter) {
    +    innerouter= qh_RIDGEouter;
    +    printvridge= qh_printvnorm;
    +  }else {
    +    qh_fprintf(qh, qh->ferr, 6219, "Qhull internal error (qh_printvdiagram): unknown print format %d.\n", format);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  vertices= qh_markvoronoi(qh, facetlist, facets, printall, &isLower, &numcenters);
    +  totcount= qh_printvdiagram2(qh, NULL, NULL, vertices, innerouter, False);
    +  qh_fprintf(qh, fp, 9231, "%d\n", totcount);
    +  totcount= qh_printvdiagram2(qh, fp, printvridge, vertices, innerouter, True /* inorder*/);
    +  qh_settempfree(qh, &vertices);
    +#if 0  /* for testing qh_eachvoronoi_all */
    +  qh_fprintf(qh, fp, 9232, "\n");
    +  totcount= qh_eachvoronoi_all(qh, fp, printvridge, qh->UPPERdelaunay, innerouter, True /* inorder*/);
    +  qh_fprintf(qh, fp, 9233, "%d\n", totcount);
    +#endif
    +} /* printvdiagram */
    +
    +/*---------------------------------
    +
    +  qh_printvdiagram2(qh, fp, printvridge, vertices, innerouter, inorder )
    +    visit all pairs of input sites (vertices) for selected Voronoi vertices
    +    vertices may include NULLs
    +
    +  innerouter:
    +    qh_RIDGEall   print inner ridges(bounded) and outer ridges(unbounded)
    +    qh_RIDGEinner print only inner ridges
    +    qh_RIDGEouter print only outer ridges
    +
    +  inorder:
    +    print 3-d Voronoi vertices in order
    +
    +  assumes:
    +    qh_markvoronoi marked facet->visitid for Voronoi vertices
    +    all facet->seen= False
    +    all facet->seen2= True
    +
    +  returns:
    +    total number of Voronoi ridges
    +    if printvridge,
    +      calls printvridge( fp, vertex, vertexA, centers) for each ridge
    +      [see qh_eachvoronoi()]
    +
    +  see:
    +    qh_eachvoronoi_all()
    +*/
    +int qh_printvdiagram2(qhT *qh, FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder) {
    +  int totcount= 0;
    +  int vertex_i, vertex_n;
    +  vertexT *vertex;
    +
    +  FORALLvertices
    +    vertex->seen= False;
    +  FOREACHvertex_i_(qh, vertices) {
    +    if (vertex) {
    +      if (qh->GOODvertex > 0 && qh_pointid(qh, vertex->point)+1 != qh->GOODvertex)
    +        continue;
    +      totcount += qh_eachvoronoi(qh, fp, printvridge, vertex, !qh_ALL, innerouter, inorder);
    +    }
    +  }
    +  return totcount;
    +} /* printvdiagram2 */
    +
    +/*---------------------------------
    +
    +  qh_printvertex(qh, fp, vertex )
    +    prints the information in a vertex
    +    Duplicated as operator<< [QhullVertex.cpp]
    +*/
    +void qh_printvertex(qhT *qh, FILE *fp, vertexT *vertex) {
    +  pointT *point;
    +  int k, count= 0;
    +  facetT *neighbor, **neighborp;
    +  realT r; /*bug fix*/
    +
    +  if (!vertex) {
    +    qh_fprintf(qh, fp, 9234, "  NULLvertex\n");
    +    return;
    +  }
    +  qh_fprintf(qh, fp, 9235, "- p%d(v%d):", qh_pointid(qh, vertex->point), vertex->id);
    +  point= vertex->point;
    +  if (point) {
    +    for (k=qh->hull_dim; k--; ) {
    +      r= *point++;
    +      qh_fprintf(qh, fp, 9236, " %5.2g", r);
    +    }
    +  }
    +  if (vertex->deleted)
    +    qh_fprintf(qh, fp, 9237, " deleted");
    +  if (vertex->delridge)
    +    qh_fprintf(qh, fp, 9238, " ridgedeleted");
    +  qh_fprintf(qh, fp, 9239, "\n");
    +  if (vertex->neighbors) {
    +    qh_fprintf(qh, fp, 9240, "  neighbors:");
    +    FOREACHneighbor_(vertex) {
    +      if (++count % 100 == 0)
    +        qh_fprintf(qh, fp, 9241, "\n     ");
    +      qh_fprintf(qh, fp, 9242, " f%d", neighbor->id);
    +    }
    +    qh_fprintf(qh, fp, 9243, "\n");
    +  }
    +} /* printvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertexlist(qh, fp, string, facetlist, facets, printall )
    +    prints vertices used by a facetlist or facet set
    +    tests qh_skipfacet() if !printall
    +*/
    +void qh_printvertexlist(qhT *qh, FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall) {
    +  vertexT *vertex, **vertexp;
    +  setT *vertices;
    +
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  qh_fprintf(qh, fp, 9244, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_printvertex(qh, fp, vertex);
    +  qh_settempfree(qh, &vertices);
    +} /* printvertexlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printvertices(qh, fp, string, vertices )
    +    prints vertices in a set
    +    duplicated as printVertexSet [QhullVertex.cpp]
    +*/
    +void qh_printvertices(qhT *qh, FILE *fp, const char* string, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  qh_fprintf(qh, fp, 9245, "%s", string);
    +  FOREACHvertex_(vertices)
    +    qh_fprintf(qh, fp, 9246, " p%d(v%d)", qh_pointid(qh, vertex->point), vertex->id);
    +  qh_fprintf(qh, fp, 9247, "\n");
    +} /* printvertices */
    +
    +/*---------------------------------
    +
    +  qh_printvneighbors(qh, fp, facetlist, facets, printall )
    +    print vertex neighbors of vertices in facetlist and facets ('FN')
    +
    +  notes:
    +    qh_countfacets clears facet->visitid for non-printed facets
    +
    +  design:
    +    collect facet count and related statistics
    +    if necessary, build neighbor sets for each vertex
    +    collect vertices in facetlist and facets
    +    build a point array for point->vertex and point->coplanar facet
    +    for each point
    +      list vertex neighbors or coplanar facet
    +*/
    +void qh_printvneighbors(qhT *qh, FILE *fp, facetT* facetlist, setT *facets, boolT printall) {
    +  int numfacets, numsimplicial, numridges, totneighbors, numneighbors, numcoplanars, numtricoplanars;
    +  setT *vertices, *vertex_points, *coplanar_points;
    +  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  vertexT *vertex, **vertexp;
    +  int vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  pointT *point, **pointp;
    +
    +  qh_countfacets(qh, facetlist, facets, printall, &numfacets, &numsimplicial,
    +      &totneighbors, &numridges, &numcoplanars, &numtricoplanars);  /* sets facet->visitid */
    +  qh_fprintf(qh, fp, 9248, "%d\n", numpoints);
    +  qh_vertexneighbors(qh);
    +  vertices= qh_facetvertices(qh, facetlist, facets, printall);
    +  vertex_points= qh_settemp(qh, numpoints);
    +  coplanar_points= qh_settemp(qh, numpoints);
    +  qh_setzero(qh, vertex_points, 0, numpoints);
    +  qh_setzero(qh, coplanar_points, 0, numpoints);
    +  FOREACHvertex_(vertices)
    +    qh_point_add(qh, vertex_points, vertex->point, vertex);
    +  FORALLfacet_(facetlist) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(qh, coplanar_points, point, facet);
    +  }
    +  FOREACHfacet_(facets) {
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(qh, coplanar_points, point, facet);
    +  }
    +  FOREACHvertex_i_(qh, vertex_points) {
    +    if (vertex) {
    +      numneighbors= qh_setsize(qh, vertex->neighbors);
    +      qh_fprintf(qh, fp, 9249, "%d", numneighbors);
    +      if (qh->hull_dim == 3)
    +        qh_order_vertexneighbors(qh, vertex);
    +      else if (qh->hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors,
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex)
    +        qh_fprintf(qh, fp, 9250, " %d",
    +                 neighbor->visitid ? neighbor->visitid - 1 : 0 - neighbor->id);
    +      qh_fprintf(qh, fp, 9251, "\n");
    +    }else if ((facet= SETelemt_(coplanar_points, vertex_i, facetT)))
    +      qh_fprintf(qh, fp, 9252, "1 %d\n",
    +                  facet->visitid ? facet->visitid - 1 : 0 - facet->id);
    +    else
    +      qh_fprintf(qh, fp, 9253, "0\n");
    +  }
    +  qh_settempfree(qh, &coplanar_points);
    +  qh_settempfree(qh, &vertex_points);
    +  qh_settempfree(qh, &vertices);
    +} /* printvneighbors */
    +
    +/*---------------------------------
    +
    +  qh_printvoronoi(qh, fp, format, facetlist, facets, printall )
    +    print voronoi diagram in 'o' or 'G' format
    +    for 'o' format
    +      prints voronoi centers for each facet and for infinity
    +      for each vertex, lists ids of printed facets or infinity
    +      assumes facetlist and facets are disjoint
    +    for 'G' format
    +      prints an OFF object
    +      adds a 0 coordinate to center
    +      prints infinity but does not list in vertices
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    if 'o',
    +      prints a line for each point except "at-infinity"
    +    if all facets are upperdelaunay,
    +      reverses lower and upper hull
    +*/
    +void qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall) {
    +  int k, numcenters, numvertices= 0, numneighbors, numinf, vid=1, vertex_i, vertex_n;
    +  facetT *facet, **facetp, *neighbor, **neighborp;
    +  setT *vertices;
    +  vertexT *vertex;
    +  boolT isLower;
    +  unsigned int numfacets= (unsigned int) qh->num_facets;
    +
    +  vertices= qh_markvoronoi(qh, facetlist, facets, printall, &isLower, &numcenters);
    +  FOREACHvertex_i_(qh, vertices) {
    +    if (vertex) {
    +      numvertices++;
    +      numneighbors = numinf = 0;
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +      if (numinf && !numneighbors) {
    +        SETelem_(vertices, vertex_i)= NULL;
    +        numvertices--;
    +      }
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(qh, fp, 9254, "{appearance {+edge -face} OFF %d %d 1 # Voronoi centers and cells\n",
    +                numcenters, numvertices);
    +  else
    +    qh_fprintf(qh, fp, 9255, "%d\n%d %d 1\n", qh->hull_dim-1, numcenters, qh_setsize(qh, vertices));
    +  if (format == qh_PRINTgeom) {
    +    for (k=qh->hull_dim-1; k--; )
    +      qh_fprintf(qh, fp, 9256, qh_REAL_1, 0.0);
    +    qh_fprintf(qh, fp, 9257, " 0 # infinity not used\n");
    +  }else {
    +    for (k=qh->hull_dim-1; k--; )
    +      qh_fprintf(qh, fp, 9258, qh_REAL_1, qh_INFINITE);
    +    qh_fprintf(qh, fp, 9259, "\n");
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(qh, fp, 9260, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(qh, fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHfacet_(facets) {
    +    if (facet->visitid && facet->visitid < numfacets) {
    +      if (format == qh_PRINTgeom)
    +        qh_fprintf(qh, fp, 9261, "# %d f%d\n", vid++, facet->id);
    +      qh_printcenter(qh, fp, format, NULL, facet);
    +    }
    +  }
    +  FOREACHvertex_i_(qh, vertices) {
    +    numneighbors= 0;
    +    numinf=0;
    +    if (vertex) {
    +      if (qh->hull_dim == 3)
    +        qh_order_vertexneighbors(qh, vertex);
    +      else if (qh->hull_dim >= 4)
    +        qsort(SETaddr_(vertex->neighbors, facetT),
    +             (size_t)qh_setsize(qh, vertex->neighbors),
    +             sizeof(facetT *), qh_compare_facetvisit);
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visitid == 0)
    +          numinf= 1;
    +        else if (neighbor->visitid < numfacets)
    +          numneighbors++;
    +      }
    +    }
    +    if (format == qh_PRINTgeom) {
    +      if (vertex) {
    +        qh_fprintf(qh, fp, 9262, "%d", numneighbors);
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid && neighbor->visitid < numfacets)
    +            qh_fprintf(qh, fp, 9263, " %d", neighbor->visitid);
    +        }
    +        qh_fprintf(qh, fp, 9264, " # p%d(v%d)\n", vertex_i, vertex->id);
    +      }else
    +        qh_fprintf(qh, fp, 9265, " # p%d is coplanar or isolated\n", vertex_i);
    +    }else {
    +      if (numinf)
    +        numneighbors++;
    +      qh_fprintf(qh, fp, 9266, "%d", numneighbors);
    +      if (vertex) {
    +        FOREACHneighbor_(vertex) {
    +          if (neighbor->visitid == 0) {
    +            if (numinf) {
    +              numinf= 0;
    +              qh_fprintf(qh, fp, 9267, " %d", neighbor->visitid);
    +            }
    +          }else if (neighbor->visitid < numfacets)
    +            qh_fprintf(qh, fp, 9268, " %d", neighbor->visitid);
    +        }
    +      }
    +      qh_fprintf(qh, fp, 9269, "\n");
    +    }
    +  }
    +  if (format == qh_PRINTgeom)
    +    qh_fprintf(qh, fp, 9270, "}\n");
    +  qh_settempfree(qh, &vertices);
    +} /* printvoronoi */
    +
    +/*---------------------------------
    +
    +  qh_printvnorm(qh, fp, vertex, vertexA, centers, unbounded )
    +    print one separating plane of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  assumes:
    +    qh_ASvoronoi and qh_vertexneighbors() already set
    +
    +  note:
    +    parameter unbounded is UNUSED by this callback
    +
    +  see:
    +    qh_printvdiagram()
    +    qh_eachvoronoi()
    +*/
    +void qh_printvnorm(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  pointT *normal;
    +  realT offset;
    +  int k;
    +  QHULL_UNUSED(unbounded);
    +
    +  normal= qh_detvnorm(qh, vertex, vertexA, centers, &offset);
    +  qh_fprintf(qh, fp, 9271, "%d %d %d ",
    +      2+qh->hull_dim, qh_pointid(qh, vertex->point), qh_pointid(qh, vertexA->point));
    +  for (k=0; k< qh->hull_dim-1; k++)
    +    qh_fprintf(qh, fp, 9272, qh_REAL_1, normal[k]);
    +  qh_fprintf(qh, fp, 9273, qh_REAL_1, offset);
    +  qh_fprintf(qh, fp, 9274, "\n");
    +} /* printvnorm */
    +
    +/*---------------------------------
    +
    +  qh_printvridge(qh, fp, vertex, vertexA, centers, unbounded )
    +    print one ridge of the Voronoi diagram for a pair of input sites
    +    unbounded==True if centers includes vertex-at-infinity
    +
    +  see:
    +    qh_printvdiagram()
    +
    +  notes:
    +    the user may use a different function
    +    parameter unbounded is UNUSED
    +*/
    +void qh_printvridge(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded) {
    +  facetT *facet, **facetp;
    +  QHULL_UNUSED(unbounded);
    +
    +  qh_fprintf(qh, fp, 9275, "%d %d %d", qh_setsize(qh, centers)+2,
    +       qh_pointid(qh, vertex->point), qh_pointid(qh, vertexA->point));
    +  FOREACHfacet_(centers)
    +    qh_fprintf(qh, fp, 9276, " %d", facet->visitid);
    +  qh_fprintf(qh, fp, 9277, "\n");
    +} /* printvridge */
    +
    +/*---------------------------------
    +
    +  qh_projectdim3(qh, source, destination )
    +    project 2-d 3-d or 4-d point to a 3-d point
    +    uses qh.DROPdim and qh.hull_dim
    +    source and destination may be the same
    +
    +  notes:
    +    allocate 4 elements to destination just in case
    +*/
    +void qh_projectdim3(qhT *qh, pointT *source, pointT *destination) {
    +  int i,k;
    +
    +  for (k=0, i=0; k < qh->hull_dim; k++) {
    +    if (qh->hull_dim == 4) {
    +      if (k != qh->DROPdim)
    +        destination[i++]= source[k];
    +    }else if (k == qh->DROPdim)
    +      destination[i++]= 0;
    +    else
    +      destination[i++]= source[k];
    +  }
    +  while (i < 3)
    +    destination[i++]= 0.0;
    +} /* projectdim3 */
    +
    +/*---------------------------------
    +
    +  qh_readfeasible(qh, dim, curline )
    +    read feasible point from current line and qh.fin
    +
    +  returns:
    +    number of lines read from qh.fin
    +    sets qh.feasible_point with malloc'd coordinates
    +
    +  notes:
    +    checks for qh.HALFspace
    +    assumes dim > 1
    +
    +  see:
    +    qh_setfeasible
    +*/
    +int qh_readfeasible(qhT *qh, int dim, const char *curline) {
    +  boolT isfirst= True;
    +  int linecount= 0, tokcount= 0;
    +  const char *s;
    +  char *t, firstline[qh_MAXfirst+1];
    +  coordT *coords, value;
    +
    +  if (!qh->HALFspace) {
    +    qh_fprintf(qh, qh->ferr, 6070, "qhull input error: feasible point(dim 1 coords) is only valid for halfspace intersection\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (qh->feasible_string)
    +    qh_fprintf(qh, qh->ferr, 7057, "qhull input warning: feasible point(dim 1 coords) overrides 'Hn,n,n' feasible point for halfspace intersection\n");
    +  if (!(qh->feasible_point= (coordT*)qh_malloc(dim* sizeof(coordT)))) {
    +    qh_fprintf(qh, qh->ferr, 6071, "qhull error: insufficient memory for feasible point\n");
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh->feasible_point;
    +  while ((s= (isfirst ?  curline : fgets(firstline, qh_MAXfirst, qh->fin)))) {
    +    if (isfirst)
    +      isfirst= False;
    +    else
    +      linecount++;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t)
    +        break;
    +      s= t;
    +      *(coords++)= value;
    +      if (++tokcount == dim) {
    +        while (isspace(*s))
    +          s++;
    +        qh_strtod(s, &t);
    +        if (s != t) {
    +          qh_fprintf(qh, qh->ferr, 6072, "qhull input error: coordinates for feasible point do not finish out the line: %s\n",
    +               s);
    +          qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +        }
    +        return linecount;
    +      }
    +    }
    +  }
    +  qh_fprintf(qh, qh->ferr, 6073, "qhull input error: only %d coordinates.  Could not read %d-d feasible point.\n",
    +           tokcount, dim);
    +  qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  return 0;
    +} /* readfeasible */
    +
    +/*---------------------------------
    +
    +  qh_readpoints(qh, numpoints, dimension, ismalloc )
    +    read points from qh.fin into qh.first_point, qh.num_points
    +    qh.fin is lines of coordinates, one per vertex, first line number of points
    +    if 'rbox D4',
    +      gives message
    +    if qh.ATinfinity,
    +      adds point-at-infinity for Delaunay triangulations
    +
    +  returns:
    +    number of points, array of point coordinates, dimension, ismalloc True
    +    if qh.DELAUNAY & !qh.PROJECTinput, projects points to paraboloid
    +        and clears qh.PROJECTdelaunay
    +    if qh.HALFspace, reads optional feasible point, reads halfspaces,
    +        converts to dual.
    +
    +  for feasible point in "cdd format" in 3-d:
    +    3 1
    +    coordinates
    +    comments
    +    begin
    +    n 4 real/integer
    +    ...
    +    end
    +
    +  notes:
    +    dimension will change in qh_initqhull_globals if qh.PROJECTinput
    +    uses malloc() since qh_mem not initialized
    +    FIXUP QH11012: qh_readpoints needs rewriting, too long
    +*/
    +coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc) {
    +  coordT *points, *coords, *infinity= NULL;
    +  realT paraboloid, maxboloid= -REALmax, value;
    +  realT *coordp= NULL, *offsetp= NULL, *normalp= NULL;
    +  char *s= 0, *t, firstline[qh_MAXfirst+1];
    +  int diminput=0, numinput=0, dimfeasible= 0, newnum, k, tempi;
    +  int firsttext=0, firstshort=0, firstlong=0, firstpoint=0;
    +  int tokcount= 0, linecount=0, maxcount, coordcount=0;
    +  boolT islong, isfirst= True, wasbegin= False;
    +  boolT isdelaunay= qh->DELAUNAY && !qh->PROJECTinput;
    +
    +  if (qh->CDDinput) {
    +    while ((s= fgets(firstline, qh_MAXfirst, qh->fin))) {
    +      linecount++;
    +      if (qh->HALFspace && linecount == 1 && isdigit(*s)) {
    +        dimfeasible= qh_strtol(s, &s);
    +        while (isspace(*s))
    +          s++;
    +        if (qh_strtol(s, &s) == 1)
    +          linecount += qh_readfeasible(qh, dimfeasible, s);
    +        else
    +          dimfeasible= 0;
    +      }else if (!memcmp(firstline, "begin", (size_t)5) || !memcmp(firstline, "BEGIN", (size_t)5))
    +        break;
    +      else if (!*qh->rbox_command)
    +        strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
    +    }
    +    if (!s) {
    +      qh_fprintf(qh, qh->ferr, 6074, "qhull input error: missing \"begin\" for cdd-formated input\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +  }
    +  while (!numinput && (s= fgets(firstline, qh_MAXfirst, qh->fin))) {
    +    linecount++;
    +    if (!memcmp(s, "begin", (size_t)5) || !memcmp(s, "BEGIN", (size_t)5))
    +      wasbegin= True;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      if (!*s)
    +        break;
    +      if (!isdigit(*s)) {
    +        if (!*qh->rbox_command) {
    +          strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
    +          firsttext= linecount;
    +        }
    +        break;
    +      }
    +      if (!diminput)
    +        diminput= qh_strtol(s, &s);
    +      else {
    +        numinput= qh_strtol(s, &s);
    +        if (numinput == 1 && diminput >= 2 && qh->HALFspace && !qh->CDDinput) {
    +          linecount += qh_readfeasible(qh, diminput, s); /* checks if ok */
    +          dimfeasible= diminput;
    +          diminput= numinput= 0;
    +        }else
    +          break;
    +      }
    +    }
    +  }
    +  if (!s) {
    +    qh_fprintf(qh, qh->ferr, 6075, "qhull input error: short input file.  Did not find dimension and number of points\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (diminput > numinput) {
    +    tempi= diminput;    /* exchange dim and n, e.g., for cdd input format */
    +    diminput= numinput;
    +    numinput= tempi;
    +  }
    +  if (diminput < 2) {
    +    qh_fprintf(qh, qh->ferr, 6220,"qhull input error: dimension %d(first number) should be at least 2\n",
    +            diminput);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (isdelaunay) {
    +    qh->PROJECTdelaunay= False;
    +    if (qh->CDDinput)
    +      *dimension= diminput;
    +    else
    +      *dimension= diminput+1;
    +    *numpoints= numinput;
    +    if (qh->ATinfinity)
    +      (*numpoints)++;
    +  }else if (qh->HALFspace) {
    +    *dimension= diminput - 1;
    +    *numpoints= numinput;
    +    if (diminput < 3) {
    +      qh_fprintf(qh, qh->ferr, 6221,"qhull input error: dimension %d(first number, includes offset) should be at least 3 for halfspaces\n",
    +            diminput);
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (dimfeasible) {
    +      if (dimfeasible != *dimension) {
    +        qh_fprintf(qh, qh->ferr, 6222,"qhull input error: dimension %d of feasible point is not one less than dimension %d for halfspaces\n",
    +          dimfeasible, diminput);
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +    }else
    +      qh_setfeasible(qh, *dimension);
    +  }else {
    +    if (qh->CDDinput)
    +      *dimension= diminput-1;
    +    else
    +      *dimension= diminput;
    +    *numpoints= numinput;
    +  }
    +  qh->normal_size= *dimension * sizeof(coordT); /* for tracing with qh_printpoint */
    +  if (qh->HALFspace) {
    +    qh->half_space= coordp= (coordT*)qh_malloc(qh->normal_size + sizeof(coordT));
    +    if (qh->CDDinput) {
    +      offsetp= qh->half_space;
    +      normalp= offsetp + 1;
    +    }else {
    +      normalp= qh->half_space;
    +      offsetp= normalp + *dimension;
    +    }
    +  }
    +  qh->maxline= diminput * (qh_REALdigits + 5);
    +  maximize_(qh->maxline, 500);
    +  qh->line= (char*)qh_malloc((qh->maxline+1) * sizeof(char));
    +  *ismalloc= True;  /* use malloc since memory not setup */
    +  coords= points= qh->temp_malloc=  /* numinput and diminput >=2 by QH6220 */
    +        (coordT*)qh_malloc((*numpoints)*(*dimension)*sizeof(coordT));
    +  if (!coords || !qh->line || (qh->HALFspace && !qh->half_space)) {
    +    qh_fprintf(qh, qh->ferr, 6076, "qhull error: insufficient memory to read %d points\n",
    +            numinput);
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  if (isdelaunay && qh->ATinfinity) {
    +    infinity= points + numinput * (*dimension);
    +    for (k= (*dimension) - 1; k--; )
    +      infinity[k]= 0.0;
    +  }
    +  maxcount= numinput * diminput;
    +  paraboloid= 0.0;
    +  while ((s= (isfirst ?  s : fgets(qh->line, qh->maxline, qh->fin)))) {
    +    if (!isfirst) {
    +      linecount++;
    +      if (*s == 'e' || *s == 'E') {
    +        if (!memcmp(s, "end", (size_t)3) || !memcmp(s, "END", (size_t)3)) {
    +          if (qh->CDDinput )
    +            break;
    +          else if (wasbegin)
    +            qh_fprintf(qh, qh->ferr, 7058, "qhull input warning: the input appears to be in cdd format.  If so, use 'Fd'\n");
    +        }
    +      }
    +    }
    +    islong= False;
    +    while (*s) {
    +      while (isspace(*s))
    +        s++;
    +      value= qh_strtod(s, &t);
    +      if (s == t) {
    +        if (!*qh->rbox_command)
    +         strncat(qh->rbox_command, s, sizeof(qh->rbox_command)-1);
    +        if (*s && !firsttext)
    +          firsttext= linecount;
    +        if (!islong && !firstshort && coordcount)
    +          firstshort= linecount;
    +        break;
    +      }
    +      if (!firstpoint)
    +        firstpoint= linecount;
    +      s= t;
    +      if (++tokcount > maxcount)
    +        continue;
    +      if (qh->HALFspace) {
    +        if (qh->CDDinput)
    +          *(coordp++)= -value; /* both coefficients and offset */
    +        else
    +          *(coordp++)= value;
    +      }else {
    +        *(coords++)= value;
    +        if (qh->CDDinput && !coordcount) {
    +          if (value != 1.0) {
    +            qh_fprintf(qh, qh->ferr, 6077, "qhull input error: for cdd format, point at line %d does not start with '1'\n",
    +                   linecount);
    +            qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +          }
    +          coords--;
    +        }else if (isdelaunay) {
    +          paraboloid += value * value;
    +          if (qh->ATinfinity) {
    +            if (qh->CDDinput)
    +              infinity[coordcount-1] += value;
    +            else
    +              infinity[coordcount] += value;
    +          }
    +        }
    +      }
    +      if (++coordcount == diminput) {
    +        coordcount= 0;
    +        if (isdelaunay) {
    +          *(coords++)= paraboloid;
    +          maximize_(maxboloid, paraboloid);
    +          paraboloid= 0.0;
    +        }else if (qh->HALFspace) {
    +          if (!qh_sethalfspace(qh, *dimension, coords, &coords, normalp, offsetp, qh->feasible_point)) {
    +            qh_fprintf(qh, qh->ferr, 8048, "The halfspace was on line %d\n", linecount);
    +            if (wasbegin)
    +              qh_fprintf(qh, qh->ferr, 8049, "The input appears to be in cdd format.  If so, you should use option 'Fd'\n");
    +            qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +          }
    +          coordp= qh->half_space;
    +        }
    +        while (isspace(*s))
    +          s++;
    +        if (*s) {
    +          islong= True;
    +          if (!firstlong)
    +            firstlong= linecount;
    +        }
    +      }
    +    }
    +    if (!islong && !firstshort && coordcount)
    +      firstshort= linecount;
    +    if (!isfirst && s - qh->line >= qh->maxline) {
    +      qh_fprintf(qh, qh->ferr, 6078, "qhull input error: line %d contained more than %d characters\n",
    +              linecount, (int) (s - qh->line));   /* WARN64 */
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    isfirst= False;
    +  }
    +  if (tokcount != maxcount) {
    +    newnum= fmin_(numinput, tokcount/diminput);
    +    qh_fprintf(qh, qh->ferr, 7073,"\
    +qhull warning: instead of %d %d-dimensional points, input contains\n\
    +%d points and %d extra coordinates.  Line %d is the first\npoint",
    +       numinput, diminput, tokcount/diminput, tokcount % diminput, firstpoint);
    +    if (firsttext)
    +      qh_fprintf(qh, qh->ferr, 8051, ", line %d is the first comment", firsttext);
    +    if (firstshort)
    +      qh_fprintf(qh, qh->ferr, 8052, ", line %d is the first short\nline", firstshort);
    +    if (firstlong)
    +      qh_fprintf(qh, qh->ferr, 8053, ", line %d is the first long line", firstlong);
    +    qh_fprintf(qh, qh->ferr, 8054, ".  Continue with %d points.\n", newnum);
    +    numinput= newnum;
    +    if (isdelaunay && qh->ATinfinity) {
    +      for (k= tokcount % diminput; k--; )
    +        infinity[k] -= *(--coords);
    +      *numpoints= newnum+1;
    +    }else {
    +      coords -= tokcount % diminput;
    +      *numpoints= newnum;
    +    }
    +  }
    +  if (isdelaunay && qh->ATinfinity) {
    +    for (k= (*dimension) -1; k--; )
    +      infinity[k] /= numinput;
    +    if (coords == infinity)
    +      coords += (*dimension) -1;
    +    else {
    +      for (k=0; k < (*dimension) -1; k++)
    +        *(coords++)= infinity[k];
    +    }
    +    *(coords++)= maxboloid * 1.1;
    +  }
    +  if (qh->rbox_command[0]) {
    +    qh->rbox_command[strlen(qh->rbox_command)-1]= '\0';
    +    if (!strcmp(qh->rbox_command, "./rbox D4"))
    +      qh_fprintf(qh, qh->ferr, 8055, "\n\
    +This is the qhull test case.  If any errors or core dumps occur,\n\
    +recompile qhull with 'make new'.  If errors still occur, there is\n\
    +an incompatibility.  You should try a different compiler.  You can also\n\
    +change the choices in user.h.  If you discover the source of the problem,\n\
    +please send mail to qhull_bug@qhull.org.\n\
    +\n\
    +Type 'qhull' for a short list of options.\n");
    +  }
    +  qh_free(qh->line);
    +  qh->line= NULL;
    +  if (qh->half_space) {
    +    qh_free(qh->half_space);
    +    qh->half_space= NULL;
    +  }
    +  qh->temp_malloc= NULL;
    +  trace1((qh, qh->ferr, 1008,"qh_readpoints: read in %d %d-dimensional points\n",
    +          numinput, diminput));
    +  return(points);
    +} /* readpoints */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfeasible(qh, dim )
    +    set qh.feasible_point from qh.feasible_string in "n,n,n" or "n n n" format
    +
    +  notes:
    +    "n,n,n" already checked by qh_initflags()
    +    see qh_readfeasible()
    +    called only once from qh_new_qhull, otherwise leaks memory
    +*/
    +void qh_setfeasible(qhT *qh, int dim) {
    +  int tokcount= 0;
    +  char *s;
    +  coordT *coords, value;
    +
    +  if (!(s= qh->feasible_string)) {
    +    qh_fprintf(qh, qh->ferr, 6223, "\
    +qhull input error: halfspace intersection needs a feasible point.\n\
    +Either prepend the input with 1 point or use 'Hn,n,n'.  See manual.\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (!(qh->feasible_point= (pointT*)qh_malloc(dim * sizeof(coordT)))) {
    +    qh_fprintf(qh, qh->ferr, 6079, "qhull error: insufficient memory for 'Hn,n,n'\n");
    +    qh_errexit(qh, qh_ERRmem, NULL, NULL);
    +  }
    +  coords= qh->feasible_point;
    +  while (*s) {
    +    value= qh_strtod(s, &s);
    +    if (++tokcount > dim) {
    +      qh_fprintf(qh, qh->ferr, 7059, "qhull input warning: more coordinates for 'H%s' than dimension %d\n",
    +          qh->feasible_string, dim);
    +      break;
    +    }
    +    *(coords++)= value;
    +    if (*s)
    +      s++;
    +  }
    +  while (++tokcount <= dim)
    +    *(coords++)= 0.0;
    +} /* setfeasible */
    +
    +/*---------------------------------
    +
    +  qh_skipfacet(qh, facet )
    +    returns 'True' if this facet is not to be printed
    +
    +  notes:
    +    based on the user provided slice thresholds and 'good' specifications
    +*/
    +boolT qh_skipfacet(qhT *qh, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +
    +  if (qh->PRINTneighbors) {
    +    if (facet->good)
    +      return !qh->PRINTgood;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->good)
    +        return False;
    +    }
    +    return True;
    +  }else if (qh->PRINTgood)
    +    return !facet->good;
    +  else if (!facet->normal)
    +    return True;
    +  return(!qh_inthresholds(qh, facet->normal, NULL));
    +} /* skipfacet */
    +
    +/*---------------------------------
    +
    +  qh_skipfilename(qh, string )
    +    returns pointer to character after filename
    +
    +  notes:
    +    skips leading spaces
    +    ends with spacing or eol
    +    if starts with ' or " ends with the same, skipping \' or \"
    +    For qhull, qh_argv_to_command() only uses double quotes
    +*/
    +char *qh_skipfilename(qhT *qh, char *filename) {
    +  char *s= filename;  /* non-const due to return */
    +  char c;
    +
    +  while (*s && isspace(*s))
    +    s++;
    +  c= *s++;
    +  if (c == '\0') {
    +    qh_fprintf(qh, qh->ferr, 6204, "qhull input error: filename expected, none found.\n");
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  if (c == '\'' || c == '"') {
    +    while (*s !=c || s[-1] == '\\') {
    +      if (!*s) {
    +        qh_fprintf(qh, qh->ferr, 6203, "qhull input error: missing quote after filename -- %s\n", filename);
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +      s++;
    +    }
    +    s++;
    +  }
    +  else while (*s && !isspace(*s))
    +      s++;
    +  return s;
    +} /* skipfilename */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/io_r.h b/xs/src/qhull/src/libqhull_r/io_r.h
    new file mode 100644
    index 0000000000..12e05ae7ac
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/io_r.h
    @@ -0,0 +1,167 @@
    +/*
      ---------------------------------
    +
    +   io_r.h
    +   declarations of Input/Output functions
    +
    +   see README, libqhull_r.h and io_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/io_r.h#3 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFio
    +#define qhDEFio 1
    +
    +#include "libqhull_r.h"
    +
    +/*============ constants and flags ==================*/
    +
    +/*----------------------------------
    +
    +  qh_MAXfirst
    +    maximum length of first two lines of stdin
    +*/
    +#define qh_MAXfirst  200
    +
    +/*----------------------------------
    +
    +  qh_MINradius
    +    min radius for Gp and Gv, fraction of maxcoord
    +*/
    +#define qh_MINradius 0.02
    +
    +/*----------------------------------
    +
    +  qh_GEOMepsilon
    +    adjust outer planes for 'lines closer' and geomview roundoff.
    +    This prevents bleed through.
    +*/
    +#define qh_GEOMepsilon 2e-3
    +
    +/*----------------------------------
    +
    +  qh_WHITESPACE
    +    possible values of white space
    +*/
    +#define qh_WHITESPACE " \n\t\v\r\f"
    +
    +
    +/*----------------------------------
    +
    +  qh_RIDGE
    +    to select which ridges to print in qh_eachvoronoi
    +*/
    +typedef enum
    +{
    +    qh_RIDGEall = 0, qh_RIDGEinner, qh_RIDGEouter
    +}
    +qh_RIDGE;
    +
    +/*----------------------------------
    +
    +  printvridgeT
    +    prints results of qh_printvdiagram
    +
    +  see:
    +    qh_printvridge for an example
    +*/
    +typedef void (*printvridgeT)(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +
    +/*============== -prototypes in alphabetical order =========*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_dfacet(qhT *qh, unsigned id);
    +void    qh_dvertex(qhT *qh, unsigned id);
    +int     qh_compare_facetarea(const void *p1, const void *p2);
    +int     qh_compare_facetmerge(const void *p1, const void *p2);
    +int     qh_compare_facetvisit(const void *p1, const void *p2);
    +/* int  qh_compare_vertexpoint(const void *p1, const void *p2); Not useable since it depends on qh */
    +void    qh_copyfilename(qhT *qh, char *filename, int size, const char* source, int length);
    +void    qh_countfacets(qhT *qh, facetT *facetlist, setT *facets, boolT printall,
    +              int *numfacetsp, int *numsimplicialp, int *totneighborsp,
    +              int *numridgesp, int *numcoplanarsp, int *numnumtricoplanarsp);
    +pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, realT *offsetp);
    +setT   *qh_detvridge(qhT *qh, vertexT *vertex);
    +setT   *qh_detvridge3(qhT *qh, vertexT *atvertex, vertexT *vertex);
    +int     qh_eachvoronoi(qhT *qh, FILE *fp, printvridgeT printvridge, vertexT *atvertex, boolT visitall, qh_RIDGE innerouter, boolT inorder);
    +int     qh_eachvoronoi_all(qhT *qh, FILE *fp, printvridgeT printvridge, boolT isUpper, qh_RIDGE innerouter, boolT inorder);
    +void    qh_facet2point(qhT *qh, facetT *facet, pointT **point0, pointT **point1, realT *mindist);
    +setT   *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets);
    +void    qh_geomplanes(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_markkeep(qhT *qh, facetT *facetlist);
    +setT   *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, boolT *isLowerp, int *numcentersp);
    +void    qh_order_vertexneighbors(qhT *qh, vertexT *vertex);
    +void    qh_prepare_output(qhT *qh);
    +void    qh_printafacet(qhT *qh, FILE *fp, qh_PRINT format, facetT *facet, boolT printall);
    +void    qh_printbegin(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, facetT *facet);
    +void    qh_printcentrum(qhT *qh, FILE *fp, facetT *facet, realT radius);
    +void    qh_printend(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printend4geom(qhT *qh, FILE *fp, facetT *facet, int *num, boolT printall);
    +void    qh_printextremes(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_2d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printextremes_d(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printfacet(qhT *qh, FILE *fp, facetT *facet);
    +void    qh_printfacet2math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet2geom(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet2geom_points(qhT *qh, FILE *fp, pointT *point1, pointT *point2,
    +                               facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3math(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format, int notfirst);
    +void    qh_printfacet3geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3geom_points(qhT *qh, FILE *fp, setT *points, facetT *facet, realT offset, realT color[3]);
    +void    qh_printfacet3geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet3vertex(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacet4geom_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacet4geom_simplicial(qhT *qh, FILE *fp, facetT *facet, realT color[3]);
    +void    qh_printfacetNvertex_nonsimplicial(qhT *qh, FILE *fp, facetT *facet, int id, qh_PRINT format);
    +void    qh_printfacetNvertex_simplicial(qhT *qh, FILE *fp, facetT *facet, qh_PRINT format);
    +void    qh_printfacetheader(qhT *qh, FILE *fp, facetT *facet);
    +void    qh_printfacetridges(qhT *qh, FILE *fp, facetT *facet);
    +void    qh_printfacets(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhyperplaneintersection(qhT *qh, FILE *fp, facetT *facet1, facetT *facet2,
    +                   setT *vertices, realT color[3]);
    +void    qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_printline3geom(qhT *qh, FILE *fp, pointT *pointA, pointT *pointB, realT color[3]);
    +void    qh_printpoint(qhT *qh, FILE *fp, const char *string, pointT *point);
    +void    qh_printpointid(qhT *qh, FILE *fp, const char *string, int dim, pointT *point, int id);
    +void    qh_printpoint3(qhT *qh, FILE *fp, pointT *point);
    +void    qh_printpoints_out(qhT *qh, FILE *fp, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printpointvect(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius, realT color[3]);
    +void    qh_printpointvect2(qhT *qh, FILE *fp, pointT *point, coordT *normal, pointT *center, realT radius);
    +void    qh_printridge(qhT *qh, FILE *fp, ridgeT *ridge);
    +void    qh_printspheres(qhT *qh, FILE *fp, setT *vertices, realT radius);
    +void    qh_printvdiagram(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +int     qh_printvdiagram2(qhT *qh, FILE *fp, printvridgeT printvridge, setT *vertices, qh_RIDGE innerouter, boolT inorder);
    +void    qh_printvertex(qhT *qh, FILE *fp, vertexT *vertex);
    +void    qh_printvertexlist(qhT *qh, FILE *fp, const char* string, facetT *facetlist,
    +                         setT *facets, boolT printall);
    +void    qh_printvertices(qhT *qh, FILE *fp, const char* string, setT *vertices);
    +void    qh_printvneighbors(qhT *qh, FILE *fp, facetT* facetlist, setT *facets, boolT printall);
    +void    qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printvnorm(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_printvridge(qhT *qh, FILE *fp, vertexT *vertex, vertexT *vertexA, setT *centers, boolT unbounded);
    +void    qh_produce_output(qhT *qh);
    +void    qh_produce_output2(qhT *qh);
    +void    qh_projectdim3(qhT *qh, pointT *source, pointT *destination);
    +int     qh_readfeasible(qhT *qh, int dim, const char *curline);
    +coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc);
    +void    qh_setfeasible(qhT *qh, int dim);
    +boolT   qh_skipfacet(qhT *qh, facetT *facet);
    +char   *qh_skipfilename(qhT *qh, char *filename);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFio */
    diff --git a/xs/src/qhull/src/libqhull_r/libqhull_r.c b/xs/src/qhull/src/libqhull_r/libqhull_r.c
    new file mode 100644
    index 0000000000..0fe0c980dc
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/libqhull_r.c
    @@ -0,0 +1,1403 @@
    +/*
      ---------------------------------
    +
    +   libqhull_r.c
    +   Quickhull algorithm for convex hulls
    +
    +   qhull() and top-level routines
    +
    +   see qh-qhull_r.htm, libqhull.h, unix_r.c
    +
    +   see qhull_ra.h for internal functions
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/libqhull_r.c#2 $$Change: 2047 $
    +   $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*============= functions in alphabetic order after qhull() =======*/
    +
    +/*---------------------------------
    +
    +  qh_qhull(qh)
    +    compute DIM3 convex hull of qh.num_points starting at qh.first_point
    +    qh->contains all global options and variables
    +
    +  returns:
    +    returns polyhedron
    +      qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices,
    +
    +    returns global variables
    +      qh.hulltime, qh.max_outside, qh.interior_point, qh.max_vertex, qh.min_vertex
    +
    +    returns precision constants
    +      qh.ANGLEround, centrum_radius, cos_max, DISTround, MAXabs_coord, ONEmerge
    +
    +  notes:
    +    unless needed for output
    +      qh.max_vertex and qh.min_vertex are max/min due to merges
    +
    +  see:
    +    to add individual points to either qh.num_points
    +      use qh_addpoint()
    +
    +    if qh.GETarea
    +      qh_produceoutput() returns qh.totarea and qh.totvol via qh_getarea()
    +
    +  design:
    +    record starting time
    +    initialize hull and partition points
    +    build convex hull
    +    unless early termination
    +      update facet->maxoutside for vertices, coplanar, and near-inside points
    +    error if temporary sets exist
    +    record end time
    +*/
    +
    +void qh_qhull(qhT *qh) {
    +  int numoutside;
    +
    +  qh->hulltime= qh_CPUclock;
    +  if (qh->RERUN || qh->JOGGLEmax < REALmax/2)
    +    qh_build_withrestart(qh);
    +  else {
    +    qh_initbuild(qh);
    +    qh_buildhull(qh);
    +  }
    +  if (!qh->STOPpoint && !qh->STOPcone) {
    +    if (qh->ZEROall_ok && !qh->TESTvneighbors && qh->MERGEexact)
    +      qh_checkzero(qh, qh_ALL);
    +    if (qh->ZEROall_ok && !qh->TESTvneighbors && !qh->WAScoplanar) {
    +      trace2((qh, qh->ferr, 2055, "qh_qhull: all facets are clearly convex and no coplanar points.  Post-merging and check of maxout not needed.\n"));
    +      qh->DOcheckmax= False;
    +    }else {
    +      if (qh->MERGEexact || (qh->hull_dim > qh_DIMreduceBuild && qh->PREmerge))
    +        qh_postmerge(qh, "First post-merge", qh->premerge_centrum, qh->premerge_cos,
    +             (qh->POSTmerge ? False : qh->TESTvneighbors));
    +      else if (!qh->POSTmerge && qh->TESTvneighbors)
    +        qh_postmerge(qh, "For testing vertex neighbors", qh->premerge_centrum,
    +             qh->premerge_cos, True);
    +      if (qh->POSTmerge)
    +        qh_postmerge(qh, "For post-merging", qh->postmerge_centrum,
    +             qh->postmerge_cos, qh->TESTvneighbors);
    +      if (qh->visible_list == qh->facet_list) { /* i.e., merging done */
    +        qh->findbestnew= True;
    +        qh_partitionvisible(qh /*qh.visible_list*/, !qh_ALL, &numoutside);
    +        qh->findbestnew= False;
    +        qh_deletevisible(qh /*qh.visible_list*/);
    +        qh_resetlists(qh, False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +      }
    +    }
    +    if (qh->DOcheckmax){
    +      if (qh->REPORTfreq) {
    +        qh_buildtracing(qh, NULL, NULL);
    +        qh_fprintf(qh, qh->ferr, 8115, "\nTesting all coplanar points.\n");
    +      }
    +      qh_check_maxout(qh);
    +    }
    +    if (qh->KEEPnearinside && !qh->maxoutdone)
    +      qh_nearcoplanar(qh);
    +  }
    +  if (qh_setsize(qh, qh->qhmem.tempstack) != 0) {
    +    qh_fprintf(qh, qh->ferr, 6164, "qhull internal error (qh_qhull): temporary sets not empty(%d)\n",
    +             qh_setsize(qh, qh->qhmem.tempstack));
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  qh->hulltime= qh_CPUclock - qh->hulltime;
    +  qh->QHULLfinished= True;
    +  trace1((qh, qh->ferr, 1036, "Qhull: algorithm completed\n"));
    +} /* qhull */
    +
    +/*---------------------------------
    +
    +  qh_addpoint(qh, furthest, facet, checkdist )
    +    add point (usually furthest point) above facet to hull
    +    if checkdist,
    +      check that point is above facet.
    +      if point is not outside of the hull, uses qh_partitioncoplanar()
    +      assumes that facet is defined by qh_findbestfacet()
    +    else if facet specified,
    +      assumes that point is above facet (major damage if below)
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns False if user requested an early termination
    +     qh.visible_list, newfacet_list, delvertex_list, NEWfacets may be defined
    +    updates qh.facet_list, qh.num_facets, qh.vertex_list, qh.num_vertices
    +    clear qh.maxoutdone (will need to call qh_check_maxout() for facet->maxoutside)
    +    if unknown point, adds a pointer to qh.other_points
    +      do not deallocate the point's coordinates
    +
    +  notes:
    +    assumes point is near its best facet and not at a local minimum of a lens
    +      distributions.  Use qh_findbestfacet to avoid this case.
    +    uses qh.visible_list, qh.newfacet_list, qh.delvertex_list, qh.NEWfacets
    +
    +  see also:
    +    qh_triangulate() -- triangulate non-simplicial facets
    +
    +  design:
    +    add point to other_points if needed
    +    if checkdist
    +      if point not above facet
    +        partition coplanar point
    +        exit
    +    exit if pre STOPpoint requested
    +    find horizon and visible facets for point
    +    make new facets for point to horizon
    +    make hyperplanes for point
    +    compute balance statistics
    +    match neighboring new facets
    +    update vertex neighbors and delete interior vertices
    +    exit if STOPcone requested
    +    merge non-convex new facets
    +    if merge found, many merges, or 'Qf'
    +       use qh_findbestnew() instead of qh_findbest()
    +    partition outside points from visible facets
    +    delete visible facets
    +    check polyhedron if requested
    +    exit if post STOPpoint requested
    +    reset working lists of facets and vertices
    +*/
    +boolT qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist) {
    +  int goodvisible, goodhorizon;
    +  vertexT *vertex;
    +  facetT *newfacet;
    +  realT dist, newbalance, pbalance;
    +  boolT isoutside= False;
    +  int numpart, numpoints, numnew, firstnew;
    +
    +  qh->maxoutdone= False;
    +  if (qh_pointid(qh, furthest) == qh_IDunknown)
    +    qh_setappend(qh, &qh->other_points, furthest);
    +  if (!facet) {
    +    qh_fprintf(qh, qh->ferr, 6213, "qhull internal error (qh_addpoint): NULL facet.  Need to call qh_findbestfacet first\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (checkdist) {
    +    facet= qh_findbest(qh, furthest, facet, !qh_ALL, !qh_ISnewfacets, !qh_NOupper,
    +                        &dist, &isoutside, &numpart);
    +    zzadd_(Zpartition, numpart);
    +    if (!isoutside) {
    +      zinc_(Znotmax);  /* last point of outsideset is no longer furthest. */
    +      facet->notfurthest= True;
    +      qh_partitioncoplanar(qh, furthest, facet, &dist);
    +      return True;
    +    }
    +  }
    +  qh_buildtracing(qh, furthest, facet);
    +  if (qh->STOPpoint < 0 && qh->furthest_id == -qh->STOPpoint-1) {
    +    facet->notfurthest= True;
    +    return False;
    +  }
    +  qh_findhorizon(qh, furthest, facet, &goodvisible, &goodhorizon);
    +  if (qh->ONLYgood && !(goodvisible+goodhorizon) && !qh->GOODclosest) {
    +    zinc_(Znotgood);
    +    facet->notfurthest= True;
    +    /* last point of outsideset is no longer furthest.  This is ok
    +       since all points of the outside are likely to be bad */
    +    qh_resetlists(qh, False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    return True;
    +  }
    +  zzinc_(Zprocessed);
    +  firstnew= qh->facet_id;
    +  vertex= qh_makenewfacets(qh, furthest /*visible_list, attaches if !ONLYgood */);
    +  qh_makenewplanes(qh /* newfacet_list */);
    +  numnew= qh->facet_id - firstnew;
    +  newbalance= numnew - (realT) (qh->num_facets-qh->num_visible)
    +                         * qh->hull_dim/qh->num_vertices;
    +  wadd_(Wnewbalance, newbalance);
    +  wadd_(Wnewbalance2, newbalance * newbalance);
    +  if (qh->ONLYgood
    +  && !qh_findgood(qh, qh->newfacet_list, goodhorizon) && !qh->GOODclosest) {
    +    FORALLnew_facets
    +      qh_delfacet(qh, newfacet);
    +    qh_delvertex(qh, vertex);
    +    qh_resetlists(qh, True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +    zinc_(Znotgoodnew);
    +    facet->notfurthest= True;
    +    return True;
    +  }
    +  if (qh->ONLYgood)
    +    qh_attachnewfacets(qh /*visible_list*/);
    +  qh_matchnewfacets(qh);
    +  qh_updatevertices(qh);
    +  if (qh->STOPcone && qh->furthest_id == qh->STOPcone-1) {
    +    facet->notfurthest= True;
    +    return False;  /* visible_list etc. still defined */
    +  }
    +  qh->findbestnew= False;
    +  if (qh->PREmerge || qh->MERGEexact) {
    +    qh_premerge(qh, vertex, qh->premerge_centrum, qh->premerge_cos);
    +    if (qh_USEfindbestnew)
    +      qh->findbestnew= True;
    +    else {
    +      FORALLnew_facets {
    +        if (!newfacet->simplicial) {
    +          qh->findbestnew= True;  /* use qh_findbestnew instead of qh_findbest*/
    +          break;
    +        }
    +      }
    +    }
    +  }else if (qh->BESToutside)
    +    qh->findbestnew= True;
    +  qh_partitionvisible(qh /*qh.visible_list*/, !qh_ALL, &numpoints);
    +  qh->findbestnew= False;
    +  qh->findbest_notsharp= False;
    +  zinc_(Zpbalance);
    +  pbalance= numpoints - (realT) qh->hull_dim /* assumes all points extreme */
    +                * (qh->num_points - qh->num_vertices)/qh->num_vertices;
    +  wadd_(Wpbalance, pbalance);
    +  wadd_(Wpbalance2, pbalance * pbalance);
    +  qh_deletevisible(qh /*qh.visible_list*/);
    +  zmax_(Zmaxvertex, qh->num_vertices);
    +  qh->NEWfacets= False;
    +  if (qh->IStracing >= 4) {
    +    if (qh->num_facets < 2000)
    +      qh_printlists(qh);
    +    qh_printfacetlist(qh, qh->newfacet_list, NULL, True);
    +    qh_checkpolygon(qh, qh->facet_list);
    +  }else if (qh->CHECKfrequently) {
    +    if (qh->num_facets < 50)
    +      qh_checkpolygon(qh, qh->facet_list);
    +    else
    +      qh_checkpolygon(qh, qh->newfacet_list);
    +  }
    +  if (qh->STOPpoint > 0 && qh->furthest_id == qh->STOPpoint-1)
    +    return False;
    +  qh_resetlists(qh, True, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  /* qh_triangulate(qh); to test qh.TRInormals */
    +  trace2((qh, qh->ferr, 2056, "qh_addpoint: added p%d new facets %d new balance %2.2g point balance %2.2g\n",
    +    qh_pointid(qh, furthest), numnew, newbalance, pbalance));
    +  return True;
    +} /* addpoint */
    +
    +/*---------------------------------
    +
    +  qh_build_withrestart(qh)
    +    allow restarts due to qh.JOGGLEmax while calling qh_buildhull()
    +       qh_errexit always undoes qh_build_withrestart()
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +       it may be moved by qh_joggleinput(qh)
    +*/
    +void qh_build_withrestart(qhT *qh) {
    +  int restart;
    +
    +  qh->ALLOWrestart= True;
    +  while (True) {
    +    restart= setjmp(qh->restartexit); /* simple statement for CRAY J916 */
    +    if (restart) {       /* only from qh_precision() */
    +      zzinc_(Zretry);
    +      wmax_(Wretrymax, qh->JOGGLEmax);
    +      /* QH7078 warns about using 'TCn' with 'QJn' */
    +      qh->STOPcone= qh_IDunknown; /* if break from joggle, prevents normal output */
    +    }
    +    if (!qh->RERUN && qh->JOGGLEmax < REALmax/2) {
    +      if (qh->build_cnt > qh_JOGGLEmaxretry) {
    +        qh_fprintf(qh, qh->ferr, 6229, "qhull precision error: %d attempts to construct a convex hull\n\
    +        with joggled input.  Increase joggle above 'QJ%2.2g'\n\
    +        or modify qh_JOGGLE... parameters in user.h\n",
    +           qh->build_cnt, qh->JOGGLEmax);
    +        qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +      }
    +      if (qh->build_cnt && !restart)
    +        break;
    +    }else if (qh->build_cnt && qh->build_cnt >= qh->RERUN)
    +      break;
    +    qh->STOPcone= 0;
    +    qh_freebuild(qh, True);  /* first call is a nop */
    +    qh->build_cnt++;
    +    if (!qh->qhull_optionsiz)
    +      qh->qhull_optionsiz= (int)strlen(qh->qhull_options);   /* WARN64 */
    +    else {
    +      qh->qhull_options [qh->qhull_optionsiz]= '\0';
    +      qh->qhull_optionlen= qh_OPTIONline;  /* starts a new line */
    +    }
    +    qh_option(qh, "_run", &qh->build_cnt, NULL);
    +    if (qh->build_cnt == qh->RERUN) {
    +      qh->IStracing= qh->TRACElastrun;  /* duplicated from qh_initqhull_globals */
    +      if (qh->TRACEpoint != qh_IDunknown || qh->TRACEdist < REALmax/2 || qh->TRACEmerge) {
    +        qh->TRACElevel= (qh->IStracing? qh->IStracing : 3);
    +        qh->IStracing= 0;
    +      }
    +      qh->qhmem.IStracing= qh->IStracing;
    +    }
    +    if (qh->JOGGLEmax < REALmax/2)
    +      qh_joggleinput(qh);
    +    qh_initbuild(qh);
    +    qh_buildhull(qh);
    +    if (qh->JOGGLEmax < REALmax/2 && !qh->MERGING)
    +      qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
    +  }
    +  qh->ALLOWrestart= False;
    +} /* qh_build_withrestart */
    +
    +/*---------------------------------
    +
    +  qh_buildhull(qh)
    +    construct a convex hull by adding outside points one at a time
    +
    +  returns:
    +
    +  notes:
    +    may be called multiple times
    +    checks facet and vertex lists for incorrect flags
    +    to recover from STOPcone, call qh_deletevisible and qh_resetlists
    +
    +  design:
    +    check visible facet and newfacet flags
    +    check newlist vertex flags and qh.STOPcone/STOPpoint
    +    for each facet with a furthest outside point
    +      add point to facet
    +      exit if qh.STOPcone or qh.STOPpoint requested
    +    if qh.NARROWhull for initial simplex
    +      partition remaining outside points to coplanar sets
    +*/
    +void qh_buildhull(qhT *qh) {
    +  facetT *facet;
    +  pointT *furthest;
    +  vertexT *vertex;
    +  int id;
    +
    +  trace1((qh, qh->ferr, 1037, "qh_buildhull: start build hull\n"));
    +  FORALLfacets {
    +    if (facet->visible || facet->newfacet) {
    +      qh_fprintf(qh, qh->ferr, 6165, "qhull internal error (qh_buildhull): visible or new facet f%d in facet list\n",
    +                   facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->newlist) {
    +      qh_fprintf(qh, qh->ferr, 6166, "qhull internal error (qh_buildhull): new vertex f%d in vertex list\n",
    +                   vertex->id);
    +      qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +    id= qh_pointid(qh, vertex->point);
    +    if ((qh->STOPpoint>0 && id == qh->STOPpoint-1) ||
    +        (qh->STOPpoint<0 && id == -qh->STOPpoint-1) ||
    +        (qh->STOPcone>0 && id == qh->STOPcone-1)) {
    +      trace1((qh, qh->ferr, 1038,"qh_buildhull: stop point or cone P%d in initial hull\n", id));
    +      return;
    +    }
    +  }
    +  qh->facet_next= qh->facet_list;      /* advance facet when processed */
    +  while ((furthest= qh_nextfurthest(qh, &facet))) {
    +    qh->num_outside--;  /* if ONLYmax, furthest may not be outside */
    +    if (!qh_addpoint(qh, furthest, facet, qh->ONLYmax))
    +      break;
    +  }
    +  if (qh->NARROWhull) /* move points from outsideset to coplanarset */
    +    qh_outcoplanar(qh /* facet_list */ );
    +  if (qh->num_outside && !furthest) {
    +    qh_fprintf(qh, qh->ferr, 6167, "qhull internal error (qh_buildhull): %d outside points were never processed.\n", qh->num_outside);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  trace1((qh, qh->ferr, 1039, "qh_buildhull: completed the hull construction\n"));
    +} /* buildhull */
    +
    +
    +/*---------------------------------
    +
    +  qh_buildtracing(qh, furthest, facet )
    +    trace an iteration of qh_buildhull() for furthest point and facet
    +    if !furthest, prints progress message
    +
    +  returns:
    +    tracks progress with qh.lastreport
    +    updates qh.furthest_id (-3 if furthest is NULL)
    +    also resets visit_id, vertext_visit on wrap around
    +
    +  see:
    +    qh_tracemerging()
    +
    +  design:
    +    if !furthest
    +      print progress message
    +      exit
    +    if 'TFn' iteration
    +      print progress message
    +    else if tracing
    +      trace furthest point and facet
    +    reset qh.visit_id and qh.vertex_visit if overflow may occur
    +    set qh.furthest_id for tracing
    +*/
    +void qh_buildtracing(qhT *qh, pointT *furthest, facetT *facet) {
    +  realT dist= 0;
    +  float cpu;
    +  int total, furthestid;
    +  time_t timedata;
    +  struct tm *tp;
    +  vertexT *vertex;
    +
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  if (!furthest) {
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    qh_fprintf(qh, qh->ferr, 8118, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  Last point was p%d\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh->facet_id -1,
    +      total, qh->num_facets, qh->num_vertices, qh->furthest_id);
    +    return;
    +  }
    +  furthestid= qh_pointid(qh, furthest);
    +  if (qh->TRACEpoint == furthestid) {
    +    qh->IStracing= qh->TRACElevel;
    +    qh->qhmem.IStracing= qh->TRACElevel;
    +  }else if (qh->TRACEpoint != qh_IDunknown && qh->TRACEdist < REALmax/2) {
    +    qh->IStracing= 0;
    +    qh->qhmem.IStracing= 0;
    +  }
    +  if (qh->REPORTfreq && (qh->facet_id-1 > qh->lastreport+qh->REPORTfreq)) {
    +    qh->lastreport= qh->facet_id-1;
    +    time(&timedata);
    +    tp= localtime(&timedata);
    +    cpu= (float)qh_CPUclock - (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +    zinc_(Zdistio);
    +    qh_distplane(qh, furthest, facet, &dist);
    +    qh_fprintf(qh, qh->ferr, 8119, "\n\
    +At %02d:%02d:%02d & %2.5g CPU secs, qhull has created %d facets and merged %d.\n\
    + The current hull contains %d facets and %d vertices.  There are %d\n\
    + outside points.  Next is point p%d(v%d), %2.2g above f%d.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu, qh->facet_id -1,
    +      total, qh->num_facets, qh->num_vertices, qh->num_outside+1,
    +      furthestid, qh->vertex_id, dist, getid_(facet));
    +  }else if (qh->IStracing >=1) {
    +    cpu= (float)qh_CPUclock - (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    qh_distplane(qh, furthest, facet, &dist);
    +    qh_fprintf(qh, qh->ferr, 8120, "qh_addpoint: add p%d(v%d) to hull of %d facets(%2.2g above f%d) and %d outside at %4.4g CPU secs.  Previous was p%d.\n",
    +      furthestid, qh->vertex_id, qh->num_facets, dist,
    +      getid_(facet), qh->num_outside+1, cpu, qh->furthest_id);
    +  }
    +  zmax_(Zvisit2max, (int)qh->visit_id/2);
    +  if (qh->visit_id > (unsigned) INT_MAX) { /* 31 bits */
    +    zinc_(Zvisit);
    +    qh->visit_id= 0;
    +    FORALLfacets
    +      facet->visitid= 0;
    +  }
    +  zmax_(Zvvisit2max, (int)qh->vertex_visit/2);
    +  if (qh->vertex_visit > (unsigned) INT_MAX) { /* 31 bits */ 
    +    zinc_(Zvvisit);
    +    qh->vertex_visit= 0;
    +    FORALLvertices
    +      vertex->visitid= 0;
    +  }
    +  qh->furthest_id= furthestid;
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* buildtracing */
    +
    +/*---------------------------------
    +
    +  qh_errexit2(qh, exitcode, facet, otherfacet )
    +    return exitcode to system after an error
    +    report two facets
    +
    +  returns:
    +    assumes exitcode non-zero
    +
    +  see:
    +    normally use qh_errexit() in user.c(reports a facet and a ridge)
    +*/
    +void qh_errexit2(qhT *qh, int exitcode, facetT *facet, facetT *otherfacet) {
    +
    +  qh_errprint(qh, "ERRONEOUS", facet, otherfacet, NULL, NULL);
    +  qh_errexit(qh, exitcode, NULL, NULL);
    +} /* errexit2 */
    +
    +
    +/*---------------------------------
    +
    +  qh_findhorizon(qh, point, facet, goodvisible, goodhorizon )
    +    given a visible facet, find the point's horizon and visible facets
    +    for all facets, !facet-visible
    +
    +  returns:
    +    returns qh.visible_list/num_visible with all visible facets
    +      marks visible facets with ->visible
    +    updates count of good visible and good horizon facets
    +    updates qh.max_outside, qh.max_vertex, facet->maxoutside
    +
    +  see:
    +    similar to qh_delpoint()
    +
    +  design:
    +    move facet to qh.visible_list at end of qh.facet_list
    +    for all visible facets
    +     for each unvisited neighbor of a visible facet
    +       compute distance of point to neighbor
    +       if point above neighbor
    +         move neighbor to end of qh.visible_list
    +       else if point is coplanar with neighbor
    +         update qh.max_outside, qh.max_vertex, neighbor->maxoutside
    +         mark neighbor coplanar (will create a samecycle later)
    +         update horizon statistics
    +*/
    +void qh_findhorizon(qhT *qh, pointT *point, facetT *facet, int *goodvisible, int *goodhorizon) {
    +  facetT *neighbor, **neighborp, *visible;
    +  int numhorizon= 0, coplanar= 0;
    +  realT dist;
    +
    +  trace1((qh, qh->ferr, 1040,"qh_findhorizon: find horizon for point p%d facet f%d\n",qh_pointid(qh, point),facet->id));
    +  *goodvisible= *goodhorizon= 0;
    +  zinc_(Ztotvisible);
    +  qh_removefacet(qh, facet);  /* visible_list at end of qh->facet_list */
    +  qh_appendfacet(qh, facet);
    +  qh->num_visible= 1;
    +  if (facet->good)
    +    (*goodvisible)++;
    +  qh->visible_list= facet;
    +  facet->visible= True;
    +  facet->f.replace= NULL;
    +  if (qh->IStracing >=4)
    +    qh_errprint(qh, "visible", facet, NULL, NULL, NULL);
    +  qh->visit_id++;
    +  FORALLvisible_facets {
    +    if (visible->tricoplanar && !qh->TRInormals) {
    +      qh_fprintf(qh, qh->ferr, 6230, "Qhull internal error (qh_findhorizon): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh, qh_ERRqhull, visible, NULL);
    +    }
    +    visible->visitid= qh->visit_id;
    +    FOREACHneighbor_(visible) {
    +      if (neighbor->visitid == qh->visit_id)
    +        continue;
    +      neighbor->visitid= qh->visit_id;
    +      zzinc_(Znumvisibility);
    +      qh_distplane(qh, point, neighbor, &dist);
    +      if (dist > qh->MINvisible) {
    +        zinc_(Ztotvisible);
    +        qh_removefacet(qh, neighbor);  /* append to end of qh->visible_list */
    +        qh_appendfacet(qh, neighbor);
    +        neighbor->visible= True;
    +        neighbor->f.replace= NULL;
    +        qh->num_visible++;
    +        if (neighbor->good)
    +          (*goodvisible)++;
    +        if (qh->IStracing >=4)
    +          qh_errprint(qh, "visible", neighbor, NULL, NULL, NULL);
    +      }else {
    +        if (dist > - qh->MAXcoplanar) {
    +          neighbor->coplanar= True;
    +          zzinc_(Zcoplanarhorizon);
    +          qh_precision(qh, "coplanar horizon");
    +          coplanar++;
    +          if (qh->MERGING) {
    +            if (dist > 0) {
    +              maximize_(qh->max_outside, dist);
    +              maximize_(qh->max_vertex, dist);
    +#if qh_MAXoutside
    +              maximize_(neighbor->maxoutside, dist);
    +#endif
    +            }else
    +              minimize_(qh->min_vertex, dist);  /* due to merge later */
    +          }
    +          trace2((qh, qh->ferr, 2057, "qh_findhorizon: point p%d is coplanar to horizon f%d, dist=%2.7g < qh->MINvisible(%2.7g)\n",
    +              qh_pointid(qh, point), neighbor->id, dist, qh->MINvisible));
    +        }else
    +          neighbor->coplanar= False;
    +        zinc_(Ztothorizon);
    +        numhorizon++;
    +        if (neighbor->good)
    +          (*goodhorizon)++;
    +        if (qh->IStracing >=4)
    +          qh_errprint(qh, "horizon", neighbor, NULL, NULL, NULL);
    +      }
    +    }
    +  }
    +  if (!numhorizon) {
    +    qh_precision(qh, "empty horizon");
    +    qh_fprintf(qh, qh->ferr, 6168, "qhull precision error (qh_findhorizon): empty horizon\n\
    +QhullPoint p%d was above all facets.\n", qh_pointid(qh, point));
    +    qh_printfacetlist(qh, qh->facet_list, NULL, True);
    +    qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +  }
    +  trace1((qh, qh->ferr, 1041, "qh_findhorizon: %d horizon facets(good %d), %d visible(good %d), %d coplanar\n",
    +       numhorizon, *goodhorizon, qh->num_visible, *goodvisible, coplanar));
    +  if (qh->IStracing >= 4 && qh->num_facets < 50)
    +    qh_printlists(qh);
    +} /* findhorizon */
    +
    +/*---------------------------------
    +
    +  qh_nextfurthest(qh, visible )
    +    returns next furthest point and visible facet for qh_addpoint()
    +    starts search at qh.facet_next
    +
    +  returns:
    +    removes furthest point from outside set
    +    NULL if none available
    +    advances qh.facet_next over facets with empty outside sets
    +
    +  design:
    +    for each facet from qh.facet_next
    +      if empty outside set
    +        advance qh.facet_next
    +      else if qh.NARROWhull
    +        determine furthest outside point
    +        if furthest point is not outside
    +          advance qh.facet_next(point will be coplanar)
    +    remove furthest point from outside set
    +*/
    +pointT *qh_nextfurthest(qhT *qh, facetT **visible) {
    +  facetT *facet;
    +  int size, idx;
    +  realT randr, dist;
    +  pointT *furthest;
    +
    +  while ((facet= qh->facet_next) != qh->facet_tail) {
    +    if (!facet->outsideset) {
    +      qh->facet_next= facet->next;
    +      continue;
    +    }
    +    SETreturnsize_(facet->outsideset, size);
    +    if (!size) {
    +      qh_setfree(qh, &facet->outsideset);
    +      qh->facet_next= facet->next;
    +      continue;
    +    }
    +    if (qh->NARROWhull) {
    +      if (facet->notfurthest)
    +        qh_furthestout(qh, facet);
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +#if qh_COMPUTEfurthest
    +      qh_distplane(qh, furthest, facet, &dist);
    +      zinc_(Zcomputefurthest);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist < qh->MINoutside) { /* remainder of outside set is coplanar for qh_outcoplanar */
    +        qh->facet_next= facet->next;
    +        continue;
    +      }
    +    }
    +    if (!qh->RANDOMoutside && !qh->VIRTUALmemory) {
    +      if (qh->PICKfurthest) {
    +        qh_furthestnext(qh /* qh->facet_list */);
    +        facet= qh->facet_next;
    +      }
    +      *visible= facet;
    +      return((pointT*)qh_setdellast(facet->outsideset));
    +    }
    +    if (qh->RANDOMoutside) {
    +      int outcoplanar = 0;
    +      if (qh->NARROWhull) {
    +        FORALLfacets {
    +          if (facet == qh->facet_next)
    +            break;
    +          if (facet->outsideset)
    +            outcoplanar += qh_setsize(qh, facet->outsideset);
    +        }
    +      }
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor((qh->num_outside - outcoplanar) * randr);
    +      FORALLfacet_(qh->facet_next) {
    +        if (facet->outsideset) {
    +          SETreturnsize_(facet->outsideset, size);
    +          if (!size)
    +            qh_setfree(qh, &facet->outsideset);
    +          else if (size > idx) {
    +            *visible= facet;
    +            return((pointT*)qh_setdelnth(qh, facet->outsideset, idx));
    +          }else
    +            idx -= size;
    +        }
    +      }
    +      qh_fprintf(qh, qh->ferr, 6169, "qhull internal error (qh_nextfurthest): num_outside %d is too low\nby at least %d, or a random real %g >= 1.0\n",
    +              qh->num_outside, idx+1, randr);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }else { /* VIRTUALmemory */
    +      facet= qh->facet_tail->previous;
    +      if (!(furthest= (pointT*)qh_setdellast(facet->outsideset))) {
    +        if (facet->outsideset)
    +          qh_setfree(qh, &facet->outsideset);
    +        qh_removefacet(qh, facet);
    +        qh_prependfacet(qh, facet, &qh->facet_list);
    +        continue;
    +      }
    +      *visible= facet;
    +      return furthest;
    +    }
    +  }
    +  return NULL;
    +} /* nextfurthest */
    +
    +/*---------------------------------
    +
    +  qh_partitionall(qh, vertices, points, numpoints )
    +    partitions all points in points/numpoints to the outsidesets of facets
    +    vertices= vertices in qh.facet_list(!partitioned)
    +
    +  returns:
    +    builds facet->outsideset
    +    does not partition qh.GOODpoint
    +    if qh.ONLYgood && !qh.MERGING,
    +      does not partition qh.GOODvertex
    +
    +  notes:
    +    faster if qh.facet_list sorted by anticipated size of outside set
    +
    +  design:
    +    initialize pointset with all points
    +    remove vertices from pointset
    +    remove qh.GOODpointp from pointset (unless it's qh.STOPcone or qh.STOPpoint)
    +    for all facets
    +      for all remaining points in pointset
    +        compute distance from point to facet
    +        if point is outside facet
    +          remove point from pointset (by not reappending)
    +          update bestpoint
    +          append point or old bestpoint to facet's outside set
    +      append bestpoint to facet's outside set (furthest)
    +    for all points remaining in pointset
    +      partition point into facets' outside sets and coplanar sets
    +*/
    +void qh_partitionall(qhT *qh, setT *vertices, pointT *points, int numpoints){
    +  setT *pointset;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp, *bestpoint;
    +  int size, point_i, point_n, point_end, remaining, i, id;
    +  facetT *facet;
    +  realT bestdist= -REALmax, dist, distoutside;
    +
    +  trace1((qh, qh->ferr, 1042, "qh_partitionall: partition all points into outside sets\n"));
    +  pointset= qh_settemp(qh, numpoints);
    +  qh->num_outside= 0;
    +  pointp= SETaddr_(pointset, pointT);
    +  for (i=numpoints, point= points; i--; point += qh->hull_dim)
    +    *(pointp++)= point;
    +  qh_settruncate(qh, pointset, numpoints);
    +  FOREACHvertex_(vertices) {
    +    if ((id= qh_pointid(qh, vertex->point)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  id= qh_pointid(qh, qh->GOODpointp);
    +  if (id >=0 && qh->STOPcone-1 != id && -qh->STOPpoint-1 != id)
    +    SETelem_(pointset, id)= NULL;
    +  if (qh->GOODvertexp && qh->ONLYgood && !qh->MERGING) { /* matches qhull()*/
    +    if ((id= qh_pointid(qh, qh->GOODvertexp)) >= 0)
    +      SETelem_(pointset, id)= NULL;
    +  }
    +  if (!qh->BESToutside) {  /* matches conditional for qh_partitionpoint below */
    +    distoutside= qh_DISToutside; /* multiple of qh.MINoutside & qh.max_outside, see user.h */
    +    zval_(Ztotpartition)= qh->num_points - qh->hull_dim - 1; /*misses GOOD... */
    +    remaining= qh->num_facets;
    +    point_end= numpoints;
    +    FORALLfacets {
    +      size= point_end/(remaining--) + 100;
    +      facet->outsideset= qh_setnew(qh, size);
    +      bestpoint= NULL;
    +      point_end= 0;
    +      FOREACHpoint_i_(qh, pointset) {
    +        if (point) {
    +          zzinc_(Zpartitionall);
    +          qh_distplane(qh, point, facet, &dist);
    +          if (dist < distoutside)
    +            SETelem_(pointset, point_end++)= point;
    +          else {
    +            qh->num_outside++;
    +            if (!bestpoint) {
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else if (dist > bestdist) {
    +              qh_setappend(qh, &facet->outsideset, bestpoint);
    +              bestpoint= point;
    +              bestdist= dist;
    +            }else
    +              qh_setappend(qh, &facet->outsideset, point);
    +          }
    +        }
    +      }
    +      if (bestpoint) {
    +        qh_setappend(qh, &facet->outsideset, bestpoint);
    +#if !qh_COMPUTEfurthest
    +        facet->furthestdist= bestdist;
    +#endif
    +      }else
    +        qh_setfree(qh, &facet->outsideset);
    +      qh_settruncate(qh, pointset, point_end);
    +    }
    +  }
    +  /* if !qh->BESToutside, pointset contains points not assigned to outsideset */
    +  if (qh->BESToutside || qh->MERGING || qh->KEEPcoplanar || qh->KEEPinside) {
    +    qh->findbestnew= True;
    +    FOREACHpoint_i_(qh, pointset) {
    +      if (point)
    +        qh_partitionpoint(qh, point, qh->facet_list);
    +    }
    +    qh->findbestnew= False;
    +  }
    +  zzadd_(Zpartitionall, zzval_(Zpartition));
    +  zzval_(Zpartition)= 0;
    +  qh_settempfree(qh, &pointset);
    +  if (qh->IStracing >= 4)
    +    qh_printfacetlist(qh, qh->facet_list, NULL, True);
    +} /* partitionall */
    +
    +
    +/*---------------------------------
    +
    +  qh_partitioncoplanar(qh, point, facet, dist )
    +    partition coplanar point to a facet
    +    dist is distance from point to facet
    +    if dist NULL,
    +      searches for bestfacet and does nothing if inside
    +    if qh.findbestnew set,
    +      searches new facets instead of using qh_findbest()
    +
    +  returns:
    +    qh.max_ouside updated
    +    if qh.KEEPcoplanar or qh.KEEPinside
    +      point assigned to best coplanarset
    +
    +  notes:
    +    facet->maxoutside is updated at end by qh_check_maxout
    +
    +  design:
    +    if dist undefined
    +      find best facet for point
    +      if point sufficiently below facet (depends on qh.NEARinside and qh.KEEPinside)
    +        exit
    +    if keeping coplanar/nearinside/inside points
    +      if point is above furthest coplanar point
    +        append point to coplanar set (it is the new furthest)
    +        update qh.max_outside
    +      else
    +        append point one before end of coplanar set
    +    else if point is clearly outside of qh.max_outside and bestfacet->coplanarset
    +    and bestfacet is more than perpendicular to facet
    +      repartition the point using qh_findbest() -- it may be put on an outsideset
    +    else
    +      update qh.max_outside
    +*/
    +void qh_partitioncoplanar(qhT *qh, pointT *point, facetT *facet, realT *dist) {
    +  facetT *bestfacet;
    +  pointT *oldfurthest;
    +  realT bestdist, dist2= 0, angle;
    +  int numpart= 0, oldfindbest;
    +  boolT isoutside;
    +
    +  qh->WAScoplanar= True;
    +  if (!dist) {
    +    if (qh->findbestnew)
    +      bestfacet= qh_findbestnew(qh, point, facet, &bestdist, qh_ALL, &isoutside, &numpart);
    +    else
    +      bestfacet= qh_findbest(qh, point, facet, qh_ALL, !qh_ISnewfacets, qh->DELAUNAY,
    +                          &bestdist, &isoutside, &numpart);
    +    zinc_(Ztotpartcoplanar);
    +    zzadd_(Zpartcoplanar, numpart);
    +    if (!qh->DELAUNAY && !qh->KEEPinside) { /*  for 'd', bestdist skips upperDelaunay facets */
    +      if (qh->KEEPnearinside) {
    +        if (bestdist < -qh->NEARinside) {
    +          zinc_(Zcoplanarinside);
    +          trace4((qh, qh->ferr, 4062, "qh_partitioncoplanar: point p%d is more than near-inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(qh, point), bestfacet->id, bestdist, qh->findbestnew));
    +          return;
    +        }
    +      }else if (bestdist < -qh->MAXcoplanar) {
    +          trace4((qh, qh->ferr, 4063, "qh_partitioncoplanar: point p%d is inside facet f%d dist %2.2g findbestnew %d\n",
    +                  qh_pointid(qh, point), bestfacet->id, bestdist, qh->findbestnew));
    +        zinc_(Zcoplanarinside);
    +        return;
    +      }
    +    }
    +  }else {
    +    bestfacet= facet;
    +    bestdist= *dist;
    +  }
    +  if (bestdist > qh->max_outside) {
    +    if (!dist && facet != bestfacet) {
    +      zinc_(Zpartangle);
    +      angle= qh_getangle(qh, facet->normal, bestfacet->normal);
    +      if (angle < 0) {
    +        /* typically due to deleted vertex and coplanar facets, e.g.,
    +             RBOX 1000 s Z1 G1e-13 t1001185205 | QHULL Tv */
    +        zinc_(Zpartflip);
    +        trace2((qh, qh->ferr, 2058, "qh_partitioncoplanar: repartition point p%d from f%d.  It is above flipped facet f%d dist %2.2g\n",
    +                qh_pointid(qh, point), facet->id, bestfacet->id, bestdist));
    +        oldfindbest= qh->findbestnew;
    +        qh->findbestnew= False;
    +        qh_partitionpoint(qh, point, bestfacet);
    +        qh->findbestnew= oldfindbest;
    +        return;
    +      }
    +    }
    +    qh->max_outside= bestdist;
    +    if (bestdist > qh->TRACEdist) {
    +      qh_fprintf(qh, qh->ferr, 8122, "qh_partitioncoplanar: ====== p%d from f%d increases max_outside to %2.2g of f%d last p%d\n",
    +                     qh_pointid(qh, point), facet->id, bestdist, bestfacet->id, qh->furthest_id);
    +      qh_errprint(qh, "DISTANT", facet, bestfacet, NULL, NULL);
    +    }
    +  }
    +  if (qh->KEEPcoplanar + qh->KEEPinside + qh->KEEPnearinside) {
    +    oldfurthest= (pointT*)qh_setlast(bestfacet->coplanarset);
    +    if (oldfurthest) {
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(qh, oldfurthest, bestfacet, &dist2);
    +    }
    +    if (!oldfurthest || dist2 < bestdist)
    +      qh_setappend(qh, &bestfacet->coplanarset, point);
    +    else
    +      qh_setappend2ndlast(qh, &bestfacet->coplanarset, point);
    +  }
    +  trace4((qh, qh->ferr, 4064, "qh_partitioncoplanar: point p%d is coplanar with facet f%d(or inside) dist %2.2g\n",
    +          qh_pointid(qh, point), bestfacet->id, bestdist));
    +} /* partitioncoplanar */
    +
    +/*---------------------------------
    +
    +  qh_partitionpoint(qh, point, facet )
    +    assigns point to an outside set, coplanar set, or inside set (i.e., dropt)
    +    if qh.findbestnew
    +      uses qh_findbestnew() to search all new facets
    +    else
    +      uses qh_findbest()
    +
    +  notes:
    +    after qh_distplane(), this and qh_findbest() are most expensive in 3-d
    +
    +  design:
    +    find best facet for point
    +      (either exhaustive search of new facets or directed search from facet)
    +    if qh.NARROWhull
    +      retain coplanar and nearinside points as outside points
    +    if point is outside bestfacet
    +      if point above furthest point for bestfacet
    +        append point to outside set (it becomes the new furthest)
    +        if outside set was empty
    +          move bestfacet to end of qh.facet_list (i.e., after qh.facet_next)
    +        update bestfacet->furthestdist
    +      else
    +        append point one before end of outside set
    +    else if point is coplanar to bestfacet
    +      if keeping coplanar points or need to update qh.max_outside
    +        partition coplanar point into bestfacet
    +    else if near-inside point
    +      partition as coplanar point into bestfacet
    +    else is an inside point
    +      if keeping inside points
    +        partition as coplanar point into bestfacet
    +*/
    +void qh_partitionpoint(qhT *qh, pointT *point, facetT *facet) {
    +  realT bestdist;
    +  boolT isoutside;
    +  facetT *bestfacet;
    +  int numpart;
    +#if qh_COMPUTEfurthest
    +  realT dist;
    +#endif
    +
    +  if (qh->findbestnew)
    +    bestfacet= qh_findbestnew(qh, point, facet, &bestdist, qh->BESToutside, &isoutside, &numpart);
    +  else
    +    bestfacet= qh_findbest(qh, point, facet, qh->BESToutside, qh_ISnewfacets, !qh_NOupper,
    +                          &bestdist, &isoutside, &numpart);
    +  zinc_(Ztotpartition);
    +  zzadd_(Zpartition, numpart);
    +  if (qh->NARROWhull) {
    +    if (qh->DELAUNAY && !isoutside && bestdist >= -qh->MAXcoplanar)
    +      qh_precision(qh, "nearly incident point(narrow hull)");
    +    if (qh->KEEPnearinside) {
    +      if (bestdist >= -qh->NEARinside)
    +        isoutside= True;
    +    }else if (bestdist >= -qh->MAXcoplanar)
    +      isoutside= True;
    +  }
    +
    +  if (isoutside) {
    +    if (!bestfacet->outsideset
    +    || !qh_setlast(bestfacet->outsideset)) {
    +      qh_setappend(qh, &(bestfacet->outsideset), point);
    +      if (!bestfacet->newfacet) {
    +        qh_removefacet(qh, bestfacet);  /* make sure it's after qh->facet_next */
    +        qh_appendfacet(qh, bestfacet);
    +      }
    +#if !qh_COMPUTEfurthest
    +      bestfacet->furthestdist= bestdist;
    +#endif
    +    }else {
    +#if qh_COMPUTEfurthest
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(qh, oldfurthest, bestfacet, &dist);
    +      if (dist < bestdist)
    +        qh_setappend(qh, &(bestfacet->outsideset), point);
    +      else
    +        qh_setappend2ndlast(qh, &(bestfacet->outsideset), point);
    +#else
    +      if (bestfacet->furthestdist < bestdist) {
    +        qh_setappend(qh, &(bestfacet->outsideset), point);
    +        bestfacet->furthestdist= bestdist;
    +      }else
    +        qh_setappend2ndlast(qh, &(bestfacet->outsideset), point);
    +#endif
    +    }
    +    qh->num_outside++;
    +    trace4((qh, qh->ferr, 4065, "qh_partitionpoint: point p%d is outside facet f%d new? %d (or narrowhull)\n",
    +          qh_pointid(qh, point), bestfacet->id, bestfacet->newfacet));
    +  }else if (qh->DELAUNAY || bestdist >= -qh->MAXcoplanar) { /* for 'd', bestdist skips upperDelaunay facets */
    +    zzinc_(Zcoplanarpart);
    +    if (qh->DELAUNAY)
    +      qh_precision(qh, "nearly incident point");
    +    if ((qh->KEEPcoplanar + qh->KEEPnearinside) || bestdist > qh->max_outside)
    +      qh_partitioncoplanar(qh, point, bestfacet, &bestdist);
    +    else {
    +      trace4((qh, qh->ferr, 4066, "qh_partitionpoint: point p%d is coplanar to facet f%d (dropped)\n",
    +          qh_pointid(qh, point), bestfacet->id));
    +    }
    +  }else if (qh->KEEPnearinside && bestdist > -qh->NEARinside) {
    +    zinc_(Zpartnear);
    +    qh_partitioncoplanar(qh, point, bestfacet, &bestdist);
    +  }else {
    +    zinc_(Zpartinside);
    +    trace4((qh, qh->ferr, 4067, "qh_partitionpoint: point p%d is inside all facets, closest to f%d dist %2.2g\n",
    +          qh_pointid(qh, point), bestfacet->id, bestdist));
    +    if (qh->KEEPinside)
    +      qh_partitioncoplanar(qh, point, bestfacet, &bestdist);
    +  }
    +} /* partitionpoint */
    +
    +/*---------------------------------
    +
    +  qh_partitionvisible(qh, allpoints, numoutside )
    +    partitions points in visible facets to qh.newfacet_list
    +    qh.visible_list= visible facets
    +    for visible facets
    +      1st neighbor (if any) points to a horizon facet or a new facet
    +    if allpoints(!used),
    +      repartitions coplanar points
    +
    +  returns:
    +    updates outside sets and coplanar sets of qh.newfacet_list
    +    updates qh.num_outside (count of outside points)
    +
    +  notes:
    +    qh.findbest_notsharp should be clear (extra work if set)
    +
    +  design:
    +    for all visible facets with outside set or coplanar set
    +      select a newfacet for visible facet
    +      if outside set
    +        partition outside set into new facets
    +      if coplanar set and keeping coplanar/near-inside/inside points
    +        if allpoints
    +          partition coplanar set into new facets, may be assigned outside
    +        else
    +          partition coplanar set into coplanar sets of new facets
    +    for each deleted vertex
    +      if allpoints
    +        partition vertex into new facets, may be assigned outside
    +      else
    +        partition vertex into coplanar sets of new facets
    +*/
    +void qh_partitionvisible(qhT *qh /*qh.visible_list*/, boolT allpoints, int *numoutside) {
    +  facetT *visible, *newfacet;
    +  pointT *point, **pointp;
    +  int coplanar=0, size;
    +  unsigned count;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh->ONLYmax)
    +    maximize_(qh->MINoutside, qh->max_vertex);
    +  *numoutside= 0;
    +  FORALLvisible_facets {
    +    if (!visible->outsideset && !visible->coplanarset)
    +      continue;
    +    newfacet= visible->f.replace;
    +    count= 0;
    +    while (newfacet && newfacet->visible) {
    +      newfacet= newfacet->f.replace;
    +      if (count++ > qh->facet_id)
    +        qh_infiniteloop(qh, visible);
    +    }
    +    if (!newfacet)
    +      newfacet= qh->newfacet_list;
    +    if (newfacet == qh->facet_tail) {
    +      qh_fprintf(qh, qh->ferr, 6170, "qhull precision error (qh_partitionvisible): all new facets deleted as\n        degenerate facets. Can not continue.\n");
    +      qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +    }
    +    if (visible->outsideset) {
    +      size= qh_setsize(qh, visible->outsideset);
    +      *numoutside += size;
    +      qh->num_outside -= size;
    +      FOREACHpoint_(visible->outsideset)
    +        qh_partitionpoint(qh, point, newfacet);
    +    }
    +    if (visible->coplanarset && (qh->KEEPcoplanar + qh->KEEPinside + qh->KEEPnearinside)) {
    +      size= qh_setsize(qh, visible->coplanarset);
    +      coplanar += size;
    +      FOREACHpoint_(visible->coplanarset) {
    +        if (allpoints) /* not used */
    +          qh_partitionpoint(qh, point, newfacet);
    +        else
    +          qh_partitioncoplanar(qh, point, newfacet, NULL);
    +      }
    +    }
    +  }
    +  FOREACHvertex_(qh->del_vertices) {
    +    if (vertex->point) {
    +      if (allpoints) /* not used */
    +        qh_partitionpoint(qh, vertex->point, qh->newfacet_list);
    +      else
    +        qh_partitioncoplanar(qh, vertex->point, qh->newfacet_list, NULL);
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1043,"qh_partitionvisible: partitioned %d points from outsidesets and %d points from coplanarsets\n", *numoutside, coplanar));
    +} /* partitionvisible */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_precision(qh, reason )
    +    restart on precision errors if not merging and if 'QJn'
    +*/
    +void qh_precision(qhT *qh, const char *reason) {
    +
    +  if (qh->ALLOWrestart && !qh->PREmerge && !qh->MERGEexact) {
    +    if (qh->JOGGLEmax < REALmax/2) {
    +      trace0((qh, qh->ferr, 26, "qh_precision: qhull restart because of %s\n", reason));
    +      /* May be called repeatedly if qh->ALLOWrestart */
    +      longjmp(qh->restartexit, qh_ERRprec);
    +    }
    +  }
    +} /* qh_precision */
    +
    +/*---------------------------------
    +
    +  qh_printsummary(qh, fp )
    +    prints summary to fp
    +
    +  notes:
    +    not in io_r.c so that user_eg.c can prevent io_r.c from loading
    +    qh_printsummary and qh_countfacets must match counts
    +
    +  design:
    +    determine number of points, vertices, and coplanar points
    +    print summary
    +*/
    +void qh_printsummary(qhT *qh, FILE *fp) {
    +  realT ratio, outerplane, innerplane;
    +  float cpu;
    +  int size, id, nummerged, numvertices, numcoplanars= 0, nonsimplicial=0;
    +  int goodused;
    +  facetT *facet;
    +  const char *s;
    +  int numdel= zzval_(Zdelvertextot);
    +  int numtricoplanars= 0;
    +
    +  size= qh->num_points + qh_setsize(qh, qh->other_points);
    +  numvertices= qh->num_vertices - qh_setsize(qh, qh->del_vertices);
    +  id= qh_pointid(qh, qh->GOODpointp);
    +  FORALLfacets {
    +    if (facet->coplanarset)
    +      numcoplanars += qh_setsize(qh, facet->coplanarset);
    +    if (facet->good) {
    +      if (facet->simplicial) {
    +        if (facet->keepcentrum && facet->tricoplanar)
    +          numtricoplanars++;
    +      }else if (qh_setsize(qh, facet->vertices) != qh->hull_dim)
    +        nonsimplicial++;
    +    }
    +  }
    +  if (id >=0 && qh->STOPcone-1 != id && -qh->STOPpoint-1 != id)
    +    size--;
    +  if (qh->STOPcone || qh->STOPpoint)
    +      qh_fprintf(qh, fp, 9288, "\nAt a premature exit due to 'TVn', 'TCn', 'TRn', or precision error with 'QJn'.");
    +  if (qh->UPPERdelaunay)
    +    goodused= qh->GOODvertex + qh->GOODpoint + qh->SPLITthresholds;
    +  else if (qh->DELAUNAY)
    +    goodused= qh->GOODvertex + qh->GOODpoint + qh->GOODthreshold;
    +  else
    +    goodused= qh->num_good;
    +  nummerged= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  if (qh->VORONOI) {
    +    if (qh->UPPERdelaunay)
    +      qh_fprintf(qh, fp, 9289, "\n\
    +Furthest-site Voronoi vertices by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    else
    +      qh_fprintf(qh, fp, 9290, "\n\
    +Voronoi diagram by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9291, "  Number of Voronoi regions%s: %d\n",
    +              qh->ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(qh, fp, 9292, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(qh, fp, 9293, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(qh, fp, 9294, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(qh, fp, 9295, "  Number of%s Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9296, "  Number of%s non-simplicial Voronoi vertices: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh->DELAUNAY) {
    +    if (qh->UPPERdelaunay)
    +      qh_fprintf(qh, fp, 9297, "\n\
    +Furthest-site Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    else
    +      qh_fprintf(qh, fp, 9298, "\n\
    +Delaunay triangulation by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9299, "  Number of input sites%s: %d\n",
    +              qh->ATinfinity ? " and at-infinity" : "", numvertices);
    +    if (numdel)
    +      qh_fprintf(qh, fp, 9300, "  Total number of deleted points due to merging: %d\n", numdel);
    +    if (numcoplanars - numdel > 0)
    +      qh_fprintf(qh, fp, 9301, "  Number of nearly incident points: %d\n", numcoplanars - numdel);
    +    else if (size - numvertices - numdel > 0)
    +      qh_fprintf(qh, fp, 9302, "  Total number of nearly incident points: %d\n", size - numvertices - numdel);
    +    qh_fprintf(qh, fp, 9303, "  Number of%s Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9304, "  Number of%s non-simplicial Delaunay regions: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else if (qh->HALFspace) {
    +    qh_fprintf(qh, fp, 9305, "\n\
    +Halfspace intersection by the convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9306, "  Number of halfspaces: %d\n", size);
    +    qh_fprintf(qh, fp, 9307, "  Number of non-redundant halfspaces: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh->KEEPinside && qh->KEEPcoplanar)
    +        s= "similar and redundant";
    +      else if (qh->KEEPinside)
    +        s= "redundant";
    +      else
    +        s= "similar";
    +      qh_fprintf(qh, fp, 9308, "  Number of %s halfspaces: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(qh, fp, 9309, "  Number of intersection points: %d\n", qh->num_facets - qh->num_visible);
    +    if (goodused)
    +      qh_fprintf(qh, fp, 9310, "  Number of 'good' intersection points: %d\n", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9311, "  Number of%s non-simplicial intersection points: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }else {
    +    qh_fprintf(qh, fp, 9312, "\n\
    +Convex hull of %d points in %d-d:\n\n", size, qh->hull_dim);
    +    qh_fprintf(qh, fp, 9313, "  Number of vertices: %d\n", numvertices);
    +    if (numcoplanars) {
    +      if (qh->KEEPinside && qh->KEEPcoplanar)
    +        s= "coplanar and interior";
    +      else if (qh->KEEPinside)
    +        s= "interior";
    +      else
    +        s= "coplanar";
    +      qh_fprintf(qh, fp, 9314, "  Number of %s points: %d\n", s, numcoplanars);
    +    }
    +    qh_fprintf(qh, fp, 9315, "  Number of facets: %d\n", qh->num_facets - qh->num_visible);
    +    if (goodused)
    +      qh_fprintf(qh, fp, 9316, "  Number of 'good' facets: %d\n", qh->num_good);
    +    if (nonsimplicial)
    +      qh_fprintf(qh, fp, 9317, "  Number of%s non-simplicial facets: %d\n",
    +              goodused ? " 'good'" : "", nonsimplicial);
    +  }
    +  if (numtricoplanars)
    +      qh_fprintf(qh, fp, 9318, "  Number of triangulated facets: %d\n", numtricoplanars);
    +  qh_fprintf(qh, fp, 9319, "\nStatistics for: %s | %s",
    +                      qh->rbox_command, qh->qhull_command);
    +  if (qh->ROTATErandom != INT_MIN)
    +    qh_fprintf(qh, fp, 9320, " QR%d\n\n", qh->ROTATErandom);
    +  else
    +    qh_fprintf(qh, fp, 9321, "\n\n");
    +  qh_fprintf(qh, fp, 9322, "  Number of points processed: %d\n", zzval_(Zprocessed));
    +  qh_fprintf(qh, fp, 9323, "  Number of hyperplanes created: %d\n", zzval_(Zsetplane));
    +  if (qh->DELAUNAY)
    +    qh_fprintf(qh, fp, 9324, "  Number of facets in hull: %d\n", qh->num_facets - qh->num_visible);
    +  qh_fprintf(qh, fp, 9325, "  Number of distance tests for qhull: %d\n", zzval_(Zpartition)+
    +      zzval_(Zpartitionall)+zzval_(Znumvisibility)+zzval_(Zpartcoplanar));
    +#if 0  /* NOTE: must print before printstatistics() */
    +  {realT stddev, ave;
    +  qh_fprintf(qh, fp, 9326, "  average new facet balance: %2.2g\n",
    +          wval_(Wnewbalance)/zval_(Zprocessed));
    +  stddev= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(qh, fp, 9327, "  new facet standard deviation: %2.2g\n", stddev);
    +  qh_fprintf(qh, fp, 9328, "  average partition balance: %2.2g\n",
    +          wval_(Wpbalance)/zval_(Zpbalance));
    +  stddev= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  qh_fprintf(qh, fp, 9329, "  partition standard deviation: %2.2g\n", stddev);
    +  }
    +#endif
    +  if (nummerged) {
    +    qh_fprintf(qh, fp, 9330,"  Number of distance tests for merging: %d\n",zzval_(Zbestdist)+
    +          zzval_(Zcentrumtests)+zzval_(Zdistconvex)+zzval_(Zdistcheck)+
    +          zzval_(Zdistzero));
    +    qh_fprintf(qh, fp, 9331,"  Number of distance tests for checking: %d\n",zzval_(Zcheckpart));
    +    qh_fprintf(qh, fp, 9332,"  Number of merged facets: %d\n", nummerged);
    +  }
    +  if (!qh->RANDOMoutside && qh->QHULLfinished) {
    +    cpu= (float)qh->hulltime;
    +    cpu /= (float)qh_SECticks;
    +    wval_(Wcpu)= cpu;
    +    qh_fprintf(qh, fp, 9333, "  CPU seconds to compute hull (after input): %2.4g\n", cpu);
    +  }
    +  if (qh->RERUN) {
    +    if (!qh->PREmerge && !qh->MERGEexact)
    +      qh_fprintf(qh, fp, 9334, "  Percentage of runs with precision errors: %4.1f\n",
    +           zzval_(Zretry)*100.0/qh->build_cnt);  /* careful of order */
    +  }else if (qh->JOGGLEmax < REALmax/2) {
    +    if (zzval_(Zretry))
    +      qh_fprintf(qh, fp, 9335, "  After %d retries, input joggled by: %2.2g\n",
    +         zzval_(Zretry), qh->JOGGLEmax);
    +    else
    +      qh_fprintf(qh, fp, 9336, "  Input joggled by: %2.2g\n", qh->JOGGLEmax);
    +  }
    +  if (qh->totarea != 0.0)
    +    qh_fprintf(qh, fp, 9337, "  %s facet area:   %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh->totarea);
    +  if (qh->totvol != 0.0)
    +    qh_fprintf(qh, fp, 9338, "  %s volume:       %2.8g\n",
    +            zzval_(Ztotmerge) ? "Approximate" : "Total", qh->totvol);
    +  if (qh->MERGING) {
    +    qh_outerinner(qh, NULL, &outerplane, &innerplane);
    +    if (outerplane > 2 * qh->DISTround) {
    +      qh_fprintf(qh, fp, 9339, "  Maximum distance of %spoint above facet: %2.2g",
    +            (qh->QHULLfinished ? "" : "merged "), outerplane);
    +      ratio= outerplane/(qh->ONEmerge + qh->DISTround);
    +      /* don't report ratio if MINoutside is large */
    +      if (ratio > 0.05 && 2* qh->ONEmerge > qh->MINoutside && qh->JOGGLEmax > REALmax/2)
    +        qh_fprintf(qh, fp, 9340, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(qh, fp, 9341, "\n");
    +    }
    +    if (innerplane < -2 * qh->DISTround) {
    +      qh_fprintf(qh, fp, 9342, "  Maximum distance of %svertex below facet: %2.2g",
    +            (qh->QHULLfinished ? "" : "merged "), innerplane);
    +      ratio= -innerplane/(qh->ONEmerge+qh->DISTround);
    +      if (ratio > 0.05 && qh->JOGGLEmax > REALmax/2)
    +        qh_fprintf(qh, fp, 9343, " (%.1fx)\n", ratio);
    +      else
    +        qh_fprintf(qh, fp, 9344, "\n");
    +    }
    +  }
    +  qh_fprintf(qh, fp, 9345, "\n");
    +} /* printsummary */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/libqhull_r.h b/xs/src/qhull/src/libqhull_r/libqhull_r.h
    new file mode 100644
    index 0000000000..363e6da6a7
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/libqhull_r.h
    @@ -0,0 +1,1134 @@
    +/*
      ---------------------------------
    +
    +   libqhull_r.h
    +   user-level header file for using qhull.a library
    +
    +   see qh-qhull_r.htm, qhull_ra.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/libqhull_r.h#8 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +
    +   includes function prototypes for libqhull_r.c, geom_r.c, global_r.c, io_r.c, user.c
    +
    +   use mem_r.h for mem_r.c
    +   use qset_r.h for qset_r.c
    +
    +   see unix_r.c for an example of using libqhull_r.h
    +
    +   recompile qhull if you change this file
    +*/
    +
    +#ifndef qhDEFlibqhull
    +#define qhDEFlibqhull 1
    +
    +/*=========================== -included files ==============*/
    +
    +/* user_r.h first for QHULL_CRTDBG */
    +#include "user_r.h"      /* user definable constants (e.g., realT). */
    +
    +#include "mem_r.h"   /* Needed for qhT in libqhull_r.h */
    +#include "qset_r.h"   /* Needed for QHULL_LIB_CHECK */
    +/* include stat_r.h after defining boolT.  statT needed for qhT in libqhull_r.h */
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifndef __STDC__
    +#ifndef __cplusplus
    +#if     !_MSC_VER
    +#error  Neither __STDC__ nor __cplusplus is defined.  Please use strict ANSI C or C++ to compile
    +#error  Qhull.  You may need to turn off compiler extensions in your project configuration.  If
    +#error  your compiler is a standard C compiler, you can delete this warning from libqhull_r.h
    +#endif
    +#endif
    +#endif
    +
    +/*============ constants and basic types ====================*/
    +
    +extern const char qh_version[]; /* defined in global_r.c */
    +extern const char qh_version2[]; /* defined in global_r.c */
    +
    +/*----------------------------------
    +
    +  coordT
    +    coordinates and coefficients are stored as realT (i.e., double)
    +
    +  notes:
    +    Qhull works well if realT is 'float'.  If so joggle (QJ) is not effective.
    +
    +    Could use 'float' for data and 'double' for calculations (realT vs. coordT)
    +      This requires many type casts, and adjusted error bounds.
    +      Also C compilers may do expressions in double anyway.
    +*/
    +#define coordT realT
    +
    +/*----------------------------------
    +
    +  pointT
    +    a point is an array of coordinates, usually qh.hull_dim
    +    qh_pointid returns
    +      qh_IDnone if point==0 or qh is undefined
    +      qh_IDinterior for qh.interior_point
    +      qh_IDunknown if point is neither in qh.first_point... nor qh.other_points
    +
    +  notes:
    +    qh.STOPcone and qh.STOPpoint assume that qh_IDunknown==-1 (other negative numbers indicate points)
    +    qh_IDunknown is also returned by getid_() for unknown facet, ridge, or vertex
    +*/
    +#define pointT coordT
    +typedef enum
    +{
    +    qh_IDnone = -3, qh_IDinterior = -2, qh_IDunknown = -1
    +}
    +qh_pointT;
    +
    +/*----------------------------------
    +
    +  flagT
    +    Boolean flag as a bit
    +*/
    +#define flagT unsigned int
    +
    +/*----------------------------------
    +
    +  boolT
    +    boolean value, either True or False
    +
    +  notes:
    +    needed for portability
    +    Use qh_False/qh_True as synonyms
    +*/
    +#define boolT unsigned int
    +#ifdef False
    +#undef False
    +#endif
    +#ifdef True
    +#undef True
    +#endif
    +#define False 0
    +#define True 1
    +#define qh_False 0
    +#define qh_True 1
    +
    +#include "stat_r.h"  /* needs boolT */
    +
    +/*----------------------------------
    +
    +  qh_CENTER
    +    to distinguish facet->center
    +*/
    +typedef enum
    +{
    +    qh_ASnone = 0,   /* If not MERGING and not VORONOI */
    +    qh_ASvoronoi,    /* Set by qh_clearcenters on qh_prepare_output, or if not MERGING and VORONOI */
    +    qh_AScentrum     /* If MERGING (assumed during merging) */
    +}
    +qh_CENTER;
    +
    +/*----------------------------------
    +
    +  qh_PRINT
    +    output formats for printing (qh.PRINTout).
    +    'Fa' 'FV' 'Fc' 'FC'
    +
    +
    +   notes:
    +   some of these names are similar to qhT names.  The similar names are only
    +   used in switch statements in qh_printbegin() etc.
    +*/
    +typedef enum {qh_PRINTnone= 0,
    +  qh_PRINTarea, qh_PRINTaverage,           /* 'Fa' 'FV' 'Fc' 'FC' */
    +  qh_PRINTcoplanars, qh_PRINTcentrums,
    +  qh_PRINTfacets, qh_PRINTfacets_xridge,   /* 'f' 'FF' 'G' 'FI' 'Fi' 'Fn' */
    +  qh_PRINTgeom, qh_PRINTids, qh_PRINTinner, qh_PRINTneighbors,
    +  qh_PRINTnormals, qh_PRINTouter, qh_PRINTmaple, /* 'n' 'Fo' 'i' 'm' 'Fm' 'FM', 'o' */
    +  qh_PRINTincidences, qh_PRINTmathematica, qh_PRINTmerges, qh_PRINToff,
    +  qh_PRINToptions, qh_PRINTpointintersect, /* 'FO' 'Fp' 'FP' 'p' 'FQ' 'FS' */
    +  qh_PRINTpointnearest, qh_PRINTpoints, qh_PRINTqhull, qh_PRINTsize,
    +  qh_PRINTsummary, qh_PRINTtriangles,      /* 'Fs' 'Ft' 'Fv' 'FN' 'Fx' */
    +  qh_PRINTvertices, qh_PRINTvneighbors, qh_PRINTextremes,
    +  qh_PRINTEND} qh_PRINT;
    +
    +/*----------------------------------
    +
    +  qh_ALL
    +    argument flag for selecting everything
    +*/
    +#define qh_ALL      True
    +#define qh_NOupper  True     /* argument for qh_findbest */
    +#define qh_IScheckmax  True     /* argument for qh_findbesthorizon */
    +#define qh_ISnewfacets  True     /* argument for qh_findbest */
    +#define qh_RESETvisible  True     /* argument for qh_resetlists */
    +
    +/*----------------------------------
    +
    +  qh_ERR
    +    Qhull exit codes, for indicating errors
    +    See: MSG_ERROR and MSG_WARNING [user.h]
    +*/
    +#define qh_ERRnone  0    /* no error occurred during qhull */
    +#define qh_ERRinput 1    /* input inconsistency */
    +#define qh_ERRsingular 2 /* singular input data */
    +#define qh_ERRprec  3    /* precision error */
    +#define qh_ERRmem   4    /* insufficient memory, matches mem_r.h */
    +#define qh_ERRqhull 5    /* internal error detected, matches mem_r.h */
    +
    +/*----------------------------------
    +
    +qh_FILEstderr
    +Fake stderr to distinguish error output from normal output
    +For C++ interface.  Must redefine qh_fprintf_qhull
    +*/
    +#define qh_FILEstderr ((FILE*)1)
    +
    +/* ============ -structures- ====================
    +   each of the following structures is defined by a typedef
    +   all realT and coordT fields occur at the beginning of a structure
    +        (otherwise space may be wasted due to alignment)
    +   define all flags together and pack into 32-bit number
    +
    +   DEFqhT and DEFsetT are likewise defined in
    +   mem_r.h, qset_r.h, and stat_r.h.
    +
    +*/
    +
    +typedef struct vertexT vertexT;
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;          /* defined below */
    +#endif
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset_r.h */
    +#endif
    +
    +/*----------------------------------
    +
    +  facetT
    +    defines a facet
    +
    +  notes:
    +   qhull() generates the hull as a list of facets.
    +
    +  topological information:
    +    f.previous,next     doubly-linked list of facets
    +    f.vertices          set of vertices
    +    f.ridges            set of ridges
    +    f.neighbors         set of neighbors
    +    f.toporient         True if facet has top-orientation (else bottom)
    +
    +  geometric information:
    +    f.offset,normal     hyperplane equation
    +    f.maxoutside        offset to outer plane -- all points inside
    +    f.center            centrum for testing convexity or Voronoi center for output
    +    f.simplicial        True if facet is simplicial
    +    f.flipped           True if facet does not include qh.interior_point
    +
    +  for constructing hull:
    +    f.visible           True if facet on list of visible facets (will be deleted)
    +    f.newfacet          True if facet on list of newly created facets
    +    f.coplanarset       set of points coplanar with this facet
    +                        (includes near-inside points for later testing)
    +    f.outsideset        set of points outside of this facet
    +    f.furthestdist      distance to furthest point of outside set
    +    f.visitid           marks visited facets during a loop
    +    f.replace           replacement facet for to-be-deleted, visible facets
    +    f.samecycle,newcycle cycle of facets for merging into horizon facet
    +
    +  see below for other flags and fields
    +*/
    +struct facetT {
    +#if !qh_COMPUTEfurthest
    +  coordT   furthestdist;/* distance to furthest point of outsideset */
    +#endif
    +#if qh_MAXoutside
    +  coordT   maxoutside;  /* max computed distance of point to facet
    +                        Before QHULLfinished this is an approximation
    +                        since maxdist not always set for mergefacet
    +                        Actual outer plane is +DISTround and
    +                        computed outer plane is +2*DISTround */
    +#endif
    +  coordT   offset;      /* exact offset of hyperplane from origin */
    +  coordT  *normal;      /* normal of hyperplane, hull_dim coefficients */
    +                        /*   if ->tricoplanar, shared with a neighbor */
    +  union {               /* in order of testing */
    +   realT   area;        /* area of facet, only in io_r.c if  ->isarea */
    +   facetT *replace;     /*  replacement facet if ->visible and NEWfacets
    +                             is NULL only if qh_mergedegen_redundant or interior */
    +   facetT *samecycle;   /*  cycle of facets from the same visible/horizon intersection,
    +                             if ->newfacet */
    +   facetT *newcycle;    /*  in horizon facet, current samecycle of new facets */
    +   facetT *trivisible;  /* visible facet for ->tricoplanar facets during qh_triangulate() */
    +   facetT *triowner;    /* owner facet for ->tricoplanar, !isarea facets w/ ->keepcentrum */
    +  }f;
    +  coordT  *center;      /* set according to qh.CENTERtype */
    +                        /*   qh_ASnone:    no center (not MERGING) */
    +                        /*   qh_AScentrum: centrum for testing convexity (qh_getcentrum) */
    +                        /*                 assumed qh_AScentrum while merging */
    +                        /*   qh_ASvoronoi: Voronoi center (qh_facetcenter) */
    +                        /* after constructing the hull, it may be changed (qh_clearcenter) */
    +                        /* if tricoplanar and !keepcentrum, shared with a neighbor */
    +  facetT  *previous;    /* previous facet in the facet_list */
    +  facetT  *next;        /* next facet in the facet_list */
    +  setT    *vertices;    /* vertices for this facet, inverse sorted by ID
    +                           if simplicial, 1st vertex was apex/furthest */
    +  setT    *ridges;      /* explicit ridges for nonsimplicial facets.
    +                           for simplicial facets, neighbors define the ridges */
    +  setT    *neighbors;   /* neighbors of the facet.  If simplicial, the kth
    +                           neighbor is opposite the kth vertex, and the first
    +                           neighbor is the horizon facet for the first vertex*/
    +  setT    *outsideset;  /* set of points outside this facet
    +                           if non-empty, last point is furthest
    +                           if NARROWhull, includes coplanars for partitioning*/
    +  setT    *coplanarset; /* set of points coplanar with this facet
    +                           > qh.min_vertex and <= facet->max_outside
    +                           a point is assigned to the furthest facet
    +                           if non-empty, last point is furthest away */
    +  unsigned visitid;     /* visit_id, for visiting all neighbors,
    +                           all uses are independent */
    +  unsigned id;          /* unique identifier from qh.facet_id */
    +  unsigned nummerge:9;  /* number of merges */
    +#define qh_MAXnummerge 511 /*     2^9-1, 32 flags total, see "flags:" in io_r.c */
    +  flagT    tricoplanar:1; /* True if TRIangulate and simplicial and coplanar with a neighbor */
    +                          /*   all tricoplanars share the same apex */
    +                          /*   all tricoplanars share the same ->center, ->normal, ->offset, ->maxoutside */
    +                          /*     ->keepcentrum is true for the owner.  It has the ->coplanareset */
    +                          /*   if ->degenerate, does not span facet (one logical ridge) */
    +                          /*   during qh_triangulate, f.trivisible points to original facet */
    +  flagT    newfacet:1;  /* True if facet on qh.newfacet_list (new or merged) */
    +  flagT    visible:1;   /* True if visible facet (will be deleted) */
    +  flagT    toporient:1; /* True if created with top orientation
    +                           after merging, use ridge orientation */
    +  flagT    simplicial:1;/* True if simplicial facet, ->ridges may be implicit */
    +  flagT    seen:1;      /* used to perform operations only once, like visitid */
    +  flagT    seen2:1;     /* used to perform operations only once, like visitid */
    +  flagT    flipped:1;   /* True if facet is flipped */
    +  flagT    upperdelaunay:1; /* True if facet is upper envelope of Delaunay triangulation */
    +  flagT    notfurthest:1; /* True if last point of outsideset is not furthest*/
    +
    +/*-------- flags primarily for output ---------*/
    +  flagT    good:1;      /* True if a facet marked good for output */
    +  flagT    isarea:1;    /* True if facet->f.area is defined */
    +
    +/*-------- flags for merging ------------------*/
    +  flagT    dupridge:1;  /* True if duplicate ridge in facet */
    +  flagT    mergeridge:1; /* True if facet or neighbor contains a qh_MERGEridge
    +                            ->normal defined (also defined for mergeridge2) */
    +  flagT    mergeridge2:1; /* True if neighbor contains a qh_MERGEridge (qhT *qh, mark_dupridges */
    +  flagT    coplanar:1;  /* True if horizon facet is coplanar at last use */
    +  flagT     mergehorizon:1; /* True if will merge into horizon (->coplanar) */
    +  flagT     cycledone:1;/* True if mergecycle_all already done */
    +  flagT    tested:1;    /* True if facet convexity has been tested (false after merge */
    +  flagT    keepcentrum:1; /* True if keep old centrum after a merge, or marks owner for ->tricoplanar */
    +  flagT    newmerge:1;  /* True if facet is newly merged for reducevertices */
    +  flagT    degenerate:1; /* True if facet is degenerate (degen_mergeset or ->tricoplanar) */
    +  flagT    redundant:1;  /* True if facet is redundant (degen_mergeset) */
    +};
    +
    +
    +/*----------------------------------
    +
    +  ridgeT
    +    defines a ridge
    +
    +  notes:
    +  a ridge is hull_dim-1 simplex between two neighboring facets.  If the
    +  facets are non-simplicial, there may be more than one ridge between
    +  two facets.  E.G. a 4-d hypercube has two triangles between each pair
    +  of neighboring facets.
    +
    +  topological information:
    +    vertices            a set of vertices
    +    top,bottom          neighboring facets with orientation
    +
    +  geometric information:
    +    tested              True if ridge is clearly convex
    +    nonconvex           True if ridge is non-convex
    +*/
    +struct ridgeT {
    +  setT    *vertices;    /* vertices belonging to this ridge, inverse sorted by ID
    +                           NULL if a degen ridge (matchsame) */
    +  facetT  *top;         /* top facet this ridge is part of */
    +  facetT  *bottom;      /* bottom facet this ridge is part of */
    +  unsigned id;          /* unique identifier.  Same size as vertex_id and ridge_id */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    tested:1;    /* True when ridge is tested for convexity */
    +  flagT    nonconvex:1; /* True if getmergeset detected a non-convex neighbor
    +                           only one ridge between neighbors may have nonconvex */
    +};
    +
    +/*----------------------------------
    +
    +  vertexT
    +     defines a vertex
    +
    +  topological information:
    +    next,previous       doubly-linked list of all vertices
    +    neighbors           set of adjacent facets (only if qh.VERTEXneighbors)
    +
    +  geometric information:
    +    point               array of DIM3 coordinates
    +*/
    +struct vertexT {
    +  vertexT *next;        /* next vertex in vertex_list */
    +  vertexT *previous;    /* previous vertex in vertex_list */
    +  pointT  *point;       /* hull_dim coordinates (coordT) */
    +  setT    *neighbors;   /* neighboring facets of vertex, qh_vertexneighbors()
    +                           inits in io_r.c or after first merge */
    +  unsigned id;          /* unique identifier.  Same size as qh.vertex_id and qh.ridge_id */
    +  unsigned visitid;    /* for use with qh.vertex_visit, size must match */
    +  flagT    seen:1;      /* used to perform operations only once */
    +  flagT    seen2:1;     /* another seen flag */
    +  flagT    delridge:1;  /* vertex was part of a deleted ridge */
    +  flagT    deleted:1;   /* true if vertex on qh.del_vertices */
    +  flagT    newlist:1;   /* true if vertex on qh.newvertex_list */
    +};
    +
    +/*======= -global variables -qh ============================*/
    +
    +/*----------------------------------
    +
    +  qhT
    +   All global variables for qhull are in qhT.  It includes qhmemT, qhstatT, and rbox globals
    +
    +   This version of Qhull is reentrant, but it is not thread-safe.
    +
    +   Do not run separate threads on the same instance of qhT.
    +
    +   QHULL_LIB_CHECK checks that a program and the corresponding
    +   qhull library were built with the same type of header files.
    +*/
    +
    +#define QHULL_NON_REENTRANT 0
    +#define QHULL_QH_POINTER 1
    +#define QHULL_REENTRANT 2
    +
    +#define QHULL_LIB_TYPE QHULL_REENTRANT
    +
    +#define QHULL_LIB_CHECK qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), sizeof(setT), sizeof(qhmemT));
    +#define QHULL_LIB_CHECK_RBOX qh_lib_check(QHULL_LIB_TYPE, sizeof(qhT), sizeof(vertexT), sizeof(ridgeT), sizeof(facetT), 0, 0);
    +
    +struct qhT {
    +
    +/*----------------------------------
    +
    +  qh constants
    +    configuration flags and constants for Qhull
    +
    +  notes:
    +    The user configures Qhull by defining flags.  They are
    +    copied into qh by qh_setflags().  qh-quick_r.htm#options defines the flags.
    +*/
    +  boolT ALLpoints;        /* true 'Qs' if search all points for initial simplex */
    +  boolT ANGLEmerge;       /* true 'Qa' if sort potential merges by angle */
    +  boolT APPROXhull;       /* true 'Wn' if MINoutside set */
    +  realT   MINoutside;     /*   'Wn' min. distance for an outside point */
    +  boolT ANNOTATEoutput;   /* true 'Ta' if annotate output with message codes */
    +  boolT ATinfinity;       /* true 'Qz' if point num_points-1 is "at-infinity"
    +                             for improving precision in Delaunay triangulations */
    +  boolT AVOIDold;         /* true 'Q4' if avoid old->new merges */
    +  boolT BESToutside;      /* true 'Qf' if partition points into best outsideset */
    +  boolT CDDinput;         /* true 'Pc' if input uses CDD format (1.0/offset first) */
    +  boolT CDDoutput;        /* true 'PC' if print normals in CDD format (offset first) */
    +  boolT CHECKfrequently;  /* true 'Tc' if checking frequently */
    +  realT premerge_cos;     /*   'A-n'   cos_max when pre merging */
    +  realT postmerge_cos;    /*   'An'    cos_max when post merging */
    +  boolT DELAUNAY;         /* true 'd' if computing DELAUNAY triangulation */
    +  boolT DOintersections;  /* true 'Gh' if print hyperplane intersections */
    +  int   DROPdim;          /* drops dim 'GDn' for 4-d -> 3-d output */
    +  boolT FORCEoutput;      /* true 'Po' if forcing output despite degeneracies */
    +  int   GOODpoint;        /* 1+n for 'QGn', good facet if visible/not(-) from point n*/
    +  pointT *GOODpointp;     /*   the actual point */
    +  boolT GOODthreshold;    /* true if qh.lower_threshold/upper_threshold defined
    +                             false if qh.SPLITthreshold */
    +  int   GOODvertex;       /* 1+n, good facet if vertex for point n */
    +  pointT *GOODvertexp;     /*   the actual point */
    +  boolT HALFspace;        /* true 'Hn,n,n' if halfspace intersection */
    +  boolT ISqhullQh;        /* Set by Qhull.cpp on initialization */
    +  int   IStracing;        /* trace execution, 0=none, 1=least, 4=most, -1=events */
    +  int   KEEParea;         /* 'PAn' number of largest facets to keep */
    +  boolT KEEPcoplanar;     /* true 'Qc' if keeping nearest facet for coplanar points */
    +  boolT KEEPinside;       /* true 'Qi' if keeping nearest facet for inside points
    +                              set automatically if 'd Qc' */
    +  int   KEEPmerge;        /* 'PMn' number of facets to keep with most merges */
    +  realT KEEPminArea;      /* 'PFn' minimum facet area to keep */
    +  realT MAXcoplanar;      /* 'Un' max distance below a facet to be coplanar*/
    +  boolT MERGEexact;       /* true 'Qx' if exact merges (coplanar, degen, dupridge, flipped) */
    +  boolT MERGEindependent; /* true 'Q2' if merging independent sets */
    +  boolT MERGING;          /* true if exact-, pre- or post-merging, with angle and centrum tests */
    +  realT   premerge_centrum;  /*   'C-n' centrum_radius when pre merging.  Default is round-off */
    +  realT   postmerge_centrum; /*   'Cn' centrum_radius when post merging.  Default is round-off */
    +  boolT MERGEvertices;    /* true 'Q3' if merging redundant vertices */
    +  realT MINvisible;       /* 'Vn' min. distance for a facet to be visible */
    +  boolT NOnarrow;         /* true 'Q10' if no special processing for narrow distributions */
    +  boolT NOnearinside;     /* true 'Q8' if ignore near-inside points when partitioning */
    +  boolT NOpremerge;       /* true 'Q0' if no defaults for C-0 or Qx */
    +  boolT NOwide;           /* true 'Q12' if no error on wide merge due to duplicate ridge */
    +  boolT ONLYgood;         /* true 'Qg' if process points with good visible or horizon facets */
    +  boolT ONLYmax;          /* true 'Qm' if only process points that increase max_outside */
    +  boolT PICKfurthest;     /* true 'Q9' if process furthest of furthest points*/
    +  boolT POSTmerge;        /* true if merging after buildhull (Cn or An) */
    +  boolT PREmerge;         /* true if merging during buildhull (C-n or A-n) */
    +                        /* NOTE: some of these names are similar to qh_PRINT names */
    +  boolT PRINTcentrums;    /* true 'Gc' if printing centrums */
    +  boolT PRINTcoplanar;    /* true 'Gp' if printing coplanar points */
    +  int   PRINTdim;         /* print dimension for Geomview output */
    +  boolT PRINTdots;        /* true 'Ga' if printing all points as dots */
    +  boolT PRINTgood;        /* true 'Pg' if printing good facets */
    +  boolT PRINTinner;       /* true 'Gi' if printing inner planes */
    +  boolT PRINTneighbors;   /* true 'PG' if printing neighbors of good facets */
    +  boolT PRINTnoplanes;    /* true 'Gn' if printing no planes */
    +  boolT PRINToptions1st;  /* true 'FO' if printing options to stderr */
    +  boolT PRINTouter;       /* true 'Go' if printing outer planes */
    +  boolT PRINTprecision;   /* false 'Pp' if not reporting precision problems */
    +  qh_PRINT PRINTout[qh_PRINTEND]; /* list of output formats to print */
    +  boolT PRINTridges;      /* true 'Gr' if print ridges */
    +  boolT PRINTspheres;     /* true 'Gv' if print vertices as spheres */
    +  boolT PRINTstatistics;  /* true 'Ts' if printing statistics to stderr */
    +  boolT PRINTsummary;     /* true 's' if printing summary to stderr */
    +  boolT PRINTtransparent; /* true 'Gt' if print transparent outer ridges */
    +  boolT PROJECTdelaunay;  /* true if DELAUNAY, no readpoints() and
    +                             need projectinput() for Delaunay in qh_init_B */
    +  int   PROJECTinput;     /* number of projected dimensions 'bn:0Bn:0' */
    +  boolT QUICKhelp;        /* true if quick help message for degen input */
    +  boolT RANDOMdist;       /* true if randomly change distplane and setfacetplane */
    +  realT RANDOMfactor;     /*    maximum random perturbation */
    +  realT RANDOMa;          /*    qh_randomfactor is randr * RANDOMa + RANDOMb */
    +  realT RANDOMb;
    +  boolT RANDOMoutside;    /* true if select a random outside point */
    +  int   REPORTfreq;       /* buildtracing reports every n facets */
    +  int   REPORTfreq2;      /* tracemerging reports every REPORTfreq/2 facets */
    +  int   RERUN;            /* 'TRn' rerun qhull n times (qh.build_cnt) */
    +  int   ROTATErandom;     /* 'QRn' seed, 0 time, >= rotate input */
    +  boolT SCALEinput;       /* true 'Qbk' if scaling input */
    +  boolT SCALElast;        /* true 'Qbb' if scale last coord to max prev coord */
    +  boolT SETroundoff;      /* true 'E' if qh.DISTround is predefined */
    +  boolT SKIPcheckmax;     /* true 'Q5' if skip qh_check_maxout */
    +  boolT SKIPconvex;       /* true 'Q6' if skip convexity testing during pre-merge */
    +  boolT SPLITthresholds;  /* true if upper_/lower_threshold defines a region
    +                               used only for printing (!for qh.ONLYgood) */
    +  int   STOPcone;         /* 'TCn' 1+n for stopping after cone for point n */
    +                          /*       also used by qh_build_withresart for err exit*/
    +  int   STOPpoint;        /* 'TVn' 'TV-n' 1+n for stopping after/before(-)
    +                                        adding point n */
    +  int   TESTpoints;       /* 'QTn' num of test points after qh.num_points.  Test points always coplanar. */
    +  boolT TESTvneighbors;   /*  true 'Qv' if test vertex neighbors at end */
    +  int   TRACElevel;       /* 'Tn' conditional IStracing level */
    +  int   TRACElastrun;     /*  qh.TRACElevel applies to last qh.RERUN */
    +  int   TRACEpoint;       /* 'TPn' start tracing when point n is a vertex */
    +  realT TRACEdist;        /* 'TWn' start tracing when merge distance too big */
    +  int   TRACEmerge;       /* 'TMn' start tracing before this merge */
    +  boolT TRIangulate;      /* true 'Qt' if triangulate non-simplicial facets */
    +  boolT TRInormals;       /* true 'Q11' if triangulate duplicates ->normal and ->center (sets Qt) */
    +  boolT UPPERdelaunay;    /* true 'Qu' if computing furthest-site Delaunay */
    +  boolT USEstdout;        /* true 'Tz' if using stdout instead of stderr */
    +  boolT VERIFYoutput;     /* true 'Tv' if verify output at end of qhull */
    +  boolT VIRTUALmemory;    /* true 'Q7' if depth-first processing in buildhull */
    +  boolT VORONOI;          /* true 'v' if computing Voronoi diagram */
    +
    +  /*--------input constants ---------*/
    +  realT AREAfactor;       /* 1/(hull_dim-1)! for converting det's to area */
    +  boolT DOcheckmax;       /* true if calling qh_check_maxout (qhT *qh, qh_initqhull_globals) */
    +  char  *feasible_string;  /* feasible point 'Hn,n,n' for halfspace intersection */
    +  coordT *feasible_point;  /*    as coordinates, both malloc'd */
    +  boolT GETarea;          /* true 'Fa', 'FA', 'FS', 'PAn', 'PFn' if compute facet area/Voronoi volume in io_r.c */
    +  boolT KEEPnearinside;   /* true if near-inside points in coplanarset */
    +  int   hull_dim;         /* dimension of hull, set by initbuffers */
    +  int   input_dim;        /* dimension of input, set by initbuffers */
    +  int   num_points;       /* number of input points */
    +  pointT *first_point;    /* array of input points, see POINTSmalloc */
    +  boolT POINTSmalloc;     /*   true if qh.first_point/num_points allocated */
    +  pointT *input_points;   /* copy of original qh.first_point for input points for qh_joggleinput */
    +  boolT input_malloc;     /* true if qh.input_points malloc'd */
    +  char  qhull_command[256];/* command line that invoked this program */
    +  int   qhull_commandsiz2; /*    size of qhull_command at qh_clear_outputflags */
    +  char  rbox_command[256]; /* command line that produced the input points */
    +  char  qhull_options[512];/* descriptive list of options */
    +  int   qhull_optionlen;  /*    length of last line */
    +  int   qhull_optionsiz;  /*    size of qhull_options at qh_build_withrestart */
    +  int   qhull_optionsiz2; /*    size of qhull_options at qh_clear_outputflags */
    +  int   run_id;           /* non-zero, random identifier for this instance of qhull */
    +  boolT VERTEXneighbors;  /* true if maintaining vertex neighbors */
    +  boolT ZEROcentrum;      /* true if 'C-0' or 'C-0 Qx'.  sets ZEROall_ok */
    +  realT *upper_threshold; /* don't print if facet->normal[k]>=upper_threshold[k]
    +                             must set either GOODthreshold or SPLITthreshold
    +                             if Delaunay, default is 0.0 for upper envelope */
    +  realT *lower_threshold; /* don't print if facet->normal[k] <=lower_threshold[k] */
    +  realT *upper_bound;     /* scale point[k] to new upper bound */
    +  realT *lower_bound;     /* scale point[k] to new lower bound
    +                             project if both upper_ and lower_bound == 0 */
    +
    +/*----------------------------------
    +
    +  qh precision constants
    +    precision constants for Qhull
    +
    +  notes:
    +    qh_detroundoff(qh) computes the maximum roundoff error for distance
    +    and other computations.  It also sets default values for the
    +    qh constants above.
    +*/
    +  realT ANGLEround;       /* max round off error for angles */
    +  realT centrum_radius;   /* max centrum radius for convexity (roundoff added) */
    +  realT cos_max;          /* max cosine for convexity (roundoff added) */
    +  realT DISTround;        /* max round off error for distances, 'E' overrides qh_distround() */
    +  realT MAXabs_coord;     /* max absolute coordinate */
    +  realT MAXlastcoord;     /* max last coordinate for qh_scalelast */
    +  realT MAXsumcoord;      /* max sum of coordinates */
    +  realT MAXwidth;         /* max rectilinear width of point coordinates */
    +  realT MINdenom_1;       /* min. abs. value for 1/x */
    +  realT MINdenom;         /*    use divzero if denominator < MINdenom */
    +  realT MINdenom_1_2;     /* min. abs. val for 1/x that allows normalization */
    +  realT MINdenom_2;       /*    use divzero if denominator < MINdenom_2 */
    +  realT MINlastcoord;     /* min. last coordinate for qh_scalelast */
    +  boolT NARROWhull;       /* set in qh_initialhull if angle < qh_MAXnarrow */
    +  realT *NEARzero;        /* hull_dim array for near zero in gausselim */
    +  realT NEARinside;       /* keep points for qh_check_maxout if close to facet */
    +  realT ONEmerge;         /* max distance for merging simplicial facets */
    +  realT outside_err;      /* application's epsilon for coplanar points
    +                             qh_check_bestdist() qh_check_points() reports error if point outside */
    +  realT WIDEfacet;        /* size of wide facet for skipping ridge in
    +                             area computation and locking centrum */
    +
    +/*----------------------------------
    +
    +  qh internal constants
    +    internal constants for Qhull
    +*/
    +  char qhull[sizeof("qhull")]; /* "qhull" for checking ownership while debugging */
    +  jmp_buf errexit;        /* exit label for qh_errexit, defined by setjmp() and NOerrexit */
    +  char jmpXtra[40];       /* extra bytes in case jmp_buf is defined wrong by compiler */
    +  jmp_buf restartexit;    /* restart label for qh_errexit, defined by setjmp() and ALLOWrestart */
    +  char jmpXtra2[40];      /* extra bytes in case jmp_buf is defined wrong by compiler*/
    +  FILE *fin;              /* pointer to input file, init by qh_initqhull_start */
    +  FILE *fout;             /* pointer to output file */
    +  FILE *ferr;             /* pointer to error file */
    +  pointT *interior_point; /* center point of the initial simplex*/
    +  int normal_size;     /* size in bytes for facet normals and point coords*/
    +  int center_size;     /* size in bytes for Voronoi centers */
    +  int   TEMPsize;         /* size for small, temporary sets (in quick mem) */
    +
    +/*----------------------------------
    +
    +  qh facet and vertex lists
    +    defines lists of facets, new facets, visible facets, vertices, and
    +    new vertices.  Includes counts, next ids, and trace ids.
    +  see:
    +    qh_resetlists()
    +*/
    +  facetT *facet_list;     /* first facet */
    +  facetT  *facet_tail;     /* end of facet_list (dummy facet) */
    +  facetT *facet_next;     /* next facet for buildhull()
    +                             previous facets do not have outside sets
    +                             NARROWhull: previous facets may have coplanar outside sets for qh_outcoplanar */
    +  facetT *newfacet_list;  /* list of new facets to end of facet_list */
    +  facetT *visible_list;   /* list of visible facets preceding newfacet_list,
    +                             facet->visible set */
    +  int       num_visible;  /* current number of visible facets */
    +  unsigned tracefacet_id;  /* set at init, then can print whenever */
    +  facetT *tracefacet;     /*   set in newfacet/mergefacet, undone in delfacet*/
    +  unsigned tracevertex_id;  /* set at buildtracing, can print whenever */
    +  vertexT *tracevertex;     /*   set in newvertex, undone in delvertex*/
    +  vertexT *vertex_list;     /* list of all vertices, to vertex_tail */
    +  vertexT  *vertex_tail;    /*      end of vertex_list (dummy vertex) */
    +  vertexT *newvertex_list; /* list of vertices in newfacet_list, to vertex_tail
    +                             all vertices have 'newlist' set */
    +  int   num_facets;       /* number of facets in facet_list
    +                             includes visible faces (num_visible) */
    +  int   num_vertices;     /* number of vertices in facet_list */
    +  int   num_outside;      /* number of points in outsidesets (for tracing and RANDOMoutside)
    +                               includes coplanar outsideset points for NARROWhull/qh_outcoplanar() */
    +  int   num_good;         /* number of good facets (after findgood_all) */
    +  unsigned facet_id;      /* ID of next, new facet from newfacet() */
    +  unsigned ridge_id;      /* ID of next, new ridge from newridge() */
    +  unsigned vertex_id;     /* ID of next, new vertex from newvertex() */
    +
    +/*----------------------------------
    +
    +  qh global variables
    +    defines minimum and maximum distances, next visit ids, several flags,
    +    and other global variables.
    +    initialize in qh_initbuild or qh_maxmin if used in qh_buildhull
    +*/
    +  unsigned long hulltime; /* ignore time to set up input and randomize */
    +                          /*   use unsigned to avoid wrap-around errors */
    +  boolT ALLOWrestart;     /* true if qh_precision can use qh.restartexit */
    +  int   build_cnt;        /* number of calls to qh_initbuild */
    +  qh_CENTER CENTERtype;   /* current type of facet->center, qh_CENTER */
    +  int   furthest_id;      /* pointid of furthest point, for tracing */
    +  facetT *GOODclosest;    /* closest facet to GOODthreshold in qh_findgood */
    +  boolT hasAreaVolume;    /* true if totarea, totvol was defined by qh_getarea */
    +  boolT hasTriangulation; /* true if triangulation created by qh_triangulate */
    +  realT JOGGLEmax;        /* set 'QJn' if randomly joggle input */
    +  boolT maxoutdone;       /* set qh_check_maxout(), cleared by qh_addpoint() */
    +  realT max_outside;      /* maximum distance from a point to a facet,
    +                               before roundoff, not simplicial vertices
    +                               actual outer plane is +DISTround and
    +                               computed outer plane is +2*DISTround */
    +  realT max_vertex;       /* maximum distance (>0) from vertex to a facet,
    +                               before roundoff, due to a merge */
    +  realT min_vertex;       /* minimum distance (<0) from vertex to a facet,
    +                               before roundoff, due to a merge
    +                               if qh.JOGGLEmax, qh_makenewplanes sets it
    +                               recomputed if qh.DOcheckmax, default -qh.DISTround */
    +  boolT NEWfacets;        /* true while visible facets invalid due to new or merge
    +                              from makecone/attachnewfacets to deletevisible */
    +  boolT findbestnew;      /* true if partitioning calls qh_findbestnew */
    +  boolT findbest_notsharp; /* true if new facets are at least 90 degrees */
    +  boolT NOerrexit;        /* true if qh.errexit is not available, cleared after setjmp */
    +  realT PRINTcradius;     /* radius for printing centrums */
    +  realT PRINTradius;      /* radius for printing vertex spheres and points */
    +  boolT POSTmerging;      /* true when post merging */
    +  int   printoutvar;      /* temporary variable for qh_printbegin, etc. */
    +  int   printoutnum;      /* number of facets printed */
    +  boolT QHULLfinished;    /* True after qhull() is finished */
    +  realT totarea;          /* 'FA': total facet area computed by qh_getarea, hasAreaVolume */
    +  realT totvol;           /* 'FA': total volume computed by qh_getarea, hasAreaVolume */
    +  unsigned int visit_id;  /* unique ID for searching neighborhoods, */
    +  unsigned int vertex_visit; /* unique ID for searching vertices, reset with qh_buildtracing */
    +  boolT ZEROall_ok;       /* True if qh_checkzero always succeeds */
    +  boolT WAScoplanar;      /* True if qh_partitioncoplanar (qhT *qh, qh_check_maxout) */
    +
    +/*----------------------------------
    +
    +  qh global sets
    +    defines sets for merging, initial simplex, hashing, extra input points,
    +    and deleted vertices
    +*/
    +  setT *facet_mergeset;   /* temporary set of merges to be done */
    +  setT *degen_mergeset;   /* temporary set of degenerate and redundant merges */
    +  setT *hash_table;       /* hash table for matching ridges in qh_matchfacets
    +                             size is setsize() */
    +  setT *other_points;     /* additional points */
    +  setT *del_vertices;     /* vertices to partition and delete with visible
    +                             facets.  Have deleted set for checkfacet */
    +
    +/*----------------------------------
    +
    +  qh global buffers
    +    defines buffers for maxtrix operations, input, and error messages
    +*/
    +  coordT *gm_matrix;      /* (dim+1)Xdim matrix for geom_r.c */
    +  coordT **gm_row;        /* array of gm_matrix rows */
    +  char* line;             /* malloc'd input line of maxline+1 chars */
    +  int maxline;
    +  coordT *half_space;     /* malloc'd input array for halfspace (qh.normal_size+coordT) */
    +  coordT *temp_malloc;    /* malloc'd input array for points */
    +
    +/*----------------------------------
    +
    +  qh static variables
    +    defines static variables for individual functions
    +
    +  notes:
    +    do not use 'static' within a function.  Multiple instances of qhull
    +    may exist.
    +
    +    do not assume zero initialization, 'QPn' may cause a restart
    +*/
    +  boolT ERREXITcalled;    /* true during qh_errexit (qhT *qh, prevents duplicate calls */
    +  boolT firstcentrum;     /* for qh_printcentrum */
    +  boolT old_randomdist;   /* save RANDOMdist flag during io, tracing, or statistics */
    +  setT *coplanarfacetset;  /* set of coplanar facets for searching qh_findbesthorizon() */
    +  realT last_low;         /* qh_scalelast parameters for qh_setdelaunay */
    +  realT last_high;
    +  realT last_newhigh;
    +  unsigned lastreport;    /* for qh_buildtracing */
    +  int mergereport;        /* for qh_tracemerging */
    +  setT *old_tempstack;    /* for saving qh->qhmem.tempstack in save_qhull */
    +  int   ridgeoutnum;      /* number of ridges for 4OFF output (qh_printbegin,etc) */
    +
    +/*----------------------------------
    +
    +  qh memory management, rbox globals, and statistics
    +
    +  Replaces global data structures defined for libqhull
    +*/
    +  int     last_random;    /* Last random number from qh_rand (random_r.c) */
    +  jmp_buf rbox_errexit;   /* errexit from rboxlib_r.c, defined by qh_rboxpoints() only */
    +  char    jmpXtra3[40];   /* extra bytes in case jmp_buf is defined wrong by compiler */
    +  int     rbox_isinteger;
    +  double  rbox_out_offset;
    +  void *  cpp_object;     /* C++ pointer.  Currently used by RboxPoints.qh_fprintf_rbox */
    +
    +  /* Last, otherwise zero'd by qh_initqhull_start2 (global_r.c */
    +  qhmemT  qhmem;          /* Qhull managed memory (mem_r.h) */
    +  /* After qhmem because its size depends on the number of statistics */
    +  qhstatT qhstat;         /* Qhull statistics (stat_r.h) */
    +};
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  otherfacet_(ridge, facet)
    +    return neighboring facet for a ridge in facet
    +*/
    +#define otherfacet_(ridge, facet) \
    +                        (((ridge)->top == (facet)) ? (ridge)->bottom : (ridge)->top)
    +
    +/*----------------------------------
    +
    +  getid_(p)
    +    return int ID for facet, ridge, or vertex
    +    return qh_IDunknown(-1) if NULL
    +*/
    +#define getid_(p)       ((p) ? (int)((p)->id) : qh_IDunknown)
    +
    +/*============== FORALL macros ===================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacets { ... }
    +    assign 'facet' to each facet in qh.facet_list
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +    assumes qh defined
    +
    +  see:
    +    FORALLfacet_( facetlist )
    +*/
    +#define FORALLfacets for (facet=qh->facet_list;facet && facet->next;facet=facet->next)
    +
    +/*----------------------------------
    +
    +  FORALLpoints { ... }
    +    assign 'point' to each point in qh.first_point, qh.num_points
    +
    +  notes:
    +    assumes qh defined
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoints FORALLpoint_(qh, qh->first_point, qh->num_points)
    +
    +/*----------------------------------
    +
    +  FORALLpoint_( qh, points, num) { ... }
    +    assign 'point' to each point in points array of num points
    +
    +  declare:
    +    coordT *point, *pointtemp;
    +*/
    +#define FORALLpoint_(qh, points, num) for (point= (points), \
    +      pointtemp= (points)+qh->hull_dim*(num); point < pointtemp; point += qh->hull_dim)
    +
    +/*----------------------------------
    +
    +  FORALLvertices { ... }
    +    assign 'vertex' to each vertex in qh.vertex_list
    +
    +  declare:
    +    vertexT *vertex;
    +
    +  notes:
    +    assumes qh.vertex_list terminated with a sentinel
    +    assumes qh defined
    +*/
    +#define FORALLvertices for (vertex=qh->vertex_list;vertex && vertex->next;vertex= vertex->next)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_( facets ) { ... }
    +    assign 'facet' to each facet in facets
    +
    +  declare:
    +    facetT *facet, **facetp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHfacet_(facets)    FOREACHsetelement_(facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_( facet ) { ... }
    +    assign 'neighbor' to each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_( vertex ) { ... }
    +    assign 'neighbor' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor, **neighborp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighbor_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_( points ) { ... }
    +    assign 'point' to each point in points set
    +
    +  declare:
    +    pointT *point, **pointp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHpoint_(points)    FOREACHsetelement_(pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_( ridges ) { ... }
    +    assign 'ridge' to each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge, **ridgep;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHridge_(ridges)    FOREACHsetelement_(ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex, **vertexp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHvertex_(vertices) FOREACHsetelement_(vertexT, vertices,vertex)
    +
    +/*----------------------------------
    +
    +  FOREACHfacet_i_( qh, facets ) { ... }
    +    assign 'facet' and 'facet_i' for each facet in facets set
    +
    +  declare:
    +    facetT *facet;
    +    int     facet_n, facet_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHfacet_i_(qh, facets)    FOREACHsetelement_i_(qh, facetT, facets, facet)
    +
    +/*----------------------------------
    +
    +  FOREACHneighbor_i_( qh, facet ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in facet->neighbors
    +
    +  FOREACHneighbor_i_( qh, vertex ) { ... }
    +    assign 'neighbor' and 'neighbor_i' for each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighbor;
    +    int     neighbor_n, neighbor_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHneighbor_i_(qh, facet)  FOREACHsetelement_i_(qh, facetT, facet->neighbors, neighbor)
    +
    +/*----------------------------------
    +
    +  FOREACHpoint_i_( qh, points ) { ... }
    +    assign 'point' and 'point_i' for each point in points set
    +
    +  declare:
    +    pointT *point;
    +    int     point_n, point_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHpoint_i_(qh, points)    FOREACHsetelement_i_(qh, pointT, points, point)
    +
    +/*----------------------------------
    +
    +  FOREACHridge_i_( qh, ridges ) { ... }
    +    assign 'ridge' and 'ridge_i' for each ridge in ridges set
    +
    +  declare:
    +    ridgeT *ridge;
    +    int     ridge_n, ridge_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHridge_i_(qh, ridges)    FOREACHsetelement_i_(qh, ridgeT, ridges, ridge)
    +
    +/*----------------------------------
    +
    +  FOREACHvertex_i_( qh, vertices ) { ... }
    +    assign 'vertex' and 'vertex_i' for each vertex in vertices set
    +
    +  declare:
    +    vertexT *vertex;
    +    int     vertex_n, vertex_i;
    +
    +  see:
    +    FOREACHsetelement_i_
    +*/
    +#define FOREACHvertex_i_(qh, vertices) FOREACHsetelement_i_(qh, vertexT, vertices,vertex)
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +/********* -libqhull_r.c prototypes (duplicated from qhull_ra.h) **********************/
    +
    +void    qh_qhull(qhT *qh);
    +boolT   qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_printsummary(qhT *qh, FILE *fp);
    +
    +/********* -user.c prototypes (alphabetical) **********************/
    +
    +void    qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge);
    +void    qh_errprint(qhT *qh, const char* string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex);
    +int     qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile);
    +void    qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall);
    +void    qh_printhelp_degenerate(qhT *qh, FILE *fp);
    +void    qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle);
    +void    qh_printhelp_singular(qhT *qh, FILE *fp);
    +void    qh_user_memsizes(qhT *qh);
    +
    +/********* -usermem_r.c prototypes (alphabetical) **********************/
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/********* -userprintf_r.c and userprintf_rbox_r.c prototypes **********************/
    +void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +
    +/***** -geom_r.c/geom2_r.c/random_r.c prototypes (duplicated from geom_r.h, random_r.h) ****************/
    +
    +facetT *qh_findbest(qhT *qh, pointT *point, facetT *startfacet,
    +                     boolT bestoutside, boolT newfacets, boolT noupper,
    +                     realT *dist, boolT *isoutside, int *numpart);
    +facetT *qh_findbestnew(qhT *qh, pointT *point, facetT *startfacet,
    +                     realT *dist, boolT bestoutside, boolT *isoutside, int *numpart);
    +boolT   qh_gram_schmidt(qhT *qh, int dim, realT **rows);
    +void    qh_outerinner(qhT *qh, facetT *facet, realT *outerplane, realT *innerplane);
    +void    qh_printsummary(qhT *qh, FILE *fp);
    +void    qh_projectinput(qhT *qh);
    +void    qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **row);
    +void    qh_rotateinput(qhT *qh, realT **rows);
    +void    qh_scaleinput(qhT *qh);
    +void    qh_setdelaunay(qhT *qh, int dim, int count, pointT *points);
    +coordT  *qh_sethalfspace_all(qhT *qh, int dim, int count, coordT *halfspaces, pointT *feasible);
    +
    +/***** -global_r.c prototypes (alphabetical) ***********************/
    +
    +unsigned long qh_clock(qhT *qh);
    +void    qh_checkflags(qhT *qh, char *command, char *hiddenflags);
    +void    qh_clear_outputflags(qhT *qh);
    +void    qh_freebuffers(qhT *qh);
    +void    qh_freeqhull(qhT *qh, boolT allmem);
    +void    qh_init_A(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile, int argc, char *argv[]);
    +void    qh_init_B(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_init_qhull_command(qhT *qh, int argc, char *argv[]);
    +void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initflags(qhT *qh, char *command);
    +void    qh_initqhull_buffers(qhT *qh);
    +void    qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +void    qh_initqhull_mem(qhT *qh);
    +void    qh_initqhull_outputflags(qhT *qh);
    +void    qh_initqhull_start(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initqhull_start2(qhT *qh, FILE *infile, FILE *outfile, FILE *errfile);
    +void    qh_initthresholds(qhT *qh, char *command);
    +void    qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeTsize, int facetTsize, int setTsize, int qhmemTsize);
    +void    qh_option(qhT *qh, const char *option, int *i, realT *r);
    +void    qh_zero(qhT *qh, FILE *errfile);
    +
    +/***** -io_r.c prototypes (duplicated from io_r.h) ***********************/
    +
    +void    qh_dfacet(qhT *qh, unsigned id);
    +void    qh_dvertex(qhT *qh, unsigned id);
    +void    qh_printneighborhood(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetA, facetT *facetB, boolT printall);
    +void    qh_produce_output(qhT *qh);
    +coordT *qh_readpoints(qhT *qh, int *numpoints, int *dimension, boolT *ismalloc);
    +
    +
    +/********* -mem_r.c prototypes (duplicated from mem_r.h) **********************/
    +
    +void qh_meminit(qhT *qh, FILE *ferr);
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong);
    +
    +/********* -poly_r.c/poly2_r.c prototypes (duplicated from poly_r.h) **********************/
    +
    +void    qh_check_output(qhT *qh);
    +void    qh_check_points(qhT *qh);
    +setT   *qh_facetvertices(qhT *qh, facetT *facetlist, setT *facets, boolT allfacets);
    +facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp);
    +pointT *qh_point(qhT *qh, int id);
    +setT   *qh_pointfacet(qhT *qh /*qh.facet_list*/);
    +int     qh_pointid(qhT *qh, pointT *point);
    +setT   *qh_pointvertex(qhT *qh /*qh.facet_list*/);
    +void    qh_setvoronoi_all(qhT *qh);
    +void    qh_triangulate(qhT *qh /*qh.facet_list*/);
    +
    +/********* -rboxpoints_r.c prototypes **********************/
    +int     qh_rboxpoints(qhT *qh, char* rbox_command);
    +void    qh_errexit_rbox(qhT *qh, int exitcode);
    +
    +/********* -stat_r.c prototypes (duplicated from stat_r.h) **********************/
    +
    +void    qh_collectstatistics(qhT *qh);
    +void    qh_printallstatistics(qhT *qh, FILE *fp, const char *string);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFlibqhull */
    diff --git a/xs/src/qhull/src/libqhull_r/libqhull_r.pro b/xs/src/qhull/src/libqhull_r/libqhull_r.pro
    new file mode 100644
    index 0000000000..6b8db44b75
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/libqhull_r.pro
    @@ -0,0 +1,67 @@
    +# -------------------------------------------------
    +# libqhull_r.pro -- Qt project for Qhull shared library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +DLLDESTDIR = ../../bin
    +TEMPLATE = lib
    +CONFIG += shared warn_on
    +CONFIG -= qt
    +
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhull_rd
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhull_r
    +    OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEF_FILE += ../../src/libqhull_r/qhull_r-exports.def
    +
    +# libqhull_r/libqhull_r.pro and ../qhull-libqhull-src_r.pri have the same SOURCES and HEADERS
    +
    +SOURCES += ../libqhull_r/global_r.c
    +SOURCES += ../libqhull_r/stat_r.c
    +SOURCES += ../libqhull_r/geom2_r.c
    +SOURCES += ../libqhull_r/poly2_r.c
    +SOURCES += ../libqhull_r/merge_r.c
    +SOURCES += ../libqhull_r/libqhull_r.c
    +SOURCES += ../libqhull_r/geom_r.c
    +SOURCES += ../libqhull_r/poly_r.c
    +SOURCES += ../libqhull_r/qset_r.c
    +SOURCES += ../libqhull_r/mem_r.c
    +SOURCES += ../libqhull_r/random_r.c
    +SOURCES += ../libqhull_r/usermem_r.c
    +SOURCES += ../libqhull_r/userprintf_r.c
    +SOURCES += ../libqhull_r/io_r.c
    +SOURCES += ../libqhull_r/user_r.c
    +SOURCES += ../libqhull_r/rboxlib_r.c
    +SOURCES += ../libqhull_r/userprintf_rbox_r.c
    +
    +HEADERS += ../libqhull_r/geom_r.h
    +HEADERS += ../libqhull_r/io_r.h
    +HEADERS += ../libqhull_r/libqhull_r.h
    +HEADERS += ../libqhull_r/mem_r.h
    +HEADERS += ../libqhull_r/merge_r.h
    +HEADERS += ../libqhull_r/poly_r.h
    +HEADERS += ../libqhull_r/random_r.h
    +HEADERS += ../libqhull_r/qhull_ra.h
    +HEADERS += ../libqhull_r/qset_r.h
    +HEADERS += ../libqhull_r/stat_r.h
    +HEADERS += ../libqhull_r/user_r.h
    +
    +OTHER_FILES += qh-geom_r.htm
    +OTHER_FILES += qh-globa_r.htm
    +OTHER_FILES += qh-io_r.htm
    +OTHER_FILES += qh-mem_r.htm
    +OTHER_FILES += qh-merge_r.htm
    +OTHER_FILES += qh-poly_r.htm
    +OTHER_FILES += qh-qhull_r.htm
    +OTHER_FILES += qh-set_r.htm
    +OTHER_FILES += qh-stat_r.htm
    +OTHER_FILES += qh-user_r.htm
    diff --git a/xs/src/qhull/src/libqhull_r/mem_r.c b/xs/src/qhull/src/libqhull_r/mem_r.c
    new file mode 100644
    index 0000000000..801a8c76a1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/mem_r.c
    @@ -0,0 +1,562 @@
    +/*
      ---------------------------------
    +
    +  mem_r.c
    +    memory management routines for qhull
    +
    +  See libqhull/mem_r.c for a standalone program.
    +
    +  To initialize memory:
    +
    +    qh_meminit(qh, stderr);
    +    qh_meminitbuffers(qh, qh->IStracing, qh_MEMalign, 7, qh_MEMbufsize,qh_MEMinitbuf);
    +    qh_memsize(qh, (int)sizeof(facetT));
    +    qh_memsize(qh, (int)sizeof(facetT));
    +    ...
    +    qh_memsetup(qh);
    +
    +  To free up all memory buffers:
    +    qh_memfreeshort(qh, &curlong, &totlong);
    +
    +  if qh_NOmem,
    +    malloc/free is used instead of mem.c
    +
    +  notes:
    +    uses Quickfit algorithm (freelists for commonly allocated sizes)
    +    assumes small sizes for freelists (it discards the tail of memory buffers)
    +
    +  see:
    +    qh-mem_r.htm and mem_r.h
    +    global_r.c (qh_initbuffers) for an example of using mem_r.c
    +
    +  Copyright (c) 1993-2015 The Geometry Center.
    +  $Id: //main/2015/qhull/src/libqhull_r/mem_r.c#5 $$Change: 2065 $
    +  $DateTime: 2016/01/18 13:51:04 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r.h"  /* includes user_r.h and mem_r.h */
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifndef qh_NOmem
    +
    +/*============= internal functions ==============*/
    +
    +static int qh_intcompare(const void *i, const void *j);
    +
    +/*========== functions in alphabetical order ======== */
    +
    +/*---------------------------------
    +
    +  qh_intcompare( i, j )
    +    used by qsort and bsearch to compare two integers
    +*/
    +static int qh_intcompare(const void *i, const void *j) {
    +  return(*((const int *)i) - *((const int *)j));
    +} /* intcompare */
    +
    +
    +/*----------------------------------
    +
    +  qh_memalloc( qh, insize )
    +    returns object of insize bytes
    +    qhmem is the global memory structure
    +
    +  returns:
    +    pointer to allocated memory
    +    errors if insufficient memory
    +
    +  notes:
    +    use explicit type conversion to avoid type warnings on some compilers
    +    actual object may be larger than insize
    +    use qh_memalloc_() for inline code for quick allocations
    +    logs allocations if 'T5'
    +    caller is responsible for freeing the memory.
    +    short memory is freed on shutdown by qh_memfreeshort unless qh_NOmem
    +
    +  design:
    +    if size < qh->qhmem.LASTsize
    +      if qh->qhmem.freelists[size] non-empty
    +        return first object on freelist
    +      else
    +        round up request to size of qh->qhmem.freelists[size]
    +        allocate new allocation buffer if necessary
    +        allocate object from allocation buffer
    +    else
    +      allocate object with qh_malloc() in user.c
    +*/
    +void *qh_memalloc(qhT *qh, int insize) {
    +  void **freelistp, *newbuffer;
    +  int idx, size, n;
    +  int outsize, bufsize;
    +  void *object;
    +
    +  if (insize<0) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6235, "qhull error (qh_memalloc): negative request size (%d).  Did int overflow due to high-D?\n", insize); /* WARN64 */
    +      qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (insize>=0 && insize <= qh->qhmem.LASTsize) {
    +    idx= qh->qhmem.indextable[insize];
    +    outsize= qh->qhmem.sizetable[idx];
    +    qh->qhmem.totshort += outsize;
    +    freelistp= qh->qhmem.freelists+idx;
    +    if ((object= *freelistp)) {
    +      qh->qhmem.cntquick++;
    +      qh->qhmem.totfree -= outsize;
    +      *freelistp= *((void **)*freelistp);  /* replace freelist with next object */
    +#ifdef qh_TRACEshort
    +      n= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
    +      if (qh->qhmem.IStracing >= 5)
    +          qh_fprintf(qh, qh->qhmem.ferr, 8141, "qh_mem %p n %8d alloc quick: %d bytes (tot %d cnt %d)\n", object, n, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
    +#endif
    +      return(object);
    +    }else {
    +      qh->qhmem.cntshort++;
    +      if (outsize > qh->qhmem.freesize) {
    +        qh->qhmem.totdropped += qh->qhmem.freesize;
    +        if (!qh->qhmem.curbuffer)
    +          bufsize= qh->qhmem.BUFinit;
    +        else
    +          bufsize= qh->qhmem.BUFsize;
    +        if (!(newbuffer= qh_malloc((size_t)bufsize))) {
    +          qh_fprintf(qh, qh->qhmem.ferr, 6080, "qhull error (qh_memalloc): insufficient memory to allocate short memory buffer (%d bytes)\n", bufsize);
    +          qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +        }
    +        *((void **)newbuffer)= qh->qhmem.curbuffer;  /* prepend newbuffer to curbuffer
    +                                                    list.  newbuffer!=0 by QH6080 */
    +        qh->qhmem.curbuffer= newbuffer;
    +        size= (sizeof(void **) + qh->qhmem.ALIGNmask) & ~qh->qhmem.ALIGNmask;
    +        qh->qhmem.freemem= (void *)((char *)newbuffer+size);
    +        qh->qhmem.freesize= bufsize - size;
    +        qh->qhmem.totbuffer += bufsize - size; /* easier to check */
    +        /* Periodically test totbuffer.  It matches at beginning and exit of every call */
    +        n = qh->qhmem.totshort + qh->qhmem.totfree + qh->qhmem.totdropped + qh->qhmem.freesize - outsize;
    +        if (qh->qhmem.totbuffer != n) {
    +            qh_fprintf(qh, qh->qhmem.ferr, 6212, "qh_memalloc internal error: short totbuffer %d != totshort+totfree... %d\n", qh->qhmem.totbuffer, n);
    +            qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +        }
    +      }
    +      object= qh->qhmem.freemem;
    +      qh->qhmem.freemem= (void *)((char *)qh->qhmem.freemem + outsize);
    +      qh->qhmem.freesize -= outsize;
    +      qh->qhmem.totunused += outsize - insize;
    +#ifdef qh_TRACEshort
    +      n= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
    +      if (qh->qhmem.IStracing >= 5)
    +          qh_fprintf(qh, qh->qhmem.ferr, 8140, "qh_mem %p n %8d alloc short: %d bytes (tot %d cnt %d)\n", object, n, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
    +#endif
    +      return object;
    +    }
    +  }else {                     /* long allocation */
    +    if (!qh->qhmem.indextable) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6081, "qhull internal error (qh_memalloc): qhmem has not been initialized.\n");
    +      qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +    }
    +    outsize= insize;
    +    qh->qhmem.cntlong++;
    +    qh->qhmem.totlong += outsize;
    +    if (qh->qhmem.maxlong < qh->qhmem.totlong)
    +      qh->qhmem.maxlong= qh->qhmem.totlong;
    +    if (!(object= qh_malloc((size_t)outsize))) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6082, "qhull error (qh_memalloc): insufficient memory to allocate %d bytes\n", outsize);
    +      qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +    }
    +    if (qh->qhmem.IStracing >= 5)
    +      qh_fprintf(qh, qh->qhmem.ferr, 8057, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, outsize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +  }
    +  return(object);
    +} /* memalloc */
    +
    +
    +/*----------------------------------
    +
    +  qh_memcheck(qh)
    +*/
    +void qh_memcheck(qhT *qh) {
    +  int i, count, totfree= 0;
    +  void *object;
    +
    +  if (!qh) {
    +    qh_fprintf_stderr(6243, "qh_memcheck(qh) error: qh is 0.  It does not point to a qhT");
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (qh->qhmem.ferr == 0 || qh->qhmem.IStracing < 0 || qh->qhmem.IStracing > 10 || (((qh->qhmem.ALIGNmask+1) & qh->qhmem.ALIGNmask) != 0)) {
    +    qh_fprintf_stderr(6244, "qh_memcheck error: either qh->qhmem is overwritten or qh->qhmem is not initialized.  Call qh_mem_new() or qh_new_qhull() before calling qh_mem routines.  ferr 0x%x IsTracing %d ALIGNmask 0x%x", qh->qhmem.ferr, qh->qhmem.IStracing, qh->qhmem.ALIGNmask);
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (qh->qhmem.IStracing != 0)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8143, "qh_memcheck: check size of freelists on qh->qhmem\nqh_memcheck: A segmentation fault indicates an overwrite of qh->qhmem\n");
    +  for (i=0; i < qh->qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qh->qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    totfree += qh->qhmem.sizetable[i] * count;
    +  }
    +  if (totfree != qh->qhmem.totfree) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6211, "Qhull internal error (qh_memcheck): totfree %d not equal to freelist total %d\n", qh->qhmem.totfree, totfree);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qh->qhmem.IStracing != 0)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8144, "qh_memcheck: total size of freelists totfree is the same as qh->qhmem.totfree\n", totfree);
    +} /* memcheck */
    +
    +/*----------------------------------
    +
    +  qh_memfree(qh, object, insize )
    +    free up an object of size bytes
    +    size is insize from qh_memalloc
    +
    +  notes:
    +    object may be NULL
    +    type checking warns if using (void **)object
    +    use qh_memfree_() for quick free's of small objects
    +
    +  design:
    +    if size <= qh->qhmem.LASTsize
    +      append object to corresponding freelist
    +    else
    +      call qh_free(object)
    +*/
    +void qh_memfree(qhT *qh, void *object, int insize) {
    +  void **freelistp;
    +  int idx, outsize;
    +
    +  if (!object)
    +    return;
    +  if (insize <= qh->qhmem.LASTsize) {
    +    qh->qhmem.freeshort++;
    +    idx= qh->qhmem.indextable[insize];
    +    outsize= qh->qhmem.sizetable[idx];
    +    qh->qhmem.totfree += outsize;
    +    qh->qhmem.totshort -= outsize;
    +    freelistp= qh->qhmem.freelists + idx;
    +    *((void **)object)= *freelistp;
    +    *freelistp= object;
    +#ifdef qh_TRACEshort
    +    idx= qh->qhmem.cntshort+qh->qhmem.cntquick+qh->qhmem.freeshort;
    +    if (qh->qhmem.IStracing >= 5)
    +        qh_fprintf(qh, qh->qhmem.ferr, 8142, "qh_mem %p n %8d free short: %d bytes (tot %d cnt %d)\n", object, idx, outsize, qh->qhmem.totshort, qh->qhmem.cntshort+qh->qhmem.cntquick-qh->qhmem.freeshort);
    +#endif
    +  }else {
    +    qh->qhmem.freelong++;
    +    qh->qhmem.totlong -= insize;
    +    if (qh->qhmem.IStracing >= 5)
    +      qh_fprintf(qh, qh->qhmem.ferr, 8058, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +    qh_free(object);
    +  }
    +} /* memfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_memfreeshort(qh, curlong, totlong )
    +    frees up all short and qhmem memory allocations
    +
    +  returns:
    +    number and size of current long allocations
    +  
    +  notes:
    +    if qh_NOmem (qh_malloc() for all allocations), 
    +       short objects (e.g., facetT) are not recovered.
    +       use qh_freeqhull(qh, qh_ALL) instead.
    + 
    +  see:
    +    qh_freeqhull(qh, allMem)
    +    qh_memtotal(qh, curlong, totlong, curshort, totshort, maxlong, totbuffer);
    +*/
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong) {
    +  void *buffer, *nextbuffer;
    +  FILE *ferr;
    +
    +  *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
    +  *totlong= qh->qhmem.totlong;
    +  for (buffer= qh->qhmem.curbuffer; buffer; buffer= nextbuffer) {
    +    nextbuffer= *((void **) buffer);
    +    qh_free(buffer);
    +  }
    +  qh->qhmem.curbuffer= NULL;
    +  if (qh->qhmem.LASTsize) {
    +    qh_free(qh->qhmem.indextable);
    +    qh_free(qh->qhmem.freelists);
    +    qh_free(qh->qhmem.sizetable);
    +  }
    +  ferr= qh->qhmem.ferr;
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +  qh->qhmem.ferr= ferr;
    +} /* memfreeshort */
    +
    +
    +/*----------------------------------
    +
    +  qh_meminit(qh, ferr )
    +    initialize qhmem and test sizeof( void*)
    +    Does not throw errors.  qh_exit on failure
    +*/
    +void qh_meminit(qhT *qh, FILE *ferr) {
    +
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +      qh->qhmem.ferr= ferr;
    +  else
    +      qh->qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6083, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  if (sizeof(void*) > sizeof(ptr_intT)) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6084, "qhull internal error (qh_meminit): sizeof(void*) %d > sizeof(ptr_intT) %d. Change ptr_intT in mem.h to 'long long'\n", (int)sizeof(void*), (int)sizeof(ptr_intT));
    +      qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qh_memcheck(qh);
    +} /* meminit */
    +
    +/*---------------------------------
    +
    +  qh_meminitbuffers(qh, tracelevel, alignment, numsizes, bufsize, bufinit )
    +    initialize qhmem
    +    if tracelevel >= 5, trace memory allocations
    +    alignment= desired address alignment for memory allocations
    +    numsizes= number of freelists
    +    bufsize=  size of additional memory buffers for short allocations
    +    bufinit=  size of initial memory buffer for short allocations
    +*/
    +void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qh->qhmem.IStracing= tracelevel;
    +  qh->qhmem.NUMsizes= numsizes;
    +  qh->qhmem.BUFsize= bufsize;
    +  qh->qhmem.BUFinit= bufinit;
    +  qh->qhmem.ALIGNmask= alignment-1;
    +  if (qh->qhmem.ALIGNmask & ~qh->qhmem.ALIGNmask) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6085, "qhull internal error (qh_meminit): memory alignment %d is not a power of 2\n", alignment);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh->qhmem.sizetable= (int *) calloc((size_t)numsizes, sizeof(int));
    +  qh->qhmem.freelists= (void **) calloc((size_t)numsizes, sizeof(void *));
    +  if (!qh->qhmem.sizetable || !qh->qhmem.freelists) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6086, "qhull error (qh_meminit): insufficient memory\n");
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (qh->qhmem.IStracing >= 1)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8059, "qh_meminitbuffers: memory initialized with alignment %d\n", alignment);
    +} /* meminitbuffers */
    +
    +/*---------------------------------
    +
    +  qh_memsetup(qh)
    +    set up memory after running memsize()
    +*/
    +void qh_memsetup(qhT *qh) {
    +  int k,i;
    +
    +  qsort(qh->qhmem.sizetable, (size_t)qh->qhmem.TABLEsize, sizeof(int), qh_intcompare);
    +  qh->qhmem.LASTsize= qh->qhmem.sizetable[qh->qhmem.TABLEsize-1];
    +  if(qh->qhmem.LASTsize >= qh->qhmem.BUFsize || qh->qhmem.LASTsize >= qh->qhmem.BUFinit) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6087, "qhull error (qh_memsetup): largest mem size %d is >= buffer size %d or initial buffer size %d\n",
    +            qh->qhmem.LASTsize, qh->qhmem.BUFsize, qh->qhmem.BUFinit);
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  if (!(qh->qhmem.indextable= (int *)qh_malloc((qh->qhmem.LASTsize+1) * sizeof(int)))) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6088, "qhull error (qh_memsetup): insufficient memory\n");
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  for (k=qh->qhmem.LASTsize+1; k--; )
    +    qh->qhmem.indextable[k]= k;
    +  i= 0;
    +  for (k=0; k <= qh->qhmem.LASTsize; k++) {
    +    if (qh->qhmem.indextable[k] <= qh->qhmem.sizetable[i])
    +      qh->qhmem.indextable[k]= i;
    +    else
    +      qh->qhmem.indextable[k]= ++i;
    +  }
    +} /* memsetup */
    +
    +/*---------------------------------
    +
    +  qh_memsize(qh, size )
    +    define a free list for this size
    +*/
    +void qh_memsize(qhT *qh, int size) {
    +  int k;
    +
    +  if(qh->qhmem.LASTsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6089, "qhull error (qh_memsize): called after qhmem_setup\n");
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  size= (size + qh->qhmem.ALIGNmask) & ~qh->qhmem.ALIGNmask;
    +  for (k=qh->qhmem.TABLEsize; k--; ) {
    +    if (qh->qhmem.sizetable[k] == size)
    +      return;
    +  }
    +  if (qh->qhmem.TABLEsize < qh->qhmem.NUMsizes)
    +    qh->qhmem.sizetable[qh->qhmem.TABLEsize++]= size;
    +  else
    +    qh_fprintf(qh, qh->qhmem.ferr, 7060, "qhull warning (memsize): free list table has room for only %d sizes\n", qh->qhmem.NUMsizes);
    +} /* memsize */
    +
    +
    +/*---------------------------------
    +
    +  qh_memstatistics(qh, fp )
    +    print out memory statistics
    +
    +    Verifies that qh->qhmem.totfree == sum of freelists
    +*/
    +void qh_memstatistics(qhT *qh, FILE *fp) {
    +  int i;
    +  int count;
    +  void *object;
    +
    +  qh_memcheck(qh);
    +  qh_fprintf(qh, fp, 9278, "\nmemory statistics:\n\
    +%7d quick allocations\n\
    +%7d short allocations\n\
    +%7d long allocations\n\
    +%7d short frees\n\
    +%7d long frees\n\
    +%7d bytes of short memory in use\n\
    +%7d bytes of short memory in freelists\n\
    +%7d bytes of dropped short memory\n\
    +%7d bytes of unused short memory (estimated)\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n\
    +%7d bytes of short memory buffers (minus links)\n\
    +%7d bytes per short memory buffer (initially %d bytes)\n",
    +           qh->qhmem.cntquick, qh->qhmem.cntshort, qh->qhmem.cntlong,
    +           qh->qhmem.freeshort, qh->qhmem.freelong,
    +           qh->qhmem.totshort, qh->qhmem.totfree,
    +           qh->qhmem.totdropped + qh->qhmem.freesize, qh->qhmem.totunused,
    +           qh->qhmem.maxlong, qh->qhmem.totlong, qh->qhmem.cntlong - qh->qhmem.freelong,
    +           qh->qhmem.totbuffer, qh->qhmem.BUFsize, qh->qhmem.BUFinit);
    +  if (qh->qhmem.cntlarger) {
    +    qh_fprintf(qh, fp, 9279, "%7d calls to qh_setlarger\n%7.2g     average copy size\n",
    +           qh->qhmem.cntlarger, ((float)qh->qhmem.totlarger)/(float)qh->qhmem.cntlarger);
    +    qh_fprintf(qh, fp, 9280, "  freelists(bytes->count):");
    +  }
    +  for (i=0; i < qh->qhmem.TABLEsize; i++) {
    +    count=0;
    +    for (object= qh->qhmem.freelists[i]; object; object= *((void **)object))
    +      count++;
    +    qh_fprintf(qh, fp, 9281, " %d->%d", qh->qhmem.sizetable[i], count);
    +  }
    +  qh_fprintf(qh, fp, 9282, "\n\n");
    +} /* memstatistics */
    +
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    uses qh_malloc() and qh_free() instead
    +*/
    +#else /* qh_NOmem */
    +
    +void *qh_memalloc(qhT *qh, int insize) {
    +  void *object;
    +
    +  if (!(object= qh_malloc((size_t)insize))) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6090, "qhull error (qh_memalloc): insufficient memory\n");
    +    qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +  }
    +  qh->qhmem.cntlong++;
    +  qh->qhmem.totlong += insize;
    +  if (qh->qhmem.maxlong < qh->qhmem.totlong)
    +      qh->qhmem.maxlong= qh->qhmem.totlong;
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8060, "qh_mem %p n %8d alloc long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +  return object;
    +}
    +
    +void qh_memfree(qhT *qh, void *object, int insize) {
    +
    +  if (!object)
    +    return;
    +  qh_free(object);
    +  qh->qhmem.freelong++;
    +  qh->qhmem.totlong -= insize;
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8061, "qh_mem %p n %8d free long: %d bytes (tot %d cnt %d)\n", object, qh->qhmem.cntlong+qh->qhmem.freelong, insize, qh->qhmem.totlong, qh->qhmem.cntlong-qh->qhmem.freelong);
    +}
    +
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong) {
    +  *totlong= qh->qhmem.totlong;
    +  *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +}
    +
    +void qh_meminit(qhT *qh, FILE *ferr) {
    +
    +  memset((char *)&qh->qhmem, 0, sizeof(qh->qhmem));  /* every field is 0, FALSE, NULL */
    +  if (ferr)
    +      qh->qhmem.ferr= ferr;
    +  else
    +      qh->qhmem.ferr= stderr;
    +  if (sizeof(void*) < sizeof(int)) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6091, "qhull internal error (qh_meminit): sizeof(void*) %d < sizeof(int) %d.  qset.c will not work\n", (int)sizeof(void*), (int)sizeof(int));
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +}
    +
    +void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes, int bufsize, int bufinit) {
    +
    +  qh->qhmem.IStracing= tracelevel;
    +}
    +
    +void qh_memsetup(qhT *qh) {
    +
    +}
    +
    +void qh_memsize(qhT *qh, int size) {
    +
    +}
    +
    +void qh_memstatistics(qhT *qh, FILE *fp) {
    +
    +  qh_fprintf(qh, fp, 9409, "\nmemory statistics:\n\
    +%7d long allocations\n\
    +%7d long frees\n\
    +%7d bytes of long memory allocated (max, except for input)\n\
    +%7d bytes of long memory in use (in %d pieces)\n",
    +           qh->qhmem.cntlong,
    +           qh->qhmem.freelong,
    +           qh->qhmem.maxlong, qh->qhmem.totlong, qh->qhmem.cntlong - qh->qhmem.freelong);
    +}
    +
    +#endif /* qh_NOmem */
    +
    +/*---------------------------------
    +
    +  qh_memtotal(qh, totlong, curlong, totshort, curshort, maxlong, totbuffer )
    +    Return the total, allocated long and short memory
    +
    +  returns:
    +    Returns the total current bytes of long and short allocations
    +    Returns the current count of long and short allocations
    +    Returns the maximum long memory and total short buffer (minus one link per buffer)
    +    Does not error (for deprecated UsingLibQhull.cpp (libqhullpcpp))
    +*/
    +void qh_memtotal(qhT *qh, int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer) {
    +    *totlong= qh->qhmem.totlong;
    +    *curlong= qh->qhmem.cntlong - qh->qhmem.freelong;
    +    *totshort= qh->qhmem.totshort;
    +    *curshort= qh->qhmem.cntshort + qh->qhmem.cntquick - qh->qhmem.freeshort;
    +    *maxlong= qh->qhmem.maxlong;
    +    *totbuffer= qh->qhmem.totbuffer;
    +} /* memtotlong */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/mem_r.h b/xs/src/qhull/src/libqhull_r/mem_r.h
    new file mode 100644
    index 0000000000..25b5513330
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/mem_r.h
    @@ -0,0 +1,234 @@
    +/*
      ---------------------------------
    +
    +   mem_r.h
    +     prototypes for memory management functions
    +
    +   see qh-mem_r.htm, mem_r.c and qset_r.h
    +
    +   for error handling, writes message and calls
    +     qh_errexit(qhT *qh, qhmem_ERRmem, NULL, NULL) if insufficient memory
    +       and
    +     qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL) otherwise
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/mem_r.h#4 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmem
    +#define qhDEFmem 1
    +
    +#include 
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;          /* defined in qset_r.h */
    +#endif
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;          /* defined in libqhull_r.h */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_NOmem
    +    turn off quick-fit memory allocation
    +
    +  notes:
    +    mem_r.c implements Quickfit memory allocation for about 20% time
    +    savings.  If it fails on your machine, try to locate the
    +    problem, and send the answer to qhull@qhull.org.  If this can
    +    not be done, define qh_NOmem to use malloc/free instead.
    +
    +   #define qh_NOmem
    +*/
    +
    +/*---------------------------------
    +
    +qh_TRACEshort
    +Trace short and quick memory allocations at T5
    +
    +*/
    +#define qh_TRACEshort
    +
    +/*-------------------------------------------
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem_r.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.  If gcc is available,
    +    use __alignof__(double) or fmax_(__alignof__(float), __alignof__(void *)).
    +
    +   see qh_MEMalign in user.h for qhull's alignment
    +*/
    +
    +#define qhmem_ERRmem 4    /* matches qh_ERRmem in libqhull_r.h */
    +#define qhmem_ERRqhull 5  /* matches qh_ERRqhull in libqhull_r.h */
    +
    +/*----------------------------------
    +
    +  ptr_intT
    +    for casting a void * to an integer-type that holds a pointer
    +    Used for integer expressions (e.g., computing qh_gethash() in poly_r.c)
    +
    +  notes:
    +    WARN64 -- these notes indicate 64-bit issues
    +    On 64-bit machines, a pointer may be larger than an 'int'.
    +    qh_meminit()/mem_r.c checks that 'ptr_intT' holds a 'void*'
    +    ptr_intT is typically a signed value, but not necessarily so
    +    size_t is typically unsigned, but should match the parameter type
    +    Qhull uses int instead of size_t except for system calls such as malloc, qsort, qh_malloc, etc.
    +    This matches Qt convention and is easier to work with.
    +*/
    +#if (defined(__MINGW64__)) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#elif (_MSC_VER) && defined(_WIN64)
    +typedef long long ptr_intT;
    +#else
    +typedef long ptr_intT;
    +#endif
    +
    +/*----------------------------------
    +
    +  qhmemT
    +    global memory structure for mem_r.c
    +
    + notes:
    +   users should ignore qhmem except for writing extensions
    +   qhmem is allocated in mem_r.c
    +
    +   qhmem could be swapable like qh and qhstat, but then
    +   multiple qh's and qhmem's would need to keep in synch.
    +   A swapable qhmem would also waste memory buffers.  As long
    +   as memory operations are atomic, there is no problem with
    +   multiple qh structures being active at the same time.
    +   If you need separate address spaces, you can swap the
    +   contents of qh->qhmem.
    +*/
    +typedef struct qhmemT qhmemT;
    +
    +/* Update qhmem in mem_r.c if add or remove fields */
    +struct qhmemT {               /* global memory management variables */
    +  int      BUFsize;           /* size of memory allocation buffer */
    +  int      BUFinit;           /* initial size of memory allocation buffer */
    +  int      TABLEsize;         /* actual number of sizes in free list table */
    +  int      NUMsizes;          /* maximum number of sizes in free list table */
    +  int      LASTsize;          /* last size in free list table */
    +  int      ALIGNmask;         /* worst-case alignment, must be 2^n-1 */
    +  void   **freelists;          /* free list table, linked by offset 0 */
    +  int     *sizetable;         /* size of each freelist */
    +  int     *indextable;        /* size->index table */
    +  void    *curbuffer;         /* current buffer, linked by offset 0 */
    +  void    *freemem;           /*   free memory in curbuffer */
    +  int      freesize;          /*   size of freemem in bytes */
    +  setT    *tempstack;         /* stack of temporary memory, managed by users */
    +  FILE    *ferr;              /* file for reporting errors when 'qh' may be undefined */
    +  int      IStracing;         /* =5 if tracing memory allocations */
    +  int      cntquick;          /* count of quick allocations */
    +                              /* Note: removing statistics doesn't effect speed */
    +  int      cntshort;          /* count of short allocations */
    +  int      cntlong;           /* count of long allocations */
    +  int      freeshort;         /* count of short memfrees */
    +  int      freelong;          /* count of long memfrees */
    +  int      totbuffer;         /* total short memory buffers minus buffer links */
    +  int      totdropped;        /* total dropped memory at end of short memory buffers (e.g., freesize) */
    +  int      totfree;           /* total size of free, short memory on freelists */
    +  int      totlong;           /* total size of long memory in use */
    +  int      maxlong;           /*   maximum totlong */
    +  int      totshort;          /* total size of short memory in use */
    +  int      totunused;         /* total unused short memory (estimated, short size - request size of first allocations) */
    +  int      cntlarger;         /* count of setlarger's */
    +  int      totlarger;         /* total copied by setlarger */
    +};
    +
    +
    +/*==================== -macros ====================*/
    +
    +/*----------------------------------
    +
    +  qh_memalloc_(qh, insize, freelistp, object, type)
    +    returns object of size bytes
    +        assumes size<=qh->qhmem.LASTsize and void **freelistp is a temp
    +*/
    +
    +#if defined qh_NOmem
    +#define qh_memalloc_(qh, insize, freelistp, object, type) {\
    +  object= (type*)qh_memalloc(qh, insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memalloc_(qh, insize, freelistp, object, type) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    object= (type*)qh_memalloc(qh, insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memalloc_(qh, insize, freelistp, object, type) {\
    +  freelistp= qh->qhmem.freelists + qh->qhmem.indextable[insize];\
    +  if ((object= (type*)*freelistp)) {\
    +    qh->qhmem.totshort += qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    qh->qhmem.totfree -= qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    qh->qhmem.cntquick++;  \
    +    *freelistp= *((void **)*freelistp);\
    +  }else object= (type*)qh_memalloc(qh, insize);}
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_memfree_(qh, object, insize, freelistp)
    +    free up an object
    +
    +  notes:
    +    object may be NULL
    +    assumes size<=qh->qhmem.LASTsize and void **freelistp is a temp
    +*/
    +#if defined qh_NOmem
    +#define qh_memfree_(qh, object, insize, freelistp) {\
    +  qh_memfree(qh, object, insize); }
    +#elif defined qh_TRACEshort
    +#define qh_memfree_(qh, object, insize, freelistp) {\
    +    freelistp= NULL; /* Avoid warnings */ \
    +    qh_memfree(qh, object, insize); }
    +#else /* !qh_NOmem */
    +
    +#define qh_memfree_(qh, object, insize, freelistp) {\
    +  if (object) { \
    +    qh->qhmem.freeshort++;\
    +    freelistp= qh->qhmem.freelists + qh->qhmem.indextable[insize];\
    +    qh->qhmem.totshort -= qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    qh->qhmem.totfree += qh->qhmem.sizetable[qh->qhmem.indextable[insize]]; \
    +    *((void **)object)= *freelistp;\
    +    *freelistp= object;}}
    +#endif
    +
    +/*=============== prototypes in alphabetical order ============*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void *qh_memalloc(qhT *qh, int insize);
    +void qh_memcheck(qhT *qh);
    +void qh_memfree(qhT *qh, void *object, int insize);
    +void qh_memfreeshort(qhT *qh, int *curlong, int *totlong);
    +void qh_meminit(qhT *qh, FILE *ferr);
    +void qh_meminitbuffers(qhT *qh, int tracelevel, int alignment, int numsizes,
    +                        int bufsize, int bufinit);
    +void qh_memsetup(qhT *qh);
    +void qh_memsize(qhT *qh, int size);
    +void qh_memstatistics(qhT *qh, FILE *fp);
    +void qh_memtotal(qhT *qh, int *totlong, int *curlong, int *totshort, int *curshort, int *maxlong, int *totbuffer);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFmem */
    diff --git a/xs/src/qhull/src/libqhull_r/merge_r.c b/xs/src/qhull/src/libqhull_r/merge_r.c
    new file mode 100644
    index 0000000000..e5823de8d1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/merge_r.c
    @@ -0,0 +1,3627 @@
    +/*
      ---------------------------------
    +
    +   merge_r.c
    +   merges non-convex facets
    +
    +   see qh-merge_r.htm and merge_r.h
    +
    +   other modules call qh_premerge() and qh_postmerge()
    +
    +   the user may call qh_postmerge() to perform additional merges.
    +
    +   To remove deleted facets and vertices (qhull() in libqhull_r.c):
    +     qh_partitionvisible(qh, !qh_ALL, &numoutside);  // visible_list, newfacet_list
    +     qh_deletevisible();         // qh.visible_list
    +     qh_resetlists(qh, False, qh_RESETvisible);       // qh.visible_list newvertex_list newfacet_list
    +
    +   assumes qh.CENTERtype= centrum
    +
    +   merges occur in qh_mergefacet and in qh_mergecycle
    +   vertex->neighbors not set until the first merge occurs
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull_r/merge_r.c#5 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +#ifndef qh_NOmerge
    +
    +/*===== functions(alphabetical after premerge and postmerge) ======*/
    +
    +/*---------------------------------
    +
    +  qh_premerge(qh, apex, maxcentrum )
    +    pre-merge nonconvex facets in qh.newfacet_list for apex
    +    maxcentrum defines coplanar and concave (qh_test_appendmerge)
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible set
    +
    +  notes:
    +    uses globals, qh.MERGEexact, qh.PREmerge
    +
    +  design:
    +    mark duplicate ridges in qh.newfacet_list
    +    merge facet cycles in qh.newfacet_list
    +    merge duplicate ridges and concave facets in qh.newfacet_list
    +    check merged facet cycles for degenerate and redundant facets
    +    merge degenerate and redundant facets
    +    collect coplanar and concave facets
    +    merge concave, coplanar, degenerate, and redundant facets
    +*/
    +void qh_premerge(qhT *qh, vertexT *apex, realT maxcentrum, realT maxangle) {
    +  boolT othermerge= False;
    +  facetT *newfacet;
    +
    +  if (qh->ZEROcentrum && qh_checkzero(qh, !qh_ALL))
    +    return;
    +  trace2((qh, qh->ferr, 2008, "qh_premerge: premerge centrum %2.2g angle %2.2g for apex v%d facetlist f%d\n",
    +            maxcentrum, maxangle, apex->id, getid_(qh->newfacet_list)));
    +  if (qh->IStracing >= 4 && qh->num_facets < 50)
    +    qh_printlists(qh);
    +  qh->centrum_radius= maxcentrum;
    +  qh->cos_max= maxangle;
    +  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  if (qh->hull_dim >=3) {
    +    qh_mark_dupridges(qh, qh->newfacet_list); /* facet_mergeset */
    +    qh_mergecycle_all(qh, qh->newfacet_list, &othermerge);
    +    qh_forcedmerges(qh, &othermerge /* qh->facet_mergeset */);
    +    FORALLnew_facets {  /* test samecycle merges */
    +      if (!newfacet->simplicial && !newfacet->mergeridge)
    +        qh_degen_redundant_neighbors(qh, newfacet, NULL);
    +    }
    +    if (qh_merge_degenredundant(qh))
    +      othermerge= True;
    +  }else /* qh->hull_dim == 2 */
    +    qh_mergecycle_all(qh, qh->newfacet_list, &othermerge);
    +  qh_flippedmerges(qh, qh->newfacet_list, &othermerge);
    +  if (!qh->MERGEexact || zzval_(Ztotmerge)) {
    +    zinc_(Zpremergetot);
    +    qh->POSTmerging= False;
    +    qh_getmergeset_initial(qh, qh->newfacet_list);
    +    qh_all_merges(qh, othermerge, False);
    +  }
    +  qh_settempfree(qh, &qh->facet_mergeset);
    +  qh_settempfree(qh, &qh->degen_mergeset);
    +} /* premerge */
    +
    +/*---------------------------------
    +
    +  qh_postmerge(qh, reason, maxcentrum, maxangle, vneighbors )
    +    post-merge nonconvex facets as defined by maxcentrum and maxangle
    +    'reason' is for reporting progress
    +    if vneighbors,
    +      calls qh_test_vneighbors at end of qh_all_merge
    +    if firstmerge,
    +      calls qh_reducevertices before qh_getmergeset
    +
    +  returns:
    +    if first call (qh.visible_list != qh.facet_list),
    +      builds qh.facet_newlist, qh.newvertex_list
    +    deleted facets added to qh.visible_list with facet->visible
    +    qh.visible_list == qh.facet_list
    +
    +  notes:
    +
    +
    +  design:
    +    if first call
    +      set qh.visible_list and qh.newfacet_list to qh.facet_list
    +      add all facets to qh.newfacet_list
    +      mark non-simplicial facets, facet->newmerge
    +      set qh.newvertext_list to qh.vertex_list
    +      add all vertices to qh.newvertex_list
    +      if a pre-merge occured
    +        set vertex->delridge {will retest the ridge}
    +        if qh.MERGEexact
    +          call qh_reducevertices()
    +      if no pre-merging
    +        merge flipped facets
    +    determine non-convex facets
    +    merge all non-convex facets
    +*/
    +void qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +  facetT *newfacet;
    +  boolT othermerges= False;
    +  vertexT *vertex;
    +
    +  if (qh->REPORTfreq || qh->IStracing) {
    +    qh_buildtracing(qh, NULL, NULL);
    +    qh_printsummary(qh, qh->ferr);
    +    if (qh->PRINTstatistics)
    +      qh_printallstatistics(qh, qh->ferr, "reason");
    +    qh_fprintf(qh, qh->ferr, 8062, "\n%s with 'C%.2g' and 'A%.2g'\n",
    +        reason, maxcentrum, maxangle);
    +  }
    +  trace2((qh, qh->ferr, 2009, "qh_postmerge: postmerge.  test vneighbors? %d\n",
    +            vneighbors));
    +  qh->centrum_radius= maxcentrum;
    +  qh->cos_max= maxangle;
    +  qh->POSTmerging= True;
    +  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  if (qh->visible_list != qh->facet_list) {  /* first call */
    +    qh->NEWfacets= True;
    +    qh->visible_list= qh->newfacet_list= qh->facet_list;
    +    FORALLnew_facets {
    +      newfacet->newfacet= True;
    +       if (!newfacet->simplicial)
    +        newfacet->newmerge= True;
    +     zinc_(Zpostfacets);
    +    }
    +    qh->newvertex_list= qh->vertex_list;
    +    FORALLvertices
    +      vertex->newlist= True;
    +    if (qh->VERTEXneighbors) { /* a merge has occurred */
    +      FORALLvertices
    +        vertex->delridge= True; /* test for redundant, needed? */
    +      if (qh->MERGEexact) {
    +        if (qh->hull_dim <= qh_DIMreduceBuild)
    +          qh_reducevertices(qh); /* was skipped during pre-merging */
    +      }
    +    }
    +    if (!qh->PREmerge && !qh->MERGEexact)
    +      qh_flippedmerges(qh, qh->newfacet_list, &othermerges);
    +  }
    +  qh_getmergeset_initial(qh, qh->newfacet_list);
    +  qh_all_merges(qh, False, vneighbors);
    +  qh_settempfree(qh, &qh->facet_mergeset);
    +  qh_settempfree(qh, &qh->degen_mergeset);
    +} /* post_merge */
    +
    +/*---------------------------------
    +
    +  qh_all_merges(qh, othermerge, vneighbors )
    +    merge all non-convex facets
    +
    +    set othermerge if already merged facets (for qh_reducevertices)
    +    if vneighbors
    +      tests vertex neighbors for convexity at end
    +    qh.facet_mergeset lists the non-convex ridges in qh_newfacet_list
    +    qh.degen_mergeset is defined
    +    if qh.MERGEexact && !qh.POSTmerging,
    +      does not merge coplanar facets
    +
    +  returns:
    +    deleted facets added to qh.visible_list with facet->visible
    +    deleted vertices added qh.delvertex_list with vertex->delvertex
    +
    +  notes:
    +    unless !qh.MERGEindependent,
    +      merges facets in independent sets
    +    uses qh.newfacet_list as argument since merges call qh_removefacet()
    +
    +  design:
    +    while merges occur
    +      for each merge in qh.facet_mergeset
    +        unless one of the facets was already merged in this pass
    +          merge the facets
    +        test merged facets for additional merges
    +        add merges to qh.facet_mergeset
    +      if vertices record neighboring facets
    +        rename redundant vertices
    +          update qh.facet_mergeset
    +    if vneighbors ??
    +      tests vertex neighbors for convexity at end
    +*/
    +void qh_all_merges(qhT *qh, boolT othermerge, boolT vneighbors) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge;
    +  boolT wasmerge= True, isreduce;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +  vertexT *vertex;
    +  mergeType mergetype;
    +  int numcoplanar=0, numconcave=0, numdegenredun= 0, numnewmerges= 0;
    +
    +  trace2((qh, qh->ferr, 2010, "qh_all_merges: starting to merge facets beginning from f%d\n",
    +            getid_(qh->newfacet_list)));
    +  while (True) {
    +    wasmerge= False;
    +    while (qh_setsize(qh, qh->facet_mergeset)) {
    +      while ((merge= (mergeT*)qh_setdellast(qh->facet_mergeset))) {
    +        facet1= merge->facet1;
    +        facet2= merge->facet2;
    +        mergetype= merge->type;
    +        qh_memfree_(qh, merge, (int)sizeof(mergeT), freelistp);
    +        if (facet1->visible || facet2->visible) /*deleted facet*/
    +          continue;
    +        if ((facet1->newfacet && !facet1->tested)
    +                || (facet2->newfacet && !facet2->tested)) {
    +          if (qh->MERGEindependent && mergetype <= MRGanglecoplanar)
    +            continue;      /* perform independent sets of merges */
    +        }
    +        qh_merge_nonconvex(qh, facet1, facet2, mergetype);
    +        numdegenredun += qh_merge_degenredundant(qh);
    +        numnewmerges++;
    +        wasmerge= True;
    +        if (mergetype == MRGconcave)
    +          numconcave++;
    +        else /* MRGcoplanar or MRGanglecoplanar */
    +          numcoplanar++;
    +      } /* while setdellast */
    +      if (qh->POSTmerging && qh->hull_dim <= qh_DIMreduceBuild
    +      && numnewmerges > qh_MAXnewmerges) {
    +        numnewmerges= 0;
    +        qh_reducevertices(qh);  /* otherwise large post merges too slow */
    +      }
    +      qh_getmergeset(qh, qh->newfacet_list); /* facet_mergeset */
    +    } /* while mergeset */
    +    if (qh->VERTEXneighbors) {
    +      isreduce= False;
    +      if (qh->hull_dim >=4 && qh->POSTmerging) {
    +        FORALLvertices
    +          vertex->delridge= True;
    +        isreduce= True;
    +      }
    +      if ((wasmerge || othermerge) && (!qh->MERGEexact || qh->POSTmerging)
    +          && qh->hull_dim <= qh_DIMreduceBuild) {
    +        othermerge= False;
    +        isreduce= True;
    +      }
    +      if (isreduce) {
    +        if (qh_reducevertices(qh)) {
    +          qh_getmergeset(qh, qh->newfacet_list); /* facet_mergeset */
    +          continue;
    +        }
    +      }
    +    }
    +    if (vneighbors && qh_test_vneighbors(qh /* qh->newfacet_list */))
    +      continue;
    +    break;
    +  } /* while (True) */
    +  if (qh->CHECKfrequently && !qh->MERGEexact) {
    +    qh->old_randomdist= qh->RANDOMdist;
    +    qh->RANDOMdist= False;
    +    qh_checkconvex(qh, qh->newfacet_list, qh_ALGORITHMfault);
    +    /* qh_checkconnect(qh); [this is slow and it changes the facet order] */
    +    qh->RANDOMdist= qh->old_randomdist;
    +  }
    +  trace1((qh, qh->ferr, 1009, "qh_all_merges: merged %d coplanar facets %d concave facets and %d degen or redundant facets.\n",
    +    numcoplanar, numconcave, numdegenredun));
    +  if (qh->IStracing >= 4 && qh->num_facets < 50)
    +    qh_printlists(qh);
    +} /* all_merges */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendmergeset(qh, facet, neighbor, mergetype, angle )
    +    appends an entry to qh.facet_mergeset or qh.degen_mergeset
    +
    +    angle ignored if NULL or !qh.ANGLEmerge
    +
    +  returns:
    +    merge appended to facet_mergeset or degen_mergeset
    +      sets ->degenerate or ->redundant if degen_mergeset
    +
    +  see:
    +    qh_test_appendmerge()
    +
    +  design:
    +    allocate merge entry
    +    if regular merge
    +      append to qh.facet_mergeset
    +    else if degenerate merge and qh.facet_mergeset is all degenerate
    +      append to qh.degen_mergeset
    +    else if degenerate merge
    +      prepend to qh.degen_mergeset
    +    else if redundant merge
    +      append to qh.degen_mergeset
    +*/
    +void qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle) {
    +  mergeT *merge, *lastmerge;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (facet->redundant)
    +    return;
    +  if (facet->degenerate && mergetype == MRGdegen)
    +    return;
    +  qh_memalloc_(qh, (int)sizeof(mergeT), freelistp, merge, mergeT);
    +  merge->facet1= facet;
    +  merge->facet2= neighbor;
    +  merge->type= mergetype;
    +  if (angle && qh->ANGLEmerge)
    +    merge->angle= *angle;
    +  if (mergetype < MRGdegen)
    +    qh_setappend(qh, &(qh->facet_mergeset), merge);
    +  else if (mergetype == MRGdegen) {
    +    facet->degenerate= True;
    +    if (!(lastmerge= (mergeT*)qh_setlast(qh->degen_mergeset))
    +    || lastmerge->type == MRGdegen)
    +      qh_setappend(qh, &(qh->degen_mergeset), merge);
    +    else
    +      qh_setaddnth(qh, &(qh->degen_mergeset), 0, merge);
    +  }else if (mergetype == MRGredundant) {
    +    facet->redundant= True;
    +    qh_setappend(qh, &(qh->degen_mergeset), merge);
    +  }else /* mergetype == MRGmirror */ {
    +    if (facet->redundant || neighbor->redundant) {
    +      qh_fprintf(qh, qh->ferr, 6092, "qhull error (qh_appendmergeset): facet f%d or f%d is already a mirrored facet\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
    +    }
    +    if (!qh_setequal(facet->vertices, neighbor->vertices)) {
    +      qh_fprintf(qh, qh->ferr, 6093, "qhull error (qh_appendmergeset): mirrored facets f%d and f%d do not have the same vertices\n",
    +           facet->id, neighbor->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet, neighbor);
    +    }
    +    facet->redundant= True;
    +    neighbor->redundant= True;
    +    qh_setappend(qh, &(qh->degen_mergeset), merge);
    +  }
    +} /* appendmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_basevertices(qh, samecycle )
    +    return temporary set of base vertices for samecycle
    +    samecycle is first facet in the cycle
    +    assumes apex is SETfirst_( samecycle->vertices )
    +
    +  returns:
    +    vertices(settemp)
    +    all ->seen are cleared
    +
    +  notes:
    +    uses qh_vertex_visit;
    +
    +  design:
    +    for each facet in samecycle
    +      for each unseen vertex in facet->vertices
    +        append to result
    +*/
    +setT *qh_basevertices(qhT *qh, facetT *samecycle) {
    +  facetT *same;
    +  vertexT *apex, *vertex, **vertexp;
    +  setT *vertices= qh_settemp(qh, qh->TEMPsize);
    +
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  apex->visitid= ++qh->vertex_visit;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->mergeridge)
    +      continue;
    +    FOREACHvertex_(same->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        qh_setappend(qh, &vertices, vertex);
    +        vertex->visitid= qh->vertex_visit;
    +        vertex->seen= False;
    +      }
    +    }
    +  }
    +  trace4((qh, qh->ferr, 4019, "qh_basevertices: found %d vertices\n",
    +         qh_setsize(qh, vertices)));
    +  return vertices;
    +} /* basevertices */
    +
    +/*---------------------------------
    +
    +  qh_checkconnect(qh)
    +    check that new facets are connected
    +    new facets are on qh.newfacet_list
    +
    +  notes:
    +    this is slow and it changes the order of the facets
    +    uses qh.visit_id
    +
    +  design:
    +    move first new facet to end of qh.facet_list
    +    for all newly appended facets
    +      append unvisited neighbors to end of qh.facet_list
    +    for all new facets
    +      report error if unvisited
    +*/
    +void qh_checkconnect(qhT *qh /* qh->newfacet_list */) {
    +  facetT *facet, *newfacet, *errfacet= NULL, *neighbor, **neighborp;
    +
    +  facet= qh->newfacet_list;
    +  qh_removefacet(qh, facet);
    +  qh_appendfacet(qh, facet);
    +  facet->visitid= ++qh->visit_id;
    +  FORALLfacet_(facet) {
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        qh_removefacet(qh, neighbor);
    +        qh_appendfacet(qh, neighbor);
    +        neighbor->visitid= qh->visit_id;
    +      }
    +    }
    +  }
    +  FORALLnew_facets {
    +    if (newfacet->visitid == qh->visit_id)
    +      break;
    +    qh_fprintf(qh, qh->ferr, 6094, "qhull error: f%d is not attached to the new facets\n",
    +         newfacet->id);
    +    errfacet= newfacet;
    +  }
    +  if (errfacet)
    +    qh_errexit(qh, qh_ERRqhull, errfacet, NULL);
    +} /* checkconnect */
    +
    +/*---------------------------------
    +
    +  qh_checkzero(qh, testall )
    +    check that facets are clearly convex for qh.DISTround with qh.MERGEexact
    +
    +    if testall,
    +      test all facets for qh.MERGEexact post-merging
    +    else
    +      test qh.newfacet_list
    +
    +    if qh.MERGEexact,
    +      allows coplanar ridges
    +      skips convexity test while qh.ZEROall_ok
    +
    +  returns:
    +    True if all facets !flipped, !dupridge, normal
    +         if all horizon facets are simplicial
    +         if all vertices are clearly below neighbor
    +         if all opposite vertices of horizon are below
    +    clears qh.ZEROall_ok if any problems or coplanar facets
    +
    +  notes:
    +    uses qh.vertex_visit
    +    horizon facets may define multiple new facets
    +
    +  design:
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      check for flagged faults (flipped, etc.)
    +    for all facets in qh.newfacet_list or qh.facet_list
    +      for each neighbor of facet
    +        skip horizon facets for qh.newfacet_list
    +        test the opposite vertex
    +      if qh.newfacet_list
    +        test the other vertices in the facet's horizon facet
    +*/
    +boolT qh_checkzero(qhT *qh, boolT testall) {
    +  facetT *facet, *neighbor, **neighborp;
    +  facetT *horizon, *facetlist;
    +  int neighbor_i;
    +  vertexT *vertex, **vertexp;
    +  realT dist;
    +
    +  if (testall)
    +    facetlist= qh->facet_list;
    +  else {
    +    facetlist= qh->newfacet_list;
    +    FORALLfacet_(facetlist) {
    +      horizon= SETfirstt_(facet->neighbors, facetT);
    +      if (!horizon->simplicial)
    +        goto LABELproblem;
    +      if (facet->flipped || facet->dupridge || !facet->normal)
    +        goto LABELproblem;
    +    }
    +    if (qh->MERGEexact && qh->ZEROall_ok) {
    +      trace2((qh, qh->ferr, 2011, "qh_checkzero: skip convexity check until first pre-merge\n"));
    +      return True;
    +    }
    +  }
    +  FORALLfacet_(facetlist) {
    +    qh->vertex_visit++;
    +    neighbor_i= 0;
    +    horizon= NULL;
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor_i && !testall) {
    +        horizon= neighbor;
    +        neighbor_i++;
    +        continue; /* horizon facet tested in qh_findhorizon */
    +      }
    +      vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +      vertex->visitid= qh->vertex_visit;
    +      zzinc_(Zdistzero);
    +      qh_distplane(qh, vertex->point, neighbor, &dist);
    +      if (dist >= -qh->DISTround) {
    +        qh->ZEROall_ok= False;
    +        if (!qh->MERGEexact || testall || dist > qh->DISTround)
    +          goto LABELnonconvex;
    +      }
    +    }
    +    if (!testall && horizon) {
    +      FOREACHvertex_(horizon->vertices) {
    +        if (vertex->visitid != qh->vertex_visit) {
    +          zzinc_(Zdistzero);
    +          qh_distplane(qh, vertex->point, facet, &dist);
    +          if (dist >= -qh->DISTround) {
    +            qh->ZEROall_ok= False;
    +            if (!qh->MERGEexact || dist > qh->DISTround)
    +              goto LABELnonconvex;
    +          }
    +          break;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh, qh->ferr, 2012, "qh_checkzero: testall %d, facets are %s\n", testall,
    +        (qh->MERGEexact && !testall) ?
    +           "not concave, flipped, or duplicate ridged" : "clearly convex"));
    +  return True;
    +
    + LABELproblem:
    +  qh->ZEROall_ok= False;
    +  trace2((qh, qh->ferr, 2013, "qh_checkzero: facet f%d needs pre-merging\n",
    +       facet->id));
    +  return False;
    +
    + LABELnonconvex:
    +  trace2((qh, qh->ferr, 2014, "qh_checkzero: facet f%d and f%d are not clearly convex.  v%d dist %.2g\n",
    +         facet->id, neighbor->id, vertex->id, dist));
    +  return False;
    +} /* checkzero */
    +
    +/*---------------------------------
    +
    +  qh_compareangle(angle1, angle2 )
    +    used by qsort() to order merges by angle
    +*/
    +int qh_compareangle(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return((a->angle > b->angle) ? 1 : -1);
    +} /* compareangle */
    +
    +/*---------------------------------
    +
    +  qh_comparemerge(merge1, merge2 )
    +    used by qsort() to order merges
    +*/
    +int qh_comparemerge(const void *p1, const void *p2) {
    +  const mergeT *a= *((mergeT *const*)p1), *b= *((mergeT *const*)p2);
    +
    +  return(a->type - b->type);
    +} /* comparemerge */
    +
    +/*---------------------------------
    +
    +  qh_comparevisit(vertex1, vertex2 )
    +    used by qsort() to order vertices by their visitid
    +*/
    +int qh_comparevisit(const void *p1, const void *p2) {
    +  const vertexT *a= *((vertexT *const*)p1), *b= *((vertexT *const*)p2);
    +
    +  return(a->visitid - b->visitid);
    +} /* comparevisit */
    +
    +/*---------------------------------
    +
    +  qh_copynonconvex(qh, atridge )
    +    set non-convex flag on other ridges (if any) between same neighbors
    +
    +  notes:
    +    may be faster if use smaller ridge set
    +
    +  design:
    +    for each ridge of atridge's top facet
    +      if ridge shares the same neighbor
    +        set nonconvex flag
    +*/
    +void qh_copynonconvex(qhT *qh, ridgeT *atridge) {
    +  facetT *facet, *otherfacet;
    +  ridgeT *ridge, **ridgep;
    +
    +  facet= atridge->top;
    +  otherfacet= atridge->bottom;
    +  FOREACHridge_(facet->ridges) {
    +    if (otherfacet == otherfacet_(ridge, facet) && ridge != atridge) {
    +      ridge->nonconvex= True;
    +      trace4((qh, qh->ferr, 4020, "qh_copynonconvex: moved nonconvex flag from r%d to r%d\n",
    +              atridge->id, ridge->id));
    +      break;
    +    }
    +  }
    +} /* copynonconvex */
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_facet(qh, facet )
    +    check facet for degen. or redundancy
    +
    +  notes:
    +    bumps vertex_visit
    +    called if a facet was redundant but no longer is (qh_merge_degenredundant)
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +
    +  see:
    +    qh_degen_redundant_neighbors()
    +
    +  design:
    +    test for redundant neighbor
    +    test for degenerate facet
    +*/
    +void qh_degen_redundant_facet(qhT *qh, facetT *facet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh, qh->ferr, 4021, "qh_degen_redundant_facet: test facet f%d for degen/redundant\n",
    +          facet->id));
    +  FOREACHneighbor_(facet) {
    +    qh->vertex_visit++;
    +    FOREACHvertex_(neighbor->vertices)
    +      vertex->visitid= qh->vertex_visit;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(qh, facet, neighbor, MRGredundant, NULL);
    +      trace2((qh, qh->ferr, 2015, "qh_degen_redundant_facet: f%d is contained in f%d.  merge\n", facet->id, neighbor->id));
    +      return;
    +    }
    +  }
    +  if (qh_setsize(qh, facet->neighbors) < qh->hull_dim) {
    +    qh_appendmergeset(qh, facet, facet, MRGdegen, NULL);
    +    trace2((qh, qh->ferr, 2016, "qh_degen_redundant_neighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* degen_redundant_facet */
    +
    +
    +/*---------------------------------
    +
    +  qh_degen_redundant_neighbors(qh, facet, delfacet,  )
    +    append degenerate and redundant neighbors to facet_mergeset
    +    if delfacet,
    +      only checks neighbors of both delfacet and facet
    +    also checks current facet for degeneracy
    +
    +  notes:
    +    bumps vertex_visit
    +    called for each qh_mergefacet() and qh_mergecycle()
    +    merge and statistics occur in merge_nonconvex
    +    qh_appendmergeset() only appends first reference to facet (i.e., redundant)
    +      it appends redundant facets after degenerate ones
    +
    +    a degenerate facet has fewer than hull_dim neighbors
    +    a redundant facet's vertices is a subset of its neighbor's vertices
    +    tests for redundant merges first (appendmergeset is nop for others)
    +    in a merge, only needs to test neighbors of merged facet
    +
    +  see:
    +    qh_merge_degenredundant() and qh_degen_redundant_facet()
    +
    +  design:
    +    test for degenerate facet
    +    test for redundant neighbor
    +    test for degenerate neighbor
    +*/
    +void qh_degen_redundant_neighbors(qhT *qh, facetT *facet, facetT *delfacet) {
    +  vertexT *vertex, **vertexp;
    +  facetT *neighbor, **neighborp;
    +  int size;
    +
    +  trace4((qh, qh->ferr, 4022, "qh_degen_redundant_neighbors: test neighbors of f%d with delfacet f%d\n",
    +          facet->id, getid_(delfacet)));
    +  if ((size= qh_setsize(qh, facet->neighbors)) < qh->hull_dim) {
    +    qh_appendmergeset(qh, facet, facet, MRGdegen, NULL);
    +    trace2((qh, qh->ferr, 2017, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.\n", facet->id, size));
    +  }
    +  if (!delfacet)
    +    delfacet= facet;
    +  qh->vertex_visit++;
    +  FOREACHvertex_(facet->vertices)
    +    vertex->visitid= qh->vertex_visit;
    +  FOREACHneighbor_(delfacet) {
    +    /* uses early out instead of checking vertex count */
    +    if (neighbor == facet)
    +      continue;
    +    FOREACHvertex_(neighbor->vertices) {
    +      if (vertex->visitid != qh->vertex_visit)
    +        break;
    +    }
    +    if (!vertex) {
    +      qh_appendmergeset(qh, neighbor, facet, MRGredundant, NULL);
    +      trace2((qh, qh->ferr, 2018, "qh_degen_redundant_neighbors: f%d is contained in f%d.  merge\n", neighbor->id, facet->id));
    +    }
    +  }
    +  FOREACHneighbor_(delfacet) {   /* redundant merges occur first */
    +    if (neighbor == facet)
    +      continue;
    +    if ((size= qh_setsize(qh, neighbor->neighbors)) < qh->hull_dim) {
    +      qh_appendmergeset(qh, neighbor, neighbor, MRGdegen, NULL);
    +      trace2((qh, qh->ferr, 2019, "qh_degen_redundant_neighbors: f%d is degenerate with %d neighbors.  Neighbor of f%d.\n", neighbor->id, size, facet->id));
    +    }
    +  }
    +} /* degen_redundant_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_find_newvertex(qh, oldvertex, vertices, ridges )
    +    locate new vertex for renaming old vertex
    +    vertices is a set of possible new vertices
    +      vertices sorted by number of deleted ridges
    +
    +  returns:
    +    newvertex or NULL
    +      each ridge includes both vertex and oldvertex
    +    vertices sorted by number of deleted ridges
    +
    +  notes:
    +    modifies vertex->visitid
    +    new vertex is in one of the ridges
    +    renaming will not cause a duplicate ridge
    +    renaming will minimize the number of deleted ridges
    +    newvertex may not be adjacent in the dual (though unlikely)
    +
    +  design:
    +    for each vertex in vertices
    +      set vertex->visitid to number of references in ridges
    +    remove unvisited vertices
    +    set qh.vertex_visit above all possible values
    +    sort vertices by number of references in ridges
    +    add each ridge to qh.hash_table
    +    for each vertex in vertices
    +      look for a vertex that would not cause a duplicate ridge after a rename
    +*/
    +vertexT *qh_find_newvertex(qhT *qh, vertexT *oldvertex, setT *vertices, setT *ridges) {
    +  vertexT *vertex, **vertexp;
    +  setT *newridges;
    +  ridgeT *ridge, **ridgep;
    +  int size, hashsize;
    +  int hash;
    +
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 4) {
    +    qh_fprintf(qh, qh->ferr, 8063, "qh_find_newvertex: find new vertex for v%d from ",
    +             oldvertex->id);
    +    FOREACHvertex_(vertices)
    +      qh_fprintf(qh, qh->ferr, 8064, "v%d ", vertex->id);
    +    FOREACHridge_(ridges)
    +      qh_fprintf(qh, qh->ferr, 8065, "r%d ", ridge->id);
    +    qh_fprintf(qh, qh->ferr, 8066, "\n");
    +  }
    +#endif
    +  FOREACHvertex_(vertices)
    +    vertex->visitid= 0;
    +  FOREACHridge_(ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->visitid++;
    +  }
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->visitid) {
    +      qh_setdelnth(qh, vertices, SETindex_(vertices,vertex));
    +      vertexp--; /* repeat since deleted this vertex */
    +    }
    +  }
    +  qh->vertex_visit += (unsigned int)qh_setsize(qh, ridges);
    +  if (!qh_setsize(qh, vertices)) {
    +    trace4((qh, qh->ferr, 4023, "qh_find_newvertex: vertices not in ridges for v%d\n",
    +            oldvertex->id));
    +    return NULL;
    +  }
    +  qsort(SETaddr_(vertices, vertexT), (size_t)qh_setsize(qh, vertices),
    +                sizeof(vertexT *), qh_comparevisit);
    +  /* can now use qh->vertex_visit */
    +  if (qh->PRINTstatistics) {
    +    size= qh_setsize(qh, vertices);
    +    zinc_(Zintersect);
    +    zadd_(Zintersecttot, size);
    +    zmax_(Zintersectmax, size);
    +  }
    +  hashsize= qh_newhashtable(qh, qh_setsize(qh, ridges));
    +  FOREACHridge_(ridges)
    +    qh_hashridge(qh, qh->hash_table, hashsize, ridge, oldvertex);
    +  FOREACHvertex_(vertices) {
    +    newridges= qh_vertexridges(qh, vertex);
    +    FOREACHridge_(newridges) {
    +      if (qh_hashridge_find(qh, qh->hash_table, hashsize, ridge, vertex, oldvertex, &hash)) {
    +        zinc_(Zdupridge);
    +        break;
    +      }
    +    }
    +    qh_settempfree(qh, &newridges);
    +    if (!ridge)
    +      break;  /* found a rename */
    +  }
    +  if (vertex) {
    +    /* counted in qh_renamevertex */
    +    trace2((qh, qh->ferr, 2020, "qh_find_newvertex: found v%d for old v%d from %d vertices and %d ridges.\n",
    +      vertex->id, oldvertex->id, qh_setsize(qh, vertices), qh_setsize(qh, ridges)));
    +  }else {
    +    zinc_(Zfindfail);
    +    trace0((qh, qh->ferr, 14, "qh_find_newvertex: no vertex for renaming v%d(all duplicated ridges) during p%d\n",
    +      oldvertex->id, qh->furthest_id));
    +  }
    +  qh_setfree(qh, &qh->hash_table);
    +  return vertex;
    +} /* find_newvertex */
    +
    +/*---------------------------------
    +
    +  qh_findbest_test(qh, testcentrum, facet, neighbor, bestfacet, dist, mindist, maxdist )
    +    test neighbor of facet for qh_findbestneighbor()
    +    if testcentrum,
    +      tests centrum (assumes it is defined)
    +    else
    +      tests vertices
    +
    +  returns:
    +    if a better facet (i.e., vertices/centrum of facet closer to neighbor)
    +      updates bestfacet, dist, mindist, and maxdist
    +*/
    +void qh_findbest_test(qhT *qh, boolT testcentrum, facetT *facet, facetT *neighbor,
    +      facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  realT dist, mindist, maxdist;
    +
    +  if (testcentrum) {
    +    zzinc_(Zbestdist);
    +    qh_distplane(qh, facet->center, neighbor, &dist);
    +    dist *= qh->hull_dim; /* estimate furthest vertex */
    +    if (dist < 0) {
    +      maxdist= 0;
    +      mindist= dist;
    +      dist= -dist;
    +    }else {
    +      mindist= 0;
    +      maxdist= dist;
    +    }
    +  }else
    +    dist= qh_getdistance(qh, facet, neighbor, &mindist, &maxdist);
    +  if (dist < *distp) {
    +    *bestfacet= neighbor;
    +    *mindistp= mindist;
    +    *maxdistp= maxdist;
    +    *distp= dist;
    +  }
    +} /* findbest_test */
    +
    +/*---------------------------------
    +
    +  qh_findbestneighbor(qh, facet, dist, mindist, maxdist )
    +    finds best neighbor (least dist) of a facet for merging
    +
    +  returns:
    +    returns min and max distances and their max absolute value
    +
    +  notes:
    +    error if qh_ASvoronoi
    +    avoids merging old into new
    +    assumes ridge->nonconvex only set on one ridge between a pair of facets
    +    could use an early out predicate but not worth it
    +
    +  design:
    +    if a large facet
    +      will test centrum
    +    else
    +      will test vertices
    +    if a large facet
    +      test nonconvex neighbors for best merge
    +    else
    +      test all neighbors for the best merge
    +    if testing centrum
    +      get distance information
    +*/
    +facetT *qh_findbestneighbor(qhT *qh, facetT *facet, realT *distp, realT *mindistp, realT *maxdistp) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  ridgeT *ridge, **ridgep;
    +  boolT nonconvex= True, testcentrum= False;
    +  int size= qh_setsize(qh, facet->vertices);
    +
    +  if(qh->CENTERtype==qh_ASvoronoi){
    +    qh_fprintf(qh, qh->ferr, 6272, "qhull error: cannot call qh_findbestneighor for f%d while qh.CENTERtype is qh_ASvoronoi\n", facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  *distp= REALmax;
    +  if (size > qh_BESTcentrum2 * qh->hull_dim + qh_BESTcentrum) {
    +    testcentrum= True;
    +    zinc_(Zbestcentrum);
    +    if (!facet->center)
    +       facet->center= qh_getcentrum(qh, facet);
    +  }
    +  if (size > qh->hull_dim + qh_BESTnonconvex) {
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->nonconvex) {
    +        neighbor= otherfacet_(ridge, facet);
    +        qh_findbest_test(qh, testcentrum, facet, neighbor,
    +                          &bestfacet, distp, mindistp, maxdistp);
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    nonconvex= False;
    +    FOREACHneighbor_(facet)
    +      qh_findbest_test(qh, testcentrum, facet, neighbor,
    +                        &bestfacet, distp, mindistp, maxdistp);
    +  }
    +  if (!bestfacet) {
    +    qh_fprintf(qh, qh->ferr, 6095, "qhull internal error (qh_findbestneighbor): no neighbors for f%d\n", facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  if (testcentrum)
    +    qh_getdistance(qh, facet, bestfacet, mindistp, maxdistp);
    +  trace3((qh, qh->ferr, 3002, "qh_findbestneighbor: f%d is best neighbor for f%d testcentrum? %d nonconvex? %d dist %2.2g min %2.2g max %2.2g\n",
    +     bestfacet->id, facet->id, testcentrum, nonconvex, *distp, *mindistp, *maxdistp));
    +  return(bestfacet);
    +} /* findbestneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_flippedmerges(qh, facetlist, wasmerge )
    +    merge flipped facets into best neighbor
    +    assumes qh.facet_mergeset at top of temporary stack
    +
    +  returns:
    +    no flipped facets on facetlist
    +    sets wasmerge if merge occurred
    +    degen/redundant merges passed through
    +
    +  notes:
    +    othermerges not needed since qh.facet_mergeset is empty before & after
    +      keep it in case of change
    +
    +  design:
    +    append flipped facets to qh.facetmergeset
    +    for each flipped merge
    +      find best neighbor
    +      merge facet into neighbor
    +      merge degenerate and redundant facets
    +    remove flipped merges from qh.facet_mergeset
    +*/
    +void qh_flippedmerges(qhT *qh, facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *neighbor, *facet1;
    +  realT dist, mindist, maxdist;
    +  mergeT *merge, **mergep;
    +  setT *othermerges;
    +  int nummerge=0;
    +
    +  trace4((qh, qh->ferr, 4024, "qh_flippedmerges: begin\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped && !facet->visible)
    +      qh_appendmergeset(qh, facet, facet, MRGflip, NULL);
    +  }
    +  othermerges= qh_settemppop(qh); /* was facet_mergeset */
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh_settemppush(qh, othermerges);
    +  FOREACHmerge_(othermerges) {
    +    facet1= merge->facet1;
    +    if (merge->type != MRGflip || facet1->visible)
    +      continue;
    +    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +      qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +    neighbor= qh_findbestneighbor(qh, facet1, &dist, &mindist, &maxdist);
    +    trace0((qh, qh->ferr, 15, "qh_flippedmerges: merge flipped f%d into f%d dist %2.2g during p%d\n",
    +      facet1->id, neighbor->id, dist, qh->furthest_id));
    +    qh_mergefacet(qh, facet1, neighbor, &mindist, &maxdist, !qh_MERGEapex);
    +    nummerge++;
    +    if (qh->PRINTstatistics) {
    +      zinc_(Zflipped);
    +      wadd_(Wflippedtot, dist);
    +      wmax_(Wflippedmax, dist);
    +    }
    +    qh_merge_degenredundant(qh);
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->facet1->visible || merge->facet2->visible)
    +      qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(qh, &qh->facet_mergeset, merge);
    +  }
    +  qh_settempfree(qh, &othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh, qh->ferr, 1010, "qh_flippedmerges: merged %d flipped facets into a good neighbor\n", nummerge));
    +} /* flippedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_forcedmerges(qh, wasmerge )
    +    merge duplicated ridges
    +
    +  returns:
    +    removes all duplicate ridges on facet_mergeset
    +    wasmerge set if merge
    +    qh.facet_mergeset may include non-forced merges(none for now)
    +    qh.degen_mergeset includes degen/redun merges
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +     could rename vertices that pinch the horizon
    +    assumes qh_merge_degenredundant() has not be called
    +    othermerges isn't needed since facet_mergeset is empty afterwards
    +      keep it in case of change
    +
    +  design:
    +    for each duplicate ridge
    +      find current facets by chasing f.replace links
    +      check for wide merge due to duplicate ridge
    +      determine best direction for facet
    +      merge one facet into the other
    +      remove duplicate ridges from qh.facet_mergeset
    +*/
    +void qh_forcedmerges(qhT *qh, boolT *wasmerge) {
    +  facetT *facet1, *facet2;
    +  mergeT *merge, **mergep;
    +  realT dist1, dist2, mindist1, mindist2, maxdist1, maxdist2;
    +  setT *othermerges;
    +  int nummerge=0, numflip=0;
    +
    +  if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +  trace4((qh, qh->ferr, 4025, "qh_forcedmerges: begin\n"));
    +  othermerges= qh_settemppop(qh); /* was facet_mergeset */
    +  qh->facet_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh_settemppush(qh, othermerges);
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type != MRGridge)
    +        continue;
    +    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +        qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    while (facet1->visible)      /* must exist, no qh_merge_degenredunant */
    +      facet1= facet1->f.replace; /* previously merged facet */
    +    while (facet2->visible)
    +      facet2= facet2->f.replace; /* previously merged facet */
    +    if (facet1 == facet2)
    +      continue;
    +    if (!qh_setin(facet2->neighbors, facet1)) {
    +      qh_fprintf(qh, qh->ferr, 6096, "qhull internal error (qh_forcedmerges): f%d and f%d had a duplicate ridge but as f%d and f%d they are no longer neighbors\n",
    +               merge->facet1->id, merge->facet2->id, facet1->id, facet2->id);
    +      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +    }
    +    dist1= qh_getdistance(qh, facet1, facet2, &mindist1, &maxdist1);
    +    dist2= qh_getdistance(qh, facet2, facet1, &mindist2, &maxdist2);
    +    qh_check_dupridge(qh, facet1, dist1, facet2, dist2);
    +    if (dist1 < dist2)
    +      qh_mergefacet(qh, facet1, facet2, &mindist1, &maxdist1, !qh_MERGEapex);
    +    else {
    +      qh_mergefacet(qh, facet2, facet1, &mindist2, &maxdist2, !qh_MERGEapex);
    +      dist1= dist2;
    +      facet1= facet2;
    +    }
    +    if (facet1->flipped) {
    +      zinc_(Zmergeflipdup);
    +      numflip++;
    +    }else
    +      nummerge++;
    +    if (qh->PRINTstatistics) {
    +      zinc_(Zduplicate);
    +      wadd_(Wduplicatetot, dist1);
    +      wmax_(Wduplicatemax, dist1);
    +    }
    +  }
    +  FOREACHmerge_(othermerges) {
    +    if (merge->type == MRGridge)
    +      qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    else
    +      qh_setappend(qh, &qh->facet_mergeset, merge);
    +  }
    +  qh_settempfree(qh, &othermerges);
    +  if (nummerge)
    +    *wasmerge= True;
    +  trace1((qh, qh->ferr, 1011, "qh_forcedmerges: merged %d facets and %d flipped facets across duplicated ridges\n",
    +                nummerge, numflip));
    +} /* forcedmerges */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset(qh, facetlist )
    +    determines nonconvex facets on facetlist
    +    tests !tested ridges and nonconvex ridges of !tested facets
    +
    +  returns:
    +    returns sorted qh.facet_mergeset of facet-neighbor pairs to be merged
    +    all ridges tested
    +
    +  notes:
    +    assumes no nonconvex ridges with both facets tested
    +    uses facet->tested/ridge->tested to prevent duplicate tests
    +    can not limit tests to modified ridges since the centrum changed
    +    uses qh.visit_id
    +
    +  see:
    +    qh_getmergeset_initial()
    +
    +  design:
    +    for each facet on facetlist
    +      for each ridge of facet
    +        if untested ridge
    +          test ridge for convexity
    +          if non-convex
    +            append ridge to qh.facet_mergeset
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  nummerges= qh_setsize(qh, qh->facet_mergeset);
    +  trace4((qh, qh->ferr, 4026, "qh_getmergeset: started.\n"));
    +  qh->visit_id++;
    +  FORALLfacet_(facetlist) {
    +    if (facet->tested)
    +      continue;
    +    facet->visitid= qh->visit_id;
    +    facet->tested= True;  /* must be non-simplicial due to merge */
    +    FOREACHneighbor_(facet)
    +      neighbor->seen= False;
    +    FOREACHridge_(facet->ridges) {
    +      if (ridge->tested && !ridge->nonconvex)
    +        continue;
    +      /* if tested & nonconvex, need to append merge */
    +      neighbor= otherfacet_(ridge, facet);
    +      if (neighbor->seen) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +      }else if (neighbor->visitid != qh->visit_id) {
    +        ridge->tested= True;
    +        ridge->nonconvex= False;
    +        neighbor->seen= True;      /* only one ridge is marked nonconvex */
    +        if (qh_test_appendmerge(qh, facet, neighbor))
    +          ridge->nonconvex= True;
    +      }
    +    }
    +  }
    +  nummerges= qh_setsize(qh, qh->facet_mergeset);
    +  if (qh->ANGLEmerge)
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh->POSTmerging) {
    +    zadd_(Zmergesettot2, nummerges);
    +  }else {
    +    zadd_(Zmergesettot, nummerges);
    +    zmax_(Zmergesetmax, nummerges);
    +  }
    +  trace2((qh, qh->ferr, 2021, "qh_getmergeset: %d merges found\n", nummerges));
    +} /* getmergeset */
    +
    +
    +/*---------------------------------
    +
    +  qh_getmergeset_initial(qh, facetlist )
    +    determine initial qh.facet_mergeset for facets
    +    tests all facet/neighbor pairs on facetlist
    +
    +  returns:
    +    sorted qh.facet_mergeset with nonconvex ridges
    +    sets facet->tested, ridge->tested, and ridge->nonconvex
    +
    +  notes:
    +    uses visit_id, assumes ridge->nonconvex is False
    +
    +  see:
    +    qh_getmergeset()
    +
    +  design:
    +    for each facet on facetlist
    +      for each untested neighbor of facet
    +        test facet and neighbor for convexity
    +        if non-convex
    +          append merge to qh.facet_mergeset
    +          mark one of the ridges as nonconvex
    +    sort qh.facet_mergeset by angle
    +*/
    +void qh_getmergeset_initial(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int nummerges;
    +
    +  qh->visit_id++;
    +  FORALLfacet_(facetlist) {
    +    facet->visitid= qh->visit_id;
    +    facet->tested= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->visitid != qh->visit_id) {
    +        if (qh_test_appendmerge(qh, facet, neighbor)) {
    +          FOREACHridge_(neighbor->ridges) {
    +            if (facet == otherfacet_(ridge, neighbor)) {
    +              ridge->nonconvex= True;
    +              break;    /* only one ridge is marked nonconvex */
    +            }
    +          }
    +        }
    +      }
    +    }
    +    FOREACHridge_(facet->ridges)
    +      ridge->tested= True;
    +  }
    +  nummerges= qh_setsize(qh, qh->facet_mergeset);
    +  if (qh->ANGLEmerge)
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_compareangle);
    +  else
    +    qsort(SETaddr_(qh->facet_mergeset, mergeT), (size_t)nummerges, sizeof(mergeT *), qh_comparemerge);
    +  if (qh->POSTmerging) {
    +    zadd_(Zmergeinittot2, nummerges);
    +  }else {
    +    zadd_(Zmergeinittot, nummerges);
    +    zmax_(Zmergeinitmax, nummerges);
    +  }
    +  trace2((qh, qh->ferr, 2022, "qh_getmergeset_initial: %d merges found\n", nummerges));
    +} /* getmergeset_initial */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge(qh, hashtable, hashsize, ridge, oldvertex )
    +    add ridge to hashtable without oldvertex
    +
    +  notes:
    +    assumes hashtable is large enough
    +
    +  design:
    +    determine hash value for ridge without oldvertex
    +    find next empty slot for ridge
    +*/
    +void qh_hashridge(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  hash= qh_gethash(qh, hashsize, ridge->vertices, qh->hull_dim-1, 0, oldvertex);
    +  while (True) {
    +    if (!(ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +      SETelem_(hashtable, hash)= ridge;
    +      break;
    +    }else if (ridgeA == ridge)
    +      break;
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +} /* hashridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_hashridge_find(qh, hashtable, hashsize, ridge, vertex, oldvertex, hashslot )
    +    returns matching ridge without oldvertex in hashtable
    +      for ridge without vertex
    +    if oldvertex is NULL
    +      matches with any one skip
    +
    +  returns:
    +    matching ridge or NULL
    +    if no match,
    +      if ridge already in   table
    +        hashslot= -1
    +      else
    +        hashslot= next NULL index
    +
    +  notes:
    +    assumes hashtable is large enough
    +    can't match ridge to itself
    +
    +  design:
    +    get hash value for ridge without vertex
    +    for each hashslot
    +      return match if ridge matches ridgeA without oldvertex
    +*/
    +ridgeT *qh_hashridge_find(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot) {
    +  int hash;
    +  ridgeT *ridgeA;
    +
    +  *hashslot= 0;
    +  zinc_(Zhashridge);
    +  hash= qh_gethash(qh, hashsize, ridge->vertices, qh->hull_dim-1, 0, vertex);
    +  while ((ridgeA= SETelemt_(hashtable, hash, ridgeT))) {
    +    if (ridgeA == ridge)
    +      *hashslot= -1;
    +    else {
    +      zinc_(Zhashridgetest);
    +      if (qh_setequal_except(ridge->vertices, vertex, ridgeA->vertices, oldvertex))
    +        return ridgeA;
    +    }
    +    if (++hash == hashsize)
    +      hash= 0;
    +  }
    +  if (!*hashslot)
    +    *hashslot= hash;
    +  return NULL;
    +} /* hashridge_find */
    +
    +
    +/*---------------------------------
    +
    +  qh_makeridges(qh, facet )
    +    creates explicit ridges between simplicial facets
    +
    +  returns:
    +    facet with ridges and without qh_MERGEridge
    +    ->simplicial is False
    +
    +  notes:
    +    allows qh_MERGEridge flag
    +    uses existing ridges
    +    duplicate neighbors ok if ridges already exist (qh_mergecycle_ridges)
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    look for qh_MERGEridge neighbors
    +    mark neighbors that already have ridges
    +    for each unprocessed neighbor of facet
    +      create a ridge for neighbor and facet
    +    if any qh_MERGEridge neighbors
    +      delete qh_MERGEridge flags (already handled by qh_mark_dupridges)
    +*/
    +void qh_makeridges(qhT *qh, facetT *facet) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  int neighbor_i, neighbor_n;
    +  boolT toporient, mergeridge= False;
    +
    +  if (!facet->simplicial)
    +    return;
    +  trace4((qh, qh->ferr, 4027, "qh_makeridges: make ridges for f%d\n", facet->id));
    +  facet->simplicial= False;
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge)
    +      mergeridge= True;
    +    else
    +      neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges)
    +    otherfacet_(ridge, facet)->seen= True;
    +  FOREACHneighbor_i_(qh, facet) {
    +    if (neighbor == qh_MERGEridge)
    +      continue;  /* fixed by qh_mark_dupridges */
    +    else if (!neighbor->seen) {  /* no current ridges */
    +      ridge= qh_newridge(qh);
    +      ridge->vertices= qh_setnew_delnthsorted(qh, facet->vertices, qh->hull_dim,
    +                                                          neighbor_i, 0);
    +      toporient= facet->toporient ^ (neighbor_i & 0x1);
    +      if (toporient) {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }else {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }
    +#if 0 /* this also works */
    +      flip= (facet->toporient ^ neighbor->toporient)^(skip1 & 0x1) ^ (skip2 & 0x1);
    +      if (facet->toporient ^ (skip1 & 0x1) ^ flip) {
    +        ridge->top= neighbor;
    +        ridge->bottom= facet;
    +      }else {
    +        ridge->top= facet;
    +        ridge->bottom= neighbor;
    +      }
    +#endif
    +      qh_setappend(qh, &(facet->ridges), ridge);
    +      qh_setappend(qh, &(neighbor->ridges), ridge);
    +    }
    +  }
    +  if (mergeridge) {
    +    while (qh_setdel(facet->neighbors, qh_MERGEridge))
    +      ; /* delete each one */
    +  }
    +} /* makeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mark_dupridges(qh, facetlist )
    +    add duplicated ridges to qh.facet_mergeset
    +    facet->dupridge is true
    +
    +  returns:
    +    duplicate ridges on qh.facet_mergeset
    +    ->mergeridge/->mergeridge2 set
    +    duplicate ridges marked by qh_MERGEridge and both sides facet->dupridge
    +    no MERGEridges in neighbor sets
    +
    +  notes:
    +    duplicate ridges occur when the horizon is pinched,
    +        i.e. a subridge occurs in more than two horizon ridges.
    +    could rename vertices that pinch the horizon (thus removing subridge)
    +    uses qh.visit_id
    +
    +  design:
    +    for all facets on facetlist
    +      if facet contains a duplicate ridge
    +        for each neighbor of facet
    +          if neighbor marked qh_MERGEridge (one side of the merge)
    +            set facet->mergeridge
    +          else
    +            if neighbor contains a duplicate ridge
    +            and the back link is qh_MERGEridge
    +              append duplicate ridge to qh.facet_mergeset
    +   for each duplicate ridge
    +     make ridge sets in preparation for merging
    +     remove qh_MERGEridge from neighbor set
    +   for each duplicate ridge
    +     restore the missing neighbor from the neighbor set that was qh_MERGEridge
    +     add the missing ridge for this neighbor
    +*/
    +void qh_mark_dupridges(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *neighbor, **neighborp;
    +  int nummerge=0;
    +  mergeT *merge, **mergep;
    +
    +
    +  trace4((qh, qh->ferr, 4028, "qh_mark_dupridges: identify duplicate ridges\n"));
    +  FORALLfacet_(facetlist) {
    +    if (facet->dupridge) {
    +      FOREACHneighbor_(facet) {
    +        if (neighbor == qh_MERGEridge) {
    +          facet->mergeridge= True;
    +          continue;
    +        }
    +        if (neighbor->dupridge
    +        && !qh_setin(neighbor->neighbors, facet)) { /* qh_MERGEridge */
    +          qh_appendmergeset(qh, facet, neighbor, MRGridge, NULL);
    +          facet->mergeridge2= True;
    +          facet->mergeridge= True;
    +          nummerge++;
    +        }
    +      }
    +    }
    +  }
    +  if (!nummerge)
    +    return;
    +  FORALLfacet_(facetlist) {            /* gets rid of qh_MERGEridge */
    +    if (facet->mergeridge && !facet->mergeridge2)
    +      qh_makeridges(qh, facet);
    +  }
    +  FOREACHmerge_(qh->facet_mergeset) {   /* restore the missing neighbors */
    +    if (merge->type == MRGridge) {
    +      qh_setappend(qh, &merge->facet2->neighbors, merge->facet1);
    +      qh_makeridges(qh, merge->facet1);   /* and the missing ridges */
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1012, "qh_mark_dupridges: found %d duplicated ridges\n",
    +                nummerge));
    +} /* mark_dupridges */
    +
    +/*---------------------------------
    +
    +  qh_maydropneighbor(qh, facet )
    +    drop neighbor relationship if no ridge between facet and neighbor
    +
    +  returns:
    +    neighbor sets updated
    +    appends degenerate facets to qh.facet_mergeset
    +
    +  notes:
    +    won't cause redundant facets since vertex inclusion is the same
    +    may drop vertex and neighbor if no ridge
    +    uses qh.visit_id
    +
    +  design:
    +    visit all neighbors with ridges
    +    for each unvisited neighbor of facet
    +      delete neighbor and facet from the neighbor sets
    +      if neighbor becomes degenerate
    +        append neighbor to qh.degen_mergeset
    +    if facet is degenerate
    +      append facet to qh.degen_mergeset
    +*/
    +void qh_maydropneighbor(qhT *qh, facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  realT angledegen= qh_ANGLEdegen;
    +  facetT *neighbor, **neighborp;
    +
    +  qh->visit_id++;
    +  trace4((qh, qh->ferr, 4029, "qh_maydropneighbor: test f%d for no ridges to a neighbor\n",
    +          facet->id));
    +  FOREACHridge_(facet->ridges) {
    +    ridge->top->visitid= qh->visit_id;
    +    ridge->bottom->visitid= qh->visit_id;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor->visitid != qh->visit_id) {
    +      trace0((qh, qh->ferr, 17, "qh_maydropneighbor: facets f%d and f%d are no longer neighbors during p%d\n",
    +            facet->id, neighbor->id, qh->furthest_id));
    +      zinc_(Zdropneighbor);
    +      qh_setdel(facet->neighbors, neighbor);
    +      neighborp--;  /* repeat, deleted a neighbor */
    +      qh_setdel(neighbor->neighbors, facet);
    +      if (qh_setsize(qh, neighbor->neighbors) < qh->hull_dim) {
    +        zinc_(Zdropdegen);
    +        qh_appendmergeset(qh, neighbor, neighbor, MRGdegen, &angledegen);
    +        trace2((qh, qh->ferr, 2023, "qh_maydropneighbors: f%d is degenerate.\n", neighbor->id));
    +      }
    +    }
    +  }
    +  if (qh_setsize(qh, facet->neighbors) < qh->hull_dim) {
    +    zinc_(Zdropdegen);
    +    qh_appendmergeset(qh, facet, facet, MRGdegen, &angledegen);
    +    trace2((qh, qh->ferr, 2024, "qh_maydropneighbors: f%d is degenerate.\n", facet->id));
    +  }
    +} /* maydropneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_merge_degenredundant(qh)
    +    merge all degenerate and redundant facets
    +    qh.degen_mergeset contains merges from qh_degen_redundant_neighbors()
    +
    +  returns:
    +    number of merges performed
    +    resets facet->degenerate/redundant
    +    if deleted (visible) facet has no neighbors
    +      sets ->f.replace to NULL
    +
    +  notes:
    +    redundant merges happen before degenerate ones
    +    merging and renaming vertices can result in degen/redundant facets
    +
    +  design:
    +    for each merge on qh.degen_mergeset
    +      if redundant merge
    +        if non-redundant facet merged into redundant facet
    +          recheck facet for redundancy
    +        else
    +          merge redundant facet into other facet
    +*/
    +int qh_merge_degenredundant(qhT *qh) {
    +  int size;
    +  mergeT *merge;
    +  facetT *bestneighbor, *facet1, *facet2;
    +  realT dist, mindist, maxdist;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +  mergeType mergetype;
    +
    +  while ((merge= (mergeT*)qh_setdellast(qh->degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    if (facet1->visible)
    +      continue;
    +    facet1->degenerate= False;
    +    facet1->redundant= False;
    +    if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +      qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +    if (mergetype == MRGredundant) {
    +      zinc_(Zneighbor);
    +      while (facet2->visible) {
    +        if (!facet2->f.replace) {
    +          qh_fprintf(qh, qh->ferr, 6097, "qhull internal error (qh_merge_degenredunant): f%d redundant but f%d has no replacement\n",
    +               facet1->id, facet2->id);
    +          qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +        }
    +        facet2= facet2->f.replace;
    +      }
    +      if (facet1 == facet2) {
    +        qh_degen_redundant_facet(qh, facet1); /* in case of others */
    +        continue;
    +      }
    +      trace2((qh, qh->ferr, 2025, "qh_merge_degenredundant: facet f%d is contained in f%d, will merge\n",
    +            facet1->id, facet2->id));
    +      qh_mergefacet(qh, facet1, facet2, NULL, NULL, !qh_MERGEapex);
    +      /* merge distance is already accounted for */
    +      nummerges++;
    +    }else {  /* mergetype == MRGdegen, other merges may have fixed */
    +      if (!(size= qh_setsize(qh, facet1->neighbors))) {
    +        zinc_(Zdelfacetdup);
    +        trace2((qh, qh->ferr, 2026, "qh_merge_degenredundant: facet f%d has no neighbors.  Deleted\n", facet1->id));
    +        qh_willdelete(qh, facet1, NULL);
    +        FOREACHvertex_(facet1->vertices) {
    +          qh_setdel(vertex->neighbors, facet1);
    +          if (!SETfirst_(vertex->neighbors)) {
    +            zinc_(Zdegenvertex);
    +            trace2((qh, qh->ferr, 2027, "qh_merge_degenredundant: deleted v%d because f%d has no neighbors\n",
    +                 vertex->id, facet1->id));
    +            vertex->deleted= True;
    +            qh_setappend(qh, &qh->del_vertices, vertex);
    +          }
    +        }
    +        nummerges++;
    +      }else if (size < qh->hull_dim) {
    +        bestneighbor= qh_findbestneighbor(qh, facet1, &dist, &mindist, &maxdist);
    +        trace2((qh, qh->ferr, 2028, "qh_merge_degenredundant: facet f%d has %d neighbors, merge into f%d dist %2.2g\n",
    +              facet1->id, size, bestneighbor->id, dist));
    +        qh_mergefacet(qh, facet1, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +        nummerges++;
    +        if (qh->PRINTstatistics) {
    +          zinc_(Zdegen);
    +          wadd_(Wdegentot, dist);
    +          wmax_(Wdegenmax, dist);
    +        }
    +      } /* else, another merge fixed the degeneracy and redundancy tested */
    +    }
    +  }
    +  return nummerges;
    +} /* merge_degenredundant */
    +
    +/*---------------------------------
    +
    +  qh_merge_nonconvex(qh, facet1, facet2, mergetype )
    +    remove non-convex ridge between facet1 into facet2
    +    mergetype gives why the facet's are non-convex
    +
    +  returns:
    +    merges one of the facets into the best neighbor
    +
    +  design:
    +    if one of the facets is a new facet
    +      prefer merging new facet into old facet
    +    find best neighbors for both facets
    +    merge the nearest facet into its best neighbor
    +    update the statistics
    +*/
    +void qh_merge_nonconvex(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype) {
    +  facetT *bestfacet, *bestneighbor, *neighbor;
    +  realT dist, dist2, mindist, mindist2, maxdist, maxdist2;
    +
    +  if (qh->TRACEmerge-1 == zzval_(Ztotmerge))
    +    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +  trace3((qh, qh->ferr, 3003, "qh_merge_nonconvex: merge #%d for f%d and f%d type %d\n",
    +      zzval_(Ztotmerge) + 1, facet1->id, facet2->id, mergetype));
    +  /* concave or coplanar */
    +  if (!facet1->newfacet) {
    +    bestfacet= facet2;   /* avoid merging old facet if new is ok */
    +    facet2= facet1;
    +    facet1= bestfacet;
    +  }else
    +    bestfacet= facet1;
    +  bestneighbor= qh_findbestneighbor(qh, bestfacet, &dist, &mindist, &maxdist);
    +  neighbor= qh_findbestneighbor(qh, facet2, &dist2, &mindist2, &maxdist2);
    +  if (dist < dist2) {
    +    qh_mergefacet(qh, bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else if (qh->AVOIDold && !facet2->newfacet
    +  && ((mindist >= -qh->MAXcoplanar && maxdist <= qh->max_outside)
    +       || dist * 1.5 < dist2)) {
    +    zinc_(Zavoidold);
    +    wadd_(Wavoidoldtot, dist);
    +    wmax_(Wavoidoldmax, dist);
    +    trace2((qh, qh->ferr, 2029, "qh_merge_nonconvex: avoid merging old facet f%d dist %2.2g.  Use f%d dist %2.2g instead\n",
    +           facet2->id, dist2, facet1->id, dist2));
    +    qh_mergefacet(qh, bestfacet, bestneighbor, &mindist, &maxdist, !qh_MERGEapex);
    +  }else {
    +    qh_mergefacet(qh, facet2, neighbor, &mindist2, &maxdist2, !qh_MERGEapex);
    +    dist= dist2;
    +  }
    +  if (qh->PRINTstatistics) {
    +    if (mergetype == MRGanglecoplanar) {
    +      zinc_(Zacoplanar);
    +      wadd_(Wacoplanartot, dist);
    +      wmax_(Wacoplanarmax, dist);
    +    }else if (mergetype == MRGconcave) {
    +      zinc_(Zconcave);
    +      wadd_(Wconcavetot, dist);
    +      wmax_(Wconcavemax, dist);
    +    }else { /* MRGcoplanar */
    +      zinc_(Zcoplanar);
    +      wadd_(Wcoplanartot, dist);
    +      wmax_(Wcoplanarmax, dist);
    +    }
    +  }
    +} /* merge_nonconvex */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle(qh, samecycle, newfacet )
    +    merge a cycle of facets starting at samecycle into a newfacet
    +    newfacet is a horizon facet with ->normal
    +    samecycle facets are simplicial from an apex
    +
    +  returns:
    +    initializes vertex neighbors on first merge
    +    samecycle deleted (placed on qh.visible_list)
    +    newfacet at end of qh.facet_list
    +    deleted vertices on qh.del_vertices
    +
    +  see:
    +    qh_mergefacet()
    +    called by qh_mergecycle_all() for multiple, same cycle facets
    +
    +  design:
    +    make vertex neighbors if necessary
    +    make ridges for newfacet
    +    merge neighbor sets of samecycle into newfacet
    +    merge ridges of samecycle into newfacet
    +    merge vertex neighbors of samecycle into newfacet
    +    make apex of samecycle the apex of newfacet
    +    if newfacet wasn't a new facet
    +      add its vertices to qh.newvertex_list
    +    delete samecycle facets a make newfacet a newfacet
    +*/
    +void qh_mergecycle(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  int traceonce= False, tracerestore= 0;
    +  vertexT *apex;
    +#ifndef qh_NOtrace
    +  facetT *same;
    +#endif
    +
    +  if (newfacet->tricoplanar) {
    +    if (!qh->TRInormals) {
    +      qh_fprintf(qh, qh->ferr, 6224, "Qhull internal error (qh_mergecycle): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
    +    }
    +    newfacet->tricoplanar= False;
    +    newfacet->keepcentrum= False;
    +  }
    +  if (!qh->VERTEXneighbors)
    +    qh_vertexneighbors(qh);
    +  zzinc_(Ztotmerge);
    +  if (qh->REPORTfreq2 && qh->POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh->mergereport + qh->REPORTfreq2)
    +      qh_tracemerging(qh);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh->TRACEmerge == zzval_(Ztotmerge))
    +    qh->qhmem.IStracing= qh->IStracing= qh->TRACElevel;
    +  trace2((qh, qh->ferr, 2030, "qh_mergecycle: merge #%d for facets from cycle f%d into coplanar horizon f%d\n",
    +        zzval_(Ztotmerge), samecycle->id, newfacet->id));
    +  if (newfacet == qh->tracefacet) {
    +    tracerestore= qh->IStracing;
    +    qh->IStracing= 4;
    +    qh_fprintf(qh, qh->ferr, 8068, "qh_mergecycle: ========= trace merge %d of samecycle %d into trace f%d, furthest is p%d\n",
    +               zzval_(Ztotmerge), samecycle->id, newfacet->id,  qh->furthest_id);
    +    traceonce= True;
    +  }
    +  if (qh->IStracing >=4) {
    +    qh_fprintf(qh, qh->ferr, 8069, "  same cycle:");
    +    FORALLsame_cycle_(samecycle)
    +      qh_fprintf(qh, qh->ferr, 8070, " f%d", same->id);
    +    qh_fprintf(qh, qh->ferr, 8071, "\n");
    +  }
    +  if (qh->IStracing >=4)
    +    qh_errprint(qh, "MERGING CYCLE", samecycle, newfacet, NULL, NULL);
    +#endif /* !qh_NOtrace */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_makeridges(qh, newfacet);
    +  qh_mergecycle_neighbors(qh, samecycle, newfacet);
    +  qh_mergecycle_ridges(qh, samecycle, newfacet);
    +  qh_mergecycle_vneighbors(qh, samecycle, newfacet);
    +  if (SETfirstt_(newfacet->vertices, vertexT) != apex)
    +    qh_setaddnth(qh, &newfacet->vertices, 0, apex);  /* apex has last id */
    +  if (!newfacet->newfacet)
    +    qh_newvertices(qh, newfacet->vertices);
    +  qh_mergecycle_facets(qh, samecycle, newfacet);
    +  qh_tracemerge(qh, samecycle, newfacet);
    +  /* check for degen_redundant_neighbors after qh_forcedmerges() */
    +  if (traceonce) {
    +    qh_fprintf(qh, qh->ferr, 8072, "qh_mergecycle: end of trace facet\n");
    +    qh->IStracing= tracerestore;
    +  }
    +} /* mergecycle */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_all(qh, facetlist, wasmerge )
    +    merge all samecycles of coplanar facets into horizon
    +    don't merge facets with ->mergeridge (these already have ->normal)
    +    all facets are simplicial from apex
    +    all facet->cycledone == False
    +
    +  returns:
    +    all newfacets merged into coplanar horizon facets
    +    deleted vertices on  qh.del_vertices
    +    sets wasmerge if any merge
    +
    +  see:
    +    calls qh_mergecycle for multiple, same cycle facets
    +
    +  design:
    +    for each facet on facetlist
    +      skip facets with duplicate ridges and normals
    +      check that facet is in a samecycle (->mergehorizon)
    +      if facet only member of samecycle
    +        sets vertex->delridge for all vertices except apex
    +        merge facet into horizon
    +      else
    +        mark all facets in samecycle
    +        remove facets with duplicate ridges from samecycle
    +        merge samecycle into horizon (deletes facets from facetlist)
    +*/
    +void qh_mergecycle_all(qhT *qh, facetT *facetlist, boolT *wasmerge) {
    +  facetT *facet, *same, *prev, *horizon;
    +  facetT *samecycle= NULL, *nextfacet, *nextsame;
    +  vertexT *apex, *vertex, **vertexp;
    +  int cycles=0, total=0, facets, nummerge;
    +
    +  trace2((qh, qh->ferr, 2031, "qh_mergecycle_all: begin\n"));
    +  for (facet= facetlist; facet && (nextfacet= facet->next); facet= nextfacet) {
    +    if (facet->normal)
    +      continue;
    +    if (!facet->mergehorizon) {
    +      qh_fprintf(qh, qh->ferr, 6225, "Qhull internal error (qh_mergecycle_all): f%d without normal\n", facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    horizon= SETfirstt_(facet->neighbors, facetT);
    +    if (facet->f.samecycle == facet) {
    +      zinc_(Zonehorizon);
    +      /* merge distance done in qh_findhorizon */
    +      apex= SETfirstt_(facet->vertices, vertexT);
    +      FOREACHvertex_(facet->vertices) {
    +        if (vertex != apex)
    +          vertex->delridge= True;
    +      }
    +      horizon->f.newcycle= NULL;
    +      qh_mergefacet(qh, facet, horizon, NULL, NULL, qh_MERGEapex);
    +    }else {
    +      samecycle= facet;
    +      facets= 0;
    +      prev= facet;
    +      for (same= facet->f.samecycle; same;  /* FORALLsame_cycle_(facet) */
    +           same= (same == facet ? NULL :nextsame)) { /* ends at facet */
    +        nextsame= same->f.samecycle;
    +        if (same->cycledone || same->visible)
    +          qh_infiniteloop(qh, same);
    +        same->cycledone= True;
    +        if (same->normal) {
    +          prev->f.samecycle= same->f.samecycle; /* unlink ->mergeridge */
    +          same->f.samecycle= NULL;
    +        }else {
    +          prev= same;
    +          facets++;
    +        }
    +      }
    +      while (nextfacet && nextfacet->cycledone)  /* will delete samecycle */
    +        nextfacet= nextfacet->next;
    +      horizon->f.newcycle= NULL;
    +      qh_mergecycle(qh, samecycle, horizon);
    +      nummerge= horizon->nummerge + facets;
    +      if (nummerge > qh_MAXnummerge)
    +        horizon->nummerge= qh_MAXnummerge;
    +      else
    +        horizon->nummerge= (short unsigned int)nummerge;
    +      zzinc_(Zcyclehorizon);
    +      total += facets;
    +      zzadd_(Zcyclefacettot, facets);
    +      zmax_(Zcyclefacetmax, facets);
    +    }
    +    cycles++;
    +  }
    +  if (cycles)
    +    *wasmerge= True;
    +  trace1((qh, qh->ferr, 1013, "qh_mergecycle_all: merged %d same cycles or facets into coplanar horizons\n", cycles));
    +} /* mergecycle_all */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_facets(qh, samecycle, newfacet )
    +    finish merge of samecycle into newfacet
    +
    +  returns:
    +    samecycle prepended to visible_list for later deletion and partitioning
    +      each facet->f.replace == newfacet
    +
    +    newfacet moved to end of qh.facet_list
    +      makes newfacet a newfacet (get's facet1->id if it was old)
    +      sets newfacet->newmerge
    +      clears newfacet->center (unless merging into a large facet)
    +      clears newfacet->tested and ridge->tested for facet1
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  design:
    +    make newfacet a new facet and set its flags
    +    move samecycle facets to qh.visible_list for later deletion
    +    unless newfacet is large
    +      remove its centrum
    +*/
    +void qh_mergecycle_facets(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *next;
    +
    +  trace4((qh, qh->ferr, 4030, "qh_mergecycle_facets: make newfacet new and samecycle deleted\n"));
    +  qh_removefacet(qh, newfacet);  /* append as a newfacet to end of qh->facet_list */
    +  qh_appendfacet(qh, newfacet);
    +  newfacet->newfacet= True;
    +  newfacet->simplicial= False;
    +  newfacet->newmerge= True;
    +
    +  for (same= samecycle->f.samecycle; same; same= (same == samecycle ?  NULL : next)) {
    +    next= same->f.samecycle;  /* reused by willdelete */
    +    qh_willdelete(qh, same, newfacet);
    +  }
    +  if (newfacet->center
    +      && qh_setsize(qh, newfacet->vertices) <= qh->hull_dim + qh_MAXnewcentrum) {
    +    qh_memfree(qh, newfacet->center, qh->normal_size);
    +    newfacet->center= NULL;
    +  }
    +  trace3((qh, qh->ferr, 3004, "qh_mergecycle_facets: merged facets from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_facets */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_neighbors(qh, samecycle, newfacet )
    +    add neighbors for samecycle facets to newfacet
    +
    +  returns:
    +    newfacet with updated neighbors and vice-versa
    +    newfacet has ridges
    +    all neighbors of newfacet marked with qh.visit_id
    +    samecycle facets marked with qh.visit_id-1
    +    ridges updated for simplicial neighbors of samecycle with a ridge
    +
    +  notes:
    +    assumes newfacet not in samecycle
    +    usually, samecycle facets are new, simplicial facets without internal ridges
    +      not so if horizon facet is coplanar to two different samecycles
    +
    +  see:
    +    qh_mergeneighbors()
    +
    +  design:
    +    check samecycle
    +    delete neighbors from newfacet that are also in samecycle
    +    for each neighbor of a facet in samecycle
    +      if neighbor is simplicial
    +        if first visit
    +          move the neighbor relation to newfacet
    +          update facet links for its ridges
    +        else
    +          make ridges for neighbor
    +          remove samecycle reference
    +      else
    +        update neighbor sets
    +*/
    +void qh_mergecycle_neighbors(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor, **neighborp;
    +  int delneighbors= 0, newneighbors= 0;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +
    +  samevisitid= ++qh->visit_id;
    +  FORALLsame_cycle_(samecycle) {
    +    if (same->visitid == samevisitid || same->visible)
    +      qh_infiniteloop(qh, samecycle);
    +    same->visitid= samevisitid;
    +  }
    +  newfacet->visitid= ++qh->visit_id;
    +  trace4((qh, qh->ferr, 4031, "qh_mergecycle_neighbors: delete shared neighbors from newfacet\n"));
    +  FOREACHneighbor_(newfacet) {
    +    if (neighbor->visitid == samevisitid) {
    +      SETref_(neighbor)= NULL;  /* samecycle neighbors deleted */
    +      delneighbors++;
    +    }else
    +      neighbor->visitid= qh->visit_id;
    +  }
    +  qh_setcompact(qh, newfacet->neighbors);
    +
    +  trace4((qh, qh->ferr, 4032, "qh_mergecycle_neighbors: update neighbors\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHneighbor_(same) {
    +      if (neighbor->visitid == samevisitid)
    +        continue;
    +      if (neighbor->simplicial) {
    +        if (neighbor->visitid != qh->visit_id) {
    +          qh_setappend(qh, &newfacet->neighbors, neighbor);
    +          qh_setreplace(qh, neighbor->neighbors, same, newfacet);
    +          newneighbors++;
    +          neighbor->visitid= qh->visit_id;
    +          FOREACHridge_(neighbor->ridges) { /* update ridge in case of qh_makeridges */
    +            if (ridge->top == same) {
    +              ridge->top= newfacet;
    +              break;
    +            }else if (ridge->bottom == same) {
    +              ridge->bottom= newfacet;
    +              break;
    +            }
    +          }
    +        }else {
    +          qh_makeridges(qh, neighbor);
    +          qh_setdel(neighbor->neighbors, same);
    +          /* same can't be horizon facet for neighbor */
    +        }
    +      }else { /* non-simplicial neighbor */
    +        qh_setdel(neighbor->neighbors, same);
    +        if (neighbor->visitid != qh->visit_id) {
    +          qh_setappend(qh, &neighbor->neighbors, newfacet);
    +          qh_setappend(qh, &newfacet->neighbors, neighbor);
    +          neighbor->visitid= qh->visit_id;
    +          newneighbors++;
    +        }
    +      }
    +    }
    +  }
    +  trace2((qh, qh->ferr, 2032, "qh_mergecycle_neighbors: deleted %d neighbors and added %d\n",
    +             delneighbors, newneighbors));
    +} /* mergecycle_neighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_ridges(qh, samecycle, newfacet )
    +    add ridges/neighbors for facets in samecycle to newfacet
    +    all new/old neighbors of newfacet marked with qh.visit_id
    +    facets in samecycle marked with qh.visit_id-1
    +    newfacet marked with qh.visit_id
    +
    +  returns:
    +    newfacet has merged ridges
    +
    +  notes:
    +    ridge already updated for simplicial neighbors of samecycle with a ridge
    +
    +  see:
    +    qh_mergeridges()
    +    qh_makeridges()
    +
    +  design:
    +    remove ridges between newfacet and samecycle
    +    for each facet in samecycle
    +      for each ridge in facet
    +        update facet pointers in ridge
    +        skip ridges processed in qh_mergecycle_neighors
    +        free ridges between newfacet and samecycle
    +        free ridges between facets of samecycle (on 2nd visit)
    +        append remaining ridges to newfacet
    +      if simpilicial facet
    +        for each neighbor of facet
    +          if simplicial facet
    +          and not samecycle facet or newfacet
    +            make ridge between neighbor and newfacet
    +*/
    +void qh_mergecycle_ridges(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *same, *neighbor= NULL;
    +  int numold=0, numnew=0;
    +  int neighbor_i, neighbor_n;
    +  unsigned int samevisitid;
    +  ridgeT *ridge, **ridgep;
    +  boolT toporient;
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh, qh->ferr, 4033, "qh_mergecycle_ridges: delete shared ridges from newfacet\n"));
    +  samevisitid= qh->visit_id -1;
    +  FOREACHridge_(newfacet->ridges) {
    +    neighbor= otherfacet_(ridge, newfacet);
    +    if (neighbor->visitid == samevisitid)
    +      SETref_(ridge)= NULL; /* ridge free'd below */
    +  }
    +  qh_setcompact(qh, newfacet->ridges);
    +
    +  trace4((qh, qh->ferr, 4034, "qh_mergecycle_ridges: add ridges to newfacet\n"));
    +  FORALLsame_cycle_(samecycle) {
    +    FOREACHridge_(same->ridges) {
    +      if (ridge->top == same) {
    +        ridge->top= newfacet;
    +        neighbor= ridge->bottom;
    +      }else if (ridge->bottom == same) {
    +        ridge->bottom= newfacet;
    +        neighbor= ridge->top;
    +      }else if (ridge->top == newfacet || ridge->bottom == newfacet) {
    +        qh_setappend(qh, &newfacet->ridges, ridge);
    +        numold++;  /* already set by qh_mergecycle_neighbors */
    +        continue;
    +      }else {
    +        qh_fprintf(qh, qh->ferr, 6098, "qhull internal error (qh_mergecycle_ridges): bad ridge r%d\n", ridge->id);
    +        qh_errexit(qh, qh_ERRqhull, NULL, ridge);
    +      }
    +      if (neighbor == newfacet) {
    +        qh_setfree(qh, &(ridge->vertices));
    +        qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else if (neighbor->visitid == samevisitid) {
    +        qh_setdel(neighbor->ridges, ridge);
    +        qh_setfree(qh, &(ridge->vertices));
    +        qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +        numold++;
    +      }else {
    +        qh_setappend(qh, &newfacet->ridges, ridge);
    +        numold++;
    +      }
    +    }
    +    if (same->ridges)
    +      qh_settruncate(qh, same->ridges, 0);
    +    if (!same->simplicial)
    +      continue;
    +    FOREACHneighbor_i_(qh, same) {       /* note: !newfact->simplicial */
    +      if (neighbor->visitid != samevisitid && neighbor->simplicial) {
    +        ridge= qh_newridge(qh);
    +        ridge->vertices= qh_setnew_delnthsorted(qh, same->vertices, qh->hull_dim,
    +                                                          neighbor_i, 0);
    +        toporient= same->toporient ^ (neighbor_i & 0x1);
    +        if (toporient) {
    +          ridge->top= newfacet;
    +          ridge->bottom= neighbor;
    +        }else {
    +          ridge->top= neighbor;
    +          ridge->bottom= newfacet;
    +        }
    +        qh_setappend(qh, &(newfacet->ridges), ridge);
    +        qh_setappend(qh, &(neighbor->ridges), ridge);
    +        numnew++;
    +      }
    +    }
    +  }
    +
    +  trace2((qh, qh->ferr, 2033, "qh_mergecycle_ridges: found %d old ridges and %d new ones\n",
    +             numold, numnew));
    +} /* mergecycle_ridges */
    +
    +/*---------------------------------
    +
    +  qh_mergecycle_vneighbors(qh, samecycle, newfacet )
    +    create vertex neighbors for newfacet from vertices of facets in samecycle
    +    samecycle marked with visitid == qh.visit_id - 1
    +
    +  returns:
    +    newfacet vertices with updated neighbors
    +    marks newfacet with qh.visit_id-1
    +    deletes vertices that are merged away
    +    sets delridge on all vertices (faster here than in mergecycle_ridges)
    +
    +  see:
    +    qh_mergevertex_neighbors()
    +
    +  design:
    +    for each vertex of samecycle facet
    +      set vertex->delridge
    +      delete samecycle facets from vertex neighbors
    +      append newfacet to vertex neighbors
    +      if vertex only in newfacet
    +        delete it from newfacet
    +        add it to qh.del_vertices for later deletion
    +*/
    +void qh_mergecycle_vneighbors(qhT *qh, facetT *samecycle, facetT *newfacet) {
    +  facetT *neighbor, **neighborp;
    +  unsigned int mergeid;
    +  vertexT *vertex, **vertexp, *apex;
    +  setT *vertices;
    +
    +  trace4((qh, qh->ferr, 4035, "qh_mergecycle_vneighbors: update vertex neighbors for newfacet\n"));
    +  mergeid= qh->visit_id - 1;
    +  newfacet->visitid= mergeid;
    +  vertices= qh_basevertices(qh, samecycle); /* temp */
    +  apex= SETfirstt_(samecycle->vertices, vertexT);
    +  qh_setappend(qh, &vertices, apex);
    +  FOREACHvertex_(vertices) {
    +    vertex->delridge= True;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == mergeid)
    +        SETref_(neighbor)= NULL;
    +    }
    +    qh_setcompact(qh, vertex->neighbors);
    +    qh_setappend(qh, &vertex->neighbors, newfacet);
    +    if (!SETsecond_(vertex->neighbors)) {
    +      zinc_(Zcyclevertex);
    +      trace2((qh, qh->ferr, 2034, "qh_mergecycle_vneighbors: deleted v%d when merging cycle f%d into f%d\n",
    +        vertex->id, samecycle->id, newfacet->id));
    +      qh_setdelsorted(newfacet->vertices, vertex);
    +      vertex->deleted= True;
    +      qh_setappend(qh, &qh->del_vertices, vertex);
    +    }
    +  }
    +  qh_settempfree(qh, &vertices);
    +  trace3((qh, qh->ferr, 3005, "qh_mergecycle_vneighbors: merged vertices from cycle f%d into f%d\n",
    +             samecycle->id, newfacet->id));
    +} /* mergecycle_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_mergefacet(qh, facet1, facet2, mindist, maxdist, mergeapex )
    +    merges facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging new facet into coplanar horizon
    +
    +  returns:
    +    qh.max_outside and qh.min_vertex updated
    +    initializes vertex neighbors on first merge
    +
    +  returns:
    +    facet2 contains facet1's vertices, neighbors, and ridges
    +      facet2 moved to end of qh.facet_list
    +      makes facet2 a newfacet
    +      sets facet2->newmerge set
    +      clears facet2->center (unless merging into a large facet)
    +      clears facet2->tested and ridge->tested for facet1
    +
    +    facet1 prepended to visible_list for later deletion and partitioning
    +      facet1->f.replace == facet2
    +
    +    adds neighboring facets to facet_mergeset if redundant or degenerate
    +
    +  notes:
    +    mindist/maxdist may be NULL (only if both NULL)
    +    traces merge if fmax_(maxdist,-mindist) > TRACEdist
    +
    +  see:
    +    qh_mergecycle()
    +
    +  design:
    +    trace merge and check for degenerate simplex
    +    make ridges for both facets
    +    update qh.max_outside, qh.max_vertex, qh.min_vertex
    +    update facet2->maxoutside and keepcentrum
    +    update facet2->nummerge
    +    update tested flags for facet2
    +    if facet1 is simplicial
    +      merge facet1 into facet2
    +    else
    +      merge facet1's neighbors into facet2
    +      merge facet1's ridges into facet2
    +      merge facet1's vertices into facet2
    +      merge facet1's vertex neighbors into facet2
    +      add facet2's vertices to qh.new_vertexlist
    +      unless qh_MERGEapex
    +        test facet2 for degenerate or redundant neighbors
    +      move facet1 to qh.visible_list for later deletion
    +      move facet2 to end of qh.newfacet_list
    +*/
    +void qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex) {
    +  boolT traceonce= False;
    +  vertexT *vertex, **vertexp;
    +  int tracerestore=0, nummerge;
    +
    +  if (facet1->tricoplanar || facet2->tricoplanar) {
    +    if (!qh->TRInormals) {
    +      qh_fprintf(qh, qh->ferr, 6226, "Qhull internal error (qh_mergefacet): does not work for tricoplanar facets.  Use option 'Q11'\n");
    +      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +    }
    +    if (facet2->tricoplanar) {
    +      facet2->tricoplanar= False;
    +      facet2->keepcentrum= False;
    +    }
    +  }
    +  zzinc_(Ztotmerge);
    +  if (qh->REPORTfreq2 && qh->POSTmerging) {
    +    if (zzval_(Ztotmerge) > qh->mergereport + qh->REPORTfreq2)
    +      qh_tracemerging(qh);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh->build_cnt >= qh->RERUN) {
    +    if (mindist && (-*mindist > qh->TRACEdist || *maxdist > qh->TRACEdist)) {
    +      tracerestore= 0;
    +      qh->IStracing= qh->TRACElevel;
    +      traceonce= True;
    +      qh_fprintf(qh, qh->ferr, 8075, "qh_mergefacet: ========= trace wide merge #%d(%2.2g) for f%d into f%d, last point was p%d\n", zzval_(Ztotmerge),
    +             fmax_(-*mindist, *maxdist), facet1->id, facet2->id, qh->furthest_id);
    +    }else if (facet1 == qh->tracefacet || facet2 == qh->tracefacet) {
    +      tracerestore= qh->IStracing;
    +      qh->IStracing= 4;
    +      traceonce= True;
    +      qh_fprintf(qh, qh->ferr, 8076, "qh_mergefacet: ========= trace merge #%d involving f%d, furthest is p%d\n",
    +                 zzval_(Ztotmerge), qh->tracefacet_id,  qh->furthest_id);
    +    }
    +  }
    +  if (qh->IStracing >= 2) {
    +    realT mergemin= -2;
    +    realT mergemax= -2;
    +
    +    if (mindist) {
    +      mergemin= *mindist;
    +      mergemax= *maxdist;
    +    }
    +    qh_fprintf(qh, qh->ferr, 8077, "qh_mergefacet: #%d merge f%d into f%d, mindist= %2.2g, maxdist= %2.2g\n",
    +    zzval_(Ztotmerge), facet1->id, facet2->id, mergemin, mergemax);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (facet1 == facet2 || facet1->visible || facet2->visible) {
    +    qh_fprintf(qh, qh->ferr, 6099, "qhull internal error (qh_mergefacet): either f%d and f%d are the same or one is a visible facet\n",
    +             facet1->id, facet2->id);
    +    qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +  }
    +  if (qh->num_facets - qh->num_visible <= qh->hull_dim + 1) {
    +    qh_fprintf(qh, qh->ferr, 6227, "\n\
    +qhull precision error: Only %d facets remain.  Can not merge another\n\
    +pair.  The input is too degenerate or the convexity constraints are\n\
    +too strong.\n", qh->hull_dim+1);
    +    if (qh->hull_dim >= 5 && !qh->MERGEexact)
    +      qh_fprintf(qh, qh->ferr, 8079, "Option 'Qx' may avoid this problem.\n");
    +    qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +  }
    +  if (!qh->VERTEXneighbors)
    +    qh_vertexneighbors(qh);
    +  qh_makeridges(qh, facet1);
    +  qh_makeridges(qh, facet2);
    +  if (qh->IStracing >=4)
    +    qh_errprint(qh, "MERGING", facet1, facet2, NULL, NULL);
    +  if (mindist) {
    +    maximize_(qh->max_outside, *maxdist);
    +    maximize_(qh->max_vertex, *maxdist);
    +#if qh_MAXoutside
    +    maximize_(facet2->maxoutside, *maxdist);
    +#endif
    +    minimize_(qh->min_vertex, *mindist);
    +    if (!facet2->keepcentrum
    +    && (*maxdist > qh->WIDEfacet || *mindist < -qh->WIDEfacet)) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidefacet);
    +    }
    +  }
    +  nummerge= facet1->nummerge + facet2->nummerge + 1;
    +  if (nummerge >= qh_MAXnummerge)
    +    facet2->nummerge= qh_MAXnummerge;
    +  else
    +    facet2->nummerge= (short unsigned int)nummerge;
    +  facet2->newmerge= True;
    +  facet2->dupridge= False;
    +  qh_updatetested(qh, facet1, facet2);
    +  if (qh->hull_dim > 2 && qh_setsize(qh, facet1->vertices) == qh->hull_dim)
    +    qh_mergesimplex(qh, facet1, facet2, mergeapex);
    +  else {
    +    qh->vertex_visit++;
    +    FOREACHvertex_(facet2->vertices)
    +      vertex->visitid= qh->vertex_visit;
    +    if (qh->hull_dim == 2)
    +      qh_mergefacet2d(qh, facet1, facet2);
    +    else {
    +      qh_mergeneighbors(qh, facet1, facet2);
    +      qh_mergevertices(qh, facet1->vertices, &facet2->vertices);
    +    }
    +    qh_mergeridges(qh, facet1, facet2);
    +    qh_mergevertex_neighbors(qh, facet1, facet2);
    +    if (!facet2->newfacet)
    +      qh_newvertices(qh, facet2->vertices);
    +  }
    +  if (!mergeapex)
    +    qh_degen_redundant_neighbors(qh, facet2, facet1);
    +  if (facet2->coplanar || !facet2->newfacet) {
    +    zinc_(Zmergeintohorizon);
    +  }else if (!facet1->newfacet && facet2->newfacet) {
    +    zinc_(Zmergehorizon);
    +  }else {
    +    zinc_(Zmergenew);
    +  }
    +  qh_willdelete(qh, facet1, facet2);
    +  qh_removefacet(qh, facet2);  /* append as a newfacet to end of qh->facet_list */
    +  qh_appendfacet(qh, facet2);
    +  facet2->newfacet= True;
    +  facet2->tested= False;
    +  qh_tracemerge(qh, facet1, facet2);
    +  if (traceonce) {
    +    qh_fprintf(qh, qh->ferr, 8080, "qh_mergefacet: end of wide tracing\n");
    +    qh->IStracing= tracerestore;
    +  }
    +} /* mergefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergefacet2d(qh, facet1, facet2 )
    +    in 2d, merges neighbors and vertices of facet1 into facet2
    +
    +  returns:
    +    build ridges for neighbors if necessary
    +    facet2 looks like a simplicial facet except for centrum, ridges
    +      neighbors are opposite the corresponding vertex
    +      maintains orientation of facet2
    +
    +  notes:
    +    qh_mergefacet() retains non-simplicial structures
    +      they are not needed in 2d, but later routines may use them
    +    preserves qh.vertex_visit for qh_mergevertex_neighbors()
    +
    +  design:
    +    get vertices and neighbors
    +    determine new vertices and neighbors
    +    set new vertices and neighbors and adjust orientation
    +    make ridges for new neighbor if needed
    +*/
    +void qh_mergefacet2d(qhT *qh, facetT *facet1, facetT *facet2) {
    +  vertexT *vertex1A, *vertex1B, *vertex2A, *vertex2B, *vertexA, *vertexB;
    +  facetT *neighbor1A, *neighbor1B, *neighbor2A, *neighbor2B, *neighborA, *neighborB;
    +
    +  vertex1A= SETfirstt_(facet1->vertices, vertexT);
    +  vertex1B= SETsecondt_(facet1->vertices, vertexT);
    +  vertex2A= SETfirstt_(facet2->vertices, vertexT);
    +  vertex2B= SETsecondt_(facet2->vertices, vertexT);
    +  neighbor1A= SETfirstt_(facet1->neighbors, facetT);
    +  neighbor1B= SETsecondt_(facet1->neighbors, facetT);
    +  neighbor2A= SETfirstt_(facet2->neighbors, facetT);
    +  neighbor2B= SETsecondt_(facet2->neighbors, facetT);
    +  if (vertex1A == vertex2A) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1A;
    +  }else if (vertex1A == vertex2B) {
    +    vertexA= vertex1B;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1A;
    +  }else if (vertex1B == vertex2A) {
    +    vertexA= vertex1A;
    +    vertexB= vertex2B;
    +    neighborA= neighbor2A;
    +    neighborB= neighbor1B;
    +  }else { /* 1B == 2B */
    +    vertexA= vertex1A;
    +    vertexB= vertex2A;
    +    neighborA= neighbor2B;
    +    neighborB= neighbor1B;
    +  }
    +  /* vertexB always from facet2, neighborB always from facet1 */
    +  if (vertexA->id > vertexB->id) {
    +    SETfirst_(facet2->vertices)= vertexA;
    +    SETsecond_(facet2->vertices)= vertexB;
    +    if (vertexB == vertex2A)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborA;
    +    SETsecond_(facet2->neighbors)= neighborB;
    +  }else {
    +    SETfirst_(facet2->vertices)= vertexB;
    +    SETsecond_(facet2->vertices)= vertexA;
    +    if (vertexB == vertex2B)
    +      facet2->toporient= !facet2->toporient;
    +    SETfirst_(facet2->neighbors)= neighborB;
    +    SETsecond_(facet2->neighbors)= neighborA;
    +  }
    +  qh_makeridges(qh, neighborB);
    +  qh_setreplace(qh, neighborB->neighbors, facet1, facet2);
    +  trace4((qh, qh->ferr, 4036, "qh_mergefacet2d: merged v%d and neighbor f%d of f%d into f%d\n",
    +       vertexA->id, neighborB->id, facet1->id, facet2->id));
    +} /* mergefacet2d */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeneighbors(qh, facet1, facet2 )
    +    merges the neighbors of facet1 into facet2
    +
    +  see:
    +    qh_mergecycle_neighbors()
    +
    +  design:
    +    for each neighbor of facet1
    +      if neighbor is also a neighbor of facet2
    +        if neighbor is simpilicial
    +          make ridges for later deletion as a degenerate facet
    +        update its neighbor set
    +      else
    +        move the neighbor relation to facet2
    +    remove the neighbor relation for facet1 and facet2
    +*/
    +void qh_mergeneighbors(qhT *qh, facetT *facet1, facetT *facet2) {
    +  facetT *neighbor, **neighborp;
    +
    +  trace4((qh, qh->ferr, 4037, "qh_mergeneighbors: merge neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  qh->visit_id++;
    +  FOREACHneighbor_(facet2) {
    +    neighbor->visitid= qh->visit_id;
    +  }
    +  FOREACHneighbor_(facet1) {
    +    if (neighbor->visitid == qh->visit_id) {
    +      if (neighbor->simplicial)    /* is degen, needs ridges */
    +        qh_makeridges(qh, neighbor);
    +      if (SETfirstt_(neighbor->neighbors, facetT) != facet1) /*keep newfacet->horizon*/
    +        qh_setdel(neighbor->neighbors, facet1);
    +      else {
    +        qh_setdel(neighbor->neighbors, facet2);
    +        qh_setreplace(qh, neighbor->neighbors, facet1, facet2);
    +      }
    +    }else if (neighbor != facet2) {
    +      qh_setappend(qh, &(facet2->neighbors), neighbor);
    +      qh_setreplace(qh, neighbor->neighbors, facet1, facet2);
    +    }
    +  }
    +  qh_setdel(facet1->neighbors, facet2);  /* here for makeridges */
    +  qh_setdel(facet2->neighbors, facet1);
    +} /* mergeneighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergeridges(qh, facet1, facet2 )
    +    merges the ridge set of facet1 into facet2
    +
    +  returns:
    +    may delete all ridges for a vertex
    +    sets vertex->delridge on deleted ridges
    +
    +  see:
    +    qh_mergecycle_ridges()
    +
    +  design:
    +    delete ridges between facet1 and facet2
    +      mark (delridge) vertices on these ridges for later testing
    +    for each remaining ridge
    +      rename facet1 to facet2
    +*/
    +void qh_mergeridges(qhT *qh, facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh, qh->ferr, 4038, "qh_mergeridges: merge ridges of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  FOREACHridge_(facet2->ridges) {
    +    if ((ridge->top == facet1) || (ridge->bottom == facet1)) {
    +      FOREACHvertex_(ridge->vertices)
    +        vertex->delridge= True;
    +      qh_delridge(qh, ridge);  /* expensive in high-d, could rebuild */
    +      ridgep--; /*repeat*/
    +    }
    +  }
    +  FOREACHridge_(facet1->ridges) {
    +    if (ridge->top == facet1)
    +      ridge->top= facet2;
    +    else
    +      ridge->bottom= facet2;
    +    qh_setappend(qh, &(facet2->ridges), ridge);
    +  }
    +} /* mergeridges */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergesimplex(qh, facet1, facet2, mergeapex )
    +    merge simplicial facet1 into facet2
    +    mergeapex==qh_MERGEapex if merging samecycle into horizon facet
    +      vertex id is latest (most recently created)
    +    facet1 may be contained in facet2
    +    ridges exist for both facets
    +
    +  returns:
    +    facet2 with updated vertices, ridges, neighbors
    +    updated neighbors for facet1's vertices
    +    facet1 not deleted
    +    sets vertex->delridge on deleted ridges
    +
    +  notes:
    +    special case code since this is the most common merge
    +    called from qh_mergefacet()
    +
    +  design:
    +    if qh_MERGEapex
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to facet2
    +    else
    +      for each ridge between facet1 and facet2
    +        set vertex->delridge
    +      determine the apex for facet1 (i.e., vertex to be merged)
    +      unless apex already in facet2
    +        insert apex into vertices for facet2
    +      add vertices of facet2 to qh.new_vertexlist if necessary
    +      add apex to qh.new_vertexlist if necessary
    +      for each vertex of facet1
    +        if apex
    +          rename facet1 to facet2 in its vertex neighbors
    +        else
    +          delete facet1 from vertex neighors
    +          if only in facet2
    +            add vertex to qh.del_vertices for later deletion
    +      for each ridge of facet1
    +        delete ridges between facet1 and facet2
    +        append other ridges to facet2 after renaming facet to facet2
    +*/
    +void qh_mergesimplex(qhT *qh, facetT *facet1, facetT *facet2, boolT mergeapex) {
    +  vertexT *vertex, **vertexp, *apex;
    +  ridgeT *ridge, **ridgep;
    +  boolT issubset= False;
    +  int vertex_i= -1, vertex_n;
    +  facetT *neighbor, **neighborp, *otherfacet;
    +
    +  if (mergeapex) {
    +    if (!facet2->newfacet)
    +      qh_newvertices(qh, facet2->vertices);  /* apex is new */
    +    apex= SETfirstt_(facet1->vertices, vertexT);
    +    if (SETfirstt_(facet2->vertices, vertexT) != apex)
    +      qh_setaddnth(qh, &facet2->vertices, 0, apex);  /* apex has last id */
    +    else
    +      issubset= True;
    +  }else {
    +    zinc_(Zmergesimplex);
    +    FOREACHvertex_(facet1->vertices)
    +      vertex->seen= False;
    +    FOREACHridge_(facet1->ridges) {
    +      if (otherfacet_(ridge, facet1) == facet2) {
    +        FOREACHvertex_(ridge->vertices) {
    +          vertex->seen= True;
    +          vertex->delridge= True;
    +        }
    +        break;
    +      }
    +    }
    +    FOREACHvertex_(facet1->vertices) {
    +      if (!vertex->seen)
    +        break;  /* must occur */
    +    }
    +    apex= vertex;
    +    trace4((qh, qh->ferr, 4039, "qh_mergesimplex: merge apex v%d of f%d into facet f%d\n",
    +          apex->id, facet1->id, facet2->id));
    +    FOREACHvertex_i_(qh, facet2->vertices) {
    +      if (vertex->id < apex->id) {
    +        break;
    +      }else if (vertex->id == apex->id) {
    +        issubset= True;
    +        break;
    +      }
    +    }
    +    if (!issubset)
    +      qh_setaddnth(qh, &facet2->vertices, vertex_i, apex);
    +    if (!facet2->newfacet)
    +      qh_newvertices(qh, facet2->vertices);
    +    else if (!apex->newlist) {
    +      qh_removevertex(qh, apex);
    +      qh_appendvertex(qh, apex);
    +    }
    +  }
    +  trace4((qh, qh->ferr, 4040, "qh_mergesimplex: update vertex neighbors of f%d\n",
    +          facet1->id));
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex == apex && !issubset)
    +      qh_setreplace(qh, vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(qh, vertex, facet1, facet2);
    +    }
    +  }
    +  trace4((qh, qh->ferr, 4041, "qh_mergesimplex: merge ridges and neighbors of f%d into f%d\n",
    +          facet1->id, facet2->id));
    +  qh->visit_id++;
    +  FOREACHneighbor_(facet2)
    +    neighbor->visitid= qh->visit_id;
    +  FOREACHridge_(facet1->ridges) {
    +    otherfacet= otherfacet_(ridge, facet1);
    +    if (otherfacet == facet2) {
    +      qh_setdel(facet2->ridges, ridge);
    +      qh_setfree(qh, &(ridge->vertices));
    +      qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +      qh_setdel(facet2->neighbors, facet1);
    +    }else {
    +      qh_setappend(qh, &facet2->ridges, ridge);
    +      if (otherfacet->visitid != qh->visit_id) {
    +        qh_setappend(qh, &facet2->neighbors, otherfacet);
    +        qh_setreplace(qh, otherfacet->neighbors, facet1, facet2);
    +        otherfacet->visitid= qh->visit_id;
    +      }else {
    +        if (otherfacet->simplicial)    /* is degen, needs ridges */
    +          qh_makeridges(qh, otherfacet);
    +        if (SETfirstt_(otherfacet->neighbors, facetT) != facet1)
    +          qh_setdel(otherfacet->neighbors, facet1);
    +        else {   /*keep newfacet->neighbors->horizon*/
    +          qh_setdel(otherfacet->neighbors, facet2);
    +          qh_setreplace(qh, otherfacet->neighbors, facet1, facet2);
    +        }
    +      }
    +      if (ridge->top == facet1) /* wait until after qh_makeridges */
    +        ridge->top= facet2;
    +      else
    +        ridge->bottom= facet2;
    +    }
    +  }
    +  SETfirst_(facet1->ridges)= NULL; /* it will be deleted */
    +  trace3((qh, qh->ferr, 3006, "qh_mergesimplex: merged simplex f%d apex v%d into facet f%d\n",
    +          facet1->id, getid_(apex), facet2->id));
    +} /* mergesimplex */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_del(qh, vertex, facet1, facet2 )
    +    delete a vertex because of merging facet1 into facet2
    +
    +  returns:
    +    deletes vertex from facet2
    +    adds vertex to qh.del_vertices for later deletion
    +*/
    +void qh_mergevertex_del(qhT *qh, vertexT *vertex, facetT *facet1, facetT *facet2) {
    +
    +  zinc_(Zmergevertex);
    +  trace2((qh, qh->ferr, 2035, "qh_mergevertex_del: deleted v%d when merging f%d into f%d\n",
    +          vertex->id, facet1->id, facet2->id));
    +  qh_setdelsorted(facet2->vertices, vertex);
    +  vertex->deleted= True;
    +  qh_setappend(qh, &qh->del_vertices, vertex);
    +} /* mergevertex_del */
    +
    +/*---------------------------------
    +
    +  qh_mergevertex_neighbors(qh, facet1, facet2 )
    +    merge the vertex neighbors of facet1 to facet2
    +
    +  returns:
    +    if vertex is current qh.vertex_visit
    +      deletes facet1 from vertex->neighbors
    +    else
    +      renames facet1 to facet2 in vertex->neighbors
    +    deletes vertices if only one neighbor
    +
    +  notes:
    +    assumes vertex neighbor sets are good
    +*/
    +void qh_mergevertex_neighbors(qhT *qh, facetT *facet1, facetT *facet2) {
    +  vertexT *vertex, **vertexp;
    +
    +  trace4((qh, qh->ferr, 4042, "qh_mergevertex_neighbors: merge vertex neighbors of f%d and f%d\n",
    +          facet1->id, facet2->id));
    +  if (qh->tracevertex) {
    +    qh_fprintf(qh, qh->ferr, 8081, "qh_mergevertex_neighbors: of f%d and f%d at furthest p%d f0= %p\n",
    +             facet1->id, facet2->id, qh->furthest_id, qh->tracevertex->neighbors->e[0].p);
    +    qh_errprint(qh, "TRACE", NULL, NULL, NULL, qh->tracevertex);
    +  }
    +  FOREACHvertex_(facet1->vertices) {
    +    if (vertex->visitid != qh->vertex_visit)
    +      qh_setreplace(qh, vertex->neighbors, facet1, facet2);
    +    else {
    +      qh_setdel(vertex->neighbors, facet1);
    +      if (!SETsecond_(vertex->neighbors))
    +        qh_mergevertex_del(qh, vertex, facet1, facet2);
    +    }
    +  }
    +  if (qh->tracevertex)
    +    qh_errprint(qh, "TRACE", NULL, NULL, NULL, qh->tracevertex);
    +} /* mergevertex_neighbors */
    +
    +
    +/*---------------------------------
    +
    +  qh_mergevertices(qh, vertices1, vertices2 )
    +    merges the vertex set of facet1 into facet2
    +
    +  returns:
    +    replaces vertices2 with merged set
    +    preserves vertex_visit for qh_mergevertex_neighbors
    +    updates qh.newvertex_list
    +
    +  design:
    +    create a merged set of both vertices (in inverse id order)
    +*/
    +void qh_mergevertices(qhT *qh, setT *vertices1, setT **vertices2) {
    +  int newsize= qh_setsize(qh, vertices1)+qh_setsize(qh, *vertices2) - qh->hull_dim + 1;
    +  setT *mergedvertices;
    +  vertexT *vertex, **vertexp, **vertex2= SETaddr_(*vertices2, vertexT);
    +
    +  mergedvertices= qh_settemp(qh, newsize);
    +  FOREACHvertex_(vertices1) {
    +    if (!*vertex2 || vertex->id > (*vertex2)->id)
    +      qh_setappend(qh, &mergedvertices, vertex);
    +    else {
    +      while (*vertex2 && (*vertex2)->id > vertex->id)
    +        qh_setappend(qh, &mergedvertices, *vertex2++);
    +      if (!*vertex2 || (*vertex2)->id < vertex->id)
    +        qh_setappend(qh, &mergedvertices, vertex);
    +      else
    +        qh_setappend(qh, &mergedvertices, *vertex2++);
    +    }
    +  }
    +  while (*vertex2)
    +    qh_setappend(qh, &mergedvertices, *vertex2++);
    +  if (newsize < qh_setsize(qh, mergedvertices)) {
    +    qh_fprintf(qh, qh->ferr, 6100, "qhull internal error (qh_mergevertices): facets did not share a ridge\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(qh, vertices2);
    +  *vertices2= mergedvertices;
    +  qh_settemppop(qh);
    +} /* mergevertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_neighbor_intersections(qh, vertex )
    +    return intersection of all vertices in vertex->neighbors except for vertex
    +
    +  returns:
    +    returns temporary set of vertices
    +    does not include vertex
    +    NULL if a neighbor is simplicial
    +    NULL if empty set
    +
    +  notes:
    +    used for renaming vertices
    +
    +  design:
    +    initialize the intersection set with vertices of the first two neighbors
    +    delete vertex from the intersection
    +    for each remaining neighbor
    +      intersect its vertex set with the intersection set
    +      return NULL if empty
    +    return the intersection set
    +*/
    +setT *qh_neighbor_intersections(qhT *qh, vertexT *vertex) {
    +  facetT *neighbor, **neighborp, *neighborA, *neighborB;
    +  setT *intersect;
    +  int neighbor_i, neighbor_n;
    +
    +  FOREACHneighbor_(vertex) {
    +    if (neighbor->simplicial)
    +      return NULL;
    +  }
    +  neighborA= SETfirstt_(vertex->neighbors, facetT);
    +  neighborB= SETsecondt_(vertex->neighbors, facetT);
    +  zinc_(Zintersectnum);
    +  if (!neighborA)
    +    return NULL;
    +  if (!neighborB)
    +    intersect= qh_setcopy(qh, neighborA->vertices, 0);
    +  else
    +    intersect= qh_vertexintersect_new(qh, neighborA->vertices, neighborB->vertices);
    +  qh_settemppush(qh, intersect);
    +  qh_setdelsorted(intersect, vertex);
    +  FOREACHneighbor_i_(qh, vertex) {
    +    if (neighbor_i >= 2) {
    +      zinc_(Zintersectnum);
    +      qh_vertexintersect(qh, &intersect, neighbor->vertices);
    +      if (!SETfirst_(intersect)) {
    +        zinc_(Zintersectfail);
    +        qh_settempfree(qh, &intersect);
    +        return NULL;
    +      }
    +    }
    +  }
    +  trace3((qh, qh->ferr, 3007, "qh_neighbor_intersections: %d vertices in neighbor intersection of v%d\n",
    +          qh_setsize(qh, intersect), vertex->id));
    +  return intersect;
    +} /* neighbor_intersections */
    +
    +/*---------------------------------
    +
    +  qh_newvertices(qh, vertices )
    +    add vertices to end of qh.vertex_list (marks as new vertices)
    +
    +  returns:
    +    vertices on qh.newvertex_list
    +    vertex->newlist set
    +*/
    +void qh_newvertices(qhT *qh, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(qh, vertex);
    +      qh_appendvertex(qh, vertex);
    +    }
    +  }
    +} /* newvertices */
    +
    +/*---------------------------------
    +
    +  qh_reducevertices(qh)
    +    reduce extra vertices, shared vertices, and redundant vertices
    +    facet->newmerge is set if merged since last call
    +    if !qh.MERGEvertices, only removes extra vertices
    +
    +  returns:
    +    True if also merged degen_redundant facets
    +    vertices are renamed if possible
    +    clears facet->newmerge and vertex->delridge
    +
    +  notes:
    +    ignored if 2-d
    +
    +  design:
    +    merge any degenerate or redundant facets
    +    for each newly merged facet
    +      remove extra vertices
    +    if qh.MERGEvertices
    +      for each newly merged facet
    +        for each vertex
    +          if vertex was on a deleted ridge
    +            rename vertex if it is shared
    +      remove delridge flag from new vertices
    +*/
    +boolT qh_reducevertices(qhT *qh) {
    +  int numshare=0, numrename= 0;
    +  boolT degenredun= False;
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh->hull_dim == 2)
    +    return False;
    +  if (qh_merge_degenredundant(qh))
    +    degenredun= True;
    + LABELrestart:
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      if (!qh->MERGEvertices)
    +        newfacet->newmerge= False;
    +      qh_remove_extravertices(qh, newfacet);
    +    }
    +  }
    +  if (!qh->MERGEvertices)
    +    return False;
    +  FORALLnew_facets {
    +    if (newfacet->newmerge) {
    +      newfacet->newmerge= False;
    +      FOREACHvertex_(newfacet->vertices) {
    +        if (vertex->delridge) {
    +          if (qh_rename_sharedvertex(qh, vertex, newfacet)) {
    +            numshare++;
    +            vertexp--; /* repeat since deleted vertex */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  FORALLvertex_(qh->newvertex_list) {
    +    if (vertex->delridge && !vertex->deleted) {
    +      vertex->delridge= False;
    +      if (qh->hull_dim >= 4 && qh_redundant_vertex(qh, vertex)) {
    +        numrename++;
    +        if (qh_merge_degenredundant(qh)) {
    +          degenredun= True;
    +          goto LABELrestart;
    +        }
    +      }
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1014, "qh_reducevertices: renamed %d shared vertices and %d redundant vertices. Degen? %d\n",
    +          numshare, numrename, degenredun));
    +  return degenredun;
    +} /* reducevertices */
    +
    +/*---------------------------------
    +
    +  qh_redundant_vertex(qh, vertex )
    +    detect and rename a redundant vertex
    +    vertices have full vertex->neighbors
    +
    +  returns:
    +    returns true if find a redundant vertex
    +      deletes vertex(vertex->deleted)
    +
    +  notes:
    +    only needed if vertex->delridge and hull_dim >= 4
    +    may add degenerate facets to qh.facet_mergeset
    +    doesn't change vertex->neighbors or create redundant facets
    +
    +  design:
    +    intersect vertices of all facet neighbors of vertex
    +    determine ridges for these vertices
    +    if find a new vertex for vertex amoung these ridges and vertices
    +      rename vertex to the new vertex
    +*/
    +vertexT *qh_redundant_vertex(qhT *qh, vertexT *vertex) {
    +  vertexT *newvertex= NULL;
    +  setT *vertices, *ridges;
    +
    +  trace3((qh, qh->ferr, 3008, "qh_redundant_vertex: check if v%d can be renamed\n", vertex->id));
    +  if ((vertices= qh_neighbor_intersections(qh, vertex))) {
    +    ridges= qh_vertexridges(qh, vertex);
    +    if ((newvertex= qh_find_newvertex(qh, vertex, vertices, ridges)))
    +      qh_renamevertex(qh, vertex, newvertex, ridges, NULL, NULL);
    +    qh_settempfree(qh, &ridges);
    +    qh_settempfree(qh, &vertices);
    +  }
    +  return newvertex;
    +} /* redundant_vertex */
    +
    +/*---------------------------------
    +
    +  qh_remove_extravertices(qh, facet )
    +    remove extra vertices from non-simplicial facets
    +
    +  returns:
    +    returns True if it finds them
    +
    +  design:
    +    for each vertex in facet
    +      if vertex not in a ridge (i.e., no longer used)
    +        delete vertex from facet
    +        delete facet from vertice's neighbors
    +        unless vertex in another facet
    +          add vertex to qh.del_vertices for later deletion
    +*/
    +boolT qh_remove_extravertices(qhT *qh, facetT *facet) {
    +  ridgeT *ridge, **ridgep;
    +  vertexT *vertex, **vertexp;
    +  boolT foundrem= False;
    +
    +  trace4((qh, qh->ferr, 4043, "qh_remove_extravertices: test f%d for extra vertices\n",
    +          facet->id));
    +  FOREACHvertex_(facet->vertices)
    +    vertex->seen= False;
    +  FOREACHridge_(facet->ridges) {
    +    FOREACHvertex_(ridge->vertices)
    +      vertex->seen= True;
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    if (!vertex->seen) {
    +      foundrem= True;
    +      zinc_(Zremvertex);
    +      qh_setdelsorted(facet->vertices, vertex);
    +      qh_setdel(vertex->neighbors, facet);
    +      if (!qh_setsize(qh, vertex->neighbors)) {
    +        vertex->deleted= True;
    +        qh_setappend(qh, &qh->del_vertices, vertex);
    +        zinc_(Zremvertexdel);
    +        trace2((qh, qh->ferr, 2036, "qh_remove_extravertices: v%d deleted because it's lost all ridges\n", vertex->id));
    +      }else
    +        trace3((qh, qh->ferr, 3009, "qh_remove_extravertices: v%d removed from f%d because it's lost all ridges\n", vertex->id, facet->id));
    +      vertexp--; /*repeat*/
    +    }
    +  }
    +  return foundrem;
    +} /* remove_extravertices */
    +
    +/*---------------------------------
    +
    +  qh_rename_sharedvertex(qh, vertex, facet )
    +    detect and rename if shared vertex in facet
    +    vertices have full ->neighbors
    +
    +  returns:
    +    newvertex or NULL
    +    the vertex may still exist in other facets (i.e., a neighbor was pinched)
    +    does not change facet->neighbors
    +    updates vertex->neighbors
    +
    +  notes:
    +    a shared vertex for a facet is only in ridges to one neighbor
    +    this may undo a pinched facet
    +
    +    it does not catch pinches involving multiple facets.  These appear
    +      to be difficult to detect, since an exhaustive search is too expensive.
    +
    +  design:
    +    if vertex only has two neighbors
    +      determine the ridges that contain the vertex
    +      determine the vertices shared by both neighbors
    +      if can find a new vertex in this set
    +        rename the vertex to the new vertex
    +*/
    +vertexT *qh_rename_sharedvertex(qhT *qh, vertexT *vertex, facetT *facet) {
    +  facetT *neighbor, **neighborp, *neighborA= NULL;
    +  setT *vertices, *ridges;
    +  vertexT *newvertex;
    +
    +  if (qh_setsize(qh, vertex->neighbors) == 2) {
    +    neighborA= SETfirstt_(vertex->neighbors, facetT);
    +    if (neighborA == facet)
    +      neighborA= SETsecondt_(vertex->neighbors, facetT);
    +  }else if (qh->hull_dim == 3)
    +    return NULL;
    +  else {
    +    qh->visit_id++;
    +    FOREACHneighbor_(facet)
    +      neighbor->visitid= qh->visit_id;
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->visitid == qh->visit_id) {
    +        if (neighborA)
    +          return NULL;
    +        neighborA= neighbor;
    +      }
    +    }
    +    if (!neighborA) {
    +      qh_fprintf(qh, qh->ferr, 6101, "qhull internal error (qh_rename_sharedvertex): v%d's neighbors not in f%d\n",
    +        vertex->id, facet->id);
    +      qh_errprint(qh, "ERRONEOUS", facet, NULL, NULL, vertex);
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +    }
    +  }
    +  /* the vertex is shared by facet and neighborA */
    +  ridges= qh_settemp(qh, qh->TEMPsize);
    +  neighborA->visitid= ++qh->visit_id;
    +  qh_vertexridges_facet(qh, vertex, facet, &ridges);
    +  trace2((qh, qh->ferr, 2037, "qh_rename_sharedvertex: p%d(v%d) is shared by f%d(%d ridges) and f%d\n",
    +    qh_pointid(qh, vertex->point), vertex->id, facet->id, qh_setsize(qh, ridges), neighborA->id));
    +  zinc_(Zintersectnum);
    +  vertices= qh_vertexintersect_new(qh, facet->vertices, neighborA->vertices);
    +  qh_setdel(vertices, vertex);
    +  qh_settemppush(qh, vertices);
    +  if ((newvertex= qh_find_newvertex(qh, vertex, vertices, ridges)))
    +    qh_renamevertex(qh, vertex, newvertex, ridges, facet, neighborA);
    +  qh_settempfree(qh, &vertices);
    +  qh_settempfree(qh, &ridges);
    +  return newvertex;
    +} /* rename_sharedvertex */
    +
    +/*---------------------------------
    +
    +  qh_renameridgevertex(qh, ridge, oldvertex, newvertex )
    +    renames oldvertex as newvertex in ridge
    +
    +  returns:
    +
    +  design:
    +    delete oldvertex from ridge
    +    if newvertex already in ridge
    +      copy ridge->noconvex to another ridge if possible
    +      delete the ridge
    +    else
    +      insert newvertex into the ridge
    +      adjust the ridge's orientation
    +*/
    +void qh_renameridgevertex(qhT *qh, ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex) {
    +  int nth= 0, oldnth;
    +  facetT *temp;
    +  vertexT *vertex, **vertexp;
    +
    +  oldnth= qh_setindex(ridge->vertices, oldvertex);
    +  qh_setdelnthsorted(qh, ridge->vertices, oldnth);
    +  FOREACHvertex_(ridge->vertices) {
    +    if (vertex == newvertex) {
    +      zinc_(Zdelridge);
    +      if (ridge->nonconvex) /* only one ridge has nonconvex set */
    +        qh_copynonconvex(qh, ridge);
    +      trace2((qh, qh->ferr, 2038, "qh_renameridgevertex: ridge r%d deleted.  It contained both v%d and v%d\n",
    +        ridge->id, oldvertex->id, newvertex->id));
    +      qh_delridge(qh, ridge);
    +      return;
    +    }
    +    if (vertex->id < newvertex->id)
    +      break;
    +    nth++;
    +  }
    +  qh_setaddnth(qh, &ridge->vertices, nth, newvertex);
    +  if (abs(oldnth - nth)%2) {
    +    trace3((qh, qh->ferr, 3010, "qh_renameridgevertex: swapped the top and bottom of ridge r%d\n",
    +            ridge->id));
    +    temp= ridge->top;
    +    ridge->top= ridge->bottom;
    +    ridge->bottom= temp;
    +  }
    +} /* renameridgevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_renamevertex(qh, oldvertex, newvertex, ridges, oldfacet, neighborA )
    +    renames oldvertex as newvertex in ridges
    +    gives oldfacet/neighborA if oldvertex is shared between two facets
    +
    +  returns:
    +    oldvertex may still exist afterwards
    +
    +
    +  notes:
    +    can not change neighbors of newvertex (since it's a subset)
    +
    +  design:
    +    for each ridge in ridges
    +      rename oldvertex to newvertex and delete degenerate ridges
    +    if oldfacet not defined
    +      for each neighbor of oldvertex
    +        delete oldvertex from neighbor's vertices
    +        remove extra vertices from neighbor
    +      add oldvertex to qh.del_vertices
    +    else if oldvertex only between oldfacet and neighborA
    +      delete oldvertex from oldfacet and neighborA
    +      add oldvertex to qh.del_vertices
    +    else oldvertex is in oldfacet and neighborA and other facets (i.e., pinched)
    +      delete oldvertex from oldfacet
    +      delete oldfacet from oldvertice's neighbors
    +      remove extra vertices (e.g., oldvertex) from neighborA
    +*/
    +void qh_renamevertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, setT *ridges, facetT *oldfacet, facetT *neighborA) {
    +  facetT *neighbor, **neighborp;
    +  ridgeT *ridge, **ridgep;
    +  boolT istrace= False;
    +
    +  if (qh->IStracing >= 2 || oldvertex->id == qh->tracevertex_id ||
    +        newvertex->id == qh->tracevertex_id)
    +    istrace= True;
    +  FOREACHridge_(ridges)
    +    qh_renameridgevertex(qh, ridge, oldvertex, newvertex);
    +  if (!oldfacet) {
    +    zinc_(Zrenameall);
    +    if (istrace)
    +      qh_fprintf(qh, qh->ferr, 8082, "qh_renamevertex: renamed v%d to v%d in several facets\n",
    +               oldvertex->id, newvertex->id);
    +    FOREACHneighbor_(oldvertex) {
    +      qh_maydropneighbor(qh, neighbor);
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +      if (qh_remove_extravertices(qh, neighbor))
    +        neighborp--; /* neighbor may be deleted */
    +    }
    +    if (!oldvertex->deleted) {
    +      oldvertex->deleted= True;
    +      qh_setappend(qh, &qh->del_vertices, oldvertex);
    +    }
    +  }else if (qh_setsize(qh, oldvertex->neighbors) == 2) {
    +    zinc_(Zrenameshare);
    +    if (istrace)
    +      qh_fprintf(qh, qh->ferr, 8083, "qh_renamevertex: renamed v%d to v%d in oldfacet f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id);
    +    FOREACHneighbor_(oldvertex)
    +      qh_setdelsorted(neighbor->vertices, oldvertex);
    +    oldvertex->deleted= True;
    +    qh_setappend(qh, &qh->del_vertices, oldvertex);
    +  }else {
    +    zinc_(Zrenamepinch);
    +    if (istrace || qh->IStracing)
    +      qh_fprintf(qh, qh->ferr, 8084, "qh_renamevertex: renamed pinched v%d to v%d between f%d and f%d\n",
    +               oldvertex->id, newvertex->id, oldfacet->id, neighborA->id);
    +    qh_setdelsorted(oldfacet->vertices, oldvertex);
    +    qh_setdel(oldvertex->neighbors, oldfacet);
    +    qh_remove_extravertices(qh, neighborA);
    +  }
    +} /* renamevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_test_appendmerge(qh, facet, neighbor )
    +    tests facet/neighbor for convexity
    +    appends to mergeset if non-convex
    +    if pre-merging,
    +      nop if qh.SKIPconvex, or qh.MERGEexact and coplanar
    +
    +  returns:
    +    true if appends facet/neighbor to mergeset
    +    sets facet->center as needed
    +    does not change facet->seen
    +
    +  design:
    +    if qh.cos_max is defined
    +      if the angle between facet normals is too shallow
    +        append an angle-coplanar merge to qh.mergeset
    +        return True
    +    make facet's centrum if needed
    +    if facet's centrum is above the neighbor
    +      set isconcave
    +    else
    +      if facet's centrum is not below the neighbor
    +        set iscoplanar
    +      make neighbor's centrum if needed
    +      if neighbor's centrum is above the facet
    +        set isconcave
    +      else if neighbor's centrum is not below the facet
    +        set iscoplanar
    +   if isconcave or iscoplanar
    +     get angle if needed
    +     append concave or coplanar merge to qh.mergeset
    +*/
    +boolT qh_test_appendmerge(qhT *qh, facetT *facet, facetT *neighbor) {
    +  realT dist, dist2= -REALmax, angle= -REALmax;
    +  boolT isconcave= False, iscoplanar= False, okangle= False;
    +
    +  if (qh->SKIPconvex && !qh->POSTmerging)
    +    return False;
    +  if ((!qh->MERGEexact || qh->POSTmerging) && qh->cos_max < REALmax/2) {
    +    angle= qh_getangle(qh, facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +    if (angle > qh->cos_max) {
    +      zinc_(Zcoplanarangle);
    +      qh_appendmergeset(qh, facet, neighbor, MRGanglecoplanar, &angle);
    +      trace2((qh, qh->ferr, 2039, "qh_test_appendmerge: coplanar angle %4.4g between f%d and f%d\n",
    +         angle, facet->id, neighbor->id));
    +      return True;
    +    }else
    +      okangle= True;
    +  }
    +  if (!facet->center)
    +    facet->center= qh_getcentrum(qh, facet);
    +  zzinc_(Zcentrumtests);
    +  qh_distplane(qh, facet->center, neighbor, &dist);
    +  if (dist > qh->centrum_radius)
    +    isconcave= True;
    +  else {
    +    if (dist > -qh->centrum_radius)
    +      iscoplanar= True;
    +    if (!neighbor->center)
    +      neighbor->center= qh_getcentrum(qh, neighbor);
    +    zzinc_(Zcentrumtests);
    +    qh_distplane(qh, neighbor->center, facet, &dist2);
    +    if (dist2 > qh->centrum_radius)
    +      isconcave= True;
    +    else if (!iscoplanar && dist2 > -qh->centrum_radius)
    +      iscoplanar= True;
    +  }
    +  if (!isconcave && (!iscoplanar || (qh->MERGEexact && !qh->POSTmerging)))
    +    return False;
    +  if (!okangle && qh->ANGLEmerge) {
    +    angle= qh_getangle(qh, facet->normal, neighbor->normal);
    +    zinc_(Zangletests);
    +  }
    +  if (isconcave) {
    +    zinc_(Zconcaveridge);
    +    if (qh->ANGLEmerge)
    +      angle += qh_ANGLEconcave + 0.5;
    +    qh_appendmergeset(qh, facet, neighbor, MRGconcave, &angle);
    +    trace0((qh, qh->ferr, 18, "qh_test_appendmerge: concave f%d to f%d dist %4.4g and reverse dist %4.4g angle %4.4g during p%d\n",
    +           facet->id, neighbor->id, dist, dist2, angle, qh->furthest_id));
    +  }else /* iscoplanar */ {
    +    zinc_(Zcoplanarcentrum);
    +    qh_appendmergeset(qh, facet, neighbor, MRGcoplanar, &angle);
    +    trace2((qh, qh->ferr, 2040, "qh_test_appendmerge: coplanar f%d to f%d dist %4.4g, reverse dist %4.4g angle %4.4g\n",
    +              facet->id, neighbor->id, dist, dist2, angle));
    +  }
    +  return True;
    +} /* test_appendmerge */
    +
    +/*---------------------------------
    +
    +  qh_test_vneighbors(qh)
    +    test vertex neighbors for convexity
    +    tests all facets on qh.newfacet_list
    +
    +  returns:
    +    true if non-convex vneighbors appended to qh.facet_mergeset
    +    initializes vertex neighbors if needed
    +
    +  notes:
    +    assumes all facet neighbors have been tested
    +    this can be expensive
    +    this does not guarantee that a centrum is below all facets
    +      but it is unlikely
    +    uses qh.visit_id
    +
    +  design:
    +    build vertex neighbors if necessary
    +    for all new facets
    +      for all vertices
    +        for each unvisited facet neighbor of the vertex
    +          test new facet and neighbor for convexity
    +*/
    +boolT qh_test_vneighbors(qhT *qh /* qh->newfacet_list */) {
    +  facetT *newfacet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  int nummerges= 0;
    +
    +  trace1((qh, qh->ferr, 1015, "qh_test_vneighbors: testing vertex neighbors for convexity\n"));
    +  if (!qh->VERTEXneighbors)
    +    qh_vertexneighbors(qh);
    +  FORALLnew_facets
    +    newfacet->seen= False;
    +  FORALLnew_facets {
    +    newfacet->seen= True;
    +    newfacet->visitid= qh->visit_id++;
    +    FOREACHneighbor_(newfacet)
    +      newfacet->visitid= qh->visit_id;
    +    FOREACHvertex_(newfacet->vertices) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->seen || neighbor->visitid == qh->visit_id)
    +          continue;
    +        if (qh_test_appendmerge(qh, newfacet, neighbor))
    +          nummerges++;
    +      }
    +    }
    +  }
    +  zadd_(Ztestvneighbor, nummerges);
    +  trace1((qh, qh->ferr, 1016, "qh_test_vneighbors: found %d non-convex, vertex neighbors\n",
    +           nummerges));
    +  return (nummerges > 0);
    +} /* test_vneighbors */
    +
    +/*---------------------------------
    +
    +  qh_tracemerge(qh, facet1, facet2 )
    +    print trace message after merge
    +*/
    +void qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2) {
    +  boolT waserror= False;
    +
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 4)
    +    qh_errprint(qh, "MERGED", facet2, NULL, NULL, NULL);
    +  if (facet2 == qh->tracefacet || (qh->tracevertex && qh->tracevertex->newlist)) {
    +    qh_fprintf(qh, qh->ferr, 8085, "qh_tracemerge: trace facet and vertex after merge of f%d and f%d, furthest p%d\n", facet1->id, facet2->id, qh->furthest_id);
    +    if (facet2 != qh->tracefacet)
    +      qh_errprint(qh, "TRACE", qh->tracefacet,
    +        (qh->tracevertex && qh->tracevertex->neighbors) ?
    +           SETfirstt_(qh->tracevertex->neighbors, facetT) : NULL,
    +        NULL, qh->tracevertex);
    +  }
    +  if (qh->tracevertex) {
    +    if (qh->tracevertex->deleted)
    +      qh_fprintf(qh, qh->ferr, 8086, "qh_tracemerge: trace vertex deleted at furthest p%d\n",
    +            qh->furthest_id);
    +    else
    +      qh_checkvertex(qh, qh->tracevertex);
    +  }
    +  if (qh->tracefacet) {
    +    qh_checkfacet(qh, qh->tracefacet, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh, qh_ERRqhull, qh->tracefacet, NULL);
    +  }
    +#endif /* !qh_NOtrace */
    +  if (qh->CHECKfrequently || qh->IStracing >= 4) { /* can't check polygon here */
    +    qh_checkfacet(qh, facet2, True, &waserror);
    +    if (waserror)
    +      qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +} /* tracemerge */
    +
    +/*---------------------------------
    +
    +  qh_tracemerging(qh)
    +    print trace message during POSTmerging
    +
    +  returns:
    +    updates qh.mergereport
    +
    +  notes:
    +    called from qh_mergecycle() and qh_mergefacet()
    +
    +  see:
    +    qh_buildtracing()
    +*/
    +void qh_tracemerging(qhT *qh) {
    +  realT cpu;
    +  int total;
    +  time_t timedata;
    +  struct tm *tp;
    +
    +  qh->mergereport= zzval_(Ztotmerge);
    +  time(&timedata);
    +  tp= localtime(&timedata);
    +  cpu= qh_CPUclock;
    +  cpu /= qh_SECticks;
    +  total= zzval_(Ztotmerge) - zzval_(Zcyclehorizon) + zzval_(Zcyclefacettot);
    +  qh_fprintf(qh, qh->ferr, 8087, "\n\
    +At %d:%d:%d & %2.5g CPU secs, qhull has merged %d facets.  The hull\n\
    +  contains %d facets and %d vertices.\n",
    +      tp->tm_hour, tp->tm_min, tp->tm_sec, cpu,
    +      total, qh->num_facets - qh->num_visible,
    +      qh->num_vertices-qh_setsize(qh, qh->del_vertices));
    +} /* tracemerging */
    +
    +/*---------------------------------
    +
    +  qh_updatetested(qh, facet1, facet2 )
    +    clear facet2->tested and facet1->ridge->tested for merge
    +
    +  returns:
    +    deletes facet2->center unless it's already large
    +      if so, clears facet2->ridge->tested
    +
    +  design:
    +    clear facet2->tested
    +    clear ridge->tested for facet1's ridges
    +    if facet2 has a centrum
    +      if facet2 is large
    +        set facet2->keepcentrum
    +      else if facet2 has 3 vertices due to many merges, or not large and post merging
    +        clear facet2->keepcentrum
    +      unless facet2->keepcentrum
    +        clear facet2->center to recompute centrum later
    +        clear ridge->tested for facet2's ridges
    +*/
    +void qh_updatetested(qhT *qh, facetT *facet1, facetT *facet2) {
    +  ridgeT *ridge, **ridgep;
    +  int size;
    +
    +  facet2->tested= False;
    +  FOREACHridge_(facet1->ridges)
    +    ridge->tested= False;
    +  if (!facet2->center)
    +    return;
    +  size= qh_setsize(qh, facet2->vertices);
    +  if (!facet2->keepcentrum) {
    +    if (size > qh->hull_dim + qh_MAXnewcentrum) {
    +      facet2->keepcentrum= True;
    +      zinc_(Zwidevertices);
    +    }
    +  }else if (size <= qh->hull_dim + qh_MAXnewcentrum) {
    +    /* center and keepcentrum was set */
    +    if (size == qh->hull_dim || qh->POSTmerging)
    +      facet2->keepcentrum= False; /* if many merges need to recompute centrum */
    +  }
    +  if (!facet2->keepcentrum) {
    +    qh_memfree(qh, facet2->center, qh->normal_size);
    +    facet2->center= NULL;
    +    FOREACHridge_(facet2->ridges)
    +      ridge->tested= False;
    +  }
    +} /* updatetested */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges(qh, vertex )
    +    return temporary set of ridges adjacent to a vertex
    +    vertex->neighbors defined
    +
    +  ntoes:
    +    uses qh.visit_id
    +    does not include implicit ridges for simplicial facets
    +
    +  design:
    +    for each neighbor of vertex
    +      add ridges that include the vertex to ridges
    +*/
    +setT *qh_vertexridges(qhT *qh, vertexT *vertex) {
    +  facetT *neighbor, **neighborp;
    +  setT *ridges= qh_settemp(qh, qh->TEMPsize);
    +  int size;
    +
    +  qh->visit_id++;
    +  FOREACHneighbor_(vertex)
    +    neighbor->visitid= qh->visit_id;
    +  FOREACHneighbor_(vertex) {
    +    if (*neighborp)   /* no new ridges in last neighbor */
    +      qh_vertexridges_facet(qh, vertex, neighbor, &ridges);
    +  }
    +  if (qh->PRINTstatistics || qh->IStracing) {
    +    size= qh_setsize(qh, ridges);
    +    zinc_(Zvertexridge);
    +    zadd_(Zvertexridgetot, size);
    +    zmax_(Zvertexridgemax, size);
    +    trace3((qh, qh->ferr, 3011, "qh_vertexridges: found %d ridges for v%d\n",
    +             size, vertex->id));
    +  }
    +  return ridges;
    +} /* vertexridges */
    +
    +/*---------------------------------
    +
    +  qh_vertexridges_facet(qh, vertex, facet, ridges )
    +    add adjacent ridges for vertex in facet
    +    neighbor->visitid==qh.visit_id if it hasn't been visited
    +
    +  returns:
    +    ridges updated
    +    sets facet->visitid to qh.visit_id-1
    +
    +  design:
    +    for each ridge of facet
    +      if ridge of visited neighbor (i.e., unprocessed)
    +        if vertex in ridge
    +          append ridge to vertex
    +    mark facet processed
    +*/
    +void qh_vertexridges_facet(qhT *qh, vertexT *vertex, facetT *facet, setT **ridges) {
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor;
    +
    +  FOREACHridge_(facet->ridges) {
    +    neighbor= otherfacet_(ridge, facet);
    +    if (neighbor->visitid == qh->visit_id
    +    && qh_setin(ridge->vertices, vertex))
    +      qh_setappend(qh, ridges, ridge);
    +  }
    +  facet->visitid= qh->visit_id-1;
    +} /* vertexridges_facet */
    +
    +/*---------------------------------
    +
    +  qh_willdelete(qh, facet, replace )
    +    moves facet to visible list
    +    sets facet->f.replace to replace (may be NULL)
    +
    +  returns:
    +    bumps qh.num_visible
    +*/
    +void qh_willdelete(qhT *qh, facetT *facet, facetT *replace) {
    +
    +  qh_removefacet(qh, facet);
    +  qh_prependfacet(qh, facet, &qh->visible_list);
    +  qh->num_visible++;
    +  facet->visible= True;
    +  facet->f.replace= replace;
    +} /* willdelete */
    +
    +#else /* qh_NOmerge */
    +void qh_premerge(qhT *qh, vertexT *apex, realT maxcentrum, realT maxangle) {
    +}
    +void qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
    +                      boolT vneighbors) {
    +}
    +boolT qh_checkzero(qhT *qh, boolT testall) {
    +   }
    +#endif /* qh_NOmerge */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/merge_r.h b/xs/src/qhull/src/libqhull_r/merge_r.h
    new file mode 100644
    index 0000000000..30a51815da
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/merge_r.h
    @@ -0,0 +1,186 @@
    +/*
      ---------------------------------
    +
    +   merge_r.h
    +   header file for merge_r.c
    +
    +   see qh-merge_r.htm and merge_r.c
    +
    +   Copyright (c) 1993-2015 C.B. Barber.
    +   $Id: //main/2015/qhull/src/libqhull_r/merge_r.h#3 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFmerge
    +#define qhDEFmerge 1
    +
    +#include "libqhull_r.h"
    +
    +
    +/*============ -constants- ==============*/
    +
    +/*----------------------------------
    +
    +  qh_ANGLEredundant
    +    indicates redundant merge in mergeT->angle
    +*/
    +#define qh_ANGLEredundant 6.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEdegen
    +    indicates degenerate facet in mergeT->angle
    +*/
    +#define qh_ANGLEdegen     5.0
    +
    +/*----------------------------------
    +
    +  qh_ANGLEconcave
    +    offset to indicate concave facets in mergeT->angle
    +
    +  notes:
    +    concave facets are assigned the range of [2,4] in mergeT->angle
    +    roundoff error may make the angle less than 2
    +*/
    +#define qh_ANGLEconcave  1.5
    +
    +/*----------------------------------
    +
    +  MRG... (mergeType)
    +    indicates the type of a merge (mergeT->type)
    +*/
    +typedef enum {  /* in sort order for facet_mergeset */
    +  MRGnone= 0,
    +  MRGcoplanar,          /* centrum coplanar */
    +  MRGanglecoplanar,     /* angle coplanar */
    +                        /* could detect half concave ridges */
    +  MRGconcave,           /* concave ridge */
    +  MRGflip,              /* flipped facet. facet1 == facet2 */
    +  MRGridge,             /* duplicate ridge (qh_MERGEridge) */
    +                        /* degen and redundant go onto degen_mergeset */
    +  MRGdegen,             /* degenerate facet (!enough neighbors) facet1 == facet2 */
    +  MRGredundant,         /* redundant facet (vertex subset) */
    +                        /* merge_degenredundant assumes degen < redundant */
    +  MRGmirror,            /* mirror facet from qh_triangulate */
    +  ENDmrg
    +} mergeType;
    +
    +/*----------------------------------
    +
    +  qh_MERGEapex
    +    flag for qh_mergefacet() to indicate an apex merge
    +*/
    +#define qh_MERGEapex     True
    +
    +/*============ -structures- ====================*/
    +
    +/*----------------------------------
    +
    +  mergeT
    +    structure used to merge facets
    +*/
    +
    +typedef struct mergeT mergeT;
    +struct mergeT {         /* initialize in qh_appendmergeset */
    +  realT   angle;        /* angle between normals of facet1 and facet2 */
    +  facetT *facet1;       /* will merge facet1 into facet2 */
    +  facetT *facet2;
    +  mergeType type;
    +};
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FOREACHmerge_( merges ) {...}
    +    assign 'merge' to each merge in merges
    +
    +  notes:
    +    uses 'mergeT *merge, **mergep;'
    +    if qh_mergefacet(),
    +      restart since qh.facet_mergeset may change
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHmerge_( merges ) FOREACHsetelement_(mergeT, merges, merge)
    +
    +/*============ prototypes in alphabetical order after pre/postmerge =======*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_premerge(qhT *qh, vertexT *apex, realT maxcentrum, realT maxangle);
    +void    qh_postmerge(qhT *qh, const char *reason, realT maxcentrum, realT maxangle,
    +             boolT vneighbors);
    +void    qh_all_merges(qhT *qh, boolT othermerge, boolT vneighbors);
    +void    qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType mergetype, realT *angle);
    +setT   *qh_basevertices(qhT *qh, facetT *samecycle);
    +void    qh_checkconnect(qhT *qh /* qh.new_facets */);
    +boolT   qh_checkzero(qhT *qh, boolT testall);
    +int     qh_compareangle(const void *p1, const void *p2);
    +int     qh_comparemerge(const void *p1, const void *p2);
    +int     qh_comparevisit(const void *p1, const void *p2);
    +void    qh_copynonconvex(qhT *qh, ridgeT *atridge);
    +void    qh_degen_redundant_facet(qhT *qh, facetT *facet);
    +void    qh_degen_redundant_neighbors(qhT *qh, facetT *facet, facetT *delfacet);
    +vertexT *qh_find_newvertex(qhT *qh, vertexT *oldvertex, setT *vertices, setT *ridges);
    +void    qh_findbest_test(qhT *qh, boolT testcentrum, facetT *facet, facetT *neighbor,
    +           facetT **bestfacet, realT *distp, realT *mindistp, realT *maxdistp);
    +facetT *qh_findbestneighbor(qhT *qh, facetT *facet, realT *distp, realT *mindistp, realT *maxdistp);
    +void    qh_flippedmerges(qhT *qh, facetT *facetlist, boolT *wasmerge);
    +void    qh_forcedmerges(qhT *qh, boolT *wasmerge);
    +void    qh_getmergeset(qhT *qh, facetT *facetlist);
    +void    qh_getmergeset_initial(qhT *qh, facetT *facetlist);
    +void    qh_hashridge(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge, vertexT *oldvertex);
    +ridgeT *qh_hashridge_find(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge,
    +              vertexT *vertex, vertexT *oldvertex, int *hashslot);
    +void    qh_makeridges(qhT *qh, facetT *facet);
    +void    qh_mark_dupridges(qhT *qh, facetT *facetlist);
    +void    qh_maydropneighbor(qhT *qh, facetT *facet);
    +int     qh_merge_degenredundant(qhT *qh);
    +void    qh_merge_nonconvex(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype);
    +void    qh_mergecycle(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_all(qhT *qh, facetT *facetlist, boolT *wasmerge);
    +void    qh_mergecycle_facets(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_neighbors(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_ridges(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergecycle_vneighbors(qhT *qh, facetT *samecycle, facetT *newfacet);
    +void    qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, realT *mindist, realT *maxdist, boolT mergeapex);
    +void    qh_mergefacet2d(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergeneighbors(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergeridges(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergesimplex(qhT *qh, facetT *facet1, facetT *facet2, boolT mergeapex);
    +void    qh_mergevertex_del(qhT *qh, vertexT *vertex, facetT *facet1, facetT *facet2);
    +void    qh_mergevertex_neighbors(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_mergevertices(qhT *qh, setT *vertices1, setT **vertices);
    +setT   *qh_neighbor_intersections(qhT *qh, vertexT *vertex);
    +void    qh_newvertices(qhT *qh, setT *vertices);
    +boolT   qh_reducevertices(qhT *qh);
    +vertexT *qh_redundant_vertex(qhT *qh, vertexT *vertex);
    +boolT   qh_remove_extravertices(qhT *qh, facetT *facet);
    +vertexT *qh_rename_sharedvertex(qhT *qh, vertexT *vertex, facetT *facet);
    +void    qh_renameridgevertex(qhT *qh, ridgeT *ridge, vertexT *oldvertex, vertexT *newvertex);
    +void    qh_renamevertex(qhT *qh, vertexT *oldvertex, vertexT *newvertex, setT *ridges,
    +                        facetT *oldfacet, facetT *neighborA);
    +boolT   qh_test_appendmerge(qhT *qh, facetT *facet, facetT *neighbor);
    +boolT   qh_test_vneighbors(qhT *qh /* qh.newfacet_list */);
    +void    qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2);
    +void    qh_tracemerging(qhT *qh);
    +void    qh_updatetested(qhT *qh, facetT *facet1, facetT *facet2);
    +setT   *qh_vertexridges(qhT *qh, vertexT *vertex);
    +void    qh_vertexridges_facet(qhT *qh, vertexT *vertex, facetT *facet, setT **ridges);
    +void    qh_willdelete(qhT *qh, facetT *facet, facetT *replace);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFmerge */
    diff --git a/xs/src/qhull/src/libqhull_r/poly2_r.c b/xs/src/qhull/src/libqhull_r/poly2_r.c
    new file mode 100644
    index 0000000000..b8ae9af9f4
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/poly2_r.c
    @@ -0,0 +1,3222 @@
    +/*
      ---------------------------------
    +
    +   poly2_r.c
    +   implements polygons and simplicies
    +
    +   see qh-poly_r.htm, poly_r.h and libqhull_r.h
    +
    +   frequently used code is in poly_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/poly2_r.c#10 $$Change: 2069 $
    +   $DateTime: 2016/01/18 22:05:03 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_addhash( newelem, hashtable, hashsize, hash )
    +    add newelem to linear hash table at hash if not already there
    +*/
    +void qh_addhash(void *newelem, setT *hashtable, int hashsize, int hash) {
    +  int scan;
    +  void *elem;
    +
    +  for (scan= (int)hash; (elem= SETelem_(hashtable, scan));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (elem == newelem)
    +      break;
    +  }
    +  /* loop terminates because qh_HASHfactor >= 1.1 by qh_initbuffers */
    +  if (!elem)
    +    SETelem_(hashtable, scan)= newelem;
    +} /* addhash */
    +
    +/*---------------------------------
    +
    +  qh_check_bestdist(qh)
    +    check that all points are within max_outside of the nearest facet
    +    if qh.ONLYgood,
    +      ignores !good facets
    +
    +  see:
    +    qh_check_maxout(), qh_outerinner()
    +
    +  notes:
    +    only called from qh_check_points()
    +      seldom used since qh.MERGING is almost always set
    +    if notverified>0 at end of routine
    +      some points were well inside the hull.  If the hull contains
    +      a lens-shaped component, these points were not verified.  Use
    +      options 'Qi Tv' to verify all points.  (Exhaustive check also verifies)
    +
    +  design:
    +    determine facet for each point (if any)
    +    for each point
    +      start with the assigned facet or with the first facet
    +      find the best facet for the point and check all coplanar facets
    +      error if point is outside of facet
    +*/
    +void qh_check_bestdist(qhT *qh) {
    +  boolT waserror= False, unassigned;
    +  facetT *facet, *bestfacet, *errfacet1= NULL, *errfacet2= NULL;
    +  facetT *facetlist;
    +  realT dist, maxoutside, maxdist= -REALmax;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0, notverified= 0;
    +  setT *facets;
    +
    +  trace1((qh, qh->ferr, 1020, "qh_check_bestdist: check points below nearest facet.  Facet_list f%d\n",
    +      qh->facet_list->id));
    +  maxoutside= qh_maxouter(qh);
    +  maxoutside += qh->DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh, qh->ferr, 1021, "qh_check_bestdist: check that all points are within %2.2g of best facet\n", maxoutside));
    +  facets= qh_pointfacet(qh /*qh.facet_list*/);
    +  if (!qh_QUICKhelp && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 8091, "\n\
    +qhull output completed.  Verifying that %d points are\n\
    +below %2.2g of the nearest %sfacet.\n",
    +             qh_setsize(qh, facets), maxoutside, (qh->ONLYgood ?  "good " : ""));
    +  FOREACHfacet_i_(qh, facets) {  /* for each point with facet assignment */
    +    if (facet)
    +      unassigned= False;
    +    else {
    +      unassigned= True;
    +      facet= qh->facet_list;
    +    }
    +    point= qh_point(qh, facet_i);
    +    if (point == qh->GOODpointp)
    +      continue;
    +    qh_distplane(qh, point, facet, &dist);
    +    numpart++;
    +    bestfacet= qh_findbesthorizon(qh, !qh_IScheckmax, point, facet, qh_NOupper, &dist, &numpart);
    +    /* occurs after statistics reported */
    +    maximize_(maxdist, dist);
    +    if (dist > maxoutside) {
    +      if (qh->ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(qh, point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +        notgood++;
    +      else {
    +        waserror= True;
    +        qh_fprintf(qh, qh->ferr, 6109, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +                facet_i, bestfacet->id, dist, maxoutside);
    +        if (errfacet1 != bestfacet) {
    +          errfacet2= errfacet1;
    +          errfacet1= bestfacet;
    +        }
    +      }
    +    }else if (unassigned && dist < -qh->MAXcoplanar)
    +      notverified++;
    +  }
    +  qh_settempfree(qh, &facets);
    +  if (notverified && !qh->DELAUNAY && !qh_QUICKhelp && qh->PRINTprecision)
    +    qh_fprintf(qh, qh->ferr, 8092, "\n%d points were well inside the hull.  If the hull contains\n\
    +a lens-shaped component, these points were not verified.  Use\n\
    +options 'Qci Tv' to verify all points.\n", notverified);
    +  if (maxdist > qh->outside_err) {
    +    qh_fprintf(qh, qh->ferr, 6110, "qhull precision error (qh_check_bestdist): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +              maxdist, qh->outside_err);
    +    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
    +  }else if (waserror && qh->outside_err > REALmax/2)
    +    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
    +  /* else if waserror, the error was logged to qh.ferr but does not effect the output */
    +  trace0((qh, qh->ferr, 20, "qh_check_bestdist: max distance outside %2.2g\n", maxdist));
    +} /* check_bestdist */
    +
    +/*---------------------------------
    +
    +  qh_check_dupridge(qh, facet1, dist1, facet2, dist2)
    +    Check duplicate ridge between facet1 and facet2 for wide merge
    +    dist1 is the maximum distance of facet1's vertices to facet2
    +    dist2 is the maximum distance of facet2's vertices to facet1
    +
    +  Returns
    +    Level 1 log of the duplicate ridge with the minimum distance between vertices
    +    Throws error if the merge will increase the maximum facet width by qh_WIDEduplicate (100x)
    +
    +  called from:
    +    qh_forcedmerges()
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_dupridge(qhT *qh, facetT *facet1, realT dist1, facetT *facet2, realT dist2) {
    +  vertexT *vertex, **vertexp, *vertexA, **vertexAp;
    +  realT dist, innerplane, mergedist, outerplane, prevdist, ratio;
    +  realT minvertex= REALmax;
    +
    +  mergedist= fmin_(dist1, dist2);
    +  qh_outerinner(qh, NULL, &outerplane, &innerplane);  /* ratio from qh_printsummary */
    +  prevdist= fmax_(outerplane, innerplane);
    +  maximize_(prevdist, qh->ONEmerge + qh->DISTround);
    +  maximize_(prevdist, qh->MINoutside + qh->DISTround);
    +  ratio= mergedist/prevdist;
    +  FOREACHvertex_(facet1->vertices) {     /* The duplicate ridge is between facet1 and facet2, so either facet can be tested */
    +    FOREACHvertexA_(facet1->vertices) {
    +      if (vertex > vertexA){   /* Test each pair once */
    +        dist= qh_pointdist(vertex->point, vertexA->point, qh->hull_dim);
    +        minimize_(minvertex, dist);
    +      }
    +    }
    +  }
    +  trace0((qh, qh->ferr, 16, "qh_check_dupridge: duplicate ridge between f%d and f%d due to nearly-coincident vertices (%2.2g), dist %2.2g, reverse dist %2.2g, ratio %2.2g while processing p%d\n",
    +        facet1->id, facet2->id, minvertex, dist1, dist2, ratio, qh->furthest_id));
    +  if (ratio > qh_WIDEduplicate) {
    +    qh_fprintf(qh, qh->ferr, 6271, "qhull precision error (qh_check_dupridge): wide merge (%.0f times wider) due to duplicate ridge with nearly coincident points (%2.2g) between f%d and f%d, merge dist %2.2g, while processing p%d\n- Ignore error with option 'Q12'\n- To be fixed in a later version of Qhull\n",
    +          ratio, minvertex, facet1->id, facet2->id, mergedist, qh->furthest_id);
    +    if (qh->DELAUNAY)
    +      qh_fprintf(qh, qh->ferr, 8145, "- A bounding box for the input sites may alleviate this error.\n");
    +    if(minvertex > qh_WIDEduplicate*prevdist)
    +      qh_fprintf(qh, qh->ferr, 8146, "- Vertex distance %2.2g is greater than %d times maximum distance %2.2g\n  Please report to bradb@shore.net with steps to reproduce and all output\n",
    +          minvertex, qh_WIDEduplicate, prevdist);
    +    if (!qh->NOwide)
    +      qh_errexit2(qh, qh_ERRqhull, facet1, facet2);
    +  }
    +} /* check_dupridge */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_maxout(qh)
    +    updates qh.max_outside by checking all points against bestfacet
    +    if qh.ONLYgood, ignores !good facets
    +
    +  returns:
    +    updates facet->maxoutside via qh_findbesthorizon()
    +    sets qh.maxoutdone
    +    if printing qh.min_vertex (qh_outerinner),
    +      it is updated to the current vertices
    +    removes inside/coplanar points from coplanarset as needed
    +
    +  notes:
    +    defines coplanar as min_vertex instead of MAXcoplanar
    +    may not need to check near-inside points because of qh.MAXcoplanar
    +      and qh.KEEPnearinside (before it was -DISTround)
    +
    +  see also:
    +    qh_check_bestdist()
    +
    +  design:
    +    if qh.min_vertex is needed
    +      for all neighbors of all vertices
    +        test distance from vertex to neighbor
    +    determine facet for each point (if any)
    +    for each point with an assigned facet
    +      find the best facet for the point and check all coplanar facets
    +        (updates outer planes)
    +    remove near-inside points from coplanar sets
    +*/
    +#ifndef qh_NOmerge
    +void qh_check_maxout(qhT *qh) {
    +  facetT *facet, *bestfacet, *neighbor, **neighborp, *facetlist;
    +  realT dist, maxoutside, minvertex, old_maxoutside;
    +  pointT *point;
    +  int numpart= 0, facet_i, facet_n, notgood= 0;
    +  setT *facets, *vertices;
    +  vertexT *vertex;
    +
    +  trace1((qh, qh->ferr, 1022, "qh_check_maxout: check and update maxoutside for each facet.\n"));
    +  maxoutside= minvertex= 0;
    +  if (qh->VERTEXneighbors
    +  && (qh->PRINTsummary || qh->KEEPinside || qh->KEEPcoplanar
    +        || qh->TRACElevel || qh->PRINTstatistics
    +        || qh->PRINTout[0] == qh_PRINTsummary || qh->PRINTout[0] == qh_PRINTnone)) {
    +    trace1((qh, qh->ferr, 1023, "qh_check_maxout: determine actual maxoutside and minvertex\n"));
    +    vertices= qh_pointvertex(qh /*qh.facet_list*/);
    +    FORALLvertices {
    +      FOREACHneighbor_(vertex) {
    +        zinc_(Zdistvertex);  /* distance also computed by main loop below */
    +        qh_distplane(qh, vertex->point, neighbor, &dist);
    +        minimize_(minvertex, dist);
    +        if (-dist > qh->TRACEdist || dist > qh->TRACEdist
    +        || neighbor == qh->tracefacet || vertex == qh->tracevertex)
    +          qh_fprintf(qh, qh->ferr, 8093, "qh_check_maxout: p%d(v%d) is %.2g from f%d\n",
    +                    qh_pointid(qh, vertex->point), vertex->id, dist, neighbor->id);
    +      }
    +    }
    +    if (qh->MERGING) {
    +      wmin_(Wminvertex, qh->min_vertex);
    +    }
    +    qh->min_vertex= minvertex;
    +    qh_settempfree(qh, &vertices);
    +  }
    +  facets= qh_pointfacet(qh /*qh.facet_list*/);
    +  do {
    +    old_maxoutside= fmax_(qh->max_outside, maxoutside);
    +    FOREACHfacet_i_(qh, facets) {     /* for each point with facet assignment */
    +      if (facet) {
    +        point= qh_point(qh, facet_i);
    +        if (point == qh->GOODpointp)
    +          continue;
    +        zzinc_(Ztotcheck);
    +        qh_distplane(qh, point, facet, &dist);
    +        numpart++;
    +        bestfacet= qh_findbesthorizon(qh, qh_IScheckmax, point, facet, !qh_NOupper, &dist, &numpart);
    +        if (bestfacet && dist > maxoutside) {
    +          if (qh->ONLYgood && !bestfacet->good
    +          && !((bestfacet= qh_findgooddist(qh, point, bestfacet, &dist, &facetlist))
    +               && dist > maxoutside))
    +            notgood++;
    +          else
    +            maxoutside= dist;
    +        }
    +        if (dist > qh->TRACEdist || (bestfacet && bestfacet == qh->tracefacet))
    +          qh_fprintf(qh, qh->ferr, 8094, "qh_check_maxout: p%d is %.2g above f%d\n",
    +          qh_pointid(qh, point), dist, (bestfacet ? bestfacet->id : UINT_MAX));
    +      }
    +    }
    +  }while
    +    (maxoutside > 2*old_maxoutside);
    +    /* if qh.maxoutside increases substantially, qh_SEARCHdist is not valid
    +          e.g., RBOX 5000 s Z1 G1e-13 t1001200614 | qhull */
    +  zzadd_(Zcheckpart, numpart);
    +  qh_settempfree(qh, &facets);
    +  wval_(Wmaxout)= maxoutside - qh->max_outside;
    +  wmax_(Wmaxoutside, qh->max_outside);
    +  qh->max_outside= maxoutside;
    +  qh_nearcoplanar(qh /*qh.facet_list*/);
    +  qh->maxoutdone= True;
    +  trace1((qh, qh->ferr, 1024, "qh_check_maxout: maxoutside %2.2g, min_vertex %2.2g, outside of not good %d\n",
    +       maxoutside, qh->min_vertex, notgood));
    +} /* check_maxout */
    +#else /* qh_NOmerge */
    +void qh_check_maxout(qhT *qh) {
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_check_output(qh)
    +    performs the checks at the end of qhull algorithm
    +    Maybe called after voronoi output.  Will recompute otherwise centrums are Voronoi centers instead
    +*/
    +void qh_check_output(qhT *qh) {
    +  int i;
    +
    +  if (qh->STOPcone)
    +    return;
    +  if (qh->VERIFYoutput | qh->IStracing | qh->CHECKfrequently) {
    +    qh_checkpolygon(qh, qh->facet_list);
    +    qh_checkflipped_all(qh, qh->facet_list);
    +    qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
    +  }else if (!qh->MERGING && qh_newstats(qh, qh->qhstat.precision, &i)) {
    +    qh_checkflipped_all(qh, qh->facet_list);
    +    qh_checkconvex(qh, qh->facet_list, qh_ALGORITHMfault);
    +  }
    +} /* check_output */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_check_point(qh, point, facet, maxoutside, maxdist, errfacet1, errfacet2 )
    +    check that point is less than maxoutside from facet
    +*/
    +void qh_check_point(qhT *qh, pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2) {
    +  realT dist;
    +
    +  /* occurs after statistics reported */
    +  qh_distplane(qh, point, facet, &dist);
    +  if (dist > *maxoutside) {
    +    if (*errfacet1 != facet) {
    +      *errfacet2= *errfacet1;
    +      *errfacet1= facet;
    +    }
    +    qh_fprintf(qh, qh->ferr, 6111, "qhull precision error: point p%d is outside facet f%d, distance= %6.8g maxoutside= %6.8g\n",
    +              qh_pointid(qh, point), facet->id, dist, *maxoutside);
    +  }
    +  maximize_(*maxdist, dist);
    +} /* qh_check_point */
    +
    +
    +/*---------------------------------
    +
    +  qh_check_points(qh)
    +    checks that all points are inside all facets
    +
    +  notes:
    +    if many points and qh_check_maxout not called (i.e., !qh.MERGING),
    +       calls qh_findbesthorizon (seldom done).
    +    ignores flipped facets
    +    maxoutside includes 2 qh.DISTrounds
    +      one qh.DISTround for the computed distances in qh_check_points
    +    qh_printafacet and qh_printsummary needs only one qh.DISTround
    +    the computation for qh.VERIFYdirect does not account for qh.other_points
    +
    +  design:
    +    if many points
    +      use qh_check_bestdist()
    +    else
    +      for all facets
    +        for all points
    +          check that point is inside facet
    +*/
    +void qh_check_points(qhT *qh) {
    +  facetT *facet, *errfacet1= NULL, *errfacet2= NULL;
    +  realT total, maxoutside, maxdist= -REALmax;
    +  pointT *point, **pointp, *pointtemp;
    +  boolT testouter;
    +
    +  maxoutside= qh_maxouter(qh);
    +  maxoutside += qh->DISTround;
    +  /* one more qh.DISTround for check computation */
    +  trace1((qh, qh->ferr, 1025, "qh_check_points: check all points below %2.2g of all facet planes\n",
    +          maxoutside));
    +  if (qh->num_good)   /* miss counts other_points and !good facets */
    +     total= (float)qh->num_good * (float)qh->num_points;
    +  else
    +     total= (float)qh->num_facets * (float)qh->num_points;
    +  if (total >= qh_VERIFYdirect && !qh->maxoutdone) {
    +    if (!qh_QUICKhelp && qh->SKIPcheckmax && qh->MERGING)
    +      qh_fprintf(qh, qh->ferr, 7075, "qhull input warning: merging without checking outer planes('Q5' or 'Po').\n\
    +Verify may report that a point is outside of a facet.\n");
    +    qh_check_bestdist(qh);
    +  }else {
    +    if (qh_MAXoutside && qh->maxoutdone)
    +      testouter= True;
    +    else
    +      testouter= False;
    +    if (!qh_QUICKhelp) {
    +      if (qh->MERGEexact)
    +        qh_fprintf(qh, qh->ferr, 7076, "qhull input warning: exact merge ('Qx').  Verify may report that a point\n\
    +is outside of a facet.  See qh-optq.htm#Qx\n");
    +      else if (qh->SKIPcheckmax || qh->NOnearinside)
    +        qh_fprintf(qh, qh->ferr, 7077, "qhull input warning: no outer plane check ('Q5') or no processing of\n\
    +near-inside points ('Q8').  Verify may report that a point is outside\n\
    +of a facet.\n");
    +    }
    +    if (qh->PRINTprecision) {
    +      if (testouter)
    +        qh_fprintf(qh, qh->ferr, 8098, "\n\
    +Output completed.  Verifying that all points are below outer planes of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              (qh->ONLYgood ?  "good " : ""), total);
    +      else
    +        qh_fprintf(qh, qh->ferr, 8099, "\n\
    +Output completed.  Verifying that all points are below %2.2g of\n\
    +all %sfacets.  Will make %2.0f distance computations.\n",
    +              maxoutside, (qh->ONLYgood ?  "good " : ""), total);
    +    }
    +    FORALLfacets {
    +      if (!facet->good && qh->ONLYgood)
    +        continue;
    +      if (facet->flipped)
    +        continue;
    +      if (!facet->normal) {
    +        qh_fprintf(qh, qh->ferr, 7061, "qhull warning (qh_check_points): missing normal for facet f%d\n", facet->id);
    +        continue;
    +      }
    +      if (testouter) {
    +#if qh_MAXoutside
    +        maxoutside= facet->maxoutside + 2* qh->DISTround;
    +        /* one DISTround to actual point and another to computed point */
    +#endif
    +      }
    +      FORALLpoints {
    +        if (point != qh->GOODpointp)
    +          qh_check_point(qh, point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +      FOREACHpoint_(qh->other_points) {
    +        if (point != qh->GOODpointp)
    +          qh_check_point(qh, point, facet, &maxoutside, &maxdist, &errfacet1, &errfacet2);
    +      }
    +    }
    +    if (maxdist > qh->outside_err) {
    +      qh_fprintf(qh, qh->ferr, 6112, "qhull precision error (qh_check_points): a coplanar point is %6.2g from convex hull.  The maximum value(qh.outside_err) is %6.2g\n",
    +                maxdist, qh->outside_err );
    +      qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2 );
    +    }else if (errfacet1 && qh->outside_err > REALmax/2)
    +        qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2 );
    +    /* else if errfacet1, the error was logged to qh.ferr but does not effect the output */
    +    trace0((qh, qh->ferr, 21, "qh_check_points: max distance outside %2.2g\n", maxdist));
    +  }
    +} /* check_points */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkconvex(qh, facetlist, fault )
    +    check that each ridge in facetlist is convex
    +    fault = qh_DATAfault if reporting errors
    +          = qh_ALGORITHMfault otherwise
    +
    +  returns:
    +    counts Zconcaveridges and Zcoplanarridges
    +    errors if concaveridge or if merging an coplanar ridge
    +
    +  note:
    +    if not merging,
    +      tests vertices for neighboring simplicial facets
    +    else if ZEROcentrum,
    +      tests vertices for neighboring simplicial   facets
    +    else
    +      tests centrums of neighboring facets
    +
    +  design:
    +    for all facets
    +      report flipped facets
    +      if ZEROcentrum and simplicial neighbors
    +        test vertices for neighboring simplicial facets
    +      else
    +        test centrum against all neighbors
    +*/
    +void qh_checkconvex(qhT *qh, facetT *facetlist, int fault) {
    +  facetT *facet, *neighbor, **neighborp, *errfacet1=NULL, *errfacet2=NULL;
    +  vertexT *vertex;
    +  realT dist;
    +  pointT *centrum;
    +  boolT waserror= False, centrum_warning= False, tempcentrum= False, allsimplicial;
    +  int neighbor_i;
    +
    +  trace1((qh, qh->ferr, 1026, "qh_checkconvex: check all ridges are convex\n"));
    +  if (!qh->RERUN) {
    +    zzval_(Zconcaveridges)= 0;
    +    zzval_(Zcoplanarridges)= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->flipped) {
    +      qh_precision(qh, "flipped facet");
    +      qh_fprintf(qh, qh->ferr, 6113, "qhull precision error: f%d is flipped(interior point is outside)\n",
    +               facet->id);
    +      errfacet1= facet;
    +      waserror= True;
    +      continue;
    +    }
    +    if (qh->MERGING && (!qh->ZEROcentrum || !facet->simplicial || facet->tricoplanar))
    +      allsimplicial= False;
    +    else {
    +      allsimplicial= True;
    +      neighbor_i= 0;
    +      FOREACHneighbor_(facet) {
    +        vertex= SETelemt_(facet->vertices, neighbor_i++, vertexT);
    +        if (!neighbor->simplicial || neighbor->tricoplanar) {
    +          allsimplicial= False;
    +          continue;
    +        }
    +        qh_distplane(qh, vertex->point, neighbor, &dist);
    +        if (dist > -qh->DISTround) {
    +          if (fault == qh_DATAfault) {
    +            qh_precision(qh, "coplanar or concave ridge");
    +            qh_fprintf(qh, qh->ferr, 6114, "qhull precision error: initial simplex is not convex. Distance=%.2g\n", dist);
    +            qh_errexit(qh, qh_ERRsingular, NULL, NULL);
    +          }
    +          if (dist > qh->DISTround) {
    +            zzinc_(Zconcaveridges);
    +            qh_precision(qh, "concave ridge");
    +            qh_fprintf(qh, qh->ferr, 6115, "qhull precision error: f%d is concave to f%d, since p%d(v%d) is %6.4g above\n",
    +              facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist);
    +            errfacet1= facet;
    +            errfacet2= neighbor;
    +            waserror= True;
    +          }else if (qh->ZEROcentrum) {
    +            if (dist > 0) {     /* qh_checkzero checks that dist < - qh->DISTround */
    +              zzinc_(Zcoplanarridges);
    +              qh_precision(qh, "coplanar ridge");
    +              qh_fprintf(qh, qh->ferr, 6116, "qhull precision error: f%d is clearly not convex to f%d, since p%d(v%d) is %6.4g above\n",
    +                facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist);
    +              errfacet1= facet;
    +              errfacet2= neighbor;
    +              waserror= True;
    +            }
    +          }else {
    +            zzinc_(Zcoplanarridges);
    +            qh_precision(qh, "coplanar ridge");
    +            trace0((qh, qh->ferr, 22, "qhull precision error: f%d may be coplanar to f%d, since p%d(v%d) is within %6.4g during p%d\n",
    +              facet->id, neighbor->id, qh_pointid(qh, vertex->point), vertex->id, dist, qh->furthest_id));
    +          }
    +        }
    +      }
    +    }
    +    if (!allsimplicial) {
    +      if (qh->CENTERtype == qh_AScentrum) {
    +        if (!facet->center)
    +          facet->center= qh_getcentrum(qh, facet);
    +        centrum= facet->center;
    +      }else {
    +        if (!centrum_warning && (!facet->simplicial || facet->tricoplanar)) {
    +           centrum_warning= True;
    +           qh_fprintf(qh, qh->ferr, 7062, "qhull warning: recomputing centrums for convexity test.  This may lead to false, precision errors.\n");
    +        }
    +        centrum= qh_getcentrum(qh, facet);
    +        tempcentrum= True;
    +      }
    +      FOREACHneighbor_(facet) {
    +        if (qh->ZEROcentrum && facet->simplicial && neighbor->simplicial)
    +          continue;
    +        if (facet->tricoplanar || neighbor->tricoplanar)
    +          continue;
    +        zzinc_(Zdistconvex);
    +        qh_distplane(qh, centrum, neighbor, &dist);
    +        if (dist > qh->DISTround) {
    +          zzinc_(Zconcaveridges);
    +          qh_precision(qh, "concave ridge");
    +          qh_fprintf(qh, qh->ferr, 6117, "qhull precision error: f%d is concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }else if (dist >= 0.0) {   /* if arithmetic always rounds the same,
    +                                     can test against centrum radius instead */
    +          zzinc_(Zcoplanarridges);
    +          qh_precision(qh, "coplanar ridge");
    +          qh_fprintf(qh, qh->ferr, 6118, "qhull precision error: f%d is coplanar or concave to f%d.  Centrum of f%d is %6.4g above f%d\n",
    +            facet->id, neighbor->id, facet->id, dist, neighbor->id);
    +          errfacet1= facet;
    +          errfacet2= neighbor;
    +          waserror= True;
    +        }
    +      }
    +      if (tempcentrum)
    +        qh_memfree(qh, centrum, qh->normal_size);
    +    }
    +  }
    +  if (waserror && !qh->FORCEoutput)
    +    qh_errexit2(qh, qh_ERRprec, errfacet1, errfacet2);
    +} /* checkconvex */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkfacet(qh, facet, newmerge, waserror )
    +    checks for consistency errors in facet
    +    newmerge set if from merge_r.c
    +
    +  returns:
    +    sets waserror if any error occurs
    +
    +  checks:
    +    vertex ids are inverse sorted
    +    unless newmerge, at least hull_dim neighbors and vertices (exactly if simplicial)
    +    if non-simplicial, at least as many ridges as neighbors
    +    neighbors are not duplicated
    +    ridges are not duplicated
    +    in 3-d, ridges=verticies
    +    (qh.hull_dim-1) ridge vertices
    +    neighbors are reciprocated
    +    ridge neighbors are facet neighbors and a ridge for every neighbor
    +    simplicial neighbors match facetintersect
    +    vertex intersection matches vertices of common ridges
    +    vertex neighbors and facet vertices agree
    +    all ridges have distinct vertex sets
    +
    +  notes:
    +    uses neighbor->seen
    +
    +  design:
    +    check sets
    +    check vertices
    +    check sizes of neighbors and vertices
    +    check for qh_MERGEridge and qh_DUPLICATEridge flags
    +    check neighbor set
    +    check ridge set
    +    check ridges, neighbors, and vertices
    +*/
    +void qh_checkfacet(qhT *qh, facetT *facet, boolT newmerge, boolT *waserrorp) {
    +  facetT *neighbor, **neighborp, *errother=NULL;
    +  ridgeT *ridge, **ridgep, *errridge= NULL, *ridge2;
    +  vertexT *vertex, **vertexp;
    +  unsigned previousid= INT_MAX;
    +  int numneighbors, numvertices, numridges=0, numRvertices=0;
    +  boolT waserror= False;
    +  int skipA, skipB, ridge_i, ridge_n, i;
    +  setT *intersection;
    +
    +  if (facet->visible) {
    +    qh_fprintf(qh, qh->ferr, 6119, "qhull internal error (qh_checkfacet): facet f%d is on the visible_list\n",
    +      facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  if (!facet->normal) {
    +    qh_fprintf(qh, qh->ferr, 6120, "qhull internal error (qh_checkfacet): facet f%d does not have  a normal\n",
    +      facet->id);
    +    waserror= True;
    +  }
    +  qh_setcheck(qh, facet->vertices, "vertices for f", facet->id);
    +  qh_setcheck(qh, facet->ridges, "ridges for f", facet->id);
    +  qh_setcheck(qh, facet->outsideset, "outsideset for f", facet->id);
    +  qh_setcheck(qh, facet->coplanarset, "coplanarset for f", facet->id);
    +  qh_setcheck(qh, facet->neighbors, "neighbors for f", facet->id);
    +  FOREACHvertex_(facet->vertices) {
    +    if (vertex->deleted) {
    +      qh_fprintf(qh, qh->ferr, 6121, "qhull internal error (qh_checkfacet): deleted vertex v%d in f%d\n", vertex->id, facet->id);
    +      qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
    +      waserror= True;
    +    }
    +    if (vertex->id >= previousid) {
    +      qh_fprintf(qh, qh->ferr, 6122, "qhull internal error (qh_checkfacet): vertices of f%d are not in descending id order at v%d\n", facet->id, vertex->id);
    +      waserror= True;
    +      break;
    +    }
    +    previousid= vertex->id;
    +  }
    +  numneighbors= qh_setsize(qh, facet->neighbors);
    +  numvertices= qh_setsize(qh, facet->vertices);
    +  numridges= qh_setsize(qh, facet->ridges);
    +  if (facet->simplicial) {
    +    if (numvertices+numneighbors != 2*qh->hull_dim
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh, qh->ferr, 6123, "qhull internal error (qh_checkfacet): for simplicial facet f%d, #vertices %d + #neighbors %d != 2*qh->hull_dim\n",
    +                facet->id, numvertices, numneighbors);
    +      qh_setprint(qh, qh->ferr, "", facet->neighbors);
    +      waserror= True;
    +    }
    +  }else { /* non-simplicial */
    +    if (!newmerge
    +    &&(numvertices < qh->hull_dim || numneighbors < qh->hull_dim)
    +    && !facet->degenerate && !facet->redundant) {
    +      qh_fprintf(qh, qh->ferr, 6124, "qhull internal error (qh_checkfacet): for facet f%d, #vertices %d or #neighbors %d < qh->hull_dim\n",
    +         facet->id, numvertices, numneighbors);
    +       waserror= True;
    +    }
    +    /* in 3-d, can get a vertex twice in an edge list, e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv TP624 TW1e-13 T4 */
    +    if (numridges < numneighbors
    +    ||(qh->hull_dim == 3 && numvertices > numridges && !qh->NEWfacets)
    +    ||(qh->hull_dim == 2 && numridges + numvertices + numneighbors != 6)) {
    +      if (!facet->degenerate && !facet->redundant) {
    +        qh_fprintf(qh, qh->ferr, 6125, "qhull internal error (qh_checkfacet): for facet f%d, #ridges %d < #neighbors %d or(3-d) > #vertices %d or(2-d) not all 2\n",
    +            facet->id, numridges, numneighbors, numvertices);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge) {
    +      qh_fprintf(qh, qh->ferr, 6126, "qhull internal error (qh_checkfacet): facet f%d still has a MERGE or DUP neighbor\n", facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    neighbor->seen= True;
    +  }
    +  FOREACHneighbor_(facet) {
    +    if (!qh_setin(neighbor->neighbors, facet)) {
    +      qh_fprintf(qh, qh->ferr, 6127, "qhull internal error (qh_checkfacet): facet f%d has neighbor f%d, but f%d does not have neighbor f%d\n",
    +              facet->id, neighbor->id, neighbor->id, facet->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    if (!neighbor->seen) {
    +      qh_fprintf(qh, qh->ferr, 6128, "qhull internal error (qh_checkfacet): facet f%d has a duplicate neighbor f%d\n",
    +              facet->id, neighbor->id);
    +      errother= neighbor;
    +      waserror= True;
    +    }
    +    neighbor->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    qh_setcheck(qh, ridge->vertices, "vertices for r", ridge->id);
    +    ridge->seen= False;
    +  }
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge->seen) {
    +      qh_fprintf(qh, qh->ferr, 6129, "qhull internal error (qh_checkfacet): facet f%d has a duplicate ridge r%d\n",
    +              facet->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    ridge->seen= True;
    +    numRvertices= qh_setsize(qh, ridge->vertices);
    +    if (numRvertices != qh->hull_dim - 1) {
    +      qh_fprintf(qh, qh->ferr, 6130, "qhull internal error (qh_checkfacet): ridge between f%d and f%d has %d vertices\n",
    +                ridge->top->id, ridge->bottom->id, numRvertices);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +    neighbor= otherfacet_(ridge, facet);
    +    neighbor->seen= True;
    +    if (!qh_setin(facet->neighbors, neighbor)) {
    +      qh_fprintf(qh, qh->ferr, 6131, "qhull internal error (qh_checkfacet): for facet f%d, neighbor f%d of ridge r%d not in facet\n",
    +           facet->id, neighbor->id, ridge->id);
    +      errridge= ridge;
    +      waserror= True;
    +    }
    +  }
    +  if (!facet->simplicial) {
    +    FOREACHneighbor_(facet) {
    +      if (!neighbor->seen) {
    +        qh_fprintf(qh, qh->ferr, 6132, "qhull internal error (qh_checkfacet): facet f%d does not have a ridge for neighbor f%d\n",
    +              facet->id, neighbor->id);
    +        errother= neighbor;
    +        waserror= True;
    +      }
    +      intersection= qh_vertexintersect_new(qh, facet->vertices, neighbor->vertices);
    +      qh_settemppush(qh, intersection);
    +      FOREACHvertex_(facet->vertices) {
    +        vertex->seen= False;
    +        vertex->seen2= False;
    +      }
    +      FOREACHvertex_(intersection)
    +        vertex->seen= True;
    +      FOREACHridge_(facet->ridges) {
    +        if (neighbor != otherfacet_(ridge, facet))
    +            continue;
    +        FOREACHvertex_(ridge->vertices) {
    +          if (!vertex->seen) {
    +            qh_fprintf(qh, qh->ferr, 6133, "qhull internal error (qh_checkfacet): vertex v%d in r%d not in f%d intersect f%d\n",
    +                  vertex->id, ridge->id, facet->id, neighbor->id);
    +            qh_errexit(qh, qh_ERRqhull, facet, ridge);
    +          }
    +          vertex->seen2= True;
    +        }
    +      }
    +      if (!newmerge) {
    +        FOREACHvertex_(intersection) {
    +          if (!vertex->seen2) {
    +            if (qh->IStracing >=3 || !qh->MERGING) {
    +              qh_fprintf(qh, qh->ferr, 6134, "qhull precision error (qh_checkfacet): vertex v%d in f%d intersect f%d but\n\
    + not in a ridge.  This is ok under merging.  Last point was p%d\n",
    +                     vertex->id, facet->id, neighbor->id, qh->furthest_id);
    +              if (!qh->FORCEoutput && !qh->MERGING) {
    +                qh_errprint(qh, "ERRONEOUS", facet, neighbor, NULL, vertex);
    +                if (!qh->MERGING)
    +                  qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +              }
    +            }
    +          }
    +        }
    +      }
    +      qh_settempfree(qh, &intersection);
    +    }
    +  }else { /* simplicial */
    +    FOREACHneighbor_(facet) {
    +      if (neighbor->simplicial) {
    +        skipA= SETindex_(facet->neighbors, neighbor);
    +        skipB= qh_setindex(neighbor->neighbors, facet);
    +        if (skipA<0 || skipB<0 || !qh_setequal_skip(facet->vertices, skipA, neighbor->vertices, skipB)) {
    +          qh_fprintf(qh, qh->ferr, 6135, "qhull internal error (qh_checkfacet): facet f%d skip %d and neighbor f%d skip %d do not match \n",
    +                   facet->id, skipA, neighbor->id, skipB);
    +          errother= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (qh->hull_dim < 5 && (qh->IStracing > 2 || qh->CHECKfrequently)) {
    +    FOREACHridge_i_(qh, facet->ridges) {           /* expensive */
    +      for (i=ridge_i+1; i < ridge_n; i++) {
    +        ridge2= SETelemt_(facet->ridges, i, ridgeT);
    +        if (qh_setequal(ridge->vertices, ridge2->vertices)) {
    +          qh_fprintf(qh, qh->ferr, 6227, "Qhull internal error (qh_checkfacet): ridges r%d and r%d have the same vertices\n",
    +                  ridge->id, ridge2->id);
    +          errridge= ridge;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint(qh, "ERRONEOUS", facet, errother, errridge, NULL);
    +    *waserrorp= True;
    +  }
    +} /* checkfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkflipped_all(qh, facetlist )
    +    checks orientation of facets in list against interior point
    +*/
    +void qh_checkflipped_all(qhT *qh, facetT *facetlist) {
    +  facetT *facet;
    +  boolT waserror= False;
    +  realT dist;
    +
    +  if (facetlist == qh->facet_list)
    +    zzval_(Zflippedfacets)= 0;
    +  FORALLfacet_(facetlist) {
    +    if (facet->normal && !qh_checkflipped(qh, facet, &dist, !qh_ALL)) {
    +      qh_fprintf(qh, qh->ferr, 6136, "qhull precision error: facet f%d is flipped, distance= %6.12g\n",
    +              facet->id, dist);
    +      if (!qh->FORCEoutput) {
    +        qh_errprint(qh, "ERRONEOUS", facet, NULL, NULL, NULL);
    +        waserror= True;
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_fprintf(qh, qh->ferr, 8101, "\n\
    +A flipped facet occurs when its distance to the interior point is\n\
    +greater than %2.2g, the maximum roundoff error.\n", -qh->DISTround);
    +    qh_errexit(qh, qh_ERRprec, NULL, NULL);
    +  }
    +} /* checkflipped_all */
    +
    +/*---------------------------------
    +
    +  qh_checkpolygon(qh, facetlist )
    +    checks the correctness of the structure
    +
    +  notes:
    +    call with either qh.facet_list or qh.newfacet_list
    +    checks num_facets and num_vertices if qh.facet_list
    +
    +  design:
    +    for each facet
    +      checks facet and outside set
    +    initializes vertexlist
    +    for each facet
    +      checks vertex set
    +    if checking all facets(qh.facetlist)
    +      check facet count
    +      if qh.VERTEXneighbors
    +        check vertex neighbors and count
    +      check vertex count
    +*/
    +void qh_checkpolygon(qhT *qh, facetT *facetlist) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp, *vertexlist;
    +  int numfacets= 0, numvertices= 0, numridges= 0;
    +  int totvneighbors= 0, totvertices= 0;
    +  boolT waserror= False, nextseen= False, visibleseen= False;
    +
    +  trace1((qh, qh->ferr, 1027, "qh_checkpolygon: check all facets from f%d\n", facetlist->id));
    +  if (facetlist != qh->facet_list || qh->ONLYgood)
    +    nextseen= True;
    +  FORALLfacet_(facetlist) {
    +    if (facet == qh->visible_list)
    +      visibleseen= True;
    +    if (!facet->visible) {
    +      if (!nextseen) {
    +        if (facet == qh->facet_next)
    +          nextseen= True;
    +        else if (qh_setsize(qh, facet->outsideset)) {
    +          if (!qh->NARROWhull
    +#if !qh_COMPUTEfurthest
    +               || facet->furthestdist >= qh->MINoutside
    +#endif
    +                        ) {
    +            qh_fprintf(qh, qh->ferr, 6137, "qhull internal error (qh_checkpolygon): f%d has outside points before qh->facet_next\n",
    +                     facet->id);
    +            qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +          }
    +        }
    +      }
    +      numfacets++;
    +      qh_checkfacet(qh, facet, False, &waserror);
    +    }
    +  }
    +  if (qh->visible_list && !visibleseen && facetlist == qh->facet_list) {
    +    qh_fprintf(qh, qh->ferr, 6138, "qhull internal error (qh_checkpolygon): visible list f%d no longer on facet list\n", qh->visible_list->id);
    +    qh_printlists(qh);
    +    qh_errexit(qh, qh_ERRqhull, qh->visible_list, NULL);
    +  }
    +  if (facetlist == qh->facet_list)
    +    vertexlist= qh->vertex_list;
    +  else if (facetlist == qh->newfacet_list)
    +    vertexlist= qh->newvertex_list;
    +  else
    +    vertexlist= NULL;
    +  FORALLvertex_(vertexlist) {
    +    vertex->seen= False;
    +    vertex->visitid= 0;
    +  }
    +  FORALLfacet_(facetlist) {
    +    if (facet->visible)
    +      continue;
    +    if (facet->simplicial)
    +      numridges += qh->hull_dim;
    +    else
    +      numridges += qh_setsize(qh, facet->ridges);
    +    FOREACHvertex_(facet->vertices) {
    +      vertex->visitid++;
    +      if (!vertex->seen) {
    +        vertex->seen= True;
    +        numvertices++;
    +        if (qh_pointid(qh, vertex->point) == qh_IDunknown) {
    +          qh_fprintf(qh, qh->ferr, 6139, "qhull internal error (qh_checkpolygon): unknown point %p for vertex v%d first_point %p\n",
    +                   vertex->point, vertex->id, qh->first_point);
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  qh->vertex_visit += (unsigned int)numfacets;
    +  if (facetlist == qh->facet_list) {
    +    if (numfacets != qh->num_facets - qh->num_visible) {
    +      qh_fprintf(qh, qh->ferr, 6140, "qhull internal error (qh_checkpolygon): actual number of facets is %d, cumulative facet count is %d - %d visible facets\n",
    +              numfacets, qh->num_facets, qh->num_visible);
    +      waserror= True;
    +    }
    +    qh->vertex_visit++;
    +    if (qh->VERTEXneighbors) {
    +      FORALLvertices {
    +        qh_setcheck(qh, vertex->neighbors, "neighbors for v", vertex->id);
    +        if (vertex->deleted)
    +          continue;
    +        totvneighbors += qh_setsize(qh, vertex->neighbors);
    +      }
    +      FORALLfacet_(facetlist)
    +        totvertices += qh_setsize(qh, facet->vertices);
    +      if (totvneighbors != totvertices) {
    +        qh_fprintf(qh, qh->ferr, 6141, "qhull internal error (qh_checkpolygon): vertex neighbors inconsistent.  Totvneighbors %d, totvertices %d\n",
    +                totvneighbors, totvertices);
    +        waserror= True;
    +      }
    +    }
    +    if (numvertices != qh->num_vertices - qh_setsize(qh, qh->del_vertices)) {
    +      qh_fprintf(qh, qh->ferr, 6142, "qhull internal error (qh_checkpolygon): actual number of vertices is %d, cumulative vertex count is %d\n",
    +              numvertices, qh->num_vertices - qh_setsize(qh, qh->del_vertices));
    +      waserror= True;
    +    }
    +    if (qh->hull_dim == 2 && numvertices != numfacets) {
    +      qh_fprintf(qh, qh->ferr, 6143, "qhull internal error (qh_checkpolygon): #vertices %d != #facets %d\n",
    +        numvertices, numfacets);
    +      waserror= True;
    +    }
    +    if (qh->hull_dim == 3 && numvertices + numfacets - numridges/2 != 2) {
    +      qh_fprintf(qh, qh->ferr, 7063, "qhull warning: #vertices %d + #facets %d - #edges %d != 2\n\
    +        A vertex appears twice in a edge list.  May occur during merging.",
    +        numvertices, numfacets, numridges/2);
    +      /* occurs if lots of merging and a vertex ends up twice in an edge list.  e.g., RBOX 1000 s W1e-13 t995849315 D2 | QHULL d Tc Tv */
    +    }
    +  }
    +  if (waserror)
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +} /* checkpolygon */
    +
    +
    +/*---------------------------------
    +
    +  qh_checkvertex(qh, vertex )
    +    check vertex for consistency
    +    checks vertex->neighbors
    +
    +  notes:
    +    neighbors checked efficiently in checkpolygon
    +*/
    +void qh_checkvertex(qhT *qh, vertexT *vertex) {
    +  boolT waserror= False;
    +  facetT *neighbor, **neighborp, *errfacet=NULL;
    +
    +  if (qh_pointid(qh, vertex->point) == qh_IDunknown) {
    +    qh_fprintf(qh, qh->ferr, 6144, "qhull internal error (qh_checkvertex): unknown point id %p\n", vertex->point);
    +    waserror= True;
    +  }
    +  if (vertex->id >= qh->vertex_id) {
    +    qh_fprintf(qh, qh->ferr, 6145, "qhull internal error (qh_checkvertex): unknown vertex id %d\n", vertex->id);
    +    waserror= True;
    +  }
    +  if (!waserror && !vertex->deleted) {
    +    if (qh_setsize(qh, vertex->neighbors)) {
    +      FOREACHneighbor_(vertex) {
    +        if (!qh_setin(neighbor->vertices, vertex)) {
    +          qh_fprintf(qh, qh->ferr, 6146, "qhull internal error (qh_checkvertex): neighbor f%d does not contain v%d\n", neighbor->id, vertex->id);
    +          errfacet= neighbor;
    +          waserror= True;
    +        }
    +      }
    +    }
    +  }
    +  if (waserror) {
    +    qh_errprint(qh, "ERRONEOUS", NULL, NULL, NULL, vertex);
    +    qh_errexit(qh, qh_ERRqhull, errfacet, NULL);
    +  }
    +} /* checkvertex */
    +
    +/*---------------------------------
    +
    +  qh_clearcenters(qh, type )
    +    clear old data from facet->center
    +
    +  notes:
    +    sets new centertype
    +    nop if CENTERtype is the same
    +*/
    +void qh_clearcenters(qhT *qh, qh_CENTER type) {
    +  facetT *facet;
    +
    +  if (qh->CENTERtype != type) {
    +    FORALLfacets {
    +      if (facet->tricoplanar && !facet->keepcentrum)
    +          facet->center= NULL;  /* center is owned by the ->keepcentrum facet */
    +      else if (qh->CENTERtype == qh_ASvoronoi){
    +        if (facet->center) {
    +          qh_memfree(qh, facet->center, qh->center_size);
    +          facet->center= NULL;
    +        }
    +      }else /* qh->CENTERtype == qh_AScentrum */ {
    +        if (facet->center) {
    +          qh_memfree(qh, facet->center, qh->normal_size);
    +          facet->center= NULL;
    +        }
    +      }
    +    }
    +    qh->CENTERtype= type;
    +  }
    +  trace2((qh, qh->ferr, 2043, "qh_clearcenters: switched to center type %d\n", type));
    +} /* clearcenters */
    +
    +/*---------------------------------
    +
    +  qh_createsimplex(qh, vertices )
    +    creates a simplex from a set of vertices
    +
    +  returns:
    +    initializes qh.facet_list to the simplex
    +    initializes qh.newfacet_list, .facet_tail
    +    initializes qh.vertex_list, .newvertex_list, .vertex_tail
    +
    +  design:
    +    initializes lists
    +    for each vertex
    +      create a new facet
    +    for each new facet
    +      create its neighbor set
    +*/
    +void qh_createsimplex(qhT *qh, setT *vertices) {
    +  facetT *facet= NULL, *newfacet;
    +  boolT toporient= True;
    +  int vertex_i, vertex_n, nth;
    +  setT *newfacets= qh_settemp(qh, qh->hull_dim+1);
    +  vertexT *vertex;
    +
    +  qh->facet_list= qh->newfacet_list= qh->facet_tail= qh_newfacet(qh);
    +  qh->num_facets= qh->num_vertices= qh->num_visible= 0;
    +  qh->vertex_list= qh->newvertex_list= qh->vertex_tail= qh_newvertex(qh, NULL);
    +  FOREACHvertex_i_(qh, vertices) {
    +    newfacet= qh_newfacet(qh);
    +    newfacet->vertices= qh_setnew_delnthsorted(qh, vertices, vertex_n,
    +                                                vertex_i, 0);
    +    newfacet->toporient= (unsigned char)toporient;
    +    qh_appendfacet(qh, newfacet);
    +    newfacet->newfacet= True;
    +    qh_appendvertex(qh, vertex);
    +    qh_setappend(qh, &newfacets, newfacet);
    +    toporient ^= True;
    +  }
    +  FORALLnew_facets {
    +    nth= 0;
    +    FORALLfacet_(qh->newfacet_list) {
    +      if (facet != newfacet)
    +        SETelem_(newfacet->neighbors, nth++)= facet;
    +    }
    +    qh_settruncate(qh, newfacet->neighbors, qh->hull_dim);
    +  }
    +  qh_settempfree(qh, &newfacets);
    +  trace1((qh, qh->ferr, 1028, "qh_createsimplex: created simplex\n"));
    +} /* createsimplex */
    +
    +/*---------------------------------
    +
    +  qh_delridge(qh, ridge )
    +    deletes ridge from data structures it belongs to
    +    frees up its memory
    +
    +  notes:
    +    in merge_r.c, caller sets vertex->delridge for each vertex
    +    ridges also freed in qh_freeqhull
    +*/
    +void qh_delridge(qhT *qh, ridgeT *ridge) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  qh_setdel(ridge->top->ridges, ridge);
    +  qh_setdel(ridge->bottom->ridges, ridge);
    +  qh_setfree(qh, &(ridge->vertices));
    +  qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +} /* delridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_delvertex(qh, vertex )
    +    deletes a vertex and frees its memory
    +
    +  notes:
    +    assumes vertex->adjacencies have been updated if needed
    +    unlinks from vertex_list
    +*/
    +void qh_delvertex(qhT *qh, vertexT *vertex) {
    +
    +  if (vertex == qh->tracevertex)
    +    qh->tracevertex= NULL;
    +  qh_removevertex(qh, vertex);
    +  qh_setfree(qh, &vertex->neighbors);
    +  qh_memfree(qh, vertex, (int)sizeof(vertexT));
    +} /* delvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_facet3vertex(qh, )
    +    return temporary set of 3-d vertices in qh_ORIENTclock order
    +
    +  design:
    +    if simplicial facet
    +      build set from facet->vertices with facet->toporient
    +    else
    +      for each ridge in order
    +        build set from ridge's vertices
    +*/
    +setT *qh_facet3vertex(qhT *qh, facetT *facet) {
    +  ridgeT *ridge, *firstridge;
    +  vertexT *vertex;
    +  int cntvertices, cntprojected=0;
    +  setT *vertices;
    +
    +  cntvertices= qh_setsize(qh, facet->vertices);
    +  vertices= qh_settemp(qh, cntvertices);
    +  if (facet->simplicial) {
    +    if (cntvertices != 3) {
    +      qh_fprintf(qh, qh->ferr, 6147, "qhull internal error (qh_facet3vertex): only %d vertices for simplicial facet f%d\n",
    +                  cntvertices, facet->id);
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    qh_setappend(qh, &vertices, SETfirst_(facet->vertices));
    +    if (facet->toporient ^ qh_ORIENTclock)
    +      qh_setappend(qh, &vertices, SETsecond_(facet->vertices));
    +    else
    +      qh_setaddnth(qh, &vertices, 0, SETsecond_(facet->vertices));
    +    qh_setappend(qh, &vertices, SETelem_(facet->vertices, 2));
    +  }else {
    +    ridge= firstridge= SETfirstt_(facet->ridges, ridgeT);   /* no infinite */
    +    while ((ridge= qh_nextridge3d(ridge, facet, &vertex))) {
    +      qh_setappend(qh, &vertices, vertex);
    +      if (++cntprojected > cntvertices || ridge == firstridge)
    +        break;
    +    }
    +    if (!ridge || cntprojected != cntvertices) {
    +      qh_fprintf(qh, qh->ferr, 6148, "qhull internal error (qh_facet3vertex): ridges for facet %d don't match up.  got at least %d\n",
    +                  facet->id, cntprojected);
    +      qh_errexit(qh, qh_ERRqhull, facet, ridge);
    +    }
    +  }
    +  return vertices;
    +} /* facet3vertex */
    +
    +/*---------------------------------
    +
    +  qh_findbestfacet(qh, point, bestoutside, bestdist, isoutside )
    +    find facet that is furthest below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    if bestoutside is set (e.g., qh_ALL)
    +      returns best facet that is not upperdelaunay
    +      if Delaunay and inside, point is outside circumsphere of bestfacet
    +    else
    +      returns first facet below point
    +      if point is inside, returns nearest, !upperdelaunay facet
    +    distance to facet
    +    isoutside set if outside of facet
    +
    +  notes:
    +    For tricoplanar facets, this finds one of the tricoplanar facets closest
    +    to the point.  For Delaunay triangulations, the point may be inside a
    +    different tricoplanar facet. See locate a facet with qh_findbestfacet()
    +
    +    If inside, qh_findbestfacet performs an exhaustive search
    +       this may be too conservative.  Sometimes it is clearly required.
    +
    +    qh_findbestfacet is not used by qhull.
    +    uses qh.visit_id and qh.coplanarset
    +
    +  see:
    +    qh_findbest
    +*/
    +facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside) {
    +  facetT *bestfacet= NULL;
    +  int numpart, totpart= 0;
    +
    +  bestfacet= qh_findbest(qh, point, qh->facet_list,
    +                            bestoutside, !qh_ISnewfacets, bestoutside /* qh_NOupper */,
    +                            bestdist, isoutside, &totpart);
    +  if (*bestdist < -qh->DISTround) {
    +    bestfacet= qh_findfacet_all(qh, point, bestdist, isoutside, &numpart);
    +    totpart += numpart;
    +    if ((isoutside && *isoutside && bestoutside)
    +    || (isoutside && !*isoutside && bestfacet->upperdelaunay)) {
    +      bestfacet= qh_findbest(qh, point, bestfacet,
    +                            bestoutside, False, bestoutside,
    +                            bestdist, isoutside, &totpart);
    +      totpart += numpart;
    +    }
    +  }
    +  trace3((qh, qh->ferr, 3014, "qh_findbestfacet: f%d dist %2.2g isoutside %d totpart %d\n",
    +          bestfacet->id, *bestdist, (isoutside ? *isoutside : UINT_MAX), totpart));
    +  return bestfacet;
    +} /* findbestfacet */
    +
    +/*---------------------------------
    +
    +  qh_findbestlower(qh, facet, point, bestdist, numpart )
    +    returns best non-upper, non-flipped neighbor of facet for point
    +    if needed, searches vertex neighbors
    +
    +  returns:
    +    returns bestdist and updates numpart
    +
    +  notes:
    +    if Delaunay and inside, point is outside of circumsphere of bestfacet
    +    called by qh_findbest() for points above an upperdelaunay facet
    +
    +*/
    +facetT *qh_findbestlower(qhT *qh, facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart) {
    +  facetT *neighbor, **neighborp, *bestfacet= NULL;
    +  realT bestdist= -REALmax/2 /* avoid underflow */;
    +  realT dist;
    +  vertexT *vertex;
    +  boolT isoutside= False;  /* not used */
    +
    +  zinc_(Zbestlower);
    +  FOREACHneighbor_(upperfacet) {
    +    if (neighbor->upperdelaunay || neighbor->flipped)
    +      continue;
    +    (*numpart)++;
    +    qh_distplane(qh, point, neighbor, &dist);
    +    if (dist > bestdist) {
    +      bestfacet= neighbor;
    +      bestdist= dist;
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerv);
    +    /* rarely called, numpart does not count nearvertex computations */
    +    vertex= qh_nearvertex(qh, upperfacet, point, &dist);
    +    qh_vertexneighbors(qh);
    +    FOREACHneighbor_(vertex) {
    +      if (neighbor->upperdelaunay || neighbor->flipped)
    +        continue;
    +      (*numpart)++;
    +      qh_distplane(qh, point, neighbor, &dist);
    +      if (dist > bestdist) {
    +        bestfacet= neighbor;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (!bestfacet) {
    +    zinc_(Zbestlowerall);  /* invoked once per point in outsideset */
    +    zmax_(Zbestloweralln, qh->num_facets);
    +    /* [dec'15] Previously reported as QH6228 */
    +    trace3((qh, qh->ferr, 3025, "qh_findbestlower: all neighbors of facet %d are flipped or upper Delaunay.  Search all facets\n",
    +       upperfacet->id));
    +    /* rarely called */
    +    bestfacet= qh_findfacet_all(qh, point, &bestdist, &isoutside, numpart);
    +  }
    +  *bestdistp= bestdist;
    +  trace3((qh, qh->ferr, 3015, "qh_findbestlower: f%d dist %2.2g for f%d p%d\n",
    +          bestfacet->id, bestdist, upperfacet->id, qh_pointid(qh, point)));
    +  return bestfacet;
    +} /* findbestlower */
    +
    +/*---------------------------------
    +
    +  qh_findfacet_all(qh, point, bestdist, isoutside, numpart )
    +    exhaustive search for facet below a point
    +
    +    for Delaunay triangulations,
    +      Use qh_setdelaunay() to lift point to paraboloid and scale by 'Qbb' if needed
    +      Do not use options 'Qbk', 'QBk', or 'QbB' since they scale the coordinates.
    +
    +  returns:
    +    returns first facet below point
    +    if point is inside,
    +      returns nearest facet
    +    distance to facet
    +    isoutside if point is outside of the hull
    +    number of distance tests
    +
    +  notes:
    +    primarily for library users, rarely used by Qhull
    +*/
    +facetT *qh_findfacet_all(qhT *qh, pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart) {
    +  facetT *bestfacet= NULL, *facet;
    +  realT dist;
    +  int totpart= 0;
    +
    +  *bestdist= -REALmax;
    +  *isoutside= False;
    +  FORALLfacets {
    +    if (facet->flipped || !facet->normal)
    +      continue;
    +    totpart++;
    +    qh_distplane(qh, point, facet, &dist);
    +    if (dist > *bestdist) {
    +      *bestdist= dist;
    +      bestfacet= facet;
    +      if (dist > qh->MINoutside) {
    +        *isoutside= True;
    +        break;
    +      }
    +    }
    +  }
    +  *numpart= totpart;
    +  trace3((qh, qh->ferr, 3016, "qh_findfacet_all: f%d dist %2.2g isoutside %d totpart %d\n",
    +          getid_(bestfacet), *bestdist, *isoutside, totpart));
    +  return bestfacet;
    +} /* findfacet_all */
    +
    +/*---------------------------------
    +
    +  qh_findgood(qh, facetlist, goodhorizon )
    +    identify good facets for qh.PRINTgood
    +    if qh.GOODvertex>0
    +      facet includes point as vertex
    +      if !match, returns goodhorizon
    +      inactive if qh.MERGING
    +    if qh.GOODpoint
    +      facet is visible or coplanar (>0) or not visible (<0)
    +    if qh.GOODthreshold
    +      facet->normal matches threshold
    +    if !goodhorizon and !match,
    +      selects facet with closest angle
    +      sets GOODclosest
    +
    +  returns:
    +    number of new, good facets found
    +    determines facet->good
    +    may update qh.GOODclosest
    +
    +  notes:
    +    qh_findgood_all further reduces the good region
    +
    +  design:
    +    count good facets
    +    mark good facets for qh.GOODpoint
    +    mark good facets for qh.GOODthreshold
    +    if necessary
    +      update qh.GOODclosest
    +*/
    +int qh_findgood(qhT *qh, facetT *facetlist, int goodhorizon) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT angle, bestangle= REALmax, dist;
    +  int  numgood=0;
    +
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh->GOODvertex>0 && !qh->MERGING) {
    +    FORALLfacet_(facetlist) {
    +      if (!qh_isvertex(qh->GOODvertexp, facet->vertices)) {
    +        facet->good= False;
    +        numgood--;
    +      }
    +    }
    +  }
    +  if (qh->GOODpoint && numgood) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        zinc_(Zdistgood);
    +        qh_distplane(qh, qh->GOODpointp, facet, &dist);
    +        if ((qh->GOODpoint > 0) ^ (dist > 0.0)) {
    +          facet->good= False;
    +          numgood--;
    +        }
    +      }
    +    }
    +  }
    +  if (qh->GOODthreshold && (numgood || goodhorizon || qh->GOODclosest)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && facet->normal) {
    +        if (!qh_inthresholds(qh, facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && (!goodhorizon || qh->GOODclosest)) {
    +      if (qh->GOODclosest) {
    +        if (qh->GOODclosest->visible)
    +          qh->GOODclosest= NULL;
    +        else {
    +          qh_inthresholds(qh, qh->GOODclosest->normal, &angle);
    +          if (angle < bestangle)
    +            bestfacet= qh->GOODclosest;
    +        }
    +      }
    +      if (bestfacet && bestfacet != qh->GOODclosest) {
    +        if (qh->GOODclosest)
    +          qh->GOODclosest->good= False;
    +        qh->GOODclosest= bestfacet;
    +        bestfacet->good= True;
    +        numgood++;
    +        trace2((qh, qh->ferr, 2044, "qh_findgood: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +        return numgood;
    +      }
    +    }else if (qh->GOODclosest) { /* numgood > 0 */
    +      qh->GOODclosest->good= False;
    +      qh->GOODclosest= NULL;
    +    }
    +  }
    +  zadd_(Zgoodfacet, numgood);
    +  trace2((qh, qh->ferr, 2045, "qh_findgood: found %d good facets with %d good horizon\n",
    +               numgood, goodhorizon));
    +  if (!numgood && qh->GOODvertex>0 && !qh->MERGING)
    +    return goodhorizon;
    +  return numgood;
    +} /* findgood */
    +
    +/*---------------------------------
    +
    +  qh_findgood_all(qh, facetlist )
    +    apply other constraints for good facets (used by qh.PRINTgood)
    +    if qh.GOODvertex
    +      facet includes (>0) or doesn't include (<0) point as vertex
    +      if last good facet and ONLYgood, prints warning and continues
    +    if qh.SPLITthresholds
    +      facet->normal matches threshold, or if none, the closest one
    +    calls qh_findgood
    +    nop if good not used
    +
    +  returns:
    +    clears facet->good if not good
    +    sets qh.num_good
    +
    +  notes:
    +    this is like qh_findgood but more restrictive
    +
    +  design:
    +    uses qh_findgood to mark good facets
    +    marks facets for qh.GOODvertex
    +    marks facets for qh.SPLITthreholds
    +*/
    +void qh_findgood_all(qhT *qh, facetT *facetlist) {
    +  facetT *facet, *bestfacet=NULL;
    +  realT angle, bestangle= REALmax;
    +  int  numgood=0, startgood;
    +
    +  if (!qh->GOODvertex && !qh->GOODthreshold && !qh->GOODpoint
    +  && !qh->SPLITthresholds)
    +    return;
    +  if (!qh->ONLYgood)
    +    qh_findgood(qh, qh->facet_list, 0);
    +  FORALLfacet_(facetlist) {
    +    if (facet->good)
    +      numgood++;
    +  }
    +  if (qh->GOODvertex <0 || (qh->GOODvertex > 0 && qh->MERGING)) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good && ((qh->GOODvertex > 0) ^ !!qh_isvertex(qh->GOODvertexp, facet->vertices))) {
    +        if (!--numgood) {
    +          if (qh->ONLYgood) {
    +            qh_fprintf(qh, qh->ferr, 7064, "qhull warning: good vertex p%d does not match last good facet f%d.  Ignored.\n",
    +               qh_pointid(qh, qh->GOODvertexp), facet->id);
    +            return;
    +          }else if (qh->GOODvertex > 0)
    +            qh_fprintf(qh, qh->ferr, 7065, "qhull warning: point p%d is not a vertex('QV%d').\n",
    +                qh->GOODvertex-1, qh->GOODvertex-1);
    +          else
    +            qh_fprintf(qh, qh->ferr, 7066, "qhull warning: point p%d is a vertex for every facet('QV-%d').\n",
    +                -qh->GOODvertex - 1, -qh->GOODvertex - 1);
    +        }
    +        facet->good= False;
    +      }
    +    }
    +  }
    +  startgood= numgood;
    +  if (qh->SPLITthresholds) {
    +    FORALLfacet_(facetlist) {
    +      if (facet->good) {
    +        if (!qh_inthresholds(qh, facet->normal, &angle)) {
    +          facet->good= False;
    +          numgood--;
    +          if (angle < bestangle) {
    +            bestangle= angle;
    +            bestfacet= facet;
    +          }
    +        }
    +      }
    +    }
    +    if (!numgood && bestfacet) {
    +      bestfacet->good= True;
    +      numgood++;
    +      trace0((qh, qh->ferr, 23, "qh_findgood_all: f%d is closest(%2.2g) to thresholds\n",
    +           bestfacet->id, bestangle));
    +      return;
    +    }
    +  }
    +  qh->num_good= numgood;
    +  trace0((qh, qh->ferr, 24, "qh_findgood_all: %d good facets remain out of %d facets\n",
    +        numgood, startgood));
    +} /* findgood_all */
    +
    +/*---------------------------------
    +
    +  qh_furthestnext()
    +    set qh.facet_next to facet with furthest of all furthest points
    +    searches all facets on qh.facet_list
    +
    +  notes:
    +    this may help avoid precision problems
    +*/
    +void qh_furthestnext(qhT *qh /* qh->facet_list */) {
    +  facetT *facet, *bestfacet= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FORALLfacets {
    +    if (facet->outsideset) {
    +#if qh_COMPUTEfurthest
    +      pointT *furthest;
    +      furthest= (pointT*)qh_setlast(facet->outsideset);
    +      zinc_(Zcomputefurthest);
    +      qh_distplane(qh, furthest, facet, &dist);
    +#else
    +      dist= facet->furthestdist;
    +#endif
    +      if (dist > bestdist) {
    +        bestfacet= facet;
    +        bestdist= dist;
    +      }
    +    }
    +  }
    +  if (bestfacet) {
    +    qh_removefacet(qh, bestfacet);
    +    qh_prependfacet(qh, bestfacet, &qh->facet_next);
    +    trace1((qh, qh->ferr, 1029, "qh_furthestnext: made f%d next facet(dist %.2g)\n",
    +            bestfacet->id, bestdist));
    +  }
    +} /* furthestnext */
    +
    +/*---------------------------------
    +
    +  qh_furthestout(qh, facet )
    +    make furthest outside point the last point of outsideset
    +
    +  returns:
    +    updates facet->outsideset
    +    clears facet->notfurthest
    +    sets facet->furthestdist
    +
    +  design:
    +    determine best point of outsideset
    +    make it the last point of outsideset
    +*/
    +void qh_furthestout(qhT *qh, facetT *facet) {
    +  pointT *point, **pointp, *bestpoint= NULL;
    +  realT dist, bestdist= -REALmax;
    +
    +  FOREACHpoint_(facet->outsideset) {
    +    qh_distplane(qh, point, facet, &dist);
    +    zinc_(Zcomputefurthest);
    +    if (dist > bestdist) {
    +      bestpoint= point;
    +      bestdist= dist;
    +    }
    +  }
    +  if (bestpoint) {
    +    qh_setdel(facet->outsideset, point);
    +    qh_setappend(qh, &facet->outsideset, point);
    +#if !qh_COMPUTEfurthest
    +    facet->furthestdist= bestdist;
    +#endif
    +  }
    +  facet->notfurthest= False;
    +  trace3((qh, qh->ferr, 3017, "qh_furthestout: p%d is furthest outside point of f%d\n",
    +          qh_pointid(qh, point), facet->id));
    +} /* furthestout */
    +
    +
    +/*---------------------------------
    +
    +  qh_infiniteloop(qh, facet )
    +    report infinite loop error due to facet
    +*/
    +void qh_infiniteloop(qhT *qh, facetT *facet) {
    +
    +  qh_fprintf(qh, qh->ferr, 6149, "qhull internal error (qh_infiniteloop): potential infinite loop detected\n");
    +  qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +} /* qh_infiniteloop */
    +
    +/*---------------------------------
    +
    +  qh_initbuild()
    +    initialize hull and outside sets with point array
    +    qh.FIRSTpoint/qh.NUMpoints is point array
    +    if qh.GOODpoint
    +      adds qh.GOODpoint to initial hull
    +
    +  returns:
    +    qh_facetlist with initial hull
    +    points partioned into outside sets, coplanar sets, or inside
    +    initializes qh.GOODpointp, qh.GOODvertexp,
    +
    +  design:
    +    initialize global variables used during qh_buildhull
    +    determine precision constants and points with max/min coordinate values
    +      if qh.SCALElast, scale last coordinate(for 'd')
    +    build initial simplex
    +    partition input points into facets of initial simplex
    +    set up lists
    +    if qh.ONLYgood
    +      check consistency
    +      add qh.GOODvertex if defined
    +*/
    +void qh_initbuild(qhT *qh) {
    +  setT *maxpoints, *vertices;
    +  facetT *facet;
    +  int i, numpart;
    +  realT dist;
    +  boolT isoutside;
    +
    +  qh->furthest_id= qh_IDunknown;
    +  qh->lastreport= 0;
    +  qh->facet_id= qh->vertex_id= qh->ridge_id= 0;
    +  qh->visit_id= qh->vertex_visit= 0;
    +  qh->maxoutdone= False;
    +
    +  if (qh->GOODpoint > 0)
    +    qh->GOODpointp= qh_point(qh, qh->GOODpoint-1);
    +  else if (qh->GOODpoint < 0)
    +    qh->GOODpointp= qh_point(qh, -qh->GOODpoint-1);
    +  if (qh->GOODvertex > 0)
    +    qh->GOODvertexp= qh_point(qh, qh->GOODvertex-1);
    +  else if (qh->GOODvertex < 0)
    +    qh->GOODvertexp= qh_point(qh, -qh->GOODvertex-1);
    +  if ((qh->GOODpoint
    +       && (qh->GOODpointp < qh->first_point  /* also catches !GOODpointp */
    +           || qh->GOODpointp > qh_point(qh, qh->num_points-1)))
    +    || (qh->GOODvertex
    +        && (qh->GOODvertexp < qh->first_point  /* also catches !GOODvertexp */
    +            || qh->GOODvertexp > qh_point(qh, qh->num_points-1)))) {
    +    qh_fprintf(qh, qh->ferr, 6150, "qhull input error: either QGn or QVn point is > p%d\n",
    +             qh->num_points-1);
    +    qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +  }
    +  maxpoints= qh_maxmin(qh, qh->first_point, qh->num_points, qh->hull_dim);
    +  if (qh->SCALElast)
    +    qh_scalelast(qh, qh->first_point, qh->num_points, qh->hull_dim,
    +               qh->MINlastcoord, qh->MAXlastcoord, qh->MAXwidth);
    +  qh_detroundoff(qh);
    +  if (qh->DELAUNAY && qh->upper_threshold[qh->hull_dim-1] > REALmax/2
    +                  && qh->lower_threshold[qh->hull_dim-1] < -REALmax/2) {
    +    for (i=qh_PRINTEND; i--; ) {
    +      if (qh->PRINTout[i] == qh_PRINTgeom && qh->DROPdim < 0
    +          && !qh->GOODthreshold && !qh->SPLITthresholds)
    +        break;  /* in this case, don't set upper_threshold */
    +    }
    +    if (i < 0) {
    +      if (qh->UPPERdelaunay) { /* matches qh.upperdelaunay in qh_setfacetplane */
    +        qh->lower_threshold[qh->hull_dim-1]= qh->ANGLEround * qh_ZEROdelaunay;
    +        qh->GOODthreshold= True;
    +      }else {
    +        qh->upper_threshold[qh->hull_dim-1]= -qh->ANGLEround * qh_ZEROdelaunay;
    +        if (!qh->GOODthreshold)
    +          qh->SPLITthresholds= True; /* build upper-convex hull even if Qg */
    +          /* qh_initqhull_globals errors if Qg without Pdk/etc. */
    +      }
    +    }
    +  }
    +  vertices= qh_initialvertices(qh, qh->hull_dim, maxpoints, qh->first_point, qh->num_points);
    +  qh_initialhull(qh, vertices);  /* initial qh->facet_list */
    +  qh_partitionall(qh, vertices, qh->first_point, qh->num_points);
    +  if (qh->PRINToptions1st || qh->TRACElevel || qh->IStracing) {
    +    if (qh->TRACElevel || qh->IStracing)
    +      qh_fprintf(qh, qh->ferr, 8103, "\nTrace level %d for %s | %s\n",
    +         qh->IStracing ? qh->IStracing : qh->TRACElevel, qh->rbox_command, qh->qhull_command);
    +    qh_fprintf(qh, qh->ferr, 8104, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
    +  }
    +  qh_resetlists(qh, False, qh_RESETvisible /*qh.visible_list newvertex_list newfacet_list */);
    +  qh->facet_next= qh->facet_list;
    +  qh_furthestnext(qh /* qh->facet_list */);
    +  if (qh->PREmerge) {
    +    qh->cos_max= qh->premerge_cos;
    +    qh->centrum_radius= qh->premerge_centrum;
    +  }
    +  if (qh->ONLYgood) {
    +    if (qh->GOODvertex > 0 && qh->MERGING) {
    +      qh_fprintf(qh, qh->ferr, 6151, "qhull input error: 'Qg QVn' (only good vertex) does not work with merging.\nUse 'QJ' to joggle the input or 'Q0' to turn off merging.\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (!(qh->GOODthreshold || qh->GOODpoint
    +         || (!qh->MERGEexact && !qh->PREmerge && qh->GOODvertexp))) {
    +      qh_fprintf(qh, qh->ferr, 6152, "qhull input error: 'Qg' (ONLYgood) needs a good threshold('Pd0D0'), a\n\
    +good point(QGn or QG-n), or a good vertex with 'QJ' or 'Q0' (QVn).\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    if (qh->GOODvertex > 0  && !qh->MERGING  /* matches qh_partitionall */
    +        && !qh_isvertex(qh->GOODvertexp, vertices)) {
    +      facet= qh_findbestnew(qh, qh->GOODvertexp, qh->facet_list,
    +                          &dist, !qh_ALL, &isoutside, &numpart);
    +      zadd_(Zdistgood, numpart);
    +      if (!isoutside) {
    +        qh_fprintf(qh, qh->ferr, 6153, "qhull input error: point for QV%d is inside initial simplex.  It can not be made a vertex.\n",
    +               qh_pointid(qh, qh->GOODvertexp));
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +      if (!qh_addpoint(qh, qh->GOODvertexp, facet, False)) {
    +        qh_settempfree(qh, &vertices);
    +        qh_settempfree(qh, &maxpoints);
    +        return;
    +      }
    +    }
    +    qh_findgood(qh, qh->facet_list, 0);
    +  }
    +  qh_settempfree(qh, &vertices);
    +  qh_settempfree(qh, &maxpoints);
    +  trace1((qh, qh->ferr, 1030, "qh_initbuild: initial hull created and points partitioned\n"));
    +} /* initbuild */
    +
    +/*---------------------------------
    +
    +  qh_initialhull(qh, vertices )
    +    constructs the initial hull as a DIM3 simplex of vertices
    +
    +  design:
    +    creates a simplex (initializes lists)
    +    determines orientation of simplex
    +    sets hyperplanes for facets
    +    doubles checks orientation (in case of axis-parallel facets with Gaussian elimination)
    +    checks for flipped facets and qh.NARROWhull
    +    checks the result
    +*/
    +void qh_initialhull(qhT *qh, setT *vertices) {
    +  facetT *facet, *firstfacet, *neighbor, **neighborp;
    +  realT dist, angle, minangle= REALmax;
    +#ifndef qh_NOtrace
    +  int k;
    +#endif
    +
    +  qh_createsimplex(qh, vertices);  /* qh->facet_list */
    +  qh_resetlists(qh, False, qh_RESETvisible);
    +  qh->facet_next= qh->facet_list;      /* advance facet when processed */
    +  qh->interior_point= qh_getcenter(qh, vertices);
    +  firstfacet= qh->facet_list;
    +  qh_setfacetplane(qh, firstfacet);
    +  zinc_(Znumvisibility); /* needs to be in printsummary */
    +  qh_distplane(qh, qh->interior_point, firstfacet, &dist);
    +  if (dist > 0) {
    +    FORALLfacets
    +      facet->toporient ^= (unsigned char)True;
    +  }
    +  FORALLfacets
    +    qh_setfacetplane(qh, facet);
    +  FORALLfacets {
    +    if (!qh_checkflipped(qh, facet, NULL, qh_ALL)) {/* due to axis-parallel facet */
    +      trace1((qh, qh->ferr, 1031, "qh_initialhull: initial orientation incorrect.  Correct all facets\n"));
    +      facet->flipped= False;
    +      FORALLfacets {
    +        facet->toporient ^= (unsigned char)True;
    +        qh_orientoutside(qh, facet);
    +      }
    +      break;
    +    }
    +  }
    +  FORALLfacets {
    +    if (!qh_checkflipped(qh, facet, NULL, !qh_ALL)) {  /* can happen with 'R0.1' */
    +      if (qh->DELAUNAY && ! qh->ATinfinity) {
    +        if (qh->UPPERdelaunay)
    +          qh_fprintf(qh, qh->ferr, 6240, "Qhull precision error: Initial simplex is cocircular or cospherical.  Option 'Qs' searches all points.  Can not compute the upper Delaunay triangulation or upper Voronoi diagram of cocircular/cospherical points.\n");
    +        else
    +          qh_fprintf(qh, qh->ferr, 6239, "Qhull precision error: Initial simplex is cocircular or cospherical.  Use option 'Qz' for the Delaunay triangulation or Voronoi diagram of cocircular/cospherical points.  Option 'Qz' adds a point \"at infinity\".  Use option 'Qs' to search all points for the initial simplex.\n");
    +        qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +      }
    +      qh_precision(qh, "initial simplex is flat");
    +      qh_fprintf(qh, qh->ferr, 6154, "Qhull precision error: Initial simplex is flat (facet %d is coplanar with the interior point)\n",
    +                   facet->id);
    +      qh_errexit(qh, qh_ERRsingular, NULL, NULL);  /* calls qh_printhelp_singular */
    +    }
    +    FOREACHneighbor_(facet) {
    +      angle= qh_getangle(qh, facet->normal, neighbor->normal);
    +      minimize_( minangle, angle);
    +    }
    +  }
    +  if (minangle < qh_MAXnarrow && !qh->NOnarrow) {
    +    realT diff= 1.0 + minangle;
    +
    +    qh->NARROWhull= True;
    +    qh_option(qh, "_narrow-hull", NULL, &diff);
    +    if (minangle < qh_WARNnarrow && !qh->RERUN && qh->PRINTprecision)
    +      qh_printhelp_narrowhull(qh, qh->ferr, minangle);
    +  }
    +  zzval_(Zprocessed)= qh->hull_dim+1;
    +  qh_checkpolygon(qh, qh->facet_list);
    +  qh_checkconvex(qh, qh->facet_list,   qh_DATAfault);
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 1) {
    +    qh_fprintf(qh, qh->ferr, 8105, "qh_initialhull: simplex constructed, interior point:");
    +    for (k=0; k < qh->hull_dim; k++)
    +      qh_fprintf(qh, qh->ferr, 8106, " %6.4g", qh->interior_point[k]);
    +    qh_fprintf(qh, qh->ferr, 8107, "\n");
    +  }
    +#endif
    +} /* initialhull */
    +
    +/*---------------------------------
    +
    +  qh_initialvertices(qh, dim, maxpoints, points, numpoints )
    +    determines a non-singular set of initial vertices
    +    maxpoints may include duplicate points
    +
    +  returns:
    +    temporary set of dim+1 vertices in descending order by vertex id
    +    if qh.RANDOMoutside && !qh.ALLpoints
    +      picks random points
    +    if dim >= qh_INITIALmax,
    +      uses min/max x and max points with non-zero determinants
    +
    +  notes:
    +    unless qh.ALLpoints,
    +      uses maxpoints as long as determinate is non-zero
    +*/
    +setT *qh_initialvertices(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints) {
    +  pointT *point, **pointp;
    +  setT *vertices, *simplex, *tested;
    +  realT randr;
    +  int idx, point_i, point_n, k;
    +  boolT nearzero= False;
    +
    +  vertices= qh_settemp(qh, dim + 1);
    +  simplex= qh_settemp(qh, dim+1);
    +  if (qh->ALLpoints)
    +    qh_maxsimplex(qh, dim, NULL, points, numpoints, &simplex);
    +  else if (qh->RANDOMoutside) {
    +    while (qh_setsize(qh, simplex) != dim+1) {
    +      randr= qh_RANDOMint;
    +      randr= randr/(qh_RANDOMmax+1);
    +      idx= (int)floor(qh->num_points * randr);
    +      while (qh_setin(simplex, qh_point(qh, idx))) {
    +            idx++; /* in case qh_RANDOMint always returns the same value */
    +        idx= idx < qh->num_points ? idx : 0;
    +      }
    +      qh_setappend(qh, &simplex, qh_point(qh, idx));
    +    }
    +  }else if (qh->hull_dim >= qh_INITIALmax) {
    +    tested= qh_settemp(qh, dim+1);
    +    qh_setappend(qh, &simplex, SETfirst_(maxpoints));   /* max and min X coord */
    +    qh_setappend(qh, &simplex, SETsecond_(maxpoints));
    +    qh_maxsimplex(qh, fmin_(qh_INITIALsearch, dim), maxpoints, points, numpoints, &simplex);
    +    k= qh_setsize(qh, simplex);
    +    FOREACHpoint_i_(qh, maxpoints) {
    +      if (point_i & 0x1) {     /* first pick up max. coord. points */
    +        if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +          qh_detsimplex(qh, point, simplex, k, &nearzero);
    +          if (nearzero)
    +            qh_setappend(qh, &tested, point);
    +          else {
    +            qh_setappend(qh, &simplex, point);
    +            if (++k == dim)  /* use search for last point */
    +              break;
    +          }
    +        }
    +      }
    +    }
    +    while (k != dim && (point= (pointT*)qh_setdellast(maxpoints))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(qh, point, simplex, k, &nearzero);
    +        if (nearzero)
    +          qh_setappend(qh, &tested, point);
    +        else {
    +          qh_setappend(qh, &simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    idx= 0;
    +    while (k != dim && (point= qh_point(qh, idx++))) {
    +      if (!qh_setin(simplex, point) && !qh_setin(tested, point)){
    +        qh_detsimplex(qh, point, simplex, k, &nearzero);
    +        if (!nearzero){
    +          qh_setappend(qh, &simplex, point);
    +          k++;
    +        }
    +      }
    +    }
    +    qh_settempfree(qh, &tested);
    +    qh_maxsimplex(qh, dim, maxpoints, points, numpoints, &simplex);
    +  }else
    +    qh_maxsimplex(qh, dim, maxpoints, points, numpoints, &simplex);
    +  FOREACHpoint_(simplex)
    +    qh_setaddnth(qh, &vertices, 0, qh_newvertex(qh, point)); /* descending order */
    +  qh_settempfree(qh, &simplex);
    +  return vertices;
    +} /* initialvertices */
    +
    +
    +/*---------------------------------
    +
    +  qh_isvertex( point, vertices )
    +    returns vertex if point is in vertex set, else returns NULL
    +
    +  notes:
    +    for qh.GOODvertex
    +*/
    +vertexT *qh_isvertex(pointT *point, setT *vertices) {
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (vertex->point == point)
    +      return vertex;
    +  }
    +  return NULL;
    +} /* isvertex */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacets(qh, point )
    +    make new facets from point and qh.visible_list
    +
    +  returns:
    +    qh.newfacet_list= list of new facets with hyperplanes and ->newfacet
    +    qh.newvertex_list= list of vertices in new facets with ->newlist set
    +
    +    if (qh.ONLYgood)
    +      newfacets reference horizon facets, but not vice versa
    +      ridges reference non-simplicial horizon ridges, but not vice versa
    +      does not change existing facets
    +    else
    +      sets qh.NEWfacets
    +      new facets attached to horizon facets and ridges
    +      for visible facets,
    +        visible->r.replace is corresponding new facet
    +
    +  see also:
    +    qh_makenewplanes() -- make hyperplanes for facets
    +    qh_attachnewfacets() -- attachnewfacets if not done here(qh->ONLYgood)
    +    qh_matchnewfacets() -- match up neighbors
    +    qh_updatevertices() -- update vertex neighbors and delvertices
    +    qh_deletevisible() -- delete visible facets
    +    qh_checkpolygon() --check the result
    +    qh_triangulate() -- triangulate a non-simplicial facet
    +
    +  design:
    +    for each visible facet
    +      make new facets to its horizon facets
    +      update its f.replace
    +      clear its neighbor set
    +*/
    +vertexT *qh_makenewfacets(qhT *qh, pointT *point /*visible_list*/) {
    +  facetT *visible, *newfacet= NULL, *newfacet2= NULL, *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  qh->newfacet_list= qh->facet_tail;
    +  qh->newvertex_list= qh->vertex_tail;
    +  apex= qh_newvertex(qh, point);
    +  qh_appendvertex(qh, apex);
    +  qh->visit_id++;
    +  if (!qh->ONLYgood)
    +    qh->NEWfacets= True;
    +  FORALLvisible_facets {
    +    FOREACHneighbor_(visible)
    +      neighbor->seen= False;
    +    if (visible->ridges) {
    +      visible->visitid= qh->visit_id;
    +      newfacet2= qh_makenew_nonsimplicial(qh, visible, apex, &numnew);
    +    }
    +    if (visible->simplicial)
    +      newfacet= qh_makenew_simplicial(qh, visible, apex, &numnew);
    +    if (!qh->ONLYgood) {
    +      if (newfacet2)  /* newfacet is null if all ridges defined */
    +        newfacet= newfacet2;
    +      if (newfacet)
    +        visible->f.replace= newfacet;
    +      else
    +        zinc_(Zinsidevisible);
    +      SETfirst_(visible->neighbors)= NULL;
    +    }
    +  }
    +  trace1((qh, qh->ferr, 1032, "qh_makenewfacets: created %d new facets from point p%d to horizon\n",
    +          numnew, qh_pointid(qh, point)));
    +  if (qh->IStracing >= 4)
    +    qh_printfacetlist(qh, qh->newfacet_list, NULL, qh_ALL);
    +  return apex;
    +} /* makenewfacets */
    +
    +/*---------------------------------
    +
    +  qh_matchduplicates(qh, atfacet, atskip, hashsize, hashcount )
    +    match duplicate ridges in qh.hash_table for atfacet/atskip
    +    duplicates marked with ->dupridge and qh_DUPLICATEridge
    +
    +  returns:
    +    picks match with worst merge (min distance apart)
    +    updates hashcount
    +
    +  see also:
    +    qh_matchneighbor
    +
    +  notes:
    +
    +  design:
    +    compute hash value for atfacet and atskip
    +    repeat twice -- once to make best matches, once to match the rest
    +      for each possible facet in qh.hash_table
    +        if it is a matching facet and pass 2
    +          make match
    +          unless tricoplanar, mark match for merging (qh_MERGEridge)
    +          [e.g., tricoplanar RBOX s 1000 t993602376 | QHULL C-1e-3 d Qbb FA Qt]
    +        if it is a matching facet and pass 1
    +          test if this is a better match
    +      if pass 1,
    +        make best match (it will not be merged)
    +*/
    +#ifndef qh_NOmerge
    +void qh_matchduplicates(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *newfacet, *maxmatch= NULL, *maxmatch2= NULL, *nextfacet;
    +  int skip, newskip, nextskip= 0, maxskip= 0, maxskip2= 0, makematch;
    +  realT maxdist= -REALmax, mindist, dist2, low, high;
    +
    +  hash= qh_gethash(qh, hashsize, atfacet->vertices, qh->hull_dim, 1,
    +                     SETelem_(atfacet->vertices, atskip));
    +  trace2((qh, qh->ferr, 2046, "qh_matchduplicates: find duplicate matches for f%d skip %d hash %d hashcount %d\n",
    +          atfacet->id, atskip, hash, *hashcount));
    +  for (makematch= 0; makematch < 2; makematch++) {
    +    qh->visit_id++;
    +    for (newfacet= atfacet, newskip= atskip; newfacet; newfacet= nextfacet, newskip= nextskip) {
    +      zinc_(Zhashlookup);
    +      nextfacet= NULL;
    +      newfacet->visitid= qh->visit_id;
    +      for (scan= hash; (facet= SETelemt_(qh->hash_table, scan, facetT));
    +           scan= (++scan >= hashsize ? 0 : scan)) {
    +        if (!facet->dupridge || facet->visitid == qh->visit_id)
    +          continue;
    +        zinc_(Zhashtests);
    +        if (qh_matchvertices(qh, 1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +          ismatch= (same == (boolT)(newfacet->toporient ^ facet->toporient));
    +          if (SETelemt_(facet->neighbors, skip, facetT) != qh_DUPLICATEridge) {
    +            if (!makematch) {
    +              qh_fprintf(qh, qh->ferr, 6155, "qhull internal error (qh_matchduplicates): missing dupridge at f%d skip %d for new f%d skip %d hash %d\n",
    +                     facet->id, skip, newfacet->id, newskip, hash);
    +              qh_errexit2(qh, qh_ERRqhull, facet, newfacet);
    +            }
    +          }else if (ismatch && makematch) {
    +            if (SETelemt_(newfacet->neighbors, newskip, facetT) == qh_DUPLICATEridge) {
    +              SETelem_(facet->neighbors, skip)= newfacet;
    +              if (newfacet->tricoplanar)
    +                SETelem_(newfacet->neighbors, newskip)= facet;
    +              else
    +                SETelem_(newfacet->neighbors, newskip)= qh_MERGEridge;
    +              *hashcount -= 2; /* removed two unmatched facets */
    +              trace4((qh, qh->ferr, 4059, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d merge\n",
    +                    facet->id, skip, newfacet->id, newskip));
    +            }
    +          }else if (ismatch) {
    +            mindist= qh_getdistance(qh, facet, newfacet, &low, &high);
    +            dist2= qh_getdistance(qh, newfacet, facet, &low, &high);
    +            minimize_(mindist, dist2);
    +            if (mindist > maxdist) {
    +              maxdist= mindist;
    +              maxmatch= facet;
    +              maxskip= skip;
    +              maxmatch2= newfacet;
    +              maxskip2= newskip;
    +            }
    +            trace3((qh, qh->ferr, 3018, "qh_matchduplicates: duplicate f%d skip %d new f%d skip %d at dist %2.2g, max is now f%d f%d\n",
    +                    facet->id, skip, newfacet->id, newskip, mindist,
    +                    maxmatch->id, maxmatch2->id));
    +          }else { /* !ismatch */
    +            nextfacet= facet;
    +            nextskip= skip;
    +          }
    +        }
    +        if (makematch && !facet
    +        && SETelemt_(facet->neighbors, skip, facetT) == qh_DUPLICATEridge) {
    +          qh_fprintf(qh, qh->ferr, 6156, "qhull internal error (qh_matchduplicates): no MERGEridge match for duplicate f%d skip %d at hash %d\n",
    +                     newfacet->id, newskip, hash);
    +          qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
    +        }
    +      }
    +    } /* end of for each new facet at hash */
    +    if (!makematch) {
    +      if (!maxmatch) {
    +        qh_fprintf(qh, qh->ferr, 6157, "qhull internal error (qh_matchduplicates): no maximum match at duplicate f%d skip %d at hash %d\n",
    +                     atfacet->id, atskip, hash);
    +        qh_errexit(qh, qh_ERRqhull, atfacet, NULL);
    +      }
    +      SETelem_(maxmatch->neighbors, maxskip)= maxmatch2; /* maxmatch!=0 by QH6157 */
    +      SETelem_(maxmatch2->neighbors, maxskip2)= maxmatch;
    +      *hashcount -= 2; /* removed two unmatched facets */
    +      zzinc_(Zmultiridge);
    +      trace0((qh, qh->ferr, 25, "qh_matchduplicates: duplicate f%d skip %d matched with new f%d skip %d keep\n",
    +              maxmatch->id, maxskip, maxmatch2->id, maxskip2));
    +      qh_precision(qh, "ridge with multiple neighbors");
    +      if (qh->IStracing >= 4)
    +        qh_errprint(qh, "DUPLICATED/MATCH", maxmatch, maxmatch2, NULL, NULL);
    +    }
    +  }
    +} /* matchduplicates */
    +
    +/*---------------------------------
    +
    +  qh_nearcoplanar()
    +    for all facets, remove near-inside points from facet->coplanarset
    +    coplanar points defined by innerplane from qh_outerinner()
    +
    +  returns:
    +    if qh->KEEPcoplanar && !qh->KEEPinside
    +      facet->coplanarset only contains coplanar points
    +    if qh.JOGGLEmax
    +      drops inner plane by another qh.JOGGLEmax diagonal since a
    +        vertex could shift out while a coplanar point shifts in
    +
    +  notes:
    +    used for qh.PREmerge and qh.JOGGLEmax
    +    must agree with computation of qh.NEARcoplanar in qh_detroundoff(qh)
    +  design:
    +    if not keeping coplanar or inside points
    +      free all coplanar sets
    +    else if not keeping both coplanar and inside points
    +      remove !coplanar or !inside points from coplanar sets
    +*/
    +void qh_nearcoplanar(qhT *qh /* qh.facet_list */) {
    +  facetT *facet;
    +  pointT *point, **pointp;
    +  int numpart;
    +  realT dist, innerplane;
    +
    +  if (!qh->KEEPcoplanar && !qh->KEEPinside) {
    +    FORALLfacets {
    +      if (facet->coplanarset)
    +        qh_setfree(qh, &facet->coplanarset);
    +    }
    +  }else if (!qh->KEEPcoplanar || !qh->KEEPinside) {
    +    qh_outerinner(qh, NULL, NULL, &innerplane);
    +    if (qh->JOGGLEmax < REALmax/2)
    +      innerplane -= qh->JOGGLEmax * sqrt((realT)qh->hull_dim);
    +    numpart= 0;
    +    FORALLfacets {
    +      if (facet->coplanarset) {
    +        FOREACHpoint_(facet->coplanarset) {
    +          numpart++;
    +          qh_distplane(qh, point, facet, &dist);
    +          if (dist < innerplane) {
    +            if (!qh->KEEPinside)
    +              SETref_(point)= NULL;
    +          }else if (!qh->KEEPcoplanar)
    +            SETref_(point)= NULL;
    +        }
    +        qh_setcompact(qh, facet->coplanarset);
    +      }
    +    }
    +    zzadd_(Zcheckpart, numpart);
    +  }
    +} /* nearcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_nearvertex(qh, facet, point, bestdist )
    +    return nearest vertex in facet to point
    +
    +  returns:
    +    vertex and its distance
    +
    +  notes:
    +    if qh.DELAUNAY
    +      distance is measured in the input set
    +    searches neighboring tricoplanar facets (requires vertexneighbors)
    +      Slow implementation.  Recomputes vertex set for each point.
    +    The vertex set could be stored in the qh.keepcentrum facet.
    +*/
    +vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp) {
    +  realT bestdist= REALmax, dist;
    +  vertexT *bestvertex= NULL, *vertex, **vertexp, *apex;
    +  coordT *center;
    +  facetT *neighbor, **neighborp;
    +  setT *vertices;
    +  int dim= qh->hull_dim;
    +
    +  if (qh->DELAUNAY)
    +    dim--;
    +  if (facet->tricoplanar) {
    +    if (!qh->VERTEXneighbors || !facet->center) {
    +      qh_fprintf(qh, qh->ferr, 6158, "qhull internal error (qh_nearvertex): qh.VERTEXneighbors and facet->center required for tricoplanar facets\n");
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +    }
    +    vertices= qh_settemp(qh, qh->TEMPsize);
    +    apex= SETfirstt_(facet->vertices, vertexT);
    +    center= facet->center;
    +    FOREACHneighbor_(apex) {
    +      if (neighbor->center == center) {
    +        FOREACHvertex_(neighbor->vertices)
    +          qh_setappend(qh, &vertices, vertex);
    +      }
    +    }
    +  }else
    +    vertices= facet->vertices;
    +  FOREACHvertex_(vertices) {
    +    dist= qh_pointdist(vertex->point, point, -dim);
    +    if (dist < bestdist) {
    +      bestdist= dist;
    +      bestvertex= vertex;
    +    }
    +  }
    +  if (facet->tricoplanar)
    +    qh_settempfree(qh, &vertices);
    +  *bestdistp= sqrt(bestdist);
    +  if (!bestvertex) {
    +      qh_fprintf(qh, qh->ferr, 6261, "qhull internal error (qh_nearvertex): did not find bestvertex for f%d p%d\n", facet->id, qh_pointid(qh, point));
    +      qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  trace3((qh, qh->ferr, 3019, "qh_nearvertex: v%d dist %2.2g for f%d p%d\n",
    +        bestvertex->id, *bestdistp, facet->id, qh_pointid(qh, point))); /* bestvertex!=0 by QH2161 */
    +  return bestvertex;
    +} /* nearvertex */
    +
    +/*---------------------------------
    +
    +  qh_newhashtable(qh, newsize )
    +    returns size of qh.hash_table of at least newsize slots
    +
    +  notes:
    +    assumes qh.hash_table is NULL
    +    qh_HASHfactor determines the number of extra slots
    +    size is not divisible by 2, 3, or 5
    +*/
    +int qh_newhashtable(qhT *qh, int newsize) {
    +  int size;
    +
    +  size= ((newsize+1)*qh_HASHfactor) | 0x1;  /* odd number */
    +  while (True) {
    +    if (newsize<0 || size<0) {
    +        qh_fprintf(qh, qh->qhmem.ferr, 6236, "qhull error (qh_newhashtable): negative request (%d) or size (%d).  Did int overflow due to high-D?\n", newsize, size); /* WARN64 */
    +        qh_errexit(qh, qhmem_ERRmem, NULL, NULL);
    +    }
    +    if ((size%3) && (size%5))
    +      break;
    +    size += 2;
    +    /* loop terminates because there is an infinite number of primes */
    +  }
    +  qh->hash_table= qh_setnew(qh, size);
    +  qh_setzero(qh, qh->hash_table, 0, size);
    +  return size;
    +} /* newhashtable */
    +
    +/*---------------------------------
    +
    +  qh_newvertex(qh, point )
    +    returns a new vertex for point
    +*/
    +vertexT *qh_newvertex(qhT *qh, pointT *point) {
    +  vertexT *vertex;
    +
    +  zinc_(Ztotvertices);
    +  vertex= (vertexT *)qh_memalloc(qh, (int)sizeof(vertexT));
    +  memset((char *) vertex, (size_t)0, sizeof(vertexT));
    +  if (qh->vertex_id == UINT_MAX) {
    +    qh_memfree(qh, vertex, (int)sizeof(vertexT));
    +    qh_fprintf(qh, qh->ferr, 6159, "qhull error: more than 2^32 vertices.  vertexT.id field overflows.  Vertices would not be sorted correctly.\n");
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  if (qh->vertex_id == qh->tracevertex_id)
    +    qh->tracevertex= vertex;
    +  vertex->id= qh->vertex_id++;
    +  vertex->point= point;
    +  trace4((qh, qh->ferr, 4060, "qh_newvertex: vertex p%d(v%d) created\n", qh_pointid(qh, vertex->point),
    +          vertex->id));
    +  return(vertex);
    +} /* newvertex */
    +
    +/*---------------------------------
    +
    +  qh_nextridge3d( atridge, facet, vertex )
    +    return next ridge and vertex for a 3d facet
    +    returns NULL on error
    +    [for QhullFacet::nextRidge3d] Does not call qh_errexit nor access qhT.
    +
    +  notes:
    +    in qh_ORIENTclock order
    +    this is a O(n^2) implementation to trace all ridges
    +    be sure to stop on any 2nd visit
    +    same as QhullRidge::nextRidge3d
    +    does not use qhT or qh_errexit [QhullFacet.cpp]
    +
    +  design:
    +    for each ridge
    +      exit if it is the ridge after atridge
    +*/
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +  vertexT *atvertex, *vertex, *othervertex;
    +  ridgeT *ridge, **ridgep;
    +
    +  if ((atridge->top == facet) ^ qh_ORIENTclock)
    +    atvertex= SETsecondt_(atridge->vertices, vertexT);
    +  else
    +    atvertex= SETfirstt_(atridge->vertices, vertexT);
    +  FOREACHridge_(facet->ridges) {
    +    if (ridge == atridge)
    +      continue;
    +    if ((ridge->top == facet) ^ qh_ORIENTclock) {
    +      othervertex= SETsecondt_(ridge->vertices, vertexT);
    +      vertex= SETfirstt_(ridge->vertices, vertexT);
    +    }else {
    +      vertex= SETsecondt_(ridge->vertices, vertexT);
    +      othervertex= SETfirstt_(ridge->vertices, vertexT);
    +    }
    +    if (vertex == atvertex) {
    +      if (vertexp)
    +        *vertexp= othervertex;
    +      return ridge;
    +    }
    +  }
    +  return NULL;
    +} /* nextridge3d */
    +#else /* qh_NOmerge */
    +void qh_matchduplicates(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount) {
    +}
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp) {
    +
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_outcoplanar()
    +    move points from all facets' outsidesets to their coplanarsets
    +
    +  notes:
    +    for post-processing under qh.NARROWhull
    +
    +  design:
    +    for each facet
    +      for each outside point for facet
    +        partition point into coplanar set
    +*/
    +void qh_outcoplanar(qhT *qh /* facet_list */) {
    +  pointT *point, **pointp;
    +  facetT *facet;
    +  realT dist;
    +
    +  trace1((qh, qh->ferr, 1033, "qh_outcoplanar: move outsideset to coplanarset for qh->NARROWhull\n"));
    +  FORALLfacets {
    +    FOREACHpoint_(facet->outsideset) {
    +      qh->num_outside--;
    +      if (qh->KEEPcoplanar || qh->KEEPnearinside) {
    +        qh_distplane(qh, point, facet, &dist);
    +        zinc_(Zpartition);
    +        qh_partitioncoplanar(qh, point, facet, &dist);
    +      }
    +    }
    +    qh_setfree(qh, &facet->outsideset);
    +  }
    +} /* outcoplanar */
    +
    +/*---------------------------------
    +
    +  qh_point(qh, id )
    +    return point for a point id, or NULL if unknown
    +
    +  alternative code:
    +    return((pointT *)((unsigned   long)qh.first_point
    +           + (unsigned long)((id)*qh.normal_size)));
    +*/
    +pointT *qh_point(qhT *qh, int id) {
    +
    +  if (id < 0)
    +    return NULL;
    +  if (id < qh->num_points)
    +    return qh->first_point + id * qh->hull_dim;
    +  id -= qh->num_points;
    +  if (id < qh_setsize(qh, qh->other_points))
    +    return SETelemt_(qh->other_points, id, pointT);
    +  return NULL;
    +} /* point */
    +
    +/*---------------------------------
    +
    +  qh_point_add(qh, set, point, elem )
    +    stores elem at set[point.id]
    +
    +  returns:
    +    access function for qh_pointfacet and qh_pointvertex
    +
    +  notes:
    +    checks point.id
    +*/
    +void qh_point_add(qhT *qh, setT *set, pointT *point, void *elem) {
    +  int id, size;
    +
    +  SETreturnsize_(set, size);
    +  if ((id= qh_pointid(qh, point)) < 0)
    +    qh_fprintf(qh, qh->ferr, 7067, "qhull internal warning (point_add): unknown point %p id %d\n",
    +      point, id);
    +  else if (id >= size) {
    +    qh_fprintf(qh, qh->ferr, 6160, "qhull internal errror(point_add): point p%d is out of bounds(%d)\n",
    +             id, size);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }else
    +    SETelem_(set, id)= elem;
    +} /* point_add */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointfacet()
    +    return temporary set of facet for each point
    +    the set is indexed by point id
    +
    +  notes:
    +    vertices assigned to one of the facets
    +    coplanarset assigned to the facet
    +    outside set assigned to the facet
    +    NULL if no facet for point (inside)
    +      includes qh.GOODpointp
    +
    +  access:
    +    FOREACHfacet_i_(qh, facets) { ... }
    +    SETelem_(facets, i)
    +
    +  design:
    +    for each facet
    +      add each vertex
    +      add each coplanar point
    +      add each outside point
    +*/
    +setT *qh_pointfacet(qhT *qh /*qh.facet_list*/) {
    +  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  setT *facets;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  pointT *point, **pointp;
    +
    +  facets= qh_settemp(qh, numpoints);
    +  qh_setzero(qh, facets, 0, numpoints);
    +  qh->vertex_visit++;
    +  FORALLfacets {
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        vertex->visitid= qh->vertex_visit;
    +        qh_point_add(qh, facets, vertex->point, facet);
    +      }
    +    }
    +    FOREACHpoint_(facet->coplanarset)
    +      qh_point_add(qh, facets, point, facet);
    +    FOREACHpoint_(facet->outsideset)
    +      qh_point_add(qh, facets, point, facet);
    +  }
    +  return facets;
    +} /* pointfacet */
    +
    +/*---------------------------------
    +
    +  qh_pointvertex(qh, )
    +    return temporary set of vertices indexed by point id
    +    entry is NULL if no vertex for a point
    +      this will include qh.GOODpointp
    +
    +  access:
    +    FOREACHvertex_i_(qh, vertices) { ... }
    +    SETelem_(vertices, i)
    +*/
    +setT *qh_pointvertex(qhT *qh /*qh.facet_list*/) {
    +  int numpoints= qh->num_points + qh_setsize(qh, qh->other_points);
    +  setT *vertices;
    +  vertexT *vertex;
    +
    +  vertices= qh_settemp(qh, numpoints);
    +  qh_setzero(qh, vertices, 0, numpoints);
    +  FORALLvertices
    +    qh_point_add(qh, vertices, vertex->point, vertex);
    +  return vertices;
    +} /* pointvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_prependfacet(qh, facet, facetlist )
    +    prepend facet to the start of a facetlist
    +
    +  returns:
    +    increments qh.numfacets
    +    updates facetlist, qh.facet_list, facet_next
    +
    +  notes:
    +    be careful of prepending since it can lose a pointer.
    +      e.g., can lose _next by deleting and then prepending before _next
    +*/
    +void qh_prependfacet(qhT *qh, facetT *facet, facetT **facetlist) {
    +  facetT *prevfacet, *list;
    +
    +
    +  trace4((qh, qh->ferr, 4061, "qh_prependfacet: prepend f%d before f%d\n",
    +          facet->id, getid_(*facetlist)));
    +  if (!*facetlist)
    +    (*facetlist)= qh->facet_tail;
    +  list= *facetlist;
    +  prevfacet= list->previous;
    +  facet->previous= prevfacet;
    +  if (prevfacet)
    +    prevfacet->next= facet;
    +  list->previous= facet;
    +  facet->next= *facetlist;
    +  if (qh->facet_list == list)  /* this may change *facetlist */
    +    qh->facet_list= facet;
    +  if (qh->facet_next == list)
    +    qh->facet_next= facet;
    +  *facetlist= facet;
    +  qh->num_facets++;
    +} /* prependfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhashtable(qh, fp )
    +    print hash table to fp
    +
    +  notes:
    +    not in I/O to avoid bringing io_r.c in
    +
    +  design:
    +    for each hash entry
    +      if defined
    +        if unmatched or will merge (NULL, qh_MERGEridge, qh_DUPLICATEridge)
    +          print entry and neighbors
    +*/
    +void qh_printhashtable(qhT *qh, FILE *fp) {
    +  facetT *facet, *neighbor;
    +  int id, facet_i, facet_n, neighbor_i= 0, neighbor_n= 0;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHfacet_i_(qh, qh->hash_table) {
    +    if (facet) {
    +      FOREACHneighbor_i_(qh, facet) {
    +        if (!neighbor || neighbor == qh_MERGEridge || neighbor == qh_DUPLICATEridge)
    +          break;
    +      }
    +      if (neighbor_i == neighbor_n)
    +        continue;
    +      qh_fprintf(qh, fp, 9283, "hash %d f%d ", facet_i, facet->id);
    +      FOREACHvertex_(facet->vertices)
    +        qh_fprintf(qh, fp, 9284, "v%d ", vertex->id);
    +      qh_fprintf(qh, fp, 9285, "\n neighbors:");
    +      FOREACHneighbor_i_(qh, facet) {
    +        if (neighbor == qh_MERGEridge)
    +          id= -3;
    +        else if (neighbor == qh_DUPLICATEridge)
    +          id= -2;
    +        else
    +          id= getid_(neighbor);
    +        qh_fprintf(qh, fp, 9286, " %d", id);
    +      }
    +      qh_fprintf(qh, fp, 9287, "\n");
    +    }
    +  }
    +} /* printhashtable */
    +
    +
    +/*---------------------------------
    +
    +  qh_printlists(qh, fp )
    +    print out facet and vertex list for debugging (without 'f/v' tags)
    +*/
    +void qh_printlists(qhT *qh) {
    +  facetT *facet;
    +  vertexT *vertex;
    +  int count= 0;
    +
    +  qh_fprintf(qh, qh->ferr, 8108, "qh_printlists: facets:");
    +  FORALLfacets {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh, qh->ferr, 8109, "\n     ");
    +    qh_fprintf(qh, qh->ferr, 8110, " %d", facet->id);
    +  }
    +  qh_fprintf(qh, qh->ferr, 8111, "\n  new facets %d visible facets %d next facet for qh_addpoint %d\n  vertices(new %d):",
    +     getid_(qh->newfacet_list), getid_(qh->visible_list), getid_(qh->facet_next),
    +     getid_(qh->newvertex_list));
    +  count = 0;
    +  FORALLvertices {
    +    if (++count % 100 == 0)
    +      qh_fprintf(qh, qh->ferr, 8112, "\n     ");
    +    qh_fprintf(qh, qh->ferr, 8113, " %d", vertex->id);
    +  }
    +  qh_fprintf(qh, qh->ferr, 8114, "\n");
    +} /* printlists */
    +
    +/*---------------------------------
    +
    +  qh_resetlists(qh, stats, qh_RESETvisible )
    +    reset newvertex_list, newfacet_list, visible_list
    +    if stats,
    +      maintains statistics
    +
    +  returns:
    +    visible_list is empty if qh_deletevisible was called
    +*/
    +void qh_resetlists(qhT *qh, boolT stats, boolT resetVisible /*qh.newvertex_list newfacet_list visible_list*/) {
    +  vertexT *vertex;
    +  facetT *newfacet, *visible;
    +  int totnew=0, totver=0;
    +
    +  if (stats) {
    +    FORALLvertex_(qh->newvertex_list)
    +      totver++;
    +    FORALLnew_facets
    +      totnew++;
    +    zadd_(Zvisvertextot, totver);
    +    zmax_(Zvisvertexmax, totver);
    +    zadd_(Znewfacettot, totnew);
    +    zmax_(Znewfacetmax, totnew);
    +  }
    +  FORALLvertex_(qh->newvertex_list)
    +    vertex->newlist= False;
    +  qh->newvertex_list= NULL;
    +  FORALLnew_facets
    +    newfacet->newfacet= False;
    +  qh->newfacet_list= NULL;
    +  if (resetVisible) {
    +    FORALLvisible_facets {
    +      visible->f.replace= NULL;
    +      visible->visible= False;
    +    }
    +    qh->num_visible= 0;
    +  }
    +  qh->visible_list= NULL; /* may still have visible facets via qh_triangulate */
    +  qh->NEWfacets= False;
    +} /* resetlists */
    +
    +/*---------------------------------
    +
    +  qh_setvoronoi_all(qh)
    +    compute Voronoi centers for all facets
    +    includes upperDelaunay facets if qh.UPPERdelaunay ('Qu')
    +
    +  returns:
    +    facet->center is the Voronoi center
    +
    +  notes:
    +    this is unused/untested code
    +      please email bradb@shore.net if this works ok for you
    +
    +  use:
    +    FORALLvertices {...} to locate the vertex for a point.
    +    FOREACHneighbor_(vertex) {...} to visit the Voronoi centers for a Voronoi cell.
    +*/
    +void qh_setvoronoi_all(qhT *qh) {
    +  facetT *facet;
    +
    +  qh_clearcenters(qh, qh_ASvoronoi);
    +  qh_vertexneighbors(qh);
    +
    +  FORALLfacets {
    +    if (!facet->normal || !facet->upperdelaunay || qh->UPPERdelaunay) {
    +      if (!facet->center)
    +        facet->center= qh_facetcenter(qh, facet->vertices);
    +    }
    +  }
    +} /* setvoronoi_all */
    +
    +#ifndef qh_NOmerge
    +
    +/*---------------------------------
    +
    +  qh_triangulate()
    +    triangulate non-simplicial facets on qh.facet_list,
    +    if qh->VORONOI, sets Voronoi centers of non-simplicial facets
    +    nop if hasTriangulation
    +
    +  returns:
    +    all facets simplicial
    +    each tricoplanar facet has ->f.triowner == owner of ->center,normal,etc.
    +
    +  notes:
    +    call after qh_check_output since may switch to Voronoi centers
    +    Output may overwrite ->f.triowner with ->f.area
    +*/
    +void qh_triangulate(qhT *qh /*qh.facet_list*/) {
    +  facetT *facet, *nextfacet, *owner;
    +  int onlygood= qh->ONLYgood;
    +  facetT *neighbor, *visible= NULL, *facet1, *facet2, *new_facet_list= NULL;
    +  facetT *orig_neighbor= NULL, *otherfacet;
    +  vertexT *new_vertex_list= NULL;
    +  mergeT *merge;
    +  mergeType mergetype;
    +  int neighbor_i, neighbor_n;
    +
    +  if (qh->hasTriangulation)
    +      return;
    +  trace1((qh, qh->ferr, 1034, "qh_triangulate: triangulate non-simplicial facets\n"));
    +  if (qh->hull_dim == 2)
    +    return;
    +  if (qh->VORONOI) {  /* otherwise lose Voronoi centers [could rebuild vertex set from tricoplanar] */
    +    qh_clearcenters(qh, qh_ASvoronoi);
    +    qh_vertexneighbors(qh);
    +  }
    +  qh->ONLYgood= False; /* for makenew_nonsimplicial */
    +  qh->visit_id++;
    +  qh->NEWfacets= True;
    +  qh->degen_mergeset= qh_settemp(qh, qh->TEMPsize);
    +  qh->newvertex_list= qh->vertex_tail;
    +  for (facet= qh->facet_list; facet && facet->next; facet= nextfacet) { /* non-simplicial facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible || facet->simplicial)
    +      continue;
    +    /* triangulate all non-simplicial facets, otherwise merging does not work, e.g., RBOX c P-0.1 P+0.1 P+0.1 D3 | QHULL d Qt Tv */
    +    if (!new_facet_list)
    +      new_facet_list= facet;  /* will be moved to end */
    +    qh_triangulate_facet(qh, facet, &new_vertex_list);
    +  }
    +  trace2((qh, qh->ferr, 2047, "qh_triangulate: delete null facets from f%d -- apex same as second vertex\n", getid_(new_facet_list)));
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* null facets moved to end */
    +    nextfacet= facet->next;
    +    if (facet->visible)
    +      continue;
    +    if (facet->ridges) {
    +      if (qh_setsize(qh, facet->ridges) > 0) {
    +        qh_fprintf(qh, qh->ferr, 6161, "qhull error (qh_triangulate): ridges still defined for f%d\n", facet->id);
    +        qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +      }
    +      qh_setfree(qh, &facet->ridges);
    +    }
    +    if (SETfirst_(facet->vertices) == SETsecond_(facet->vertices)) {
    +      zinc_(Ztrinull);
    +      qh_triangulate_null(qh, facet);
    +    }
    +  }
    +  trace2((qh, qh->ferr, 2048, "qh_triangulate: delete %d or more mirror facets -- same vertices and neighbors\n", qh_setsize(qh, qh->degen_mergeset)));
    +  qh->visible_list= qh->facet_tail;
    +  while ((merge= (mergeT*)qh_setdellast(qh->degen_mergeset))) {
    +    facet1= merge->facet1;
    +    facet2= merge->facet2;
    +    mergetype= merge->type;
    +    qh_memfree(qh, merge, (int)sizeof(mergeT));
    +    if (mergetype == MRGmirror) {
    +      zinc_(Ztrimirror);
    +      qh_triangulate_mirror(qh, facet1, facet2);
    +    }
    +  }
    +  qh_settempfree(qh, &qh->degen_mergeset);
    +  trace2((qh, qh->ferr, 2049, "qh_triangulate: update neighbor lists for vertices from v%d\n", getid_(new_vertex_list)));
    +  qh->newvertex_list= new_vertex_list;  /* all vertices of new facets */
    +  qh->visible_list= NULL;
    +  qh_updatevertices(qh /*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +  qh_resetlists(qh, False, !qh_RESETvisible /*qh.newvertex_list, empty newfacet_list and visible_list*/);
    +
    +  trace2((qh, qh->ferr, 2050, "qh_triangulate: identify degenerate tricoplanar facets from f%d\n", getid_(new_facet_list)));
    +  trace2((qh, qh->ferr, 2051, "qh_triangulate: and replace facet->f.triowner with tricoplanar facets that own center, normal, etc.\n"));
    +  FORALLfacet_(new_facet_list) {
    +    if (facet->tricoplanar && !facet->visible) {
    +      FOREACHneighbor_i_(qh, facet) {
    +        if (neighbor_i == 0) {  /* first iteration */
    +          if (neighbor->tricoplanar)
    +            orig_neighbor= neighbor->f.triowner;
    +          else
    +            orig_neighbor= neighbor;
    +        }else {
    +          if (neighbor->tricoplanar)
    +            otherfacet= neighbor->f.triowner;
    +          else
    +            otherfacet= neighbor;
    +          if (orig_neighbor == otherfacet) {
    +            zinc_(Ztridegen);
    +            facet->degenerate= True;
    +            break;
    +          }
    +        }
    +      }
    +    }
    +  }
    +
    +  trace2((qh, qh->ferr, 2052, "qh_triangulate: delete visible facets -- non-simplicial, null, and mirrored facets\n"));
    +  owner= NULL;
    +  visible= NULL;
    +  for (facet= new_facet_list; facet && facet->next; facet= nextfacet) { /* may delete facet */
    +    nextfacet= facet->next;
    +    if (facet->visible) {
    +      if (facet->tricoplanar) { /* a null or mirrored facet */
    +        qh_delfacet(qh, facet);
    +        qh->num_visible--;
    +      }else {  /* a non-simplicial facet followed by its tricoplanars */
    +        if (visible && !owner) {
    +          /*  RBOX 200 s D5 t1001471447 | QHULL Qt C-0.01 Qx Qc Tv Qt -- f4483 had 6 vertices/neighbors and 8 ridges */
    +          trace2((qh, qh->ferr, 2053, "qh_triangulate: all tricoplanar facets degenerate for non-simplicial facet f%d\n",
    +                       visible->id));
    +          qh_delfacet(qh, visible);
    +          qh->num_visible--;
    +        }
    +        visible= facet;
    +        owner= NULL;
    +      }
    +    }else if (facet->tricoplanar) {
    +      if (facet->f.triowner != visible || visible==NULL) {
    +        qh_fprintf(qh, qh->ferr, 6162, "qhull error (qh_triangulate): tricoplanar facet f%d not owned by its visible, non-simplicial facet f%d\n", facet->id, getid_(visible));
    +        qh_errexit2(qh, qh_ERRqhull, facet, visible);
    +      }
    +      if (owner)
    +        facet->f.triowner= owner;
    +      else if (!facet->degenerate) {
    +        owner= facet;
    +        nextfacet= visible->next; /* rescan tricoplanar facets with owner, visible!=0 by QH6162 */
    +        facet->keepcentrum= True;  /* one facet owns ->normal, etc. */
    +        facet->coplanarset= visible->coplanarset;
    +        facet->outsideset= visible->outsideset;
    +        visible->coplanarset= NULL;
    +        visible->outsideset= NULL;
    +        if (!qh->TRInormals) { /* center and normal copied to tricoplanar facets */
    +          visible->center= NULL;
    +          visible->normal= NULL;
    +        }
    +        qh_delfacet(qh, visible);
    +        qh->num_visible--;
    +      }
    +    }
    +  }
    +  if (visible && !owner) {
    +    trace2((qh, qh->ferr, 2054, "qh_triangulate: all tricoplanar facets degenerate for last non-simplicial facet f%d\n",
    +                 visible->id));
    +    qh_delfacet(qh, visible);
    +    qh->num_visible--;
    +  }
    +  qh->NEWfacets= False;
    +  qh->ONLYgood= onlygood; /* restore value */
    +  if (qh->CHECKfrequently)
    +    qh_checkpolygon(qh, qh->facet_list);
    +  qh->hasTriangulation= True;
    +} /* triangulate */
    +
    +
    +/*---------------------------------
    +
    +  qh_triangulate_facet(qh, facetA, &firstVertex )
    +    triangulate a non-simplicial facet
    +      if qh.CENTERtype=qh_ASvoronoi, sets its Voronoi center
    +  returns:
    +    qh.newfacet_list == simplicial facets
    +      facet->tricoplanar set and ->keepcentrum false
    +      facet->degenerate set if duplicated apex
    +      facet->f.trivisible set to facetA
    +      facet->center copied from facetA (created if qh_ASvoronoi)
    +        qh_eachvoronoi, qh_detvridge, qh_detvridge3 assume centers copied
    +      facet->normal,offset,maxoutside copied from facetA
    +
    +  notes:
    +      only called by qh_triangulate
    +      qh_makenew_nonsimplicial uses neighbor->seen for the same
    +      if qh.TRInormals, newfacet->normal will need qh_free
    +        if qh.TRInormals and qh_AScentrum, newfacet->center will need qh_free
    +        keepcentrum is also set on Zwidefacet in qh_mergefacet
    +        freed by qh_clearcenters
    +
    +  see also:
    +      qh_addpoint() -- add a point
    +      qh_makenewfacets() -- construct a cone of facets for a new vertex
    +
    +  design:
    +      if qh_ASvoronoi,
    +         compute Voronoi center (facet->center)
    +      select first vertex (highest ID to preserve ID ordering of ->vertices)
    +      triangulate from vertex to ridges
    +      copy facet->center, normal, offset
    +      update vertex neighbors
    +*/
    +void qh_triangulate_facet(qhT *qh, facetT *facetA, vertexT **first_vertex) {
    +  facetT *newfacet;
    +  facetT *neighbor, **neighborp;
    +  vertexT *apex;
    +  int numnew=0;
    +
    +  trace3((qh, qh->ferr, 3020, "qh_triangulate_facet: triangulate facet f%d\n", facetA->id));
    +
    +  if (qh->IStracing >= 4)
    +    qh_printfacet(qh, qh->ferr, facetA);
    +  FOREACHneighbor_(facetA) {
    +    neighbor->seen= False;
    +    neighbor->coplanar= False;
    +  }
    +  if (qh->CENTERtype == qh_ASvoronoi && !facetA->center  /* matches upperdelaunay in qh_setfacetplane() */
    +        && fabs_(facetA->normal[qh->hull_dim -1]) >= qh->ANGLEround * qh_ZEROdelaunay) {
    +    facetA->center= qh_facetcenter(qh, facetA->vertices);
    +  }
    +  qh_willdelete(qh, facetA, NULL);
    +  qh->newfacet_list= qh->facet_tail;
    +  facetA->visitid= qh->visit_id;
    +  apex= SETfirstt_(facetA->vertices, vertexT);
    +  qh_makenew_nonsimplicial(qh, facetA, apex, &numnew);
    +  SETfirst_(facetA->neighbors)= NULL;
    +  FORALLnew_facets {
    +    newfacet->tricoplanar= True;
    +    newfacet->f.trivisible= facetA;
    +    newfacet->degenerate= False;
    +    newfacet->upperdelaunay= facetA->upperdelaunay;
    +    newfacet->good= facetA->good;
    +    if (qh->TRInormals) { /* 'Q11' triangulate duplicates ->normal and ->center */
    +      newfacet->keepcentrum= True;
    +      if(facetA->normal){
    +        newfacet->normal= qh_memalloc(qh, qh->normal_size);
    +        memcpy((char *)newfacet->normal, facetA->normal, qh->normal_size);
    +      }
    +      if (qh->CENTERtype == qh_AScentrum)
    +        newfacet->center= qh_getcentrum(qh, newfacet);
    +      else if (qh->CENTERtype == qh_ASvoronoi && facetA->center){
    +        newfacet->center= qh_memalloc(qh, qh->center_size);
    +        memcpy((char *)newfacet->center, facetA->center, qh->center_size);
    +      }
    +    }else {
    +      newfacet->keepcentrum= False;
    +      /* one facet will have keepcentrum=True at end of qh_triangulate */
    +      newfacet->normal= facetA->normal;
    +      newfacet->center= facetA->center;
    +    }
    +    newfacet->offset= facetA->offset;
    +#if qh_MAXoutside
    +    newfacet->maxoutside= facetA->maxoutside;
    +#endif
    +  }
    +  qh_matchnewfacets(qh /*qh.newfacet_list*/);
    +  zinc_(Ztricoplanar);
    +  zadd_(Ztricoplanartot, numnew);
    +  zmax_(Ztricoplanarmax, numnew);
    +  qh->visible_list= NULL;
    +  if (!(*first_vertex))
    +    (*first_vertex)= qh->newvertex_list;
    +  qh->newvertex_list= NULL;
    +  qh_updatevertices(qh /*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +  qh_resetlists(qh, False, !qh_RESETvisible /*qh.newfacet_list, qh.empty visible_list and qh.newvertex_list*/);
    +} /* triangulate_facet */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_link(qh, oldfacetA, facetA, oldfacetB, facetB)
    +    relink facetA to facetB via oldfacets
    +  returns:
    +    adds mirror facets to qh->degen_mergeset (4-d and up only)
    +  design:
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_link(qhT *qh, facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB) {
    +  int errmirror= False;
    +
    +  trace3((qh, qh->ferr, 3021, "qh_triangulate_link: relink old facets f%d and f%d between neighbors f%d and f%d\n",
    +         oldfacetA->id, oldfacetB->id, facetA->id, facetB->id));
    +  if (qh_setin(facetA->neighbors, facetB)) {
    +    if (!qh_setin(facetB->neighbors, facetA))
    +      errmirror= True;
    +    else
    +      qh_appendmergeset(qh, facetA, facetB, MRGmirror, NULL);
    +  }else if (qh_setin(facetB->neighbors, facetA))
    +    errmirror= True;
    +  if (errmirror) {
    +    qh_fprintf(qh, qh->ferr, 6163, "qhull error (qh_triangulate_link): mirror facets f%d and f%d do not match for old facets f%d and f%d\n",
    +       facetA->id, facetB->id, oldfacetA->id, oldfacetB->id);
    +    qh_errexit2(qh, qh_ERRqhull, facetA, facetB);
    +  }
    +  qh_setreplace(qh, facetB->neighbors, oldfacetB, facetA);
    +  qh_setreplace(qh, facetA->neighbors, oldfacetA, facetB);
    +} /* triangulate_link */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_mirror(qh, facetA, facetB)
    +    delete mirrored facets from qh_triangulate_null() and qh_triangulate_mirror
    +      a mirrored facet shares the same vertices of a logical ridge
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_mirror(qhT *qh, facetT *facetA, facetT *facetB) {
    +  facetT *neighbor, *neighborB;
    +  int neighbor_i, neighbor_n;
    +
    +  trace3((qh, qh->ferr, 3022, "qh_triangulate_mirror: delete mirrored facets f%d and f%d\n",
    +         facetA->id, facetB->id));
    +  FOREACHneighbor_i_(qh, facetA) {
    +    neighborB= SETelemt_(facetB->neighbors, neighbor_i, facetT);
    +    if (neighbor == neighborB)
    +      continue; /* occurs twice */
    +    qh_triangulate_link(qh, facetA, neighbor, facetB, neighborB);
    +  }
    +  qh_willdelete(qh, facetA, NULL);
    +  qh_willdelete(qh, facetB, NULL);
    +} /* triangulate_mirror */
    +
    +/*---------------------------------
    +
    +  qh_triangulate_null(qh, facetA)
    +    remove null facetA from qh_triangulate_facet()
    +      a null facet has vertex #1 (apex) == vertex #2
    +  returns:
    +    adds facetA to ->visible for deletion after qh_updatevertices
    +    qh->degen_mergeset contains mirror facets (4-d and up only)
    +  design:
    +    since a null facet duplicates the first two vertices, the opposing neighbors absorb the null facet
    +    if they are already neighbors, the opposing neighbors become MRGmirror facets
    +*/
    +void qh_triangulate_null(qhT *qh, facetT *facetA) {
    +  facetT *neighbor, *otherfacet;
    +
    +  trace3((qh, qh->ferr, 3023, "qh_triangulate_null: delete null facet f%d\n", facetA->id));
    +  neighbor= SETfirstt_(facetA->neighbors, facetT);
    +  otherfacet= SETsecondt_(facetA->neighbors, facetT);
    +  qh_triangulate_link(qh, facetA, neighbor, facetA, otherfacet);
    +  qh_willdelete(qh, facetA, NULL);
    +} /* triangulate_null */
    +
    +#else /* qh_NOmerge */
    +void qh_triangulate(qhT *qh) {
    +}
    +#endif /* qh_NOmerge */
    +
    +   /*---------------------------------
    +
    +  qh_vertexintersect(qh, vertexsetA, vertexsetB )
    +    intersects two vertex sets (inverse id ordered)
    +    vertexsetA is a temporary set at the top of qh->qhmem.tempstack
    +
    +  returns:
    +    replaces vertexsetA with the intersection
    +
    +  notes:
    +    could overwrite vertexsetA if currently too slow
    +*/
    +void qh_vertexintersect(qhT *qh, setT **vertexsetA,setT *vertexsetB) {
    +  setT *intersection;
    +
    +  intersection= qh_vertexintersect_new(qh, *vertexsetA, vertexsetB);
    +  qh_settempfree(qh, vertexsetA);
    +  *vertexsetA= intersection;
    +  qh_settemppush(qh, intersection);
    +} /* vertexintersect */
    +
    +/*---------------------------------
    +
    +  qh_vertexintersect_new(qh, )
    +    intersects two vertex sets (inverse id ordered)
    +
    +  returns:
    +    a new set
    +*/
    +setT *qh_vertexintersect_new(qhT *qh, setT *vertexsetA,setT *vertexsetB) {
    +  setT *intersection= qh_setnew(qh, qh->hull_dim - 1);
    +  vertexT **vertexA= SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= SETaddr_(vertexsetB, vertexT);
    +
    +  while (*vertexA && *vertexB) {
    +    if (*vertexA  == *vertexB) {
    +      qh_setappend(qh, &intersection, *vertexA);
    +      vertexA++; vertexB++;
    +    }else {
    +      if ((*vertexA)->id > (*vertexB)->id)
    +        vertexA++;
    +      else
    +        vertexB++;
    +    }
    +  }
    +  return intersection;
    +} /* vertexintersect_new */
    +
    +/*---------------------------------
    +
    +  qh_vertexneighbors(qh)
    +    for each vertex in qh.facet_list,
    +      determine its neighboring facets
    +
    +  returns:
    +    sets qh.VERTEXneighbors
    +      nop if qh.VERTEXneighbors already set
    +      qh_addpoint() will maintain them
    +
    +  notes:
    +    assumes all vertex->neighbors are NULL
    +
    +  design:
    +    for each facet
    +      for each vertex
    +        append facet to vertex->neighbors
    +*/
    +void qh_vertexneighbors(qhT *qh /*qh.facet_list*/) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  if (qh->VERTEXneighbors)
    +    return;
    +  trace1((qh, qh->ferr, 1035, "qh_vertexneighbors: determining neighboring facets for each vertex\n"));
    +  qh->vertex_visit++;
    +  FORALLfacets {
    +    if (facet->visible)
    +      continue;
    +    FOREACHvertex_(facet->vertices) {
    +      if (vertex->visitid != qh->vertex_visit) {
    +        vertex->visitid= qh->vertex_visit;
    +        vertex->neighbors= qh_setnew(qh, qh->hull_dim);
    +      }
    +      qh_setappend(qh, &vertex->neighbors, facet);
    +    }
    +  }
    +  qh->VERTEXneighbors= True;
    +} /* vertexneighbors */
    +
    +/*---------------------------------
    +
    +  qh_vertexsubset( vertexsetA, vertexsetB )
    +    returns True if vertexsetA is a subset of vertexsetB
    +    assumes vertexsets are sorted
    +
    +  note:
    +    empty set is a subset of any other set
    +*/
    +boolT qh_vertexsubset(setT *vertexsetA, setT *vertexsetB) {
    +  vertexT **vertexA= (vertexT **) SETaddr_(vertexsetA, vertexT);
    +  vertexT **vertexB= (vertexT **) SETaddr_(vertexsetB, vertexT);
    +
    +  while (True) {
    +    if (!*vertexA)
    +      return True;
    +    if (!*vertexB)
    +      return False;
    +    if ((*vertexA)->id > (*vertexB)->id)
    +      return False;
    +    if (*vertexA  == *vertexB)
    +      vertexA++;
    +    vertexB++;
    +  }
    +  return False; /* avoid warnings */
    +} /* vertexsubset */
    diff --git a/xs/src/qhull/src/libqhull_r/poly_r.c b/xs/src/qhull/src/libqhull_r/poly_r.c
    new file mode 100644
    index 0000000000..e5b4797437
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/poly_r.c
    @@ -0,0 +1,1205 @@
    +/*
      ---------------------------------
    +
    +   poly_r.c
    +   implements polygons and simplices
    +
    +   see qh-poly_r.htm, poly_r.h and libqhull_r.h
    +
    +   infrequent code is in poly2_r.c
    +   (all but top 50 and their callers 12/3/95)
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/poly_r.c#3 $$Change: 2064 $
    +   $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*======== functions in alphabetical order ==========*/
    +
    +/*---------------------------------
    +
    +  qh_appendfacet(qh, facet )
    +    appends facet to end of qh.facet_list,
    +
    +  returns:
    +    updates qh.newfacet_list, facet_next, facet_list
    +    increments qh.numfacets
    +
    +  notes:
    +    assumes qh.facet_list/facet_tail is defined (createsimplex)
    +
    +  see:
    +    qh_removefacet()
    +
    +*/
    +void qh_appendfacet(qhT *qh, facetT *facet) {
    +  facetT *tail= qh->facet_tail;
    +
    +  if (tail == qh->newfacet_list)
    +    qh->newfacet_list= facet;
    +  if (tail == qh->facet_next)
    +    qh->facet_next= facet;
    +  facet->previous= tail->previous;
    +  facet->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= facet;
    +  else
    +    qh->facet_list= facet;
    +  tail->previous= facet;
    +  qh->num_facets++;
    +  trace4((qh, qh->ferr, 4044, "qh_appendfacet: append f%d to facet_list\n", facet->id));
    +} /* appendfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_appendvertex(qh, vertex )
    +    appends vertex to end of qh.vertex_list,
    +
    +  returns:
    +    sets vertex->newlist
    +    updates qh.vertex_list, newvertex_list
    +    increments qh.num_vertices
    +
    +  notes:
    +    assumes qh.vertex_list/vertex_tail is defined (createsimplex)
    +
    +*/
    +void qh_appendvertex(qhT *qh, vertexT *vertex) {
    +  vertexT *tail= qh->vertex_tail;
    +
    +  if (tail == qh->newvertex_list)
    +    qh->newvertex_list= vertex;
    +  vertex->newlist= True;
    +  vertex->previous= tail->previous;
    +  vertex->next= tail;
    +  if (tail->previous)
    +    tail->previous->next= vertex;
    +  else
    +    qh->vertex_list= vertex;
    +  tail->previous= vertex;
    +  qh->num_vertices++;
    +  trace4((qh, qh->ferr, 4045, "qh_appendvertex: append v%d to vertex_list\n", vertex->id));
    +} /* appendvertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_attachnewfacets(qh, )
    +    attach horizon facets to new facets in qh.newfacet_list
    +    newfacets have neighbor and ridge links to horizon but not vice versa
    +    only needed for qh.ONLYgood
    +
    +  returns:
    +    set qh.NEWfacets
    +    horizon facets linked to new facets
    +      ridges changed from visible facets to new facets
    +      simplicial ridges deleted
    +    qh.visible_list, no ridges valid
    +    facet->f.replace is a newfacet (if any)
    +
    +  design:
    +    delete interior ridges and neighbor sets by
    +      for each visible, non-simplicial facet
    +        for each ridge
    +          if last visit or if neighbor is simplicial
    +            if horizon neighbor
    +              delete ridge for horizon's ridge set
    +            delete ridge
    +        erase neighbor set
    +    attach horizon facets and new facets by
    +      for all new facets
    +        if corresponding horizon facet is simplicial
    +          locate corresponding visible facet {may be more than one}
    +          link visible facet to new facet
    +          replace visible facet with new facet in horizon
    +        else it's non-simplicial
    +          for all visible neighbors of the horizon facet
    +            link visible neighbor to new facet
    +            delete visible neighbor from horizon facet
    +          append new facet to horizon's neighbors
    +          the first ridge of the new facet is the horizon ridge
    +          link the new facet into the horizon ridge
    +*/
    +void qh_attachnewfacets(qhT *qh /* qh.visible_list, newfacet_list */) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *horizon, *visible;
    +  ridgeT *ridge, **ridgep;
    +
    +  qh->NEWfacets= True;
    +  trace3((qh, qh->ferr, 3012, "qh_attachnewfacets: delete interior ridges\n"));
    +  qh->visit_id++;
    +  FORALLvisible_facets {
    +    visible->visitid= qh->visit_id;
    +    if (visible->ridges) {
    +      FOREACHridge_(visible->ridges) {
    +        neighbor= otherfacet_(ridge, visible);
    +        if (neighbor->visitid == qh->visit_id
    +            || (!neighbor->visible && neighbor->simplicial)) {
    +          if (!neighbor->visible)  /* delete ridge for simplicial horizon */
    +            qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(qh, &(ridge->vertices)); /* delete on 2nd visit */
    +          qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +        }
    +      }
    +      SETfirst_(visible->ridges)= NULL;
    +    }
    +    SETfirst_(visible->neighbors)= NULL;
    +  }
    +  trace1((qh, qh->ferr, 1017, "qh_attachnewfacets: attach horizon facets to new facets\n"));
    +  FORALLnew_facets {
    +    horizon= SETfirstt_(newfacet->neighbors, facetT);
    +    if (horizon->simplicial) {
    +      visible= NULL;
    +      FOREACHneighbor_(horizon) {   /* may have more than one horizon ridge */
    +        if (neighbor->visible) {
    +          if (visible) {
    +            if (qh_setequal_skip(newfacet->vertices, 0, horizon->vertices,
    +                                  SETindex_(horizon->neighbors, neighbor))) {
    +              visible= neighbor;
    +              break;
    +            }
    +          }else
    +            visible= neighbor;
    +        }
    +      }
    +      if (visible) {
    +        visible->f.replace= newfacet;
    +        qh_setreplace(qh, horizon->neighbors, visible, newfacet);
    +      }else {
    +        qh_fprintf(qh, qh->ferr, 6102, "qhull internal error (qh_attachnewfacets): couldn't find visible facet for horizon f%d of newfacet f%d\n",
    +                 horizon->id, newfacet->id);
    +        qh_errexit2(qh, qh_ERRqhull, horizon, newfacet);
    +      }
    +    }else { /* non-simplicial, with a ridge for newfacet */
    +      FOREACHneighbor_(horizon) {    /* may hold for many new facets */
    +        if (neighbor->visible) {
    +          neighbor->f.replace= newfacet;
    +          qh_setdelnth(qh, horizon->neighbors,
    +                        SETindex_(horizon->neighbors, neighbor));
    +          neighborp--; /* repeat */
    +        }
    +      }
    +      qh_setappend(qh, &horizon->neighbors, newfacet);
    +      ridge= SETfirstt_(newfacet->ridges, ridgeT);
    +      if (ridge->top == horizon)
    +        ridge->bottom= newfacet;
    +      else
    +        ridge->top= newfacet;
    +      }
    +  } /* newfacets */
    +  if (qh->PRINTstatistics) {
    +    FORALLvisible_facets {
    +      if (!visible->f.replace)
    +        zinc_(Zinsidevisible);
    +    }
    +  }
    +} /* attachnewfacets */
    +
    +/*---------------------------------
    +
    +  qh_checkflipped(qh, facet, dist, allerror )
    +    checks facet orientation to interior point
    +
    +    if allerror set,
    +      tests against qh.DISTround
    +    else
    +      tests against 0 since tested against DISTround before
    +
    +  returns:
    +    False if it flipped orientation (sets facet->flipped)
    +    distance if non-NULL
    +*/
    +boolT qh_checkflipped(qhT *qh, facetT *facet, realT *distp, boolT allerror) {
    +  realT dist;
    +
    +  if (facet->flipped && !distp)
    +    return False;
    +  zzinc_(Zdistcheck);
    +  qh_distplane(qh, qh->interior_point, facet, &dist);
    +  if (distp)
    +    *distp= dist;
    +  if ((allerror && dist > -qh->DISTround)|| (!allerror && dist >= 0.0)) {
    +    facet->flipped= True;
    +    zzinc_(Zflippedfacets);
    +    trace0((qh, qh->ferr, 19, "qh_checkflipped: facet f%d is flipped, distance= %6.12g during p%d\n",
    +              facet->id, dist, qh->furthest_id));
    +    qh_precision(qh, "flipped facet");
    +    return False;
    +  }
    +  return True;
    +} /* checkflipped */
    +
    +/*---------------------------------
    +
    +  qh_delfacet(qh, facet )
    +    removes facet from facet_list and frees up its memory
    +
    +  notes:
    +    assumes vertices and ridges already freed
    +*/
    +void qh_delfacet(qhT *qh, facetT *facet) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +
    +  trace4((qh, qh->ferr, 4046, "qh_delfacet: delete f%d\n", facet->id));
    +  if (facet == qh->tracefacet)
    +    qh->tracefacet= NULL;
    +  if (facet == qh->GOODclosest)
    +    qh->GOODclosest= NULL;
    +  qh_removefacet(qh, facet);
    +  if (!facet->tricoplanar || facet->keepcentrum) {
    +    qh_memfree_(qh, facet->normal, qh->normal_size, freelistp);
    +    if (qh->CENTERtype == qh_ASvoronoi) {   /* braces for macro calls */
    +      qh_memfree_(qh, facet->center, qh->center_size, freelistp);
    +    }else /* AScentrum */ {
    +      qh_memfree_(qh, facet->center, qh->normal_size, freelistp);
    +    }
    +  }
    +  qh_setfree(qh, &(facet->neighbors));
    +  if (facet->ridges)
    +    qh_setfree(qh, &(facet->ridges));
    +  qh_setfree(qh, &(facet->vertices));
    +  if (facet->outsideset)
    +    qh_setfree(qh, &(facet->outsideset));
    +  if (facet->coplanarset)
    +    qh_setfree(qh, &(facet->coplanarset));
    +  qh_memfree_(qh, facet, (int)sizeof(facetT), freelistp);
    +} /* delfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_deletevisible()
    +    delete visible facets and vertices
    +
    +  returns:
    +    deletes each facet and removes from facetlist
    +    at exit, qh.visible_list empty (== qh.newfacet_list)
    +
    +  notes:
    +    ridges already deleted
    +    horizon facets do not reference facets on qh.visible_list
    +    new facets in qh.newfacet_list
    +    uses   qh.visit_id;
    +*/
    +void qh_deletevisible(qhT *qh /*qh.visible_list*/) {
    +  facetT *visible, *nextfacet;
    +  vertexT *vertex, **vertexp;
    +  int numvisible= 0, numdel= qh_setsize(qh, qh->del_vertices);
    +
    +  trace1((qh, qh->ferr, 1018, "qh_deletevisible: delete %d visible facets and %d vertices\n",
    +         qh->num_visible, numdel));
    +  for (visible= qh->visible_list; visible && visible->visible;
    +                visible= nextfacet) { /* deleting current */
    +    nextfacet= visible->next;
    +    numvisible++;
    +    qh_delfacet(qh, visible);
    +  }
    +  if (numvisible != qh->num_visible) {
    +    qh_fprintf(qh, qh->ferr, 6103, "qhull internal error (qh_deletevisible): qh->num_visible %d is not number of visible facets %d\n",
    +             qh->num_visible, numvisible);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  qh->num_visible= 0;
    +  zadd_(Zvisfacettot, numvisible);
    +  zmax_(Zvisfacetmax, numvisible);
    +  zzadd_(Zdelvertextot, numdel);
    +  zmax_(Zdelvertexmax, numdel);
    +  FOREACHvertex_(qh->del_vertices)
    +    qh_delvertex(qh, vertex);
    +  qh_settruncate(qh, qh->del_vertices, 0);
    +} /* deletevisible */
    +
    +/*---------------------------------
    +
    +  qh_facetintersect(qh, facetA, facetB, skipa, skipB, prepend )
    +    return vertices for intersection of two simplicial facets
    +    may include 1 prepended entry (if more, need to settemppush)
    +
    +  returns:
    +    returns set of qh.hull_dim-1 + prepend vertices
    +    returns skipped index for each test and checks for exactly one
    +
    +  notes:
    +    does not need settemp since set in quick memory
    +
    +  see also:
    +    qh_vertexintersect and qh_vertexintersect_new
    +    use qh_setnew_delnthsorted to get nth ridge (no skip information)
    +
    +  design:
    +    locate skipped vertex by scanning facet A's neighbors
    +    locate skipped vertex by scanning facet B's neighbors
    +    intersect the vertex sets
    +*/
    +setT *qh_facetintersect(qhT *qh, facetT *facetA, facetT *facetB,
    +                         int *skipA,int *skipB, int prepend) {
    +  setT *intersect;
    +  int dim= qh->hull_dim, i, j;
    +  facetT **neighborsA, **neighborsB;
    +
    +  neighborsA= SETaddr_(facetA->neighbors, facetT);
    +  neighborsB= SETaddr_(facetB->neighbors, facetT);
    +  i= j= 0;
    +  if (facetB == *neighborsA++)
    +    *skipA= 0;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 1;
    +  else if (facetB == *neighborsA++)
    +    *skipA= 2;
    +  else {
    +    for (i=3; i < dim; i++) {
    +      if (facetB == *neighborsA++) {
    +        *skipA= i;
    +        break;
    +      }
    +    }
    +  }
    +  if (facetA == *neighborsB++)
    +    *skipB= 0;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 1;
    +  else if (facetA == *neighborsB++)
    +    *skipB= 2;
    +  else {
    +    for (j=3; j < dim; j++) {
    +      if (facetA == *neighborsB++) {
    +        *skipB= j;
    +        break;
    +      }
    +    }
    +  }
    +  if (i >= dim || j >= dim) {
    +    qh_fprintf(qh, qh->ferr, 6104, "qhull internal error (qh_facetintersect): f%d or f%d not in others neighbors\n",
    +            facetA->id, facetB->id);
    +    qh_errexit2(qh, qh_ERRqhull, facetA, facetB);
    +  }
    +  intersect= qh_setnew_delnthsorted(qh, facetA->vertices, qh->hull_dim, *skipA, prepend);
    +  trace4((qh, qh->ferr, 4047, "qh_facetintersect: f%d skip %d matches f%d skip %d\n",
    +          facetA->id, *skipA, facetB->id, *skipB));
    +  return(intersect);
    +} /* facetintersect */
    +
    +/*---------------------------------
    +
    +  qh_gethash(qh, hashsize, set, size, firstindex, skipelem )
    +    return hashvalue for a set with firstindex and skipelem
    +
    +  notes:
    +    returned hash is in [0,hashsize)
    +    assumes at least firstindex+1 elements
    +    assumes skipelem is NULL, in set, or part of hash
    +
    +    hashes memory addresses which may change over different runs of the same data
    +    using sum for hash does badly in high d
    +*/
    +int qh_gethash(qhT *qh, int hashsize, setT *set, int size, int firstindex, void *skipelem) {
    +  void **elemp= SETelemaddr_(set, firstindex, void);
    +  ptr_intT hash = 0, elem;
    +  unsigned result;
    +  int i;
    +#ifdef _MSC_VER                   /* Microsoft Visual C++ -- warn about 64-bit issues */
    +#pragma warning( push)            /* WARN64 -- ptr_intT holds a 64-bit pointer */
    +#pragma warning( disable : 4311)  /* 'type cast': pointer truncation from 'void*' to 'ptr_intT' */
    +#endif
    +
    +  switch (size-firstindex) {
    +  case 1:
    +    hash= (ptr_intT)(*elemp) - (ptr_intT) skipelem;
    +    break;
    +  case 2:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] - (ptr_intT) skipelem;
    +    break;
    +  case 3:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      - (ptr_intT) skipelem;
    +    break;
    +  case 4:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] - (ptr_intT) skipelem;
    +    break;
    +  case 5:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4] - (ptr_intT) skipelem;
    +    break;
    +  case 6:
    +    hash= (ptr_intT)(*elemp) + (ptr_intT)elemp[1] + (ptr_intT)elemp[2]
    +      + (ptr_intT)elemp[3] + (ptr_intT)elemp[4]+ (ptr_intT)elemp[5]
    +      - (ptr_intT) skipelem;
    +    break;
    +  default:
    +    hash= 0;
    +    i= 3;
    +    do {     /* this is about 10% in 10-d */
    +      if ((elem= (ptr_intT)*elemp++) != (ptr_intT)skipelem) {
    +        hash ^= (elem << i) + (elem >> (32-i));
    +        i += 3;
    +        if (i >= 32)
    +          i -= 32;
    +      }
    +    }while (*elemp);
    +    break;
    +  }
    +  if (hashsize<0) {
    +    qh_fprintf(qh, qh->ferr, 6202, "qhull internal error: negative hashsize %d passed to qh_gethash [poly.c]\n", hashsize);
    +    qh_errexit2(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +  result= (unsigned)hash;
    +  result %= (unsigned)hashsize;
    +  /* result= 0; for debugging */
    +  return result;
    +#ifdef _MSC_VER
    +#pragma warning( pop)
    +#endif
    +} /* gethash */
    +
    +/*---------------------------------
    +
    +  qh_makenewfacet(qh, vertices, toporient, horizon )
    +    creates a toporient? facet from vertices
    +
    +  returns:
    +    returns newfacet
    +      adds newfacet to qh.facet_list
    +      newfacet->vertices= vertices
    +      if horizon
    +        newfacet->neighbor= horizon, but not vice versa
    +    newvertex_list updated with vertices
    +*/
    +facetT *qh_makenewfacet(qhT *qh, setT *vertices, boolT toporient,facetT *horizon) {
    +  facetT *newfacet;
    +  vertexT *vertex, **vertexp;
    +
    +  FOREACHvertex_(vertices) {
    +    if (!vertex->newlist) {
    +      qh_removevertex(qh, vertex);
    +      qh_appendvertex(qh, vertex);
    +    }
    +  }
    +  newfacet= qh_newfacet(qh);
    +  newfacet->vertices= vertices;
    +  newfacet->toporient= (unsigned char)toporient;
    +  if (horizon)
    +    qh_setappend(qh, &(newfacet->neighbors), horizon);
    +  qh_appendfacet(qh, newfacet);
    +  return(newfacet);
    +} /* makenewfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_makenewplanes()
    +    make new hyperplanes for facets on qh.newfacet_list
    +
    +  returns:
    +    all facets have hyperplanes or are marked for   merging
    +    doesn't create hyperplane if horizon is coplanar (will merge)
    +    updates qh.min_vertex if qh.JOGGLEmax
    +
    +  notes:
    +    facet->f.samecycle is defined for facet->mergehorizon facets
    +*/
    +void qh_makenewplanes(qhT *qh /* qh.newfacet_list */) {
    +  facetT *newfacet;
    +
    +  FORALLnew_facets {
    +    if (!newfacet->mergehorizon)
    +      qh_setfacetplane(qh, newfacet);
    +  }
    +  if (qh->JOGGLEmax < REALmax/2)
    +    minimize_(qh->min_vertex, -wwval_(Wnewvertexmax));
    +} /* makenewplanes */
    +
    +/*---------------------------------
    +
    +  qh_makenew_nonsimplicial(qh, visible, apex, numnew )
    +    make new facets for ridges of a visible facet
    +
    +  returns:
    +    first newfacet, bumps numnew as needed
    +    attaches new facets if !qh.ONLYgood
    +    marks ridge neighbors for simplicial visible
    +    if (qh.ONLYgood)
    +      ridges on newfacet, horizon, and visible
    +    else
    +      ridge and neighbors between newfacet and   horizon
    +      visible facet's ridges are deleted
    +
    +  notes:
    +    qh.visit_id if visible has already been processed
    +    sets neighbor->seen for building f.samecycle
    +      assumes all 'seen' flags initially false
    +
    +  design:
    +    for each ridge of visible facet
    +      get neighbor of visible facet
    +      if neighbor was already processed
    +        delete the ridge (will delete all visible facets later)
    +      if neighbor is a horizon facet
    +        create a new facet
    +        if neighbor coplanar
    +          adds newfacet to f.samecycle for later merging
    +        else
    +          updates neighbor's neighbor set
    +          (checks for non-simplicial facet with multiple ridges to visible facet)
    +        updates neighbor's ridge set
    +        (checks for simplicial neighbor to non-simplicial visible facet)
    +        (deletes ridge if neighbor is simplicial)
    +
    +*/
    +#ifndef qh_NOmerge
    +facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
    +  void **freelistp; /* used if !qh_NOmem by qh_memfree_() */
    +  ridgeT *ridge, **ridgep;
    +  facetT *neighbor, *newfacet= NULL, *samecycle;
    +  setT *vertices;
    +  boolT toporient;
    +  int ridgeid;
    +
    +  FOREACHridge_(visible->ridges) {
    +    ridgeid= ridge->id;
    +    neighbor= otherfacet_(ridge, visible);
    +    if (neighbor->visible) {
    +      if (!qh->ONLYgood) {
    +        if (neighbor->visitid == qh->visit_id) {
    +          qh_setfree(qh, &(ridge->vertices));  /* delete on 2nd visit */
    +          qh_memfree_(qh, ridge, (int)sizeof(ridgeT), freelistp);
    +        }
    +      }
    +    }else {  /* neighbor is an horizon facet */
    +      toporient= (ridge->top == visible);
    +      vertices= qh_setnew(qh, qh->hull_dim); /* makes sure this is quick */
    +      qh_setappend(qh, &vertices, apex);
    +      qh_setappend_set(qh, &vertices, ridge->vertices);
    +      newfacet= qh_makenewfacet(qh, vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar) {
    +        newfacet->mergehorizon= True;
    +        if (!neighbor->seen) {
    +          newfacet->f.samecycle= newfacet;
    +          neighbor->f.newcycle= newfacet;
    +        }else {
    +          samecycle= neighbor->f.newcycle;
    +          newfacet->f.samecycle= samecycle->f.samecycle;
    +          samecycle->f.samecycle= newfacet;
    +        }
    +      }
    +      if (qh->ONLYgood) {
    +        if (!neighbor->simplicial)
    +          qh_setappend(qh, &(newfacet->ridges), ridge);
    +      }else {  /* qh_attachnewfacets */
    +        if (neighbor->seen) {
    +          if (neighbor->simplicial) {
    +            qh_fprintf(qh, qh->ferr, 6105, "qhull internal error (qh_makenew_nonsimplicial): simplicial f%d sharing two ridges with f%d\n",
    +                   neighbor->id, visible->id);
    +            qh_errexit2(qh, qh_ERRqhull, neighbor, visible);
    +          }
    +          qh_setappend(qh, &(neighbor->neighbors), newfacet);
    +        }else
    +          qh_setreplace(qh, neighbor->neighbors, visible, newfacet);
    +        if (neighbor->simplicial) {
    +          qh_setdel(neighbor->ridges, ridge);
    +          qh_setfree(qh, &(ridge->vertices));
    +          qh_memfree(qh, ridge, (int)sizeof(ridgeT));
    +        }else {
    +          qh_setappend(qh, &(newfacet->ridges), ridge);
    +          if (toporient)
    +            ridge->top= newfacet;
    +          else
    +            ridge->bottom= newfacet;
    +        }
    +      trace4((qh, qh->ferr, 4048, "qh_makenew_nonsimplicial: created facet f%d from v%d and r%d of horizon f%d\n",
    +            newfacet->id, apex->id, ridgeid, neighbor->id));
    +      }
    +    }
    +    neighbor->seen= True;
    +  } /* for each ridge */
    +  if (!qh->ONLYgood)
    +    SETfirst_(visible->ridges)= NULL;
    +  return newfacet;
    +} /* makenew_nonsimplicial */
    +#else /* qh_NOmerge */
    +facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
    +  return NULL;
    +}
    +#endif /* qh_NOmerge */
    +
    +/*---------------------------------
    +
    +  qh_makenew_simplicial(qh, visible, apex, numnew )
    +    make new facets for simplicial visible facet and apex
    +
    +  returns:
    +    attaches new facets if (!qh.ONLYgood)
    +      neighbors between newfacet and horizon
    +
    +  notes:
    +    nop if neighbor->seen or neighbor->visible(see qh_makenew_nonsimplicial)
    +
    +  design:
    +    locate neighboring horizon facet for visible facet
    +    determine vertices and orientation
    +    create new facet
    +    if coplanar,
    +      add new facet to f.samecycle
    +    update horizon facet's neighbor list
    +*/
    +facetT *qh_makenew_simplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew) {
    +  facetT *neighbor, **neighborp, *newfacet= NULL;
    +  setT *vertices;
    +  boolT flip, toporient;
    +  int horizonskip= 0, visibleskip= 0;
    +
    +  FOREACHneighbor_(visible) {
    +    if (!neighbor->seen && !neighbor->visible) {
    +      vertices= qh_facetintersect(qh, neighbor,visible, &horizonskip, &visibleskip, 1);
    +      SETfirst_(vertices)= apex;
    +      flip= ((horizonskip & 0x1) ^ (visibleskip & 0x1));
    +      if (neighbor->toporient)
    +        toporient= horizonskip & 0x1;
    +      else
    +        toporient= (horizonskip & 0x1) ^ 0x1;
    +      newfacet= qh_makenewfacet(qh, vertices, toporient, neighbor);
    +      (*numnew)++;
    +      if (neighbor->coplanar && (qh->PREmerge || qh->MERGEexact)) {
    +#ifndef qh_NOmerge
    +        newfacet->f.samecycle= newfacet;
    +        newfacet->mergehorizon= True;
    +#endif
    +      }
    +      if (!qh->ONLYgood)
    +        SETelem_(neighbor->neighbors, horizonskip)= newfacet;
    +      trace4((qh, qh->ferr, 4049, "qh_makenew_simplicial: create facet f%d top %d from v%d and horizon f%d skip %d top %d and visible f%d skip %d, flip? %d\n",
    +            newfacet->id, toporient, apex->id, neighbor->id, horizonskip,
    +              neighbor->toporient, visible->id, visibleskip, flip));
    +    }
    +  }
    +  return newfacet;
    +} /* makenew_simplicial */
    +
    +/*---------------------------------
    +
    +  qh_matchneighbor(qh, newfacet, newskip, hashsize, hashcount )
    +    either match subridge of newfacet with neighbor or add to hash_table
    +
    +  returns:
    +    duplicate ridges are unmatched and marked by qh_DUPLICATEridge
    +
    +  notes:
    +    ridge is newfacet->vertices w/o newskip vertex
    +    do not allocate memory (need to free hash_table cleanly)
    +    uses linear hash chains
    +
    +  see also:
    +    qh_matchduplicates
    +
    +  design:
    +    for each possible matching facet in qh.hash_table
    +      if vertices match
    +        set ismatch, if facets have opposite orientation
    +        if ismatch and matching facet doesn't have a match
    +          match the facets by updating their neighbor sets
    +        else
    +          indicate a duplicate ridge
    +          set facet hyperplane for later testing
    +          add facet to hashtable
    +          unless the other facet was already a duplicate ridge
    +            mark both facets with a duplicate ridge
    +            add other facet (if defined) to hash table
    +*/
    +void qh_matchneighbor(qhT *qh, facetT *newfacet, int newskip, int hashsize, int *hashcount) {
    +  boolT newfound= False;   /* True, if new facet is already in hash chain */
    +  boolT same, ismatch;
    +  int hash, scan;
    +  facetT *facet, *matchfacet;
    +  int skip, matchskip;
    +
    +  hash= qh_gethash(qh, hashsize, newfacet->vertices, qh->hull_dim, 1,
    +                     SETelem_(newfacet->vertices, newskip));
    +  trace4((qh, qh->ferr, 4050, "qh_matchneighbor: newfacet f%d skip %d hash %d hashcount %d\n",
    +          newfacet->id, newskip, hash, *hashcount));
    +  zinc_(Zhashlookup);
    +  for (scan= hash; (facet= SETelemt_(qh->hash_table, scan, facetT));
    +       scan= (++scan >= hashsize ? 0 : scan)) {
    +    if (facet == newfacet) {
    +      newfound= True;
    +      continue;
    +    }
    +    zinc_(Zhashtests);
    +    if (qh_matchvertices(qh, 1, newfacet->vertices, newskip, facet->vertices, &skip, &same)) {
    +      if (SETelem_(newfacet->vertices, newskip) ==
    +          SETelem_(facet->vertices, skip)) {
    +        qh_precision(qh, "two facets with the same vertices");
    +        qh_fprintf(qh, qh->ferr, 6106, "qhull precision error: Vertex sets are the same for f%d and f%d.  Can not force output.\n",
    +          facet->id, newfacet->id);
    +        qh_errexit2(qh, qh_ERRprec, facet, newfacet);
    +      }
    +      ismatch= (same == (boolT)((newfacet->toporient ^ facet->toporient)));
    +      matchfacet= SETelemt_(facet->neighbors, skip, facetT);
    +      if (ismatch && !matchfacet) {
    +        SETelem_(facet->neighbors, skip)= newfacet;
    +        SETelem_(newfacet->neighbors, newskip)= facet;
    +        (*hashcount)--;
    +        trace4((qh, qh->ferr, 4051, "qh_matchneighbor: f%d skip %d matched with new f%d skip %d\n",
    +           facet->id, skip, newfacet->id, newskip));
    +        return;
    +      }
    +      if (!qh->PREmerge && !qh->MERGEexact) {
    +        qh_precision(qh, "a ridge with more than two neighbors");
    +        qh_fprintf(qh, qh->ferr, 6107, "qhull precision error: facets f%d, f%d and f%d meet at a ridge with more than 2 neighbors.  Can not continue.\n",
    +                 facet->id, newfacet->id, getid_(matchfacet));
    +        qh_errexit2(qh, qh_ERRprec, facet, newfacet);
    +      }
    +      SETelem_(newfacet->neighbors, newskip)= qh_DUPLICATEridge;
    +      newfacet->dupridge= True;
    +      if (!newfacet->normal)
    +        qh_setfacetplane(qh, newfacet);
    +      qh_addhash(newfacet, qh->hash_table, hashsize, hash);
    +      (*hashcount)++;
    +      if (!facet->normal)
    +        qh_setfacetplane(qh, facet);
    +      if (matchfacet != qh_DUPLICATEridge) {
    +        SETelem_(facet->neighbors, skip)= qh_DUPLICATEridge;
    +        facet->dupridge= True;
    +        if (!facet->normal)
    +          qh_setfacetplane(qh, facet);
    +        if (matchfacet) {
    +          matchskip= qh_setindex(matchfacet->neighbors, facet);
    +          if (matchskip<0) {
    +              qh_fprintf(qh, qh->ferr, 6260, "qhull internal error (qh_matchneighbor): matchfacet f%d is in f%d neighbors but not vice versa.  Can not continue.\n",
    +                  matchfacet->id, facet->id);
    +              qh_errexit2(qh, qh_ERRqhull, matchfacet, facet);
    +          }
    +          SETelem_(matchfacet->neighbors, matchskip)= qh_DUPLICATEridge; /* matchskip>=0 by QH6260 */
    +          matchfacet->dupridge= True;
    +          if (!matchfacet->normal)
    +            qh_setfacetplane(qh, matchfacet);
    +          qh_addhash(matchfacet, qh->hash_table, hashsize, hash);
    +          *hashcount += 2;
    +        }
    +      }
    +      trace4((qh, qh->ferr, 4052, "qh_matchneighbor: new f%d skip %d duplicates ridge for f%d skip %d matching f%d ismatch %d at hash %d\n",
    +           newfacet->id, newskip, facet->id, skip,
    +           (matchfacet == qh_DUPLICATEridge ? -2 : getid_(matchfacet)),
    +           ismatch, hash));
    +      return; /* end of duplicate ridge */
    +    }
    +  }
    +  if (!newfound)
    +    SETelem_(qh->hash_table, scan)= newfacet;  /* same as qh_addhash */
    +  (*hashcount)++;
    +  trace4((qh, qh->ferr, 4053, "qh_matchneighbor: no match for f%d skip %d at hash %d\n",
    +           newfacet->id, newskip, hash));
    +} /* matchneighbor */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchnewfacets()
    +    match newfacets in qh.newfacet_list to their newfacet neighbors
    +
    +  returns:
    +    qh.newfacet_list with full neighbor sets
    +      get vertices with nth neighbor by deleting nth vertex
    +    if qh.PREmerge/MERGEexact or qh.FORCEoutput
    +      sets facet->flippped if flipped normal (also prevents point partitioning)
    +    if duplicate ridges and qh.PREmerge/MERGEexact
    +      sets facet->dupridge
    +      missing neighbor links identifies extra ridges to be merging (qh_MERGEridge)
    +
    +  notes:
    +    newfacets already have neighbor[0] (horizon facet)
    +    assumes qh.hash_table is NULL
    +    vertex->neighbors has not been updated yet
    +    do not allocate memory after qh.hash_table (need to free it cleanly)
    +
    +  design:
    +    delete neighbor sets for all new facets
    +    initialize a hash table
    +    for all new facets
    +      match facet with neighbors
    +    if unmatched facets (due to duplicate ridges)
    +      for each new facet with a duplicate ridge
    +        match it with a facet
    +    check for flipped facets
    +*/
    +void qh_matchnewfacets(qhT *qh /* qh.newfacet_list */) {
    +  int numnew=0, hashcount=0, newskip;
    +  facetT *newfacet, *neighbor;
    +  int dim= qh->hull_dim, hashsize, neighbor_i, neighbor_n;
    +  setT *neighbors;
    +#ifndef qh_NOtrace
    +  int facet_i, facet_n, numfree= 0;
    +  facetT *facet;
    +#endif
    +
    +  trace1((qh, qh->ferr, 1019, "qh_matchnewfacets: match neighbors for new facets.\n"));
    +  FORALLnew_facets {
    +    numnew++;
    +    {  /* inline qh_setzero(qh, newfacet->neighbors, 1, qh->hull_dim); */
    +      neighbors= newfacet->neighbors;
    +      neighbors->e[neighbors->maxsize].i= dim+1; /*may be overwritten*/
    +      memset((char *)SETelemaddr_(neighbors, 1, void), 0, dim * SETelemsize);
    +    }
    +  }
    +
    +  qh_newhashtable(qh, numnew*(qh->hull_dim-1)); /* twice what is normally needed,
    +                                     but every ridge could be DUPLICATEridge */
    +  hashsize= qh_setsize(qh, qh->hash_table);
    +  FORALLnew_facets {
    +    for (newskip=1; newskiphull_dim; newskip++) /* furthest/horizon already matched */
    +      /* hashsize>0 because hull_dim>1 and numnew>0 */
    +      qh_matchneighbor(qh, newfacet, newskip, hashsize, &hashcount);
    +#if 0   /* use the following to trap hashcount errors */
    +    {
    +      int count= 0, k;
    +      facetT *facet, *neighbor;
    +
    +      count= 0;
    +      FORALLfacet_(qh->newfacet_list) {  /* newfacet already in use */
    +        for (k=1; k < qh->hull_dim; k++) {
    +          neighbor= SETelemt_(facet->neighbors, k, facetT);
    +          if (!neighbor || neighbor == qh_DUPLICATEridge)
    +            count++;
    +        }
    +        if (facet == newfacet)
    +          break;
    +      }
    +      if (count != hashcount) {
    +        qh_fprintf(qh, qh->ferr, 8088, "qh_matchnewfacets: after adding facet %d, hashcount %d != count %d\n",
    +                 newfacet->id, hashcount, count);
    +        qh_errexit(qh, qh_ERRqhull, newfacet, NULL);
    +      }
    +    }
    +#endif  /* end of trap code */
    +  }
    +  if (hashcount) {
    +    FORALLnew_facets {
    +      if (newfacet->dupridge) {
    +        FOREACHneighbor_i_(qh, newfacet) {
    +          if (neighbor == qh_DUPLICATEridge) {
    +            qh_matchduplicates(qh, newfacet, neighbor_i, hashsize, &hashcount);
    +                    /* this may report MERGEfacet */
    +          }
    +        }
    +      }
    +    }
    +  }
    +  if (hashcount) {
    +    qh_fprintf(qh, qh->ferr, 6108, "qhull internal error (qh_matchnewfacets): %d neighbors did not match up\n",
    +        hashcount);
    +    qh_printhashtable(qh, qh->ferr);
    +    qh_errexit(qh, qh_ERRqhull, NULL, NULL);
    +  }
    +#ifndef qh_NOtrace
    +  if (qh->IStracing >= 2) {
    +    FOREACHfacet_i_(qh, qh->hash_table) {
    +      if (!facet)
    +        numfree++;
    +    }
    +    qh_fprintf(qh, qh->ferr, 8089, "qh_matchnewfacets: %d new facets, %d unused hash entries .  hashsize %d\n",
    +             numnew, numfree, qh_setsize(qh, qh->hash_table));
    +  }
    +#endif /* !qh_NOtrace */
    +  qh_setfree(qh, &qh->hash_table);
    +  if (qh->PREmerge || qh->MERGEexact) {
    +    if (qh->IStracing >= 4)
    +      qh_printfacetlist(qh, qh->newfacet_list, NULL, qh_ALL);
    +    FORALLnew_facets {
    +      if (newfacet->normal)
    +        qh_checkflipped(qh, newfacet, NULL, qh_ALL);
    +    }
    +  }else if (qh->FORCEoutput)
    +    qh_checkflipped_all(qh, qh->newfacet_list);  /* prints warnings for flipped */
    +} /* matchnewfacets */
    +
    +
    +/*---------------------------------
    +
    +  qh_matchvertices(qh, firstindex, verticesA, skipA, verticesB, skipB, same )
    +    tests whether vertices match with a single skip
    +    starts match at firstindex since all new facets have a common vertex
    +
    +  returns:
    +    true if matched vertices
    +    skip index for each set
    +    sets same iff vertices have the same orientation
    +
    +  notes:
    +    assumes skipA is in A and both sets are the same size
    +
    +  design:
    +    set up pointers
    +    scan both sets checking for a match
    +    test orientation
    +*/
    +boolT qh_matchvertices(qhT *qh, int firstindex, setT *verticesA, int skipA,
    +       setT *verticesB, int *skipB, boolT *same) {
    +  vertexT **elemAp, **elemBp, **skipBp=NULL, **skipAp;
    +
    +  elemAp= SETelemaddr_(verticesA, firstindex, vertexT);
    +  elemBp= SETelemaddr_(verticesB, firstindex, vertexT);
    +  skipAp= SETelemaddr_(verticesA, skipA, vertexT);
    +  do if (elemAp != skipAp) {
    +    while (*elemAp != *elemBp++) {
    +      if (skipBp)
    +        return False;
    +      skipBp= elemBp;  /* one extra like FOREACH */
    +    }
    +  }while (*(++elemAp));
    +  if (!skipBp)
    +    skipBp= ++elemBp;
    +  *skipB= SETindex_(verticesB, skipB); /* i.e., skipBp - verticesB */
    +  *same= !((skipA & 0x1) ^ (*skipB & 0x1)); /* result is 0 or 1 */
    +  trace4((qh, qh->ferr, 4054, "qh_matchvertices: matched by skip %d(v%d) and skip %d(v%d) same? %d\n",
    +          skipA, (*skipAp)->id, *skipB, (*(skipBp-1))->id, *same));
    +  return(True);
    +} /* matchvertices */
    +
    +/*---------------------------------
    +
    +  qh_newfacet(qh)
    +    return a new facet
    +
    +  returns:
    +    all fields initialized or cleared   (NULL)
    +    preallocates neighbors set
    +*/
    +facetT *qh_newfacet(qhT *qh) {
    +  facetT *facet;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(qh, (int)sizeof(facetT), freelistp, facet, facetT);
    +  memset((char *)facet, (size_t)0, sizeof(facetT));
    +  if (qh->facet_id == qh->tracefacet_id)
    +    qh->tracefacet= facet;
    +  facet->id= qh->facet_id++;
    +  facet->neighbors= qh_setnew(qh, qh->hull_dim);
    +#if !qh_COMPUTEfurthest
    +  facet->furthestdist= 0.0;
    +#endif
    +#if qh_MAXoutside
    +  if (qh->FORCEoutput && qh->APPROXhull)
    +    facet->maxoutside= qh->MINoutside;
    +  else
    +    facet->maxoutside= qh->DISTround;
    +#endif
    +  facet->simplicial= True;
    +  facet->good= True;
    +  facet->newfacet= True;
    +  trace4((qh, qh->ferr, 4055, "qh_newfacet: created facet f%d\n", facet->id));
    +  return(facet);
    +} /* newfacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_newridge()
    +    return a new ridge
    +*/
    +ridgeT *qh_newridge(qhT *qh) {
    +  ridgeT *ridge;
    +  void **freelistp;   /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  qh_memalloc_(qh, (int)sizeof(ridgeT), freelistp, ridge, ridgeT);
    +  memset((char *)ridge, (size_t)0, sizeof(ridgeT));
    +  zinc_(Ztotridges);
    +  if (qh->ridge_id == UINT_MAX) {
    +    qh_fprintf(qh, qh->ferr, 7074, "\
    +qhull warning: more than 2^32 ridges.  Qhull results are OK.  Since the ridge ID wraps around to 0, two ridges may have the same identifier.\n");
    +  }
    +  ridge->id= qh->ridge_id++;
    +  trace4((qh, qh->ferr, 4056, "qh_newridge: created ridge r%d\n", ridge->id));
    +  return(ridge);
    +} /* newridge */
    +
    +
    +/*---------------------------------
    +
    +  qh_pointid(qh, point )
    +    return id for a point,
    +    returns qh_IDnone(-3) if null, qh_IDinterior(-2) if interior, or qh_IDunknown(-1) if not known
    +
    +  alternative code if point is in qh.first_point...
    +    unsigned long id;
    +    id= ((unsigned long)point - (unsigned long)qh.first_point)/qh.normal_size;
    +
    +  notes:
    +    Valid points are non-negative
    +    WARN64 -- id truncated to 32-bits, at most 2G points
    +    NOerrors returned (QhullPoint::id)
    +    if point not in point array
    +      the code does a comparison of unrelated pointers.
    +*/
    +int qh_pointid(qhT *qh, pointT *point) {
    +  ptr_intT offset, id;
    +
    +  if (!point || !qh)
    +    return qh_IDnone;
    +  else if (point == qh->interior_point)
    +    return qh_IDinterior;
    +  else if (point >= qh->first_point
    +  && point < qh->first_point + qh->num_points * qh->hull_dim) {
    +    offset= (ptr_intT)(point - qh->first_point);
    +    id= offset / qh->hull_dim;
    +  }else if ((id= qh_setindex(qh->other_points, point)) != -1)
    +    id += qh->num_points;
    +  else
    +    return qh_IDunknown;
    +  return (int)id;
    +} /* pointid */
    +
    +/*---------------------------------
    +
    +  qh_removefacet(qh, facet )
    +    unlinks facet from qh.facet_list,
    +
    +  returns:
    +    updates qh.facet_list .newfacet_list .facet_next visible_list
    +    decrements qh.num_facets
    +
    +  see:
    +    qh_appendfacet
    +*/
    +void qh_removefacet(qhT *qh, facetT *facet) {
    +  facetT *next= facet->next, *previous= facet->previous;
    +
    +  if (facet == qh->newfacet_list)
    +    qh->newfacet_list= next;
    +  if (facet == qh->facet_next)
    +    qh->facet_next= next;
    +  if (facet == qh->visible_list)
    +    qh->visible_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st facet in qh->facet_list */
    +    qh->facet_list= next;
    +    qh->facet_list->previous= NULL;
    +  }
    +  qh->num_facets--;
    +  trace4((qh, qh->ferr, 4057, "qh_removefacet: remove f%d from facet_list\n", facet->id));
    +} /* removefacet */
    +
    +
    +/*---------------------------------
    +
    +  qh_removevertex(qh, vertex )
    +    unlinks vertex from qh.vertex_list,
    +
    +  returns:
    +    updates qh.vertex_list .newvertex_list
    +    decrements qh.num_vertices
    +*/
    +void qh_removevertex(qhT *qh, vertexT *vertex) {
    +  vertexT *next= vertex->next, *previous= vertex->previous;
    +
    +  if (vertex == qh->newvertex_list)
    +    qh->newvertex_list= next;
    +  if (previous) {
    +    previous->next= next;
    +    next->previous= previous;
    +  }else {  /* 1st vertex in qh->vertex_list */
    +    qh->vertex_list= vertex->next;
    +    qh->vertex_list->previous= NULL;
    +  }
    +  qh->num_vertices--;
    +  trace4((qh, qh->ferr, 4058, "qh_removevertex: remove v%d from vertex_list\n", vertex->id));
    +} /* removevertex */
    +
    +
    +/*---------------------------------
    +
    +  qh_updatevertices()
    +    update vertex neighbors and delete interior vertices
    +
    +  returns:
    +    if qh.VERTEXneighbors, updates neighbors for each vertex
    +      if qh.newvertex_list,
    +         removes visible neighbors  from vertex neighbors
    +      if qh.newfacet_list
    +         adds new facets to vertex neighbors
    +    if qh.visible_list
    +       interior vertices added to qh.del_vertices for later partitioning
    +
    +  design:
    +    if qh.VERTEXneighbors
    +      deletes references to visible facets from vertex neighbors
    +      appends new facets to the neighbor list for each vertex
    +      checks all vertices of visible facets
    +        removes visible facets from neighbor lists
    +        marks unused vertices for deletion
    +*/
    +void qh_updatevertices(qhT *qh /*qh.newvertex_list, newfacet_list, visible_list*/) {
    +  facetT *newfacet= NULL, *neighbor, **neighborp, *visible;
    +  vertexT *vertex, **vertexp;
    +
    +  trace3((qh, qh->ferr, 3013, "qh_updatevertices: delete interior vertices and update vertex->neighbors\n"));
    +  if (qh->VERTEXneighbors) {
    +    FORALLvertex_(qh->newvertex_list) {
    +      FOREACHneighbor_(vertex) {
    +        if (neighbor->visible)
    +          SETref_(neighbor)= NULL;
    +      }
    +      qh_setcompact(qh, vertex->neighbors);
    +    }
    +    FORALLnew_facets {
    +      FOREACHvertex_(newfacet->vertices)
    +        qh_setappend(qh, &vertex->neighbors, newfacet);
    +    }
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          FOREACHneighbor_(vertex) { /* this can happen under merging */
    +            if (!neighbor->visible)
    +              break;
    +          }
    +          if (neighbor)
    +            qh_setdel(vertex->neighbors, visible);
    +          else {
    +            vertex->deleted= True;
    +            qh_setappend(qh, &qh->del_vertices, vertex);
    +            trace2((qh, qh->ferr, 2041, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(qh, vertex->point), vertex->id, visible->id));
    +          }
    +        }
    +      }
    +    }
    +  }else {  /* !VERTEXneighbors */
    +    FORALLvisible_facets {
    +      FOREACHvertex_(visible->vertices) {
    +        if (!vertex->newlist && !vertex->deleted) {
    +          vertex->deleted= True;
    +          qh_setappend(qh, &qh->del_vertices, vertex);
    +          trace2((qh, qh->ferr, 2042, "qh_updatevertices: delete vertex p%d(v%d) in f%d\n",
    +                  qh_pointid(qh, vertex->point), vertex->id, visible->id));
    +        }
    +      }
    +    }
    +  }
    +} /* updatevertices */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/poly_r.h b/xs/src/qhull/src/libqhull_r/poly_r.h
    new file mode 100644
    index 0000000000..c71511bd69
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/poly_r.h
    @@ -0,0 +1,303 @@
    +/*
      ---------------------------------
    +
    +   poly_r.h
    +   header file for poly_r.c and poly2_r.c
    +
    +   see qh-poly_r.htm, libqhull_r.h and poly_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/poly_r.h#5 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFpoly
    +#define qhDEFpoly 1
    +
    +#include "libqhull_r.h"
    +
    +/*===============   constants ========================== */
    +
    +/*----------------------------------
    +
    +  ALGORITHMfault
    +    use as argument to checkconvex() to report errors during buildhull
    +*/
    +#define qh_ALGORITHMfault 0
    +
    +/*----------------------------------
    +
    +  DATAfault
    +    use as argument to checkconvex() to report errors during initialhull
    +*/
    +#define qh_DATAfault 1
    +
    +/*----------------------------------
    +
    +  DUPLICATEridge
    +    special value for facet->neighbor to indicate a duplicate ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_DUPLICATEridge (facetT *)1L
    +
    +/*----------------------------------
    +
    +  MERGEridge       flag in facet
    +    special value for facet->neighbor to indicate a merged ridge
    +
    +  notes:
    +    set by matchneighbor, used by matchmatch and mark_dupridge
    +*/
    +#define qh_MERGEridge (facetT *)2L
    +
    +
    +/*============ -structures- ====================*/
    +
    +/*=========== -macros- =========================*/
    +
    +/*----------------------------------
    +
    +  FORALLfacet_( facetlist ) { ... }
    +    assign 'facet' to each facet in facetlist
    +
    +  notes:
    +    uses 'facetT *facet;'
    +    assumes last facet is a sentinel
    +
    +  see:
    +    FORALLfacets
    +*/
    +#define FORALLfacet_( facetlist ) if (facetlist ) for ( facet=( facetlist ); facet && facet->next; facet= facet->next )
    +
    +/*----------------------------------
    +
    +  FORALLnew_facets { ... }
    +    assign 'newfacet' to each facet in qh.newfacet_list
    +
    +  notes:
    +    uses 'facetT *newfacet;'
    +    at exit, newfacet==NULL
    +*/
    +#define FORALLnew_facets for ( newfacet=qh->newfacet_list;newfacet && newfacet->next;newfacet=newfacet->next )
    +
    +/*----------------------------------
    +
    +  FORALLvertex_( vertexlist ) { ... }
    +    assign 'vertex' to each vertex in vertexlist
    +
    +  notes:
    +    uses 'vertexT *vertex;'
    +    at exit, vertex==NULL
    +*/
    +#define FORALLvertex_( vertexlist ) for (vertex=( vertexlist );vertex && vertex->next;vertex= vertex->next )
    +
    +/*----------------------------------
    +
    +  FORALLvisible_facets { ... }
    +    assign 'visible' to each visible facet in qh.visible_list
    +
    +  notes:
    +    uses 'vacetT *visible;'
    +    at exit, visible==NULL
    +*/
    +#define FORALLvisible_facets for (visible=qh->visible_list; visible && visible->visible; visible= visible->next)
    +
    +/*----------------------------------
    +
    +  FORALLsame_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    stops when it returns to newfacet
    +*/
    +#define FORALLsame_(newfacet) for (same= newfacet->f.samecycle; same != newfacet; same= same->f.samecycle)
    +
    +/*----------------------------------
    +
    +  FORALLsame_cycle_( newfacet ) { ... }
    +    assign 'same' to each facet in newfacet->f.samecycle
    +
    +  notes:
    +    uses 'facetT *same;'
    +    at exit, same == NULL
    +*/
    +#define FORALLsame_cycle_(newfacet) \
    +     for (same= newfacet->f.samecycle; \
    +         same; same= (same == newfacet ?  NULL : same->f.samecycle))
    +
    +/*----------------------------------
    +
    +  FOREACHneighborA_( facet ) { ... }
    +    assign 'neighborA' to each neighbor in facet->neighbors
    +
    +  FOREACHneighborA_( vertex ) { ... }
    +    assign 'neighborA' to each neighbor in vertex->neighbors
    +
    +  declare:
    +    facetT *neighborA, **neighborAp;
    +
    +  see:
    +    FOREACHsetelement_
    +*/
    +#define FOREACHneighborA_(facet)  FOREACHsetelement_(facetT, facet->neighbors, neighborA)
    +
    +/*----------------------------------
    +
    +  FOREACHvisible_( facets ) { ... }
    +    assign 'visible' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *facet, *facetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvisible_(facets) FOREACHsetelement_(facetT, facets, visible)
    +
    +/*----------------------------------
    +
    +  FOREACHnewfacet_( facets ) { ... }
    +    assign 'newfacet' to each facet in facets
    +
    +  notes:
    +    uses 'facetT *newfacet, *newfacetp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHnewfacet_(facets) FOREACHsetelement_(facetT, facets, newfacet)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexA_( vertices ) { ... }
    +    assign 'vertexA' to each vertex in vertices
    +
    +  notes:
    +    uses 'vertexT *vertexA, *vertexAp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexA_(vertices) FOREACHsetelement_(vertexT, vertices, vertexA)
    +
    +/*----------------------------------
    +
    +  FOREACHvertexreverse12_( vertices ) { ... }
    +    assign 'vertex' to each vertex in vertices
    +    reverse order of first two vertices
    +
    +  notes:
    +    uses 'vertexT *vertex, *vertexp;'
    +    see FOREACHsetelement_
    +*/
    +#define FOREACHvertexreverse12_(vertices) FOREACHsetelementreverse12_(vertexT, vertices, vertex)
    +
    +
    +/*=============== prototypes poly_r.c in alphabetical order ================*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_appendfacet(qhT *qh, facetT *facet);
    +void    qh_appendvertex(qhT *qh, vertexT *vertex);
    +void    qh_attachnewfacets(qhT *qh /* qh.visible_list, qh.newfacet_list */);
    +boolT   qh_checkflipped(qhT *qh, facetT *facet, realT *dist, boolT allerror);
    +void    qh_delfacet(qhT *qh, facetT *facet);
    +void    qh_deletevisible(qhT *qh /* qh.visible_list, qh.horizon_list */);
    +setT   *qh_facetintersect(qhT *qh, facetT *facetA, facetT *facetB, int *skipAp,int *skipBp, int extra);
    +int     qh_gethash(qhT *qh, int hashsize, setT *set, int size, int firstindex, void *skipelem);
    +facetT *qh_makenewfacet(qhT *qh, setT *vertices, boolT toporient, facetT *facet);
    +void    qh_makenewplanes(qhT *qh /* qh.newfacet_list */);
    +facetT *qh_makenew_nonsimplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew);
    +facetT *qh_makenew_simplicial(qhT *qh, facetT *visible, vertexT *apex, int *numnew);
    +void    qh_matchneighbor(qhT *qh, facetT *newfacet, int newskip, int hashsize,
    +                          int *hashcount);
    +void    qh_matchnewfacets(qhT *qh);
    +boolT   qh_matchvertices(qhT *qh, int firstindex, setT *verticesA, int skipA,
    +                          setT *verticesB, int *skipB, boolT *same);
    +facetT *qh_newfacet(qhT *qh);
    +ridgeT *qh_newridge(qhT *qh);
    +int     qh_pointid(qhT *qh, pointT *point);
    +void    qh_removefacet(qhT *qh, facetT *facet);
    +void    qh_removevertex(qhT *qh, vertexT *vertex);
    +void    qh_updatevertices(qhT *qh);
    +
    +
    +/*========== -prototypes poly2_r.c in alphabetical order ===========*/
    +
    +void    qh_addhash(void *newelem, setT *hashtable, int hashsize, int hash);
    +void    qh_check_bestdist(qhT *qh);
    +void    qh_check_dupridge(qhT *qh, facetT *facet1, realT dist1, facetT *facet2, realT dist2);
    +void    qh_check_maxout(qhT *qh);
    +void    qh_check_output(qhT *qh);
    +void    qh_check_point(qhT *qh, pointT *point, facetT *facet, realT *maxoutside, realT *maxdist, facetT **errfacet1, facetT **errfacet2);
    +void    qh_check_points(qhT *qh);
    +void    qh_checkconvex(qhT *qh, facetT *facetlist, int fault);
    +void    qh_checkfacet(qhT *qh, facetT *facet, boolT newmerge, boolT *waserrorp);
    +void    qh_checkflipped_all(qhT *qh, facetT *facetlist);
    +void    qh_checkpolygon(qhT *qh, facetT *facetlist);
    +void    qh_checkvertex(qhT *qh, vertexT *vertex);
    +void    qh_clearcenters(qhT *qh, qh_CENTER type);
    +void    qh_createsimplex(qhT *qh, setT *vertices);
    +void    qh_delridge(qhT *qh, ridgeT *ridge);
    +void    qh_delvertex(qhT *qh, vertexT *vertex);
    +setT   *qh_facet3vertex(qhT *qh, facetT *facet);
    +facetT *qh_findbestfacet(qhT *qh, pointT *point, boolT bestoutside,
    +           realT *bestdist, boolT *isoutside);
    +facetT *qh_findbestlower(qhT *qh, facetT *upperfacet, pointT *point, realT *bestdistp, int *numpart);
    +facetT *qh_findfacet_all(qhT *qh, pointT *point, realT *bestdist, boolT *isoutside,
    +                          int *numpart);
    +int     qh_findgood(qhT *qh, facetT *facetlist, int goodhorizon);
    +void    qh_findgood_all(qhT *qh, facetT *facetlist);
    +void    qh_furthestnext(qhT *qh /* qh.facet_list */);
    +void    qh_furthestout(qhT *qh, facetT *facet);
    +void    qh_infiniteloop(qhT *qh, facetT *facet);
    +void    qh_initbuild(qhT *qh);
    +void    qh_initialhull(qhT *qh, setT *vertices);
    +setT   *qh_initialvertices(qhT *qh, int dim, setT *maxpoints, pointT *points, int numpoints);
    +vertexT *qh_isvertex(pointT *point, setT *vertices);
    +vertexT *qh_makenewfacets(qhT *qh, pointT *point /*horizon_list, visible_list*/);
    +void    qh_matchduplicates(qhT *qh, facetT *atfacet, int atskip, int hashsize, int *hashcount);
    +void    qh_nearcoplanar(qhT *qh /* qh.facet_list */);
    +vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp);
    +int     qh_newhashtable(qhT *qh, int newsize);
    +vertexT *qh_newvertex(qhT *qh, pointT *point);
    +ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp);
    +void    qh_outcoplanar(qhT *qh /* qh.facet_list */);
    +pointT *qh_point(qhT *qh, int id);
    +void    qh_point_add(qhT *qh, setT *set, pointT *point, void *elem);
    +setT   *qh_pointfacet(qhT *qh /*qh.facet_list*/);
    +setT   *qh_pointvertex(qhT *qh /*qh.facet_list*/);
    +void    qh_prependfacet(qhT *qh, facetT *facet, facetT **facetlist);
    +void    qh_printhashtable(qhT *qh, FILE *fp);
    +void    qh_printlists(qhT *qh);
    +void    qh_resetlists(qhT *qh, boolT stats, boolT resetVisible /*qh.newvertex_list qh.newfacet_list qh.visible_list*/);
    +void    qh_setvoronoi_all(qhT *qh);
    +void    qh_triangulate(qhT *qh /*qh.facet_list*/);
    +void    qh_triangulate_facet(qhT *qh, facetT *facetA, vertexT **first_vertex);
    +void    qh_triangulate_link(qhT *qh, facetT *oldfacetA, facetT *facetA, facetT *oldfacetB, facetT *facetB);
    +void    qh_triangulate_mirror(qhT *qh, facetT *facetA, facetT *facetB);
    +void    qh_triangulate_null(qhT *qh, facetT *facetA);
    +void    qh_vertexintersect(qhT *qh, setT **vertexsetA,setT *vertexsetB);
    +setT   *qh_vertexintersect_new(qhT *qh, setT *vertexsetA,setT *vertexsetB);
    +void    qh_vertexneighbors(qhT *qh /*qh.facet_list*/);
    +boolT   qh_vertexsubset(setT *vertexsetA, setT *vertexsetB);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFpoly */
    diff --git a/xs/src/qhull/src/libqhull_r/qh-geom_r.htm b/xs/src/qhull/src/libqhull_r/qh-geom_r.htm
    new file mode 100644
    index 0000000000..eeefc0c758
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/qh-geom_r.htm
    @@ -0,0 +1,295 @@
    +
    +
    +
    +
    +geom_r.c, geom2_r.c -- geometric and floating point routines
    +
    +
    +
    +
    +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    geom_r.c, geom2_r.c, random_r.c -- geometric and floating point routines

    +
    +

    Geometrically, a vertex is a point with d coordinates +and a facet is a halfspace. A halfspace is defined by an +oriented hyperplane through the facet's vertices. A hyperplane +is defined by d normalized coefficients and an offset. A +point is above a facet if its distance to the facet is +positive.

    + +

    Qhull uses floating point coordinates for input points, +vertices, halfspace equations, centrums, and an interior point.

    + +

    Qhull may be configured for single precision or double +precision floating point arithmetic (see realT +).

    + +

    Each floating point operation may incur round-off error (see +Merge). The maximum error for distance +computations is determined at initialization. The roundoff error +in halfspace computation is accounted for by computing the +distance from vertices to the halfspace.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to geom_r.c, +geom2_r.c, geom_r.h, +random_r.c, random_r.h +

    + + + +

    »geometric data types +and constants

    + +
      +
    • coordT coordinates and +coefficients are stored as realT
    • +
    • pointT a point is an array +of DIM3 coordinates
    • +
    + +

    »mathematical macros

    + +
      +
    • fabs_ returns the absolute +value of a
    • +
    • fmax_ returns the maximum +value of a and b
    • +
    • fmin_ returns the minimum +value of a and b
    • +
    • maximize_ maximize a value +
    • +
    • minimize_ minimize a value +
    • +
    • det2_ compute a 2-d +determinate
    • +
    • det3_ compute a 3-d +determinate
    • +
    • dX, dY, dZ compute the difference +between two coordinates
    • +
    + +

    »mathematical functions

    + + + +

    »computational geometry functions

    + + + +

    »point array functions

    + + +

    »geometric facet functions

    + + +

    »geometric roundoff functions

    +
      +
    • qh_detjoggle determine +default joggle for points and distance roundoff error
    • +
    • qh_detroundoff +determine maximum roundoff error and other precision constants
    • +
    • qh_distround compute +maximum roundoff error due to a distance computation to a +normalized hyperplane
    • +
    • qh_divzero divide by a +number that is nearly zero
    • +
    • qh_maxouter return maximum outer +plane
    • +
    • qh_outerinner return actual +outer and inner planes +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    + + +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-globa_r.htm b/xs/src/qhull/src/libqhull_r/qh-globa_r.htm new file mode 100644 index 0000000000..45437a0597 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-globa_r.htm @@ -0,0 +1,163 @@ + + + + +global_r.c -- global variables and their functions + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    + +
    + + +

    global_r.c -- global variables and their functions

    +
    +

    Qhull uses a data structure, qhT, to store +globally defined constants, lists, sets, and variables. It is passed as the +first argument to most functions. +

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly • +QhullSet • +StatUser

    + +

    Index to global_r.c and +libqhull_r.h

    + + + +

    »Qhull's global +variables

    + + + +

    »Global variable and +initialization routines

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-io_r.htm b/xs/src/qhull/src/libqhull_r/qh-io_r.htm new file mode 100644 index 0000000000..8a8a96300f --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-io_r.htm @@ -0,0 +1,305 @@ + + + + +io_r.c -- input and output operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    io_r.c -- input and output operations

    +
    + +

    Qhull provides a wide range of input +and output options. To organize the code, most output formats use +the same driver:

    + +
    +    qh_printbegin( fp, format, facetlist, facets, printall );
    +
    +    FORALLfacet_( facetlist )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    FOREACHfacet_( facets )
    +      qh_printafacet( fp, format, facet, printall );
    +
    +    qh_printend( fp, format );
    +
    + +

    Note the 'printall' flag. It selects whether or not +qh_skipfacet() is tested.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom +GlobalIo • +MemMerge • +PolyQhull • +SetStat • +User

    + +

    Index to io_r.c and io_r.h

    + + + +

    »io_r.h constants and types

    + +
      +
    • qh_MAXfirst maximum length +of first two lines of stdin
    • +
    • qh_WHITESPACE possible +values of white space
    • +
    • printvridgeT function to +print results of qh_printvdiagram or qh_eachvoronoi
    • +
    + +

    »User level functions

    + + + +

    »Print functions for all +output formats

    + + + +

    »Text output functions

    + + +

    »Text utility functions

    + + +

    »Geomview output functions

    + +

    »Geomview utility functions

    + +

    +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-mem_r.htm b/xs/src/qhull/src/libqhull_r/qh-mem_r.htm new file mode 100644 index 0000000000..db59119cb9 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-mem_r.htm @@ -0,0 +1,145 @@ + + + + +mem_r.c -- memory operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    mem_r.c -- memory operations

    +
    +

    Qhull uses quick-fit memory allocation. It maintains a +set of free lists for a variety of small allocations. A +small request returns a block from the best fitting free +list. If the free list is empty, Qhull allocates a block +from a reserved buffer.

    +

    Use 'T5' to trace memory allocations.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to mem_r.c and +mem_r.h

    + +

    »mem_r.h data types and constants

    +
      +
    • ptr_intT for casting +a void* to an integer-type
    • +
    • qhmemT global memory +structure for mem_r.c
    • +
    • qh_NOmem disable memory allocation
    • +
    +

    »mem_r.h macros

    + +

    »User level +functions

    + + +

    »Initialization and +termination functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-merge_r.htm b/xs/src/qhull/src/libqhull_r/qh-merge_r.htm new file mode 100644 index 0000000000..63e5135be1 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-merge_r.htm @@ -0,0 +1,366 @@ + + + + +merge_r.c -- facet merge operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    merge_r.c -- facet merge operations

    +
    +

    Qhull handles precision problems by merged facets or joggled input. +Except for redundant vertices, it corrects a problem by +merging two facets. When done, all facets are clearly +convex. See Imprecision in Qhull +for further information.

    +

    Users may joggle the input ('QJn') +instead of merging facets.

    +

    Qhull detects and corrects the following problems:

    +
      +
    • More than two facets meeting at a ridge. When +Qhull creates facets, it creates an even number +of facets for each ridge. A convex hull always +has two facets for each ridge. More than two +facets may be created if non-adjacent facets +share a vertex. This is called a duplicate +ridge. In 2-d, a duplicate ridge would +create a loop of facets.
    • +
    +
      +
    • A facet contained in another facet. Facet +merging may leave all vertices of one facet as a +subset of the vertices of another facet. This is +called a redundant facet.
    • +
    +
      +
    • A facet with fewer than three neighbors. Facet +merging may leave a facet with one or two +neighbors. This is called a degenerate facet. +
    • +
    +
      +
    • A facet with flipped orientation. A +facet's hyperplane may define a halfspace that +does not include the interior point.This is +called a flipped facet.
    • +
    +
      +
    • A coplanar horizon facet. A +newly processed point may be coplanar with an +horizon facet. Qhull creates a new facet without +a hyperplane. It links new facets for the same +horizon facet together. This is called a samecycle. +The new facet or samecycle is merged into the +horizon facet.
    • +
    +
      +
    • Concave facets. A facet's centrum may be +above a neighboring facet. If so, the facets meet +at a concave angle.
    • +
    +
      +
    • Coplanar facets. A facet's centrum may be +coplanar with a neighboring facet (i.e., it is +neither clearly below nor clearly above the +facet's hyperplane). Qhull removes coplanar +facets in independent sets sorted by angle.
    • +
    +
      +
    • Redundant vertex. A vertex may have fewer +than three neighboring facets. If so, it is +redundant and may be renamed to an adjacent +vertex without changing the topological +structure.This is called a redundant vertex. +
    • +
    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to merge_r.c and +merge_r.h

    + + +

    »merge_r.h data +types, macros, and global sets

    +
      +
    • mergeT structure to +identify a merge of two facets
    • +
    • FOREACHmerge_ +assign 'merge' to each merge in merges
    • +
    • qh global sets +qh.facet_mergeset contains non-convex merges +while qh.degen_mergeset contains degenerate and +redundant facets
    • +
    +

    »merge_r.h +constants

    + +

    »top-level merge +functions

    + + +

    »functions for +identifying merges

    + + +

    »functions for +determining the best merge

    + + +

    »functions for +merging facets

    + + +

    »functions for +merging a cycle of facets

    +

    If a point is coplanar with an horizon facet, the +corresponding new facets are linked together (a samecycle) +for merging.

    + +

    »functions +for renaming a vertex

    + + +

    »functions +for identifying vertices for renaming

    + + +

    »functions for check and +trace

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-poly_r.htm b/xs/src/qhull/src/libqhull_r/qh-poly_r.htm new file mode 100644 index 0000000000..c5b6f2f836 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-poly_r.htm @@ -0,0 +1,485 @@ + + + + +poly_r.c, poly2_r.c -- polyhedron operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    poly_r.c, poly2_r.c -- polyhedron operations

    +
    + +

    Qhull uses dimension-free terminology. Qhull builds a +polyhedron in dimension d. A polyhedron is a +simplicial complex of faces with geometric information for the +top and bottom-level faces. A (d-1)-face is a facet, +a (d-2)-face is a ridge, and a 0-face +is a vertex. For example in 3-d, a facet is a polygon +and a ridge is an edge. A facet is built from a ridge (the base) +and a vertex (the apex). See +Qhull's data structures.

    + +

    Qhull's primary data structure is a polyhedron. A +polyhedron is a list of facets. Each facet has a set of +neighboring facets and a set of vertices. Each facet has a +hyperplane. For example, a tetrahedron has four facets. +If its vertices are a, b, c, d, and its facets +are 1, 2, 3, 4, the tetrahedron is

    +
    +
      +
    • facet 1
        +
      • vertices: b c d
      • +
      • neighbors: 2 3 4
      • +
      +
    • +
    • facet 2
        +
      • vertices: a c d
      • +
      • neighbors: 1 3 4
      • +
      +
    • +
    • facet 3
        +
      • vertices: a b d
      • +
      • neighbors: 1 2 4
      • +
      +
    • +
    • facet 4
        +
      • vertices: a b c
      • +
      • neighbors: 1 2 3
      • +
      +
    • +
    +
    +

    A facet may be simplicial or non-simplicial. In 3-d, a +simplicial facet has three vertices and three +neighbors. A nonsimplicial facet has more than +three vertices and more than three neighbors. A +nonsimplicial facet has a set of ridges and a centrum.

    +

    +A simplicial facet has an orientation. An orientation +is either top or bottom. +The flag, facet->toporient, +defines the orientation of the facet's vertices. For example in 3-d, +'top' is left-handed orientation (i.e., the vertex order follows the direction +of the left-hand fingers when the thumb is pointing away from the center). +Except for axis-parallel facets in 5-d and higher, topological orientation +determines the geometric orientation of the facet's hyperplane. + +

    A nonsimplicial facet is due to merging two or more +facets. The facet's ridge set determine a simplicial +decomposition of the facet. Each ridge is a 1-face (i.e., +it has two vertices and two neighboring facets). The +orientation of a ridge is determined by the order of the +neighboring facets. The flag, facet->toporient,is +ignored.

    +

    A nonsimplicial facet has a centrum for testing +convexity. A centrum is a point on the facet's +hyperplane that is near the center of the facet. Except +for large facets, it is the arithmetic average of the +facet's vertices.

    +

    A nonsimplicial facet is an approximation that is +defined by offsets from the facet's hyperplane. When +Qhull finishes, the outer plane is above all +points while the inner plane is below the facet's +vertices. This guarantees that any exact convex hull +passes between the inner and outer planes. The outer +plane is defined by facet->maxoutside while +the inner plane is computed from the facet's vertices.

    + +

    Qhull 3.1 includes triangulation of non-simplicial facets +('Qt'). +These facets, +called tricoplanar, share the same normal. centrum, and Voronoi center. +One facet (keepcentrum) owns these data structures. +While tricoplanar facets are more accurate than the simplicial facets from +joggled input, they +may have zero area or flipped orientation. + +

    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to poly_r.c, +poly2_r.c, poly_r.h, +and libqhull_r.h

    + +

    »Data +types and global lists for polyhedrons

    + +

    »poly_r.h constants

    +
      +
    • ALGORITHMfault +flag to not report errors in qh_checkconvex()
    • +
    • DATAfault flag to +report errors in qh_checkconvex()
    • +
    • DUPLICATEridge +special value for facet->neighbor to indicate +a duplicate ridge
    • +
    • MERGEridge +special value for facet->neighbor to indicate +a merged ridge
    • +
    +

    »Global FORALL +macros

    + +

    »FORALL macros

    + +

    »FOREACH macros

    + +

    »Indexed +FOREACH macros

    +
      +
    • FOREACHfacet_i_ +assign 'facet' and 'facet_i' to each facet in +facet set
    • +
    • FOREACHneighbor_i_ +assign 'neighbor' and 'neighbor_i' to each facet +in facet->neighbors or vertex->neighbors
    • +
    • FOREACHpoint_i_ +assign 'point' and 'point_i' to each point in +points set
    • +
    • FOREACHridge_i_ +assign 'ridge' and 'ridge_i' to each ridge in +ridges set
    • +
    • FOREACHvertex_i_ +assign 'vertex' and 'vertex_i' to each vertex in +vertices set
    • +
    • FOREACHvertexreverse12_ +assign 'vertex' to each vertex in vertex set; +reverse the order of first two vertices
    • +
    +

    »Other macros for polyhedrons

    +
      +
    • getid_ return ID for +a facet, ridge, or vertex
    • +
    • otherfacet_ +return neighboring facet for a ridge in a facet
    • +
    +

    »Facetlist +functions

    + +

    »Facet +functions

    + +

    »Vertex, +ridge, and point functions

    + +

    »Hashtable functions

    + +

    »Allocation and +deallocation functions

    + +

    »Check +functions

    + + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-qhull_r.htm b/xs/src/qhull/src/libqhull_r/qh-qhull_r.htm new file mode 100644 index 0000000000..25d5e49722 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-qhull_r.htm @@ -0,0 +1,279 @@ + + + + +libqhull_r.c -- top-level functions and basic data types + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    libqhull_r.c -- top-level functions and basic data types

    +
    +

    Qhull implements the Quickhull algorithm for computing +the convex hull. The Quickhull algorithm combines two +well-known algorithms: the 2-d quickhull algorithm and +the n-d beneath-beyond algorithm. See +Description of Qhull.

    +

    This section provides an index to the top-level +functions and base data types. The top-level header file, libqhull_r.h, +contains prototypes for these functions.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to libqhull_r.c, +libqhull_r.h, and +unix_r.c

    + + +

    »libqhull_r.h and unix_r.c +data types and constants

    +
      +
    • flagT Boolean flag as +a bit
    • +
    • boolT boolean value, +either True or False
    • +
    • CENTERtype to +distinguish facet->center
    • +
    • qh_PRINT output +formats for printing (qh.PRINTout)
    • +
    • qh_ALL argument flag +for selecting everything
    • +
    • qh_ERR Qhull exit +codes for indicating errors
    • +
    • qh_FILEstderr Fake stderr +to distinguish error output from normal output [C++ only]
    • +
    • qh_prompt version and long prompt for Qhull
    • +
    • qh_prompt2 synopsis for Qhull
    • +
    • qh_prompt3 concise prompt for Qhull
    • +
    • qh_version version stamp
    • +
    + +

    »libqhull_r.h other +macros

    +
      +
    • traceN print trace +message if qh.IStracing >= N.
    • +
    • QHULL_UNUSED declare an + unused variable to avoid warnings.
    • +
    + +

    »Quickhull +routines in call order

    + + +

    »Top-level routines for initializing and terminating Qhull (in other modules)

    + + +

    »Top-level routines for reading and modifying the input (in other modules)

    + + +

    »Top-level routines for calling Qhull (in other modules)

    + + +

    »Top-level routines for returning results (in other modules)

    + + +

    »Top-level routines for testing and debugging (in other modules)

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-set_r.htm b/xs/src/qhull/src/libqhull_r/qh-set_r.htm new file mode 100644 index 0000000000..cf8ab63af9 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-set_r.htm @@ -0,0 +1,308 @@ + + + + +qset_r.c -- set data type and operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    qset_r.c -- set data type and operations

    +
    +

    Qhull's data structures are constructed from sets. The +functions and macros in qset_r.c construct, iterate, and +modify these sets. They are the most frequently called +functions in Qhull. For this reason, efficiency is the +primary concern.

    +

    In Qhull, a set is represented by an unordered +array of pointers with a maximum size and a NULL +terminator (setT). +Most sets correspond to mathematical sets +(i.e., the pointers are unique). Some sets are sorted to +enforce uniqueness. Some sets are ordered. For example, +the order of vertices in a ridge determine the ridge's +orientation. If you reverse the order of adjacent +vertices, the orientation reverses. Some sets are not +mathematical sets. They may be indexed as an array and +they may include NULL pointers.

    +

    The most common operation on a set is to iterate its +members. This is done with a 'FOREACH...' macro. Each set +has a custom macro. For example, 'FOREACHvertex_' +iterates over a set of vertices. Each vertex is assigned +to the variable 'vertex' from the pointer 'vertexp'.

    +

    Most sets are constructed by appending elements to the +set. The last element of a set is either NULL or the +index of the terminating NULL for a partially full set. +If a set is full, appending an element copies the set to +a larger array.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global • +IoMem • +MergePoly +• QhullSet +• StatUser +

    +

    Index to qset_r.c and +qset_r.h

    + +

    »Data types and +constants

    +
      +
    • SETelemsize size +of a set element in bytes
    • +
    • setT a set with a +maximum size and a current size
    • +
    • qh global sets +global sets for temporary sets, etc.
    • +
    +

    »FOREACH macros

    + +

    »Access and +size macros

    + +

    »Internal macros

    +
      +
    • SETsizeaddr_ +return pointer to end element of a set (indicates +current size)
    • +
    + +

    »address macros

    +
      +
    • SETaddr_ return +address of a set's elements
    • +
    • SETelemaddr_ +return address of the n'th element of a set
    • +
    • SETref_ l_r.h.s. for +modifying the current element in a FOREACH +iteration
    • +
    + +

    »Allocation and +deallocation functions

    + + +

    »Access and +predicate functions

    + + +

    »Add functions

    + + +

    »Check and print functions

    + + +

    »Copy, compact, and zero functions

    + + +

    »Delete functions

    + + +

    »Temporary set functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-stat_r.htm b/xs/src/qhull/src/libqhull_r/qh-stat_r.htm new file mode 100644 index 0000000000..ea9d7fc565 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-stat_r.htm @@ -0,0 +1,161 @@ + + + + +stat_r.c -- statistical operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    + +

    stat_r.c -- statistical operations

    +
    +

    Qhull records many statistics. These functions and +macros make it inexpensive to add a statistic. +

    As with Qhull's global variables, the statistics data structure is +accessed by a macro, 'qhstat'. If qh_QHpointer is defined, the macro +is 'qh_qhstat->', otherwise the macro is 'qh_qhstat.'. +Statistics +may be turned off in user_r.h. If so, all but the 'zz' +statistics are ignored.

    +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to stat_r.c and +stat_r.h

    + + +

    »stat_r.h types

    +
      +
    • intrealT union of +integer and real
    • +
    • qhstat global data +structure for statistics
    • +
    +

    »stat_r.h +constants

    +
      +
    • qh_KEEPstatistics 0 turns off most statistics
    • +
    • Z..., W... integer (Z) and real (W) statistics +
    • +
    • ZZstat Z.../W... statistics that +remain defined if qh_KEEPstatistics=0 +
    • +
    • ztype zdoc, zinc, etc. +for definining statistics
    • +
    +

    »stat_r.h macros

    +
      +
    • MAYdebugx called +frequently for error trapping
    • +
    • zadd_/wadd_ add value +to an integer or real statistic
    • +
    • zdef_ define a +statistic
    • +
    • zinc_ increment an +integer statistic
    • +
    • zmax_/wmax_ update +integer or real maximum statistic
    • +
    • zmin_/wmin_ update +integer or real minimum statistic
    • +
    • zval_/wval_ set or +return value of a statistic
    • +
    + +

    »stat_r.c +functions

    + + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qh-user_r.htm b/xs/src/qhull/src/libqhull_r/qh-user_r.htm new file mode 100644 index 0000000000..909fec6564 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qh-user_r.htm @@ -0,0 +1,271 @@ + + + + +user_r.c -- user-definable operations + + + + +

    Up: Home page for Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: GeomGlobal +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +
    +

    user_r.c -- user-definable operations

    +
    +

    This section contains functions and constants that the +user may want to change.

    + +
    +

    Copyright © 1995-2015 C.B. Barber

    +
    +

    » Geom + Global +• IoMem +• MergePoly +• QhullSet +• StatUser +

    +

    Index to user_r.c, usermem_r.c, userprintf_r.c, userprintf_rbox_r.c and +user_r.h

    + + +

    »Qhull library constants

    + + + +

    »user_r.h data +types and configuration macros

    + + +

    »definition constants

    +
      +
    • qh_DEFAULTbox +define default box size for rbox, 'Qbb', and 'QbB' (Geomview expects 0.5)
    • +
    • qh_INFINITE on +output, indicates Voronoi center at infinity
    • +
    • qh_ORIENTclock +define convention for orienting facets
    • +
    • qh_ZEROdelaunay +define facets that are ignored in Delaunay triangulations
    • +
    + +

    »joggle constants

    + + +

    »performance +related constants

    + + +

    »memory constants

    + + +

    »conditional compilation

    +
      +
    • compiler defined symbols, +e.g., _STDC_ and _cplusplus + +
    • qh_COMPUTEfurthest + compute furthest distance to an outside point instead of storing it with the facet +
    • qh_KEEPstatistics + enable statistic gathering and reporting with option 'Ts' +
    • qh_MAXoutside +record outer plane for each facet +
    • qh_NOmerge +disable facet merging +
    • qh_NOtrace +disable tracing with option 'T4' +
    • qh_QHpointer +access global data with pointer or static structure +
    • qh_QUICKhelp +use abbreviated help messages, e.g., for degenerate inputs +
    + +

    »merge +constants

    + + +

    »user_r.c +functions

    + + +

    »usermem_r.c +functions

    +
      +
    • qh_exit exit program, same as exit(). May be redefined as throw "QH10003.." by libqhullcpp/usermem_r-cpp.cpp
    • +
    • qh_fprintf_stderr print to stderr when qh->ferr is not defined.
    • +
    • qh_free free memory, same as free().
    • +
    • qh_malloc allocate memory, same as malloc()
    • +
    + +

    »userprintf_r.c + and userprintf_rbox,c functions

    +
      +
    • qh_fprintf print +information from Qhull, sames as fprintf().
    • +
    • qh_fprintf_rbox print +information from Rbox, sames as fprintf().
    • +
    + +

    +
    +

    Up: +Home page for +Qhull
    +Up: Qhull manual: Table of Contents
    +Up: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    +Up: Qhull code: Table of Contents
    +To: Qhull functions, macros, and data structures
    +To: Geom • +GlobalIo +• MemMerge +• PolyQhull +• SetStat +• User
    +

    +

    +
    +

    The +Geometry Center Home Page

    +

    Comments to: qhull@qhull.org +
    +Created: May 2, 1997 --- Last modified: see top

    + + diff --git a/xs/src/qhull/src/libqhull_r/qhull_r-exports.def b/xs/src/qhull/src/libqhull_r/qhull_r-exports.def new file mode 100644 index 0000000000..325d57c3b8 --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qhull_r-exports.def @@ -0,0 +1,404 @@ +; qhull_r-exports.def -- msvc module-definition file +; +; Generated from depends.exe by cut-and-paste of exported symbols by mingw gcc +; [jan'14] 391 symbols +; Same as ../libqhullp/qhull-exports.def without DATA items (reentrant) +; +; $Id: //main/2015/qhull/src/libqhull_r/qhull_r-exports.def#3 $$Change: 2047 $ +; $DateTime: 2016/01/04 22:03:18 $$Author: bbarber $ +; +; Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, and qhull-warn.pri +VERSION 7.0 +EXPORTS +qh_addhash +qh_addpoint +qh_all_merges +qh_allstatA +qh_allstatB +qh_allstatC +qh_allstatD +qh_allstatE +qh_allstatE2 +qh_allstatF +qh_allstatG +qh_allstatH +qh_allstatI +qh_allstatistics +qh_appendfacet +qh_appendmergeset +qh_appendprint +qh_appendvertex +qh_argv_to_command +qh_argv_to_command_size +qh_attachnewfacets +qh_backnormal +qh_basevertices +qh_build_withrestart +qh_buildhull +qh_buildtracing +qh_check_bestdist +qh_check_dupridge +qh_check_maxout +qh_check_output +qh_check_point +qh_check_points +qh_checkconnect +qh_checkconvex +qh_checkfacet +qh_checkflags +qh_checkflipped +qh_checkflipped_all +qh_checkpolygon +qh_checkvertex +qh_checkzero +qh_clear_outputflags +qh_clearcenters +qh_clock +qh_collectstatistics +qh_compare_facetarea +qh_compare_facetmerge +qh_compare_facetvisit +qh_compareangle +qh_comparemerge +qh_comparevisit +qh_copyfilename +qh_copynonconvex +qh_copypoints +qh_countfacets +qh_createsimplex +qh_crossproduct +qh_degen_redundant_facet +qh_degen_redundant_neighbors +qh_deletevisible +qh_delfacet +qh_delridge +qh_delvertex +qh_determinant +qh_detjoggle +qh_detroundoff +qh_detsimplex +qh_detvnorm +qh_detvridge +qh_detvridge3 +qh_dfacet +qh_distnorm +qh_distplane +qh_distround +qh_divzero +qh_dvertex +qh_eachvoronoi +qh_eachvoronoi_all +qh_errexit +qh_errexit2 +qh_errexit_rbox +qh_errprint +qh_exit +qh_facet2point +qh_facet3vertex +qh_facetarea +qh_facetarea_simplex +qh_facetcenter +qh_facetintersect +qh_facetvertices +qh_find_newvertex +qh_findbest +qh_findbest_test +qh_findbestfacet +qh_findbesthorizon +qh_findbestlower +qh_findbestneighbor +qh_findbestnew +qh_findfacet_all +qh_findgood +qh_findgood_all +qh_findgooddist +qh_findhorizon +qh_flippedmerges +qh_forcedmerges +qh_fprintf +qh_fprintf_rbox +qh_fprintf_stderr +qh_free +qh_freebuffers +qh_freebuild +qh_freeqhull +qh_furthestnext +qh_furthestout +qh_gausselim +qh_geomplanes +qh_getangle +qh_getarea +qh_getcenter +qh_getcentrum +qh_getdistance +qh_gethash +qh_getmergeset +qh_getmergeset_initial +qh_gram_schmidt +qh_hashridge +qh_hashridge_find +qh_infiniteloop +qh_init_A +qh_init_B +qh_init_qhull_command +qh_initbuild +qh_initflags +qh_initialhull +qh_initialvertices +qh_initqhull_buffers +qh_initqhull_globals +qh_initqhull_mem +qh_initqhull_outputflags +qh_initqhull_start +qh_initqhull_start2 +qh_initstatistics +qh_initthresholds +qh_inthresholds +qh_isvertex +qh_joggleinput +qh_lib_check +qh_makenew_nonsimplicial +qh_makenew_simplicial +qh_makenewfacet +qh_makenewfacets +qh_makenewplanes +qh_makeridges +qh_malloc +qh_mark_dupridges +qh_markkeep +qh_markvoronoi +qh_matchduplicates +qh_matchneighbor +qh_matchnewfacets +qh_matchvertices +qh_maxabsval +qh_maxmin +qh_maxouter +qh_maxsimplex +qh_maydropneighbor +qh_memalloc +qh_memfree +qh_memfreeshort +qh_meminit +qh_meminitbuffers +qh_memsetup +qh_memsize +qh_memstatistics +qh_memtotal +qh_merge_degenredundant +qh_merge_nonconvex +qh_mergecycle +qh_mergecycle_all +qh_mergecycle_facets +qh_mergecycle_neighbors +qh_mergecycle_ridges +qh_mergecycle_vneighbors +qh_mergefacet +qh_mergefacet2d +qh_mergeneighbors +qh_mergeridges +qh_mergesimplex +qh_mergevertex_del +qh_mergevertex_neighbors +qh_mergevertices +qh_minabsval +qh_mindiff +qh_nearcoplanar +qh_nearvertex +qh_neighbor_intersections +qh_new_qhull +qh_newfacet +qh_newhashtable +qh_newridge +qh_newstats +qh_newvertex +qh_newvertices +qh_nextfurthest +qh_nextridge3d +qh_normalize +qh_normalize2 +qh_nostatistic +qh_option +qh_order_vertexneighbors +qh_orientoutside +qh_out1 +qh_out2n +qh_out3n +qh_outcoplanar +qh_outerinner +qh_partitionall +qh_partitioncoplanar +qh_partitionpoint +qh_partitionvisible +qh_point +qh_point_add +qh_pointdist +qh_pointfacet +qh_pointid +qh_pointvertex +qh_postmerge +qh_precision +qh_premerge +qh_prepare_output +qh_prependfacet +qh_printafacet +qh_printallstatistics +qh_printbegin +qh_printcenter +qh_printcentrum +qh_printend +qh_printend4geom +qh_printextremes +qh_printextremes_2d +qh_printextremes_d +qh_printfacet +qh_printfacet2geom +qh_printfacet2geom_points +qh_printfacet2math +qh_printfacet3geom_nonsimplicial +qh_printfacet3geom_points +qh_printfacet3geom_simplicial +qh_printfacet3math +qh_printfacet3vertex +qh_printfacet4geom_nonsimplicial +qh_printfacet4geom_simplicial +qh_printfacetNvertex_nonsimplicial +qh_printfacetNvertex_simplicial +qh_printfacetheader +qh_printfacetlist +qh_printfacetridges +qh_printfacets +qh_printhashtable +qh_printhelp_degenerate +qh_printhelp_narrowhull +qh_printhelp_singular +qh_printhyperplaneintersection +qh_printline3geom +qh_printlists +qh_printmatrix +qh_printneighborhood +qh_printpoint +qh_printpoint3 +qh_printpointid +qh_printpoints +qh_printpoints_out +qh_printpointvect +qh_printpointvect2 +qh_printridge +qh_printspheres +qh_printstatistics +qh_printstatlevel +qh_printstats +qh_printsummary +qh_printvdiagram +qh_printvdiagram2 +qh_printvertex +qh_printvertexlist +qh_printvertices +qh_printvneighbors +qh_printvnorm +qh_printvoronoi +qh_printvridge +qh_produce_output +qh_produce_output2 +qh_projectdim3 +qh_projectinput +qh_projectpoint +qh_projectpoints +qh_qhull +qh_rand +qh_randomfactor +qh_randommatrix +qh_rboxpoints +qh_readfeasible +qh_readpoints +qh_reducevertices +qh_redundant_vertex +qh_remove_extravertices +qh_removefacet +qh_removevertex +qh_rename_sharedvertex +qh_renameridgevertex +qh_renamevertex +qh_resetlists +qh_rotateinput +qh_rotatepoints +qh_roundi +qh_scaleinput +qh_scalelast +qh_scalepoints +qh_setaddnth +qh_setaddsorted +qh_setappend +qh_setappend2ndlast +qh_setappend_set +qh_setcheck +qh_setcompact +qh_setcopy +qh_setdel +qh_setdelaunay +qh_setdellast +qh_setdelnth +qh_setdelnthsorted +qh_setdelsorted +qh_setduplicate +qh_setequal +qh_setequal_except +qh_setequal_skip +qh_setfacetplane +qh_setfeasible +qh_setfree +qh_setfree2 +qh_setfreelong +qh_sethalfspace +qh_sethalfspace_all +qh_sethyperplane_det +qh_sethyperplane_gauss +qh_setin +qh_setindex +qh_setlarger +qh_setlast +qh_setnew +qh_setnew_delnthsorted +qh_setprint +qh_setreplace +qh_setsize +qh_settemp +qh_settempfree +qh_settempfree_all +qh_settemppop +qh_settemppush +qh_settruncate +qh_setunique +qh_setvoronoi_all +qh_setzero +qh_sharpnewfacets +qh_skipfacet +qh_skipfilename +qh_srand +qh_stddev +qh_strtod +qh_strtol +qh_test_appendmerge +qh_test_vneighbors +qh_tracemerge +qh_tracemerging +qh_triangulate +qh_triangulate_facet +qh_triangulate_link +qh_triangulate_mirror +qh_triangulate_null +qh_updatetested +qh_updatevertices +qh_user_memsizes +qh_version +qh_version2 +qh_vertexintersect +qh_vertexintersect_new +qh_vertexneighbors +qh_vertexridges +qh_vertexridges_facet +qh_vertexsubset +qh_voronoi_center +qh_willdelete +qh_zero diff --git a/xs/src/qhull/src/libqhull_r/qhull_ra.h b/xs/src/qhull/src/libqhull_r/qhull_ra.h new file mode 100644 index 0000000000..5c5bd8779c --- /dev/null +++ b/xs/src/qhull/src/libqhull_r/qhull_ra.h @@ -0,0 +1,158 @@ +/*
      ---------------------------------
    +
    +   qhull_ra.h
    +   all header files for compiling qhull with reentrant code
    +   included before C++ headers for user_r.h:QHULL_CRTDBG
    +
    +   see qh-qhull.htm
    +
    +   see libqhull_r.h for user-level definitions
    +
    +   see user_r.h for user-definable constants
    +
    +   defines internal functions for libqhull_r.c global_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/qhull_ra.h#6 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +
    +   Notes:  grep for ((" and (" to catch fprintf("lkasdjf");
    +           full parens around (x?y:z)
    +           use '#include "libqhull_r/qhull_ra.h"' to avoid name clashes
    +*/
    +
    +#ifndef qhDEFqhulla
    +#define qhDEFqhulla 1
    +
    +#include "libqhull_r.h"  /* Includes user_r.h and data types */
    +
    +#include "stat_r.h"
    +#include "random_r.h"
    +#include "mem_r.h"
    +#include "qset_r.h"
    +#include "geom_r.h"
    +#include "merge_r.h"
    +#include "poly_r.h"
    +#include "io_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include     /* some compilers will not need float.h */
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +/*** uncomment here and qset_r.c
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#if qh_CLOCKtype == 2  /* defined in user_r.h from libqhull_r.h */
    +#include 
    +#include 
    +#include 
    +#endif
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4100)  /* unreferenced formal parameter */
    +#pragma warning( disable : 4127)  /* conditional expression is constant */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/* ======= -macros- =========== */
    +
    +/*----------------------------------
    +
    +  traceN((qh, qh->ferr, 0Nnnn, "format\n", vars));
    +    calls qh_fprintf if qh.IStracing >= N
    +
    +    Add debugging traps to the end of qh_fprintf
    +
    +  notes:
    +    removing tracing reduces code size but doesn't change execution speed
    +*/
    +#ifndef qh_NOtrace
    +#define trace0(args) {if (qh->IStracing) qh_fprintf args;}
    +#define trace1(args) {if (qh->IStracing >= 1) qh_fprintf args;}
    +#define trace2(args) {if (qh->IStracing >= 2) qh_fprintf args;}
    +#define trace3(args) {if (qh->IStracing >= 3) qh_fprintf args;}
    +#define trace4(args) {if (qh->IStracing >= 4) qh_fprintf args;}
    +#define trace5(args) {if (qh->IStracing >= 5) qh_fprintf args;}
    +#else /* qh_NOtrace */
    +#define trace0(args) {}
    +#define trace1(args) {}
    +#define trace2(args) {}
    +#define trace3(args) {}
    +#define trace4(args) {}
    +#define trace5(args) {}
    +#endif /* qh_NOtrace */
    +
    +/*----------------------------------
    +
    +  Define an unused variable to avoid compiler warnings
    +
    +  Derived from Qt's corelib/global/qglobal.h
    +
    +*/
    +
    +#if defined(__cplusplus) && defined(__INTEL_COMPILER) && !defined(QHULL_OS_WIN)
    +template 
    +inline void qhullUnused(T &x) { (void)x; }
    +#  define QHULL_UNUSED(x) qhullUnused(x);
    +#else
    +#  define QHULL_UNUSED(x) (void)x;
    +#endif
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +/***** -libqhull_r.c prototypes (alphabetical after qhull) ********************/
    +
    +void    qh_qhull(qhT *qh);
    +boolT   qh_addpoint(qhT *qh, pointT *furthest, facetT *facet, boolT checkdist);
    +void    qh_buildhull(qhT *qh);
    +void    qh_buildtracing(qhT *qh, pointT *furthest, facetT *facet);
    +void    qh_build_withrestart(qhT *qh);
    +void    qh_errexit2(qhT *qh, int exitcode, facetT *facet, facetT *otherfacet);
    +void    qh_findhorizon(qhT *qh, pointT *point, facetT *facet, int *goodvisible,int *goodhorizon);
    +pointT *qh_nextfurthest(qhT *qh, facetT **visible);
    +void    qh_partitionall(qhT *qh, setT *vertices, pointT *points,int npoints);
    +void    qh_partitioncoplanar(qhT *qh, pointT *point, facetT *facet, realT *dist);
    +void    qh_partitionpoint(qhT *qh, pointT *point, facetT *facet);
    +void    qh_partitionvisible(qhT *qh, boolT allpoints, int *numpoints);
    +void    qh_precision(qhT *qh, const char *reason);
    +void    qh_printsummary(qhT *qh, FILE *fp);
    +
    +/***** -global_r.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_appendprint(qhT *qh, qh_PRINT format);
    +void    qh_freebuild(qhT *qh, boolT allmem);
    +void    qh_freebuffers(qhT *qh);
    +void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +/***** -stat_r.c internal prototypes (alphabetical) ***********************/
    +
    +void    qh_allstatA(qhT *qh);
    +void    qh_allstatB(qhT *qh);
    +void    qh_allstatC(qhT *qh);
    +void    qh_allstatD(qhT *qh);
    +void    qh_allstatE(qhT *qh);
    +void    qh_allstatE2(qhT *qh);
    +void    qh_allstatF(qhT *qh);
    +void    qh_allstatG(qhT *qh);
    +void    qh_allstatH(qhT *qh);
    +void    qh_freebuffers(qhT *qh);
    +void    qh_initbuffers(qhT *qh, coordT *points, int numpoints, int dim, boolT ismalloc);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFqhulla */
    diff --git a/xs/src/qhull/src/libqhull_r/qset_r.c b/xs/src/qhull/src/libqhull_r/qset_r.c
    new file mode 100644
    index 0000000000..15cd3c0e29
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/qset_r.c
    @@ -0,0 +1,1340 @@
    +/*
      ---------------------------------
    +
    +   qset_r.c
    +   implements set manipulations needed for quickhull
    +
    +   see qh-set_r.htm and qset_r.h
    +
    +   Be careful of strict aliasing (two pointers of different types
    +   that reference the same location).  The last slot of a set is
    +   either the actual size of the set plus 1, or the NULL terminator
    +   of the set (i.e., setelemT).
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/qset_r.c#3 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r.h" /* for qhT and QHULL_CRTDBG */
    +#include "qset_r.h"
    +#include "mem_r.h"
    +#include 
    +#include 
    +/*** uncomment here and qhull_ra.h
    +     if string.h does not define memcpy()
    +#include 
    +*/
    +
    +#ifndef qhDEFlibqhull
    +typedef struct ridgeT ridgeT;
    +typedef struct facetT facetT;
    +void    qh_errexit(qhT *qh, int exitcode, facetT *, ridgeT *);
    +void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +#  ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#  pragma warning( disable : 4127)  /* conditional expression is constant */
    +#  pragma warning( disable : 4706)  /* assignment within conditional function */
    +#  endif
    +#endif
    +
    +/*=============== internal macros ===========================*/
    +
    +/*============ functions in alphabetical order ===================*/
    +
    +/*----------------------------------
    +
    +  qh_setaddnth(qh, setp, nth, newelem)
    +    adds newelem as n'th element of sorted or unsorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nth=0 is first element
    +    errors if nth is out of bounds
    +
    +  design:
    +    expand *setp if empty or full
    +    move tail of *setp up one
    +    insert newelem
    +*/
    +void qh_setaddnth(qhT *qh, setT **setp, int nth, void *newelem) {
    +  int oldsize, i;
    +  setelemT *sizep;          /* avoid strict aliasing */
    +  setelemT *oldp, *newp;
    +
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(qh, setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  oldsize= sizep->i - 1;
    +  if (nth < 0 || nth > oldsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6171, "qhull internal error (qh_setaddnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", *setp);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  sizep->i++;
    +  oldp= (setelemT *)SETelemaddr_(*setp, oldsize, void);   /* NULL */
    +  newp= oldp+1;
    +  for (i=oldsize-nth+1; i--; )  /* move at least NULL  */
    +    (newp--)->p= (oldp--)->p;       /* may overwrite *sizep */
    +  newp->p= newelem;
    +} /* setaddnth */
    +
    +
    +/*----------------------------------
    +
    +  setaddsorted( setp, newelem )
    +    adds an newelem into sorted *setp
    +
    +  notes:
    +    *setp and newelem must be defined
    +    *setp may be a temp set
    +    nop if newelem already in set
    +
    +  design:
    +    find newelem's position in *setp
    +    insert newelem
    +*/
    +void qh_setaddsorted(qhT *qh, setT **setp, void *newelem) {
    +  int newindex=0;
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(*setp) {          /* could use binary search instead */
    +    if (elem < newelem)
    +      newindex++;
    +    else if (elem == newelem)
    +      return;
    +    else
    +      break;
    +  }
    +  qh_setaddnth(qh, setp, newindex, newelem);
    +} /* setaddsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend(qh, setp, newelem)
    +    append newelem to *setp
    +
    +  notes:
    +    *setp may be a temp set
    +    *setp and newelem may be NULL
    +
    +  design:
    +    expand *setp if empty or full
    +    append newelem to *setp
    +
    +*/
    +void qh_setappend(qhT *qh, setT **setp, void *newelem) {
    +  setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +  setelemT *endp;
    +  int count;
    +
    +  if (!newelem)
    +    return;
    +  if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +    qh_setlarger(qh, setp);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  count= (sizep->i)++ - 1;
    +  endp= (setelemT *)SETelemaddr_(*setp, count, void);
    +  (endp++)->p= newelem;
    +  endp->p= NULL;
    +} /* setappend */
    +
    +/*---------------------------------
    +
    +  qh_setappend_set(qh, setp, setA)
    +    appends setA to *setp
    +
    +  notes:
    +    *setp can not be a temp set
    +    *setp and setA may be NULL
    +
    +  design:
    +    setup for copy
    +    expand *setp if it is too small
    +    append all elements of setA to *setp
    +*/
    +void qh_setappend_set(qhT *qh, setT **setp, setT *setA) {
    +  int sizeA, size;
    +  setT *oldset;
    +  setelemT *sizep;
    +
    +  if (!setA)
    +    return;
    +  SETreturnsize_(setA, sizeA);
    +  if (!*setp)
    +    *setp= qh_setnew(qh, sizeA);
    +  sizep= SETsizeaddr_(*setp);
    +  if (!(size= sizep->i))
    +    size= (*setp)->maxsize;
    +  else
    +    size--;
    +  if (size + sizeA > (*setp)->maxsize) {
    +    oldset= *setp;
    +    *setp= qh_setcopy(qh, oldset, sizeA);
    +    qh_setfree(qh, &oldset);
    +    sizep= SETsizeaddr_(*setp);
    +  }
    +  if (sizeA > 0) {
    +    sizep->i= size+sizeA+1;   /* memcpy may overwrite */
    +    memcpy((char *)&((*setp)->e[size].p), (char *)&(setA->e[0].p), (size_t)(sizeA+1) * SETelemsize);
    +  }
    +} /* setappend_set */
    +
    +
    +/*---------------------------------
    +
    +  qh_setappend2ndlast(qh, setp, newelem )
    +    makes newelem the next to the last element in *setp
    +
    +  notes:
    +    *setp must have at least one element
    +    newelem must be defined
    +    *setp may be a temp set
    +
    +  design:
    +    expand *setp if empty or full
    +    move last element of *setp up one
    +    insert newelem
    +*/
    +void qh_setappend2ndlast(qhT *qh, setT **setp, void *newelem) {
    +    setelemT *sizep;  /* Avoid strict aliasing.  Writing to *endp may overwrite *sizep */
    +    setelemT *endp, *lastp;
    +    int count;
    +
    +    if (!*setp || (sizep= SETsizeaddr_(*setp))->i==0) {
    +        qh_setlarger(qh, setp);
    +        sizep= SETsizeaddr_(*setp);
    +    }
    +    count= (sizep->i)++ - 1;
    +    endp= (setelemT *)SETelemaddr_(*setp, count, void); /* NULL */
    +    lastp= endp-1;
    +    *(endp++)= *lastp;
    +    endp->p= NULL;    /* may overwrite *sizep */
    +    lastp->p= newelem;
    +} /* setappend2ndlast */
    +
    +/*---------------------------------
    +
    +  qh_setcheck(qh, set, typename, id )
    +    check set for validity
    +    report errors with typename and id
    +
    +  design:
    +    checks that maxsize, actual size, and NULL terminator agree
    +*/
    +void qh_setcheck(qhT *qh, setT *set, const char *tname, unsigned id) {
    +  int maxsize, size;
    +  int waserr= 0;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  maxsize= set->maxsize;
    +  if (size > maxsize || !maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6172, "qhull internal error (qh_setcheck): actual size %d of %s%d is greater than max size %d\n",
    +             size, tname, id, maxsize);
    +    waserr= 1;
    +  }else if (set->e[size].p) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6173, "qhull internal error (qh_setcheck): %s%d(size %d max %d) is not null terminated.\n",
    +             tname, id, size-1, maxsize);
    +    waserr= 1;
    +  }
    +  if (waserr) {
    +    qh_setprint(qh, qh->qhmem.ferr, "ERRONEOUS", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setcheck */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcompact(qh, set )
    +    remove internal NULLs from an unsorted set
    +
    +  returns:
    +    updated set
    +
    +  notes:
    +    set may be NULL
    +    it would be faster to swap tail of set into holes, like qh_setdel
    +
    +  design:
    +    setup pointers into set
    +    skip NULLs while copying elements to start of set
    +    update the actual size
    +*/
    +void qh_setcompact(qhT *qh, setT *set) {
    +  int size;
    +  void **destp, **elemp, **endp, **firstp;
    +
    +  if (!set)
    +    return;
    +  SETreturnsize_(set, size);
    +  destp= elemp= firstp= SETaddr_(set, void);
    +  endp= destp + size;
    +  while (1) {
    +    if (!(*destp++ = *elemp++)) {
    +      destp--;
    +      if (elemp > endp)
    +        break;
    +    }
    +  }
    +  qh_settruncate(qh, set, (int)(destp-firstp));   /* WARN64 */
    +} /* setcompact */
    +
    +
    +/*---------------------------------
    +
    +  qh_setcopy(qh, set, extra )
    +    make a copy of a sorted or unsorted set with extra slots
    +
    +  returns:
    +    new set
    +
    +  design:
    +    create a newset with extra slots
    +    copy the elements to the newset
    +
    +*/
    +setT *qh_setcopy(qhT *qh, setT *set, int extra) {
    +  setT *newset;
    +  int size;
    +
    +  if (extra < 0)
    +    extra= 0;
    +  SETreturnsize_(set, size);
    +  newset= qh_setnew(qh, size+extra);
    +  SETsizeaddr_(newset)->i= size+1;    /* memcpy may overwrite */
    +  memcpy((char *)&(newset->e[0].p), (char *)&(set->e[0].p), (size_t)(size+1) * SETelemsize);
    +  return(newset);
    +} /* setcopy */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdel(set, oldelem )
    +    delete oldelem from an unsorted set
    +
    +  returns:
    +    returns oldelem if found
    +    returns NULL otherwise
    +
    +  notes:
    +    set may be NULL
    +    oldelem must not be NULL;
    +    only deletes one copy of oldelem in set
    +
    +  design:
    +    locate oldelem
    +    update actual size if it was full
    +    move the last element to the oldelem's location
    +*/
    +void *qh_setdel(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *elemp;
    +  setelemT *lastp;
    +
    +  if (!set)
    +    return NULL;
    +  elemp= (setelemT *)SETaddr_(set, void);
    +  while (elemp->p != oldelem && elemp->p)
    +    elemp++;
    +  if (elemp->p) {
    +    sizep= SETsizeaddr_(set);
    +    if (!(sizep->i)--)         /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +    lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +    elemp->p= lastp->p;      /* may overwrite itself */
    +    lastp->p= NULL;
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdel */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdellast(set)
    +    return last element of set or NULL
    +
    +  notes:
    +    deletes element from set
    +    set may be NULL
    +
    +  design:
    +    return NULL if empty
    +    if full set
    +      delete last element and set actual size
    +    else
    +      delete last element and update actual size
    +*/
    +void *qh_setdellast(setT *set) {
    +  int setsize;  /* actually, actual_size + 1 */
    +  int maxsize;
    +  setelemT *sizep;
    +  void *returnvalue;
    +
    +  if (!set || !(set->e[0].p))
    +    return NULL;
    +  sizep= SETsizeaddr_(set);
    +  if ((setsize= sizep->i)) {
    +    returnvalue= set->e[setsize - 2].p;
    +    set->e[setsize - 2].p= NULL;
    +    sizep->i--;
    +  }else {
    +    maxsize= set->maxsize;
    +    returnvalue= set->e[maxsize - 1].p;
    +    set->e[maxsize - 1].p= NULL;
    +    sizep->i= maxsize;
    +  }
    +  return returnvalue;
    +} /* setdellast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelnth(qh, set, nth )
    +    deletes nth element from unsorted set
    +    0 is first element
    +
    +  returns:
    +    returns the element (needs type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  design:
    +    setup points and check nth
    +    delete nth element and overwrite with last element
    +*/
    +void *qh_setdelnth(qhT *qh, setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *elemp, *lastp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +    sizep->i= set->maxsize;  /*     *sizep= (maxsize-1)+ 1 */
    +  if (nth < 0 || nth >= sizep->i) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6174, "qhull internal error (qh_setdelnth): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  elemp= (setelemT *)SETelemaddr_(set, nth, void); /* nth valid by QH6174 */
    +  lastp= (setelemT *)SETelemaddr_(set, sizep->i-1, void);
    +  elem= elemp->p;
    +  elemp->p= lastp->p;      /* may overwrite itself */
    +  lastp->p= NULL;
    +  return elem;
    +} /* setdelnth */
    +
    +/*---------------------------------
    +
    +  qh_setdelnthsorted(qh, set, nth )
    +    deletes nth element from sorted set
    +
    +  returns:
    +    returns the element (use type conversion)
    +
    +  notes:
    +    errors if nth invalid
    +
    +  see also:
    +    setnew_delnthsorted
    +
    +  design:
    +    setup points and check nth
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelnthsorted(qhT *qh, setT *set, int nth) {
    +  void *elem;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  sizep= SETsizeaddr_(set);
    +  if (nth < 0 || (sizep->i && nth >= sizep->i-1) || nth >= set->maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6175, "qhull internal error (qh_setdelnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newp= (setelemT *)SETelemaddr_(set, nth, void);
    +  elem= newp->p;
    +  oldp= newp+1;
    +  while (((newp++)->p= (oldp++)->p))
    +    ; /* copy remaining elements and NULL */
    +  if ((sizep->i--)==0)         /*  if was a full set */
    +    sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +  return elem;
    +} /* setdelnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setdelsorted(set, oldelem )
    +    deletes oldelem from sorted set
    +
    +  returns:
    +    returns oldelem if it was deleted
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    locate oldelem in set
    +    copy remaining elements down one
    +    update actual size
    +*/
    +void *qh_setdelsorted(setT *set, void *oldelem) {
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (!set)
    +    return NULL;
    +  newp= (setelemT *)SETaddr_(set, void);
    +  while(newp->p != oldelem && newp->p)
    +    newp++;
    +  if (newp->p) {
    +    oldp= newp+1;
    +    while (((newp++)->p= (oldp++)->p))
    +      ; /* copy remaining elements */
    +    sizep= SETsizeaddr_(set);
    +    if ((sizep->i--)==0)    /*  if was a full set */
    +      sizep->i= set->maxsize;  /*     *sizep= (max size-1)+ 1 */
    +    return oldelem;
    +  }
    +  return NULL;
    +} /* setdelsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setduplicate(qh, set, elemsize )
    +    duplicate a set of elemsize elements
    +
    +  notes:
    +    use setcopy if retaining old elements
    +
    +  design:
    +    create a new set
    +    for each elem of the old set
    +      create a newelem
    +      append newelem to newset
    +*/
    +setT *qh_setduplicate(qhT *qh, setT *set, int elemsize) {
    +  void          *elem, **elemp, *newElem;
    +  setT          *newSet;
    +  int           size;
    +
    +  if (!(size= qh_setsize(qh, set)))
    +    return NULL;
    +  newSet= qh_setnew(qh, size);
    +  FOREACHelem_(set) {
    +    newElem= qh_memalloc(qh, elemsize);
    +    memcpy(newElem, elem, (size_t)elemsize);
    +    qh_setappend(qh, &newSet, newElem);
    +  }
    +  return newSet;
    +} /* setduplicate */
    +
    +
    +/*---------------------------------
    +
    +  qh_setendpointer( set )
    +    Returns pointer to NULL terminator of a set's elements
    +    set can not be NULL
    +
    +*/
    +void **qh_setendpointer(setT *set) {
    +
    +  setelemT *sizep= SETsizeaddr_(set);
    +  int n= sizep->i;
    +  return (n ? &set->e[n-1].p : &sizep->p);
    +} /* qh_setendpointer */
    +
    +/*---------------------------------
    +
    +  qh_setequal( setA, setB )
    +    returns 1 if two sorted sets are equal, otherwise returns 0
    +
    +  notes:
    +    either set may be NULL
    +
    +  design:
    +    check size of each set
    +    setup pointers
    +    compare elements of each set
    +*/
    +int qh_setequal(setT *setA, setT *setB) {
    +  void **elemAp, **elemBp;
    +  int sizeA= 0, sizeB= 0;
    +
    +  if (setA) {
    +    SETreturnsize_(setA, sizeA);
    +  }
    +  if (setB) {
    +    SETreturnsize_(setB, sizeB);
    +  }
    +  if (sizeA != sizeB)
    +    return 0;
    +  if (!sizeA)
    +    return 1;
    +  elemAp= SETaddr_(setA, void);
    +  elemBp= SETaddr_(setB, void);
    +  if (!memcmp((char *)elemAp, (char *)elemBp, sizeA*SETelemsize))
    +    return 1;
    +  return 0;
    +} /* setequal */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_except( setA, skipelemA, setB, skipelemB )
    +    returns 1 if sorted setA and setB are equal except for skipelemA & B
    +
    +  returns:
    +    false if either skipelemA or skipelemB are missing
    +
    +  notes:
    +    neither set may be NULL
    +
    +    if skipelemB is NULL,
    +      can skip any one element of setB
    +
    +  design:
    +    setup pointers
    +    search for skipelemA, skipelemB, and mismatches
    +    check results
    +*/
    +int qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB) {
    +  void **elemA, **elemB;
    +  int skip=0;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  while (1) {
    +    if (*elemA == skipelemA) {
    +      skip++;
    +      elemA++;
    +    }
    +    if (skipelemB) {
    +      if (*elemB == skipelemB) {
    +        skip++;
    +        elemB++;
    +      }
    +    }else if (*elemA != *elemB) {
    +      skip++;
    +      if (!(skipelemB= *elemB++))
    +        return 0;
    +    }
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (skip != 2 || *elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_except */
    +
    +
    +/*---------------------------------
    +
    +  qh_setequal_skip( setA, skipA, setB, skipB )
    +    returns 1 if sorted setA and setB are equal except for elements skipA & B
    +
    +  returns:
    +    false if different size
    +
    +  notes:
    +    neither set may be NULL
    +
    +  design:
    +    setup pointers
    +    search for mismatches while skipping skipA and skipB
    +*/
    +int qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB) {
    +  void **elemA, **elemB, **skipAp, **skipBp;
    +
    +  elemA= SETaddr_(setA, void);
    +  elemB= SETaddr_(setB, void);
    +  skipAp= SETelemaddr_(setA, skipA, void);
    +  skipBp= SETelemaddr_(setB, skipB, void);
    +  while (1) {
    +    if (elemA == skipAp)
    +      elemA++;
    +    if (elemB == skipBp)
    +      elemB++;
    +    if (!*elemA)
    +      break;
    +    if (*elemA++ != *elemB++)
    +      return 0;
    +  }
    +  if (*elemB)
    +    return 0;
    +  return 1;
    +} /* setequal_skip */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree(qh, setp )
    +    frees the space occupied by a sorted or unsorted set
    +
    +  returns:
    +    sets setp to NULL
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free array
    +    free set
    +*/
    +void qh_setfree(qhT *qh, setT **setp) {
    +  int size;
    +  void **freelistp;  /* used if !qh_NOmem by qh_memfree_() */
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size <= qh->qhmem.LASTsize) {
    +      qh_memfree_(qh, *setp, size, freelistp);
    +    }else
    +      qh_memfree(qh, *setp, size);
    +    *setp= NULL;
    +  }
    +} /* setfree */
    +
    +
    +/*---------------------------------
    +
    +  qh_setfree2(qh, setp, elemsize )
    +    frees the space occupied by a set and its elements
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    free each element
    +    free set
    +*/
    +void qh_setfree2(qhT *qh, setT **setp, int elemsize) {
    +  void          *elem, **elemp;
    +
    +  FOREACHelem_(*setp)
    +    qh_memfree(qh, elem, elemsize);
    +  qh_setfree(qh, setp);
    +} /* setfree2 */
    +
    +
    +
    +/*---------------------------------
    +
    +  qh_setfreelong(qh, setp )
    +    frees a set only if it's in long memory
    +
    +  returns:
    +    sets setp to NULL if it is freed
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    if set is large
    +      free it
    +*/
    +void qh_setfreelong(qhT *qh, setT **setp) {
    +  int size;
    +
    +  if (*setp) {
    +    size= sizeof(setT) + ((*setp)->maxsize)*SETelemsize;
    +    if (size > qh->qhmem.LASTsize) {
    +      qh_memfree(qh, *setp, size);
    +      *setp= NULL;
    +    }
    +  }
    +} /* setfreelong */
    +
    +
    +/*---------------------------------
    +
    +  qh_setin(set, setelem )
    +    returns 1 if setelem is in a set, 0 otherwise
    +
    +  notes:
    +    set may be NULL or unsorted
    +
    +  design:
    +    scans set for setelem
    +*/
    +int qh_setin(setT *set, void *setelem) {
    +  void *elem, **elemp;
    +
    +  FOREACHelem_(set) {
    +    if (elem == setelem)
    +      return 1;
    +  }
    +  return 0;
    +} /* setin */
    +
    +
    +/*---------------------------------
    +
    +  qh_setindex(set, atelem )
    +    returns the index of atelem in set.
    +    returns -1, if not in set or maxsize wrong
    +
    +  notes:
    +    set may be NULL and may contain nulls.
    +    NOerrors returned (qh_pointid, QhullPoint::id)
    +
    +  design:
    +    checks maxsize
    +    scans set for atelem
    +*/
    +int qh_setindex(setT *set, void *atelem) {
    +  void **elem;
    +  int size, i;
    +
    +  if (!set)
    +    return -1;
    +  SETreturnsize_(set, size);
    +  if (size > set->maxsize)
    +    return -1;
    +  elem= SETaddr_(set, void);
    +  for (i=0; i < size; i++) {
    +    if (*elem++ == atelem)
    +      return i;
    +  }
    +  return -1;
    +} /* setindex */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlarger(qh, oldsetp )
    +    returns a larger set that contains all elements of *oldsetp
    +
    +  notes:
    +    the set is at least twice as large
    +    if temp set, updates qh->qhmem.tempstack
    +
    +  design:
    +    creates a new set
    +    copies the old set to the new set
    +    updates pointers in tempstack
    +    deletes the old set
    +*/
    +void qh_setlarger(qhT *qh, setT **oldsetp) {
    +  int size= 1;
    +  setT *newset, *set, **setp, *oldset;
    +  setelemT *sizep;
    +  setelemT *newp, *oldp;
    +
    +  if (*oldsetp) {
    +    oldset= *oldsetp;
    +    SETreturnsize_(oldset, size);
    +    qh->qhmem.cntlarger++;
    +    qh->qhmem.totlarger += size+1;
    +    newset= qh_setnew(qh, 2 * size);
    +    oldp= (setelemT *)SETaddr_(oldset, void);
    +    newp= (setelemT *)SETaddr_(newset, void);
    +    memcpy((char *)newp, (char *)oldp, (size_t)(size+1) * SETelemsize);
    +    sizep= SETsizeaddr_(newset);
    +    sizep->i= size+1;
    +    FOREACHset_((setT *)qh->qhmem.tempstack) {
    +      if (set == oldset)
    +        *(setp-1)= newset;
    +    }
    +    qh_setfree(qh, oldsetp);
    +  }else
    +    newset= qh_setnew(qh, 3);
    +  *oldsetp= newset;
    +} /* setlarger */
    +
    +
    +/*---------------------------------
    +
    +  qh_setlast( set )
    +    return last element of set or NULL (use type conversion)
    +
    +  notes:
    +    set may be NULL
    +
    +  design:
    +    return last element
    +*/
    +void *qh_setlast(setT *set) {
    +  int size;
    +
    +  if (set) {
    +    size= SETsizeaddr_(set)->i;
    +    if (!size)
    +      return SETelem_(set, set->maxsize - 1);
    +    else if (size > 1)
    +      return SETelem_(set, size - 2);
    +  }
    +  return NULL;
    +} /* setlast */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew(qh, setsize )
    +    creates and allocates space for a set
    +
    +  notes:
    +    setsize means the number of elements (!including the NULL terminator)
    +    use qh_settemp/qh_setfreetemp if set is temporary
    +
    +  design:
    +    allocate memory for set
    +    roundup memory if small set
    +    initialize as empty set
    +*/
    +setT *qh_setnew(qhT *qh, int setsize) {
    +  setT *set;
    +  int sizereceived; /* used if !qh_NOmem */
    +  int size;
    +  void **freelistp; /* used if !qh_NOmem by qh_memalloc_() */
    +
    +  if (!setsize)
    +    setsize++;
    +  size= sizeof(setT) + setsize * SETelemsize;
    +  if (size>0 && size <= qh->qhmem.LASTsize) {
    +    qh_memalloc_(qh, size, freelistp, set, setT);
    +#ifndef qh_NOmem
    +    sizereceived= qh->qhmem.sizetable[ qh->qhmem.indextable[size]];
    +    if (sizereceived > size)
    +      setsize += (sizereceived - size)/SETelemsize;
    +#endif
    +  }else
    +    set= (setT*)qh_memalloc(qh, size);
    +  set->maxsize= setsize;
    +  set->e[setsize].i= 1;
    +  set->e[0].p= NULL;
    +  return(set);
    +} /* setnew */
    +
    +
    +/*---------------------------------
    +
    +  qh_setnew_delnthsorted(qh, set, size, nth, prepend )
    +    creates a sorted set not containing nth element
    +    if prepend, the first prepend elements are undefined
    +
    +  notes:
    +    set must be defined
    +    checks nth
    +    see also: setdelnthsorted
    +
    +  design:
    +    create new set
    +    setup pointers and allocate room for prepend'ed entries
    +    append head of old set to new set
    +    append tail of old set to new set
    +*/
    +setT *qh_setnew_delnthsorted(qhT *qh, setT *set, int size, int nth, int prepend) {
    +  setT *newset;
    +  void **oldp, **newp;
    +  int tailsize= size - nth -1, newsize;
    +
    +  if (tailsize < 0) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6176, "qhull internal error (qh_setnew_delnthsorted): nth %d is out-of-bounds for set:\n", nth);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  newsize= size-1 + prepend;
    +  newset= qh_setnew(qh, newsize);
    +  newset->e[newset->maxsize].i= newsize+1;  /* may be overwritten */
    +  oldp= SETaddr_(set, void);
    +  newp= SETaddr_(newset, void) + prepend;
    +  switch (nth) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)nth * SETelemsize);
    +    newp += nth;
    +    oldp += nth;
    +    break;
    +  }
    +  oldp++;
    +  switch (tailsize) {
    +  case 0:
    +    break;
    +  case 1:
    +    *(newp++)= *oldp++;
    +    break;
    +  case 2:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 3:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  case 4:
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    *(newp++)= *oldp++;
    +    break;
    +  default:
    +    memcpy((char *)newp, (char *)oldp, (size_t)tailsize * SETelemsize);
    +    newp += tailsize;
    +  }
    +  *newp= NULL;
    +  return(newset);
    +} /* setnew_delnthsorted */
    +
    +
    +/*---------------------------------
    +
    +  qh_setprint(qh, fp, string, set )
    +    print set elements to fp with identifying string
    +
    +  notes:
    +    never errors
    +*/
    +void qh_setprint(qhT *qh, FILE *fp, const char* string, setT *set) {
    +  int size, k;
    +
    +  if (!set)
    +    qh_fprintf(qh, fp, 9346, "%s set is null\n", string);
    +  else {
    +    SETreturnsize_(set, size);
    +    qh_fprintf(qh, fp, 9347, "%s set=%p maxsize=%d size=%d elems=",
    +             string, set, set->maxsize, size);
    +    if (size > set->maxsize)
    +      size= set->maxsize+1;
    +    for (k=0; k < size; k++)
    +      qh_fprintf(qh, fp, 9348, " %p", set->e[k].p);
    +    qh_fprintf(qh, fp, 9349, "\n");
    +  }
    +} /* setprint */
    +
    +/*---------------------------------
    +
    +  qh_setreplace(qh, set, oldelem, newelem )
    +    replaces oldelem in set with newelem
    +
    +  notes:
    +    errors if oldelem not in the set
    +    newelem may be NULL, but it turns the set into an indexed set (no FOREACH)
    +
    +  design:
    +    find oldelem
    +    replace with newelem
    +*/
    +void qh_setreplace(qhT *qh, setT *set, void *oldelem, void *newelem) {
    +  void **elemp;
    +
    +  elemp= SETaddr_(set, void);
    +  while (*elemp != oldelem && *elemp)
    +    elemp++;
    +  if (*elemp)
    +    *elemp= newelem;
    +  else {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6177, "qhull internal error (qh_setreplace): elem %p not found in set\n",
    +       oldelem);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +} /* setreplace */
    +
    +
    +/*---------------------------------
    +
    +  qh_setsize(qh, set )
    +    returns the size of a set
    +
    +  notes:
    +    errors if set's maxsize is incorrect
    +    same as SETreturnsize_(set)
    +    same code for qh_setsize [qset_r.c] and QhullSetBase::count
    +
    +  design:
    +    determine actual size of set from maxsize
    +*/
    +int qh_setsize(qhT *qh, setT *set) {
    +  int size;
    +  setelemT *sizep;
    +
    +  if (!set)
    +    return(0);
    +  sizep= SETsizeaddr_(set);
    +  if ((size= sizep->i)) {
    +    size--;
    +    if (size > set->maxsize) {
    +      qh_fprintf(qh, qh->qhmem.ferr, 6178, "qhull internal error (qh_setsize): current set size %d is greater than maximum size %d\n",
    +               size, set->maxsize);
    +      qh_setprint(qh, qh->qhmem.ferr, "set: ", set);
    +      qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +    }
    +  }else
    +    size= set->maxsize;
    +  return size;
    +} /* setsize */
    +
    +/*---------------------------------
    +
    +  qh_settemp(qh, setsize )
    +    return a stacked, temporary set of upto setsize elements
    +
    +  notes:
    +    use settempfree or settempfree_all to release from qh->qhmem.tempstack
    +    see also qh_setnew
    +
    +  design:
    +    allocate set
    +    append to qh->qhmem.tempstack
    +
    +*/
    +setT *qh_settemp(qhT *qh, int setsize) {
    +  setT *newset;
    +
    +  newset= qh_setnew(qh, setsize);
    +  qh_setappend(qh, &qh->qhmem.tempstack, newset);
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8123, "qh_settemp: temp set %p of %d elements, depth %d\n",
    +       newset, newset->maxsize, qh_setsize(qh, qh->qhmem.tempstack));
    +  return newset;
    +} /* settemp */
    +
    +/*---------------------------------
    +
    +  qh_settempfree(qh, set )
    +    free temporary set at top of qh->qhmem.tempstack
    +
    +  notes:
    +    nop if set is NULL
    +    errors if set not from previous   qh_settemp
    +
    +  to locate errors:
    +    use 'T2' to find source and then find mis-matching qh_settemp
    +
    +  design:
    +    check top of qh->qhmem.tempstack
    +    free it
    +*/
    +void qh_settempfree(qhT *qh, setT **set) {
    +  setT *stackedset;
    +
    +  if (!*set)
    +    return;
    +  stackedset= qh_settemppop(qh);
    +  if (stackedset != *set) {
    +    qh_settemppush(qh, stackedset);
    +    qh_fprintf(qh, qh->qhmem.ferr, 6179, "qhull internal error (qh_settempfree): set %p(size %d) was not last temporary allocated(depth %d, set %p, size %d)\n",
    +             *set, qh_setsize(qh, *set), qh_setsize(qh, qh->qhmem.tempstack)+1,
    +             stackedset, qh_setsize(qh, stackedset));
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setfree(qh, set);
    +} /* settempfree */
    +
    +/*---------------------------------
    +
    +  qh_settempfree_all(qh)
    +    free all temporary sets in qh->qhmem.tempstack
    +
    +  design:
    +    for each set in tempstack
    +      free set
    +    free qh->qhmem.tempstack
    +*/
    +void qh_settempfree_all(qhT *qh) {
    +  setT *set, **setp;
    +
    +  FOREACHset_(qh->qhmem.tempstack)
    +    qh_setfree(qh, &set);
    +  qh_setfree(qh, &qh->qhmem.tempstack);
    +} /* settempfree_all */
    +
    +/*---------------------------------
    +
    +  qh_settemppop(qh)
    +    pop and return temporary set from qh->qhmem.tempstack
    +
    +  notes:
    +    the returned set is permanent
    +
    +  design:
    +    pop and check top of qh->qhmem.tempstack
    +*/
    +setT *qh_settemppop(qhT *qh) {
    +  setT *stackedset;
    +
    +  stackedset= (setT*)qh_setdellast(qh->qhmem.tempstack);
    +  if (!stackedset) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6180, "qhull internal error (qh_settemppop): pop from empty temporary stack\n");
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8124, "qh_settemppop: depth %d temp set %p of %d elements\n",
    +       qh_setsize(qh, qh->qhmem.tempstack)+1, stackedset, qh_setsize(qh, stackedset));
    +  return stackedset;
    +} /* settemppop */
    +
    +/*---------------------------------
    +
    +  qh_settemppush(qh, set )
    +    push temporary set unto qh->qhmem.tempstack (makes it temporary)
    +
    +  notes:
    +    duplicates settemp() for tracing
    +
    +  design:
    +    append set to tempstack
    +*/
    +void qh_settemppush(qhT *qh, setT *set) {
    +  if (!set) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6267, "qhull error (qh_settemppush): can not push a NULL temp\n");
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  qh_setappend(qh, &qh->qhmem.tempstack, set);
    +  if (qh->qhmem.IStracing >= 5)
    +    qh_fprintf(qh, qh->qhmem.ferr, 8125, "qh_settemppush: depth %d temp set %p of %d elements\n",
    +      qh_setsize(qh, qh->qhmem.tempstack), set, qh_setsize(qh, set));
    +} /* settemppush */
    +
    +
    +/*---------------------------------
    +
    +  qh_settruncate(qh, set, size )
    +    truncate set to size elements
    +
    +  notes:
    +    set must be defined
    +
    +  see:
    +    SETtruncate_
    +
    +  design:
    +    check size
    +    update actual size of set
    +*/
    +void qh_settruncate(qhT *qh, setT *set, int size) {
    +
    +  if (size < 0 || size > set->maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6181, "qhull internal error (qh_settruncate): size %d out of bounds for set:\n", size);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i= size+1;   /* maybe overwritten */
    +  set->e[size].p= NULL;
    +} /* settruncate */
    +
    +/*---------------------------------
    +
    +  qh_setunique(qh, set, elem )
    +    add elem to unsorted set unless it is already in set
    +
    +  notes:
    +    returns 1 if it is appended
    +
    +  design:
    +    if elem not in set
    +      append elem to set
    +*/
    +int qh_setunique(qhT *qh, setT **set, void *elem) {
    +
    +  if (!qh_setin(*set, elem)) {
    +    qh_setappend(qh, set, elem);
    +    return 1;
    +  }
    +  return 0;
    +} /* setunique */
    +
    +/*---------------------------------
    +
    +  qh_setzero(qh, set, index, size )
    +    zero elements from index on
    +    set actual size of set to size
    +
    +  notes:
    +    set must be defined
    +    the set becomes an indexed set (can not use FOREACH...)
    +
    +  see also:
    +    qh_settruncate
    +
    +  design:
    +    check index and size
    +    update actual size
    +    zero elements starting at e[index]
    +*/
    +void qh_setzero(qhT *qh, setT *set, int idx, int size) {
    +  int count;
    +
    +  if (idx < 0 || idx >= size || size > set->maxsize) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6182, "qhull internal error (qh_setzero): index %d or size %d out of bounds for set:\n", idx, size);
    +    qh_setprint(qh, qh->qhmem.ferr, "", set);
    +    qh_errexit(qh, qhmem_ERRqhull, NULL, NULL);
    +  }
    +  set->e[set->maxsize].i=  size+1;  /* may be overwritten */
    +  count= size - idx + 1;   /* +1 for NULL terminator */
    +  memset((char *)SETelemaddr_(set, idx, void), 0, (size_t)count * SETelemsize);
    +} /* setzero */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/qset_r.h b/xs/src/qhull/src/libqhull_r/qset_r.h
    new file mode 100644
    index 0000000000..7ba199d6f4
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/qset_r.h
    @@ -0,0 +1,502 @@
    +/*
      ---------------------------------
    +
    +   qset_r.h
    +     header file for qset_r.c that implements set
    +
    +   see qh-set_r.htm and qset_r.c
    +
    +   only uses mem_r.c, malloc/free
    +
    +   for error handling, writes message and calls
    +      qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL);
    +
    +   set operations satisfy the following properties:
    +    - sets have a max size, the actual size (if different) is stored at the end
    +    - every set is NULL terminated
    +    - sets may be sorted or unsorted, the caller must distinguish this
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/qset_r.h#4 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFset
    +#define qhDEFset 1
    +
    +#include 
    +
    +/*================= -structures- ===============*/
    +
    +#ifndef DEFsetT
    +#define DEFsetT 1
    +typedef struct setT setT;   /* a set is a sorted or unsorted array of pointers */
    +#endif
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;          /* defined in libqhull_r.h */
    +#endif
    +
    +/* [jan'15] Decided not to use countT.  Most sets are small.  The code uses signed tests */
    +
    +/*------------------------------------------
    +
    +setT
    +  a set or list of pointers with maximum size and actual size.
    +
    +variations:
    +  unsorted, unique   -- a list of unique pointers with NULL terminator
    +                           user guarantees uniqueness
    +  sorted             -- a sorted list of unique pointers with NULL terminator
    +                           qset_r.c guarantees uniqueness
    +  unsorted           -- a list of pointers terminated with NULL
    +  indexed            -- an array of pointers with NULL elements
    +
    +structure for set of n elements:
    +
    +        --------------
    +        |  maxsize
    +        --------------
    +        |  e[0] - a pointer, may be NULL for indexed sets
    +        --------------
    +        |  e[1]
    +
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[n-1]
    +        --------------
    +        |  e[n] = NULL
    +        --------------
    +        |  ...
    +        --------------
    +        |  e[maxsize] - n+1 or NULL (determines actual size of set)
    +        --------------
    +
    +*/
    +
    +/*-- setelemT -- internal type to allow both pointers and indices
    +*/
    +typedef union setelemT setelemT;
    +union setelemT {
    +  void    *p;
    +  int   i;         /* integer used for e[maxSize] */
    +};
    +
    +struct setT {
    +  int maxsize;          /* maximum number of elements (except NULL) */
    +  setelemT e[1];        /* array of pointers, tail is NULL */
    +                        /* last slot (unless NULL) is actual size+1
    +                           e[maxsize]==NULL or e[e[maxsize]-1]==NULL */
    +                        /* this may generate a warning since e[] contains
    +                           maxsize elements */
    +};
    +
    +/*=========== -constants- =========================*/
    +
    +/*-------------------------------------
    +
    +  SETelemsize
    +    size of a set element in bytes
    +*/
    +#define SETelemsize ((int)sizeof(setelemT))
    +
    +
    +/*=========== -macros- =========================*/
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelement_(type, set, variable)
    +     define FOREACH iterator
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     no space in "variable)" [DEC Alpha cc compiler]
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one beyond variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example:
    +     #define FOREACHfacet_( facets ) FOREACHsetelement_( facetT, facets, facet )
    +
    +   notes:
    +     use FOREACHsetelement_i_() if need index or include NULLs
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[0].p); \
    +          (variable= *variable##p++);)
    +
    +/*------------------------------------------
    +
    +   FOREACHsetelement_i_(qh, type, set, variable)
    +     define indexed FOREACH iterator
    +
    +   declare:
    +     type *variable, variable_n, variable_i;
    +
    +   each iteration:
    +     variable is set element, may be NULL
    +     variable_i is index, variable_n is qh_setsize()
    +
    +   to repeat an element:
    +     variable_i--; variable_n-- repeats for deleted element
    +
    +   at exit:
    +     variable==NULL and variable_i==variable_n
    +
    +   example:
    +     #define FOREACHfacet_i_( qh, facets ) FOREACHsetelement_i_( qh, facetT, facets, facet )
    +
    +   WARNING:
    +     nested loops can't use the same variable (define another FOREACH)
    +
    +     needs braces if nested inside another FOREACH
    +     this includes intervening blocks, e.g. FOREACH...{ if () FOREACH...} )
    +*/
    +#define FOREACHsetelement_i_(qh, type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##_i= 0, variable= (type *)((set)->e[0].p), \
    +                   variable##_n= qh_setsize(qh, set);\
    +          variable##_i < variable##_n;\
    +          variable= (type *)((set)->e[++variable##_i].p) )
    +
    +/*----------------------------------------
    +
    +   FOREACHsetelementreverse_(qh, type, set, variable)-
    +     define FOREACH iterator in reverse order
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +     also declare 'int variabletemp'
    +
    +   each iteration:
    +     variable is set element
    +
    +   to repeat an element:
    +     variabletemp++; / *repeat* /
    +
    +   at exit:
    +     variable is NULL
    +
    +   example:
    +     #define FOREACHvertexreverse_( vertices ) FOREACHsetelementreverse_( vertexT, vertices, vertex )
    +
    +   notes:
    +     use FOREACHsetelementreverse12_() to reverse first two elements
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse_(qh, type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +           variable##temp= qh_setsize(qh, set)-1, variable= qh_setlast(qh, set);\
    +           variable; variable= \
    +           ((--variable##temp >= 0) ? SETelemt_(set, variable##temp, type) : NULL))
    +
    +/*-------------------------------------
    +
    +   FOREACHsetelementreverse12_(type, set, variable)-
    +     define FOREACH iterator with e[1] and e[0] reversed
    +
    +   declare:
    +     assumes *variable and **variablep are declared
    +
    +   each iteration:
    +     variable is set element
    +     variablep is one after variable.
    +
    +   to repeat an element:
    +     variablep--; / *repeat* /
    +
    +   at exit:
    +     variable is NULL at end of loop
    +
    +   example
    +     #define FOREACHvertexreverse12_( vertices ) FOREACHsetelementreverse12_( vertexT, vertices, vertex )
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHsetelementreverse12_(type, set, variable) \
    +        if (((variable= NULL), set)) for (\
    +          variable##p= (type **)&((set)->e[1].p); \
    +          (variable= *variable##p); \
    +          variable##p == ((type **)&((set)->e[0].p))?variable##p += 2: \
    +              (variable##p == ((type **)&((set)->e[1].p))?variable##p--:variable##p++))
    +
    +/*-------------------------------------
    +
    +   FOREACHelem_( set )-
    +     iterate elements in a set
    +
    +   declare:
    +     void *elem, *elemp;
    +
    +   each iteration:
    +     elem is set element
    +     elemp is one beyond
    +
    +   to repeat an element:
    +     elemp--; / *repeat* /
    +
    +   at exit:
    +     elem == NULL at end of loop
    +
    +   example:
    +     FOREACHelem_(set) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHelem_(set) FOREACHsetelement_(void, set, elem)
    +
    +/*-------------------------------------
    +
    +   FOREACHset_( set )-
    +     iterate a set of sets
    +
    +   declare:
    +     setT *set, **setp;
    +
    +   each iteration:
    +     set is set element
    +     setp is one beyond
    +
    +   to repeat an element:
    +     setp--; / *repeat* /
    +
    +   at exit:
    +     set == NULL at end of loop
    +
    +   example
    +     FOREACHset_(sets) {
    +
    +   notes:
    +     WARNING: needs braces if nested inside another FOREACH
    +*/
    +#define FOREACHset_(sets) FOREACHsetelement_(setT, sets, set)
    +
    +/*-------------------------------------------
    +
    +   SETindex_( set, elem )
    +     return index of elem in set
    +
    +   notes:
    +     for use with FOREACH iteration
    +     WARN64 -- Maximum set size is 2G
    +
    +   example:
    +     i= SETindex_(ridges, ridge)
    +*/
    +#define SETindex_(set, elem) ((int)((void **)elem##p - (void **)&(set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETref_( elem )
    +     l.h.s. for modifying the current element in a FOREACH iteration
    +
    +   example:
    +     SETref_(ridge)= anotherridge;
    +*/
    +#define SETref_(elem) (elem##p[-1])
    +
    +/*-----------------------------------------
    +
    +   SETelem_(set, n)
    +     return the n'th element of set
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +      use SETelemt_() for type cast
    +*/
    +#define SETelem_(set, n)           ((set)->e[n].p)
    +
    +/*-----------------------------------------
    +
    +   SETelemt_(set, n, type)
    +     return the n'th element of set as a type
    +
    +   notes:
    +      assumes that n is valid [0..size] and that set is defined
    +*/
    +#define SETelemt_(set, n, type)    ((type*)((set)->e[n].p))
    +
    +/*-----------------------------------------
    +
    +   SETelemaddr_(set, n, type)
    +     return address of the n'th element of a set
    +
    +   notes:
    +      assumes that n is valid [0..size] and set is defined
    +*/
    +#define SETelemaddr_(set, n, type) ((type **)(&((set)->e[n].p)))
    +
    +/*-----------------------------------------
    +
    +   SETfirst_(set)
    +     return first element of set
    +
    +*/
    +#define SETfirst_(set)             ((set)->e[0].p)
    +
    +/*-----------------------------------------
    +
    +   SETfirstt_(set, type)
    +     return first element of set as a type
    +
    +*/
    +#define SETfirstt_(set, type)      ((type*)((set)->e[0].p))
    +
    +/*-----------------------------------------
    +
    +   SETsecond_(set)
    +     return second element of set
    +
    +*/
    +#define SETsecond_(set)            ((set)->e[1].p)
    +
    +/*-----------------------------------------
    +
    +   SETsecondt_(set, type)
    +     return second element of set as a type
    +*/
    +#define SETsecondt_(set, type)     ((type*)((set)->e[1].p))
    +
    +/*-----------------------------------------
    +
    +   SETaddr_(set, type)
    +       return address of set's elements
    +*/
    +#define SETaddr_(set,type)         ((type **)(&((set)->e[0].p)))
    +
    +/*-----------------------------------------
    +
    +   SETreturnsize_(set, size)
    +     return size of a set
    +
    +   notes:
    +      set must be defined
    +      use qh_setsize(qhT *qh, set) unless speed is critical
    +*/
    +#define SETreturnsize_(set, size) (((size)= ((set)->e[(set)->maxsize].i))?(--(size)):((size)= (set)->maxsize))
    +
    +/*-----------------------------------------
    +
    +   SETempty_(set)
    +     return true(1) if set is empty
    +
    +   notes:
    +      set may be NULL
    +*/
    +#define SETempty_(set)            (!set || (SETfirst_(set) ? 0 : 1))
    +
    +/*---------------------------------
    +
    +  SETsizeaddr_(set)
    +    return pointer to 'actual size+1' of set (set CANNOT be NULL!!)
    +    Its type is setelemT* for strict aliasing
    +    All SETelemaddr_ must be cast to setelemT
    +
    +
    +  notes:
    +    *SETsizeaddr==NULL or e[*SETsizeaddr-1].p==NULL
    +*/
    +#define SETsizeaddr_(set) (&((set)->e[(set)->maxsize]))
    +
    +/*-----------------------------------------
    +
    +   SETtruncate_(set, size)
    +     truncate set to size
    +
    +   see:
    +     qh_settruncate()
    +
    +*/
    +#define SETtruncate_(set, size) {set->e[set->maxsize].i= size+1; /* maybe overwritten */ \
    +      set->e[size].p= NULL;}
    +
    +/*======= prototypes in alphabetical order ============*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void  qh_setaddsorted(qhT *qh, setT **setp, void *elem);
    +void  qh_setaddnth(qhT *qh, setT **setp, int nth, void *newelem);
    +void  qh_setappend(qhT *qh, setT **setp, void *elem);
    +void  qh_setappend_set(qhT *qh, setT **setp, setT *setA);
    +void  qh_setappend2ndlast(qhT *qh, setT **setp, void *elem);
    +void  qh_setcheck(qhT *qh, setT *set, const char *tname, unsigned id);
    +void  qh_setcompact(qhT *qh, setT *set);
    +setT *qh_setcopy(qhT *qh, setT *set, int extra);
    +void *qh_setdel(setT *set, void *elem);
    +void *qh_setdellast(setT *set);
    +void *qh_setdelnth(qhT *qh, setT *set, int nth);
    +void *qh_setdelnthsorted(qhT *qh, setT *set, int nth);
    +void *qh_setdelsorted(setT *set, void *newelem);
    +setT *qh_setduplicate(qhT *qh, setT *set, int elemsize);
    +void **qh_setendpointer(setT *set);
    +int   qh_setequal(setT *setA, setT *setB);
    +int   qh_setequal_except(setT *setA, void *skipelemA, setT *setB, void *skipelemB);
    +int   qh_setequal_skip(setT *setA, int skipA, setT *setB, int skipB);
    +void  qh_setfree(qhT *qh, setT **set);
    +void  qh_setfree2(qhT *qh, setT **setp, int elemsize);
    +void  qh_setfreelong(qhT *qh, setT **set);
    +int   qh_setin(setT *set, void *setelem);
    +int qh_setindex(setT *set, void *setelem);
    +void  qh_setlarger(qhT *qh, setT **setp);
    +void *qh_setlast(setT *set);
    +setT *qh_setnew(qhT *qh, int size);
    +setT *qh_setnew_delnthsorted(qhT *qh, setT *set, int size, int nth, int prepend);
    +void  qh_setprint(qhT *qh, FILE *fp, const char* string, setT *set);
    +void  qh_setreplace(qhT *qh, setT *set, void *oldelem, void *newelem);
    +int qh_setsize(qhT *qh, setT *set);
    +setT *qh_settemp(qhT *qh, int setsize);
    +void  qh_settempfree(qhT *qh, setT **set);
    +void  qh_settempfree_all(qhT *qh);
    +setT *qh_settemppop(qhT *qh);
    +void  qh_settemppush(qhT *qh, setT *set);
    +void  qh_settruncate(qhT *qh, setT *set, int size);
    +int   qh_setunique(qhT *qh, setT **set, void *elem);
    +void  qh_setzero(qhT *qh, setT *set, int idx, int size);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFset */
    diff --git a/xs/src/qhull/src/libqhull_r/random_r.c b/xs/src/qhull/src/libqhull_r/random_r.c
    new file mode 100644
    index 0000000000..1fefb51c36
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/random_r.c
    @@ -0,0 +1,247 @@
    +/*
      ---------------------------------
    +
    +   random_r.c and utilities
    +     Park & Miller's minimimal standard random number generator
    +     argc/argv conversion
    +
    +     Used by rbox.  Do not use 'qh' 
    +*/
    +
    +#include "libqhull_r.h"
    +#include "random_r.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#pragma warning( disable : 4996)  /* function was declared deprecated(strcpy, localtime, etc.) */
    +#endif
    +
    +/*---------------------------------
    +
    + qh_argv_to_command(argc, argv, command, max_size )
    +
    +    build command from argc/argv
    +    max_size is at least
    +
    + returns:
    +    a space-delimited string of options (just as typed)
    +    returns false if max_size is too short
    +
    + notes:
    +    silently removes
    +    makes option string easy to input and output
    +    matches qh_argv_to_command_size()
    +
    +    argc may be 0
    +*/
    +int qh_argv_to_command(int argc, char *argv[], char* command, int max_size) {
    +  int i, remaining;
    +  char *s;
    +  *command= '\0';  /* max_size > 0 */
    +
    +  if (argc) {
    +    if ((s= strrchr( argv[0], '\\')) /* get filename w/o .exe extension */
    +    || (s= strrchr( argv[0], '/')))
    +        s++;
    +    else
    +        s= argv[0];
    +    if ((int)strlen(s) < max_size)   /* WARN64 */
    +        strcpy(command, s);
    +    else
    +        goto error_argv;
    +    if ((s= strstr(command, ".EXE"))
    +    ||  (s= strstr(command, ".exe")))
    +        *s= '\0';
    +  }
    +  for (i=1; i < argc; i++) {
    +    s= argv[i];
    +    remaining= max_size - (int)strlen(command) - (int)strlen(s) - 2;   /* WARN64 */
    +    if (!*s || strchr(s, ' ')) {
    +      char *t= command + strlen(command);
    +      remaining -= 2;
    +      if (remaining < 0) {
    +        goto error_argv;
    +      }
    +      *t++= ' ';
    +      *t++= '"';
    +      while (*s) {
    +        if (*s == '"') {
    +          if (--remaining < 0)
    +            goto error_argv;
    +          *t++= '\\';
    +        }
    +        *t++= *s++;
    +      }
    +      *t++= '"';
    +      *t= '\0';
    +    }else if (remaining < 0) {
    +      goto error_argv;
    +    }else
    +      strcat(command, " ");
    +      strcat(command, s);
    +  }
    +  return 1;
    +
    +error_argv:
    +  return 0;
    +} /* argv_to_command */
    +
    +/*---------------------------------
    +
    +qh_argv_to_command_size(argc, argv )
    +
    +    return size to allocate for qh_argv_to_command()
    +
    +notes:
    +    argc may be 0
    +    actual size is usually shorter
    +*/
    +int qh_argv_to_command_size(int argc, char *argv[]) {
    +    unsigned int count= 1; /* null-terminator if argc==0 */
    +    int i;
    +    char *s;
    +
    +    for (i=0; i0 && strchr(argv[i], ' ')) {
    +        count += 2;  /* quote delimiters */
    +        for (s=argv[i]; *s; s++) {
    +          if (*s == '"') {
    +            count++;
    +          }
    +        }
    +      }
    +    }
    +    return count;
    +} /* argv_to_command_size */
    +
    +/*---------------------------------
    +
    +  qh_rand()
    +  qh_srand(qh, seed )
    +    generate pseudo-random number between 1 and 2^31 -2
    +
    +  notes:
    +    For qhull and rbox, called from qh_RANDOMint(),etc. [user.h]
    +
    +    From Park & Miller's minimal standard random number generator
    +      Communications of the ACM, 31:1192-1201, 1988.
    +    Does not use 0 or 2^31 -1
    +      this is silently enforced by qh_srand()
    +    Can make 'Rn' much faster by moving qh_rand to qh_distplane
    +*/
    +
    +/* Global variables and constants */
    +
    +#define qh_rand_a 16807
    +#define qh_rand_m 2147483647
    +#define qh_rand_q 127773  /* m div a */
    +#define qh_rand_r 2836    /* m mod a */
    +
    +int qh_rand(qhT *qh) {
    +    int lo, hi, test;
    +    int seed = qh->last_random;
    +
    +    hi = seed / qh_rand_q;  /* seed div q */
    +    lo = seed % qh_rand_q;  /* seed mod q */
    +    test = qh_rand_a * lo - qh_rand_r * hi;
    +    if (test > 0)
    +        seed= test;
    +    else
    +        seed= test + qh_rand_m;
    +    qh->last_random= seed;
    +    /* seed = seed < qh_RANDOMmax/2 ? 0 : qh_RANDOMmax;  for testing */
    +    /* seed = qh_RANDOMmax;  for testing */
    +    return seed;
    +} /* rand */
    +
    +void qh_srand(qhT *qh, int seed) {
    +    if (seed < 1)
    +        qh->last_random= 1;
    +    else if (seed >= qh_rand_m)
    +        qh->last_random= qh_rand_m - 1;
    +    else
    +        qh->last_random= seed;
    +} /* qh_srand */
    +
    +/*---------------------------------
    +
    +qh_randomfactor(qh, scale, offset )
    +  return a random factor r * scale + offset
    +
    +notes:
    +  qh.RANDOMa/b are defined in global_r.c
    +  qh_RANDOMint requires 'qh'
    +*/
    +realT qh_randomfactor(qhT *qh, realT scale, realT offset) {
    +    realT randr;
    +
    +    randr= qh_RANDOMint;
    +    return randr * scale + offset;
    +} /* randomfactor */
    +
    +/*---------------------------------
    +
    +qh_randommatrix(qh, buffer, dim, rows )
    +  generate a random dim X dim matrix in range [-1,1]
    +  assumes buffer is [dim+1, dim]
    +
    +  returns:
    +    sets buffer to random numbers
    +    sets rows to rows of buffer
    +    sets row[dim] as scratch row
    +
    +  notes:
    +    qh_RANDOMint requires 'qh'
    +*/
    +void qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **rows) {
    +    int i, k;
    +    realT **rowi, *coord, realr;
    +
    +    coord= buffer;
    +    rowi= rows;
    +    for (i=0; i < dim; i++) {
    +        *(rowi++)= coord;
    +        for (k=0; k < dim; k++) {
    +            realr= qh_RANDOMint;
    +            *(coord++)= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
    +        }
    +    }
    +    *rowi= coord;
    +} /* randommatrix */
    +
    +/*---------------------------------
    +
    +  qh_strtol( s, endp) qh_strtod( s, endp)
    +    internal versions of strtol() and strtod()
    +    does not skip trailing spaces
    +  notes:
    +    some implementations of strtol()/strtod() skip trailing spaces
    +*/
    +double qh_strtod(const char *s, char **endp) {
    +  double result;
    +
    +  result= strtod(s, endp);
    +  if (s < (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtod */
    +
    +int qh_strtol(const char *s, char **endp) {
    +  int result;
    +
    +  result= (int) strtol(s, endp, 10);     /* WARN64 */
    +  if (s< (*endp) && (*endp)[-1] == ' ')
    +    (*endp)--;
    +  return result;
    +} /* strtol */
    diff --git a/xs/src/qhull/src/libqhull_r/random_r.h b/xs/src/qhull/src/libqhull_r/random_r.h
    new file mode 100644
    index 0000000000..dc884b33cb
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/random_r.h
    @@ -0,0 +1,41 @@
    +/*
      ---------------------------------
    +
    +  random.h
    +    header file for random and utility routines
    +
    +   see qh-geom_r.htm and random_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/random_r.h#4 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +*/
    +
    +#ifndef qhDEFrandom
    +#define qhDEFrandom 1
    +
    +#include "libqhull_r.h"
    +
    +/*============= prototypes in alphabetical order ======= */
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +int     qh_argv_to_command(int argc, char *argv[], char* command, int max_size);
    +int     qh_argv_to_command_size(int argc, char *argv[]);
    +int     qh_rand(qhT *qh);
    +void    qh_srand(qhT *qh, int seed);
    +realT   qh_randomfactor(qhT *qh, realT scale, realT offset);
    +void    qh_randommatrix(qhT *qh, realT *buffer, int dim, realT **row);
    +int     qh_strtol(const char *s, char **endp);
    +double  qh_strtod(const char *s, char **endp);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif /* qhDEFrandom */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/rboxlib_r.c b/xs/src/qhull/src/libqhull_r/rboxlib_r.c
    new file mode 100644
    index 0000000000..6c0c7e35ef
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/rboxlib_r.c
    @@ -0,0 +1,842 @@
    +/*
      ---------------------------------
    +
    +   rboxlib_r.c
    +     Generate input points
    +
    +   notes:
    +     For documentation, see prompt[] of rbox_r.c
    +     50 points generated for 'rbox D4'
    +
    +   WARNING:
    +     incorrect range if qh_RANDOMmax is defined wrong (user_r.h)
    +*/
    +
    +#include "libqhull_r.h"  /* First for user_r.h */
    +#include "random_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ */
    +#pragma warning( disable : 4706)  /* assignment within conditional expression. */
    +#pragma warning( disable : 4996)  /* this function (strncat,sprintf,strcpy) or variable may be unsafe. */
    +#endif
    +
    +#define MAXdim 200
    +#define PI 3.1415926535897932384
    +
    +/* ------------------------------ prototypes ----------------*/
    +int qh_roundi(qhT *qh, double a);
    +void qh_out1(qhT *qh, double a);
    +void qh_out2n(qhT *qh, double a, double b);
    +void qh_out3n(qhT *qh, double a, double b, double c);
    +void qh_outcoord(qhT *qh, int iscdd, double *coord, int dim);
    +void qh_outcoincident(qhT *qh, int coincidentpoints, double radius, int iscdd, double *coord, int dim);
    +
    +void    qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +int     qh_rand(qhT *qh);
    +void    qh_srand(qhT *qh, int seed);
    +
    +/*---------------------------------
    +
    +  qh_rboxpoints(qh, rbox_command )
    +    Generate points to qh->fout according to rbox options
    +    Report errors on qh->ferr
    +
    +  returns:
    +    0 (qh_ERRnone) on success
    +    1 (qh_ERRinput) on input error
    +    4 (qh_ERRmem) on memory error
    +    5 (qh_ERRqhull) on internal error
    +
    +  notes:
    +    To avoid using stdio, redefine qh_malloc, qh_free, and qh_fprintf_rbox (user_r.c)
    +
    +  design:
    +    Straight line code (consider defining a struct and functions):
    +
    +    Parse arguments into variables
    +    Determine the number of points
    +    Generate the points
    +*/
    +int qh_rboxpoints(qhT *qh, char* rbox_command) {
    +  int i,j,k;
    +  int gendim;
    +  int coincidentcount=0, coincidenttotal=0, coincidentpoints=0;
    +  int cubesize, diamondsize, seed=0, count, apex;
    +  int dim=3 , numpoints= 0, totpoints, addpoints=0;
    +  int issphere=0, isaxis=0,  iscdd= 0, islens= 0, isregular=0, iswidth=0, addcube=0;
    +  int isgap=0, isspiral=0, NOcommand= 0, adddiamond=0;
    +  int israndom=0, istime=0;
    +  int isbox=0, issimplex=0, issimplex2=0, ismesh=0;
    +  double width=0.0, gap=0.0, radius=0.0, coincidentradius=0.0;
    +  double coord[MAXdim], offset, meshm=3.0, meshn=4.0, meshr=5.0;
    +  double *coordp, *simplex= NULL, *simplexp;
    +  int nthroot, mult[MAXdim];
    +  double norm, factor, randr, rangap, lensangle= 0, lensbase= 1;
    +  double anglediff, angle, x, y, cube= 0.0, diamond= 0.0;
    +  double box= qh_DEFAULTbox; /* scale all numbers before output */
    +  double randmax= qh_RANDOMmax;
    +  char command[200], seedbuf[200];
    +  char *s= command, *t, *first_point= NULL;
    +  time_t timedata;
    +  int exitcode;
    +
    +  exitcode= setjmp(qh->rbox_errexit);
    +  if (exitcode) {
    +    /* same code for error exit and normal return, qh->NOerrexit is set */
    +    if (simplex)
    +        qh_free(simplex);
    +    return exitcode;
    +  }
    +
    +  *command= '\0';
    +  strncat(command, rbox_command, sizeof(command)-strlen(command)-1);
    +
    +  while (*s && !isspace(*s))  /* skip program name */
    +    s++;
    +  while (*s) {
    +    while (*s && isspace(*s))
    +      s++;
    +    if (*s == '-')
    +      s++;
    +    if (!*s)
    +      break;
    +    if (isdigit(*s)) {
    +      numpoints= qh_strtol(s, &s);
    +      continue;
    +    }
    +    /* ============= read flags =============== */
    +    switch (*s++) {
    +    case 'c':
    +      addcube= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        cube= qh_strtod(++t, &s);
    +      break;
    +    case 'd':
    +      adddiamond= 1;
    +      t= s;
    +      while (isspace(*t))
    +        t++;
    +      if (*t == 'G')
    +        diamond= qh_strtod(++t, &s);
    +      break;
    +    case 'h':
    +      iscdd= 1;
    +      break;
    +    case 'l':
    +      isspiral= 1;
    +      break;
    +    case 'n':
    +      NOcommand= 1;
    +      break;
    +    case 'r':
    +      isregular= 1;
    +      break;
    +    case 's':
    +      issphere= 1;
    +      break;
    +    case 't':
    +      istime= 1;
    +      if (isdigit(*s)) {
    +        seed= qh_strtol(s, &s);
    +        israndom= 0;
    +      }else
    +        israndom= 1;
    +      break;
    +    case 'x':
    +      issimplex= 1;
    +      break;
    +    case 'y':
    +      issimplex2= 1;
    +      break;
    +    case 'z':
    +      qh->rbox_isinteger= 1;
    +      break;
    +    case 'B':
    +      box= qh_strtod(s, &s);
    +      isbox= 1;
    +      break;
    +    case 'C':
    +      if (*s)
    +        coincidentpoints=  qh_strtol(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        coincidentradius=  qh_strtod(s, &s);
    +      }
    +      if (*s == ',') {
    +        ++s;
    +        coincidenttotal=  qh_strtol(s, &s);
    +      }
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(qh, qh->ferr, 7080, "rbox error: arguments for 'Cn,r,m' are not 'int', 'float', and 'int'.  Remaining string is '%s'\n", s);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      if (coincidentpoints==0){
    +        qh_fprintf_rbox(qh, qh->ferr, 6268, "rbox error: missing arguments for 'Cn,r,m' where n is the number of coincident points, r is the radius (default 0.0), and m is the number of points\n");
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      if (coincidentpoints<0 || coincidenttotal<0 || coincidentradius<0.0){
    +        qh_fprintf_rbox(qh, qh->ferr, 6269, "rbox error: negative arguments for 'Cn,m,r' where n (%d) is the number of coincident points, m (%d) is the number of points, and r (%.2g) is the radius (default 0.0)\n", coincidentpoints, coincidenttotal, coincidentradius);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      break;
    +    case 'D':
    +      dim= qh_strtol(s, &s);
    +      if (dim < 1
    +      || dim > MAXdim) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6189, "rbox error: dimension, D%d, out of bounds (>=%d or <=0)", dim, MAXdim);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      break;
    +    case 'G':
    +      if (isdigit(*s))
    +        gap= qh_strtod(s, &s);
    +      else
    +        gap= 0.5;
    +      isgap= 1;
    +      break;
    +    case 'L':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 10;
    +      islens= 1;
    +      break;
    +    case 'M':
    +      ismesh= 1;
    +      if (*s)
    +        meshn= qh_strtod(s, &s);
    +      if (*s == ',') {
    +        ++s;
    +        meshm= qh_strtod(s, &s);
    +      }else
    +        meshm= 0.0;
    +      if (*s == ',') {
    +        ++s;
    +        meshr= qh_strtod(s, &s);
    +      }else
    +        meshr= sqrt(meshn*meshn + meshm*meshm);
    +      if (*s && !isspace(*s)) {
    +        qh_fprintf_rbox(qh, qh->ferr, 7069, "rbox warning: assuming 'M3,4,5' since mesh args are not integers or reals\n");
    +        meshn= 3.0, meshm=4.0, meshr=5.0;
    +      }
    +      break;
    +    case 'O':
    +      qh->rbox_out_offset= qh_strtod(s, &s);
    +      break;
    +    case 'P':
    +      if (!first_point)
    +        first_point= s-1;
    +      addpoints++;
    +      while (*s && !isspace(*s))   /* read points later */
    +        s++;
    +      break;
    +    case 'W':
    +      width= qh_strtod(s, &s);
    +      iswidth= 1;
    +      break;
    +    case 'Z':
    +      if (isdigit(*s))
    +        radius= qh_strtod(s, &s);
    +      else
    +        radius= 1.0;
    +      isaxis= 1;
    +      break;
    +    default:
    +      qh_fprintf_rbox(qh, qh->ferr, 7070, "rbox error: unknown flag at %s.\nExecute 'rbox' without arguments for documentation.\n", s);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    if (*s && !isspace(*s)) {
    +      qh_fprintf_rbox(qh, qh->ferr, 7071, "rbox error: missing space between flags at %s.\n", s);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +  }
    +
    +  /* ============= defaults, constants, and sizes =============== */
    +  if (qh->rbox_isinteger && !isbox)
    +    box= qh_DEFAULTzbox;
    +  if (addcube) {
    +    cubesize= (int)floor(ldexp(1.0,dim)+0.5);
    +    if (cube == 0.0)
    +      cube= box;
    +  }else
    +    cubesize= 0;
    +  if (adddiamond) {
    +    diamondsize= 2*dim;
    +    if (diamond == 0.0)
    +      diamond= box;
    +  }else
    +    diamondsize= 0;
    +  if (islens) {
    +    if (isaxis) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6190, "rbox error: can not combine 'Ln' with 'Zn'\n");
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    if (radius <= 1.0) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6191, "rbox error: lens radius %.2g should be greater than 1.0\n",
    +               radius);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    lensangle= asin(1.0/radius);
    +    lensbase= radius * cos(lensangle);
    +  }
    +
    +  if (!numpoints) {
    +    if (issimplex2)
    +        ; /* ok */
    +    else if (isregular + issimplex + islens + issphere + isaxis + isspiral + iswidth + ismesh) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6192, "rbox error: missing count\n");
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +    }else if (adddiamond + addcube + addpoints)
    +        ; /* ok */
    +    else {
    +        numpoints= 50;  /* ./rbox D4 is the test case */
    +        issphere= 1;
    +    }
    +  }
    +  if ((issimplex + islens + isspiral + ismesh > 1)
    +  || (issimplex + issphere + isspiral + ismesh > 1)) {
    +    qh_fprintf_rbox(qh, qh->ferr, 6193, "rbox error: can only specify one of 'l', 's', 'x', 'Ln', or 'Mn,m,r' ('Ln s' is ok).\n");
    +    qh_errexit_rbox(qh, qh_ERRinput);
    +  }
    +  if (coincidentpoints>0 && (numpoints == 0 || coincidenttotal > numpoints)) {
    +    qh_fprintf_rbox(qh, qh->ferr, 6270, "rbox error: 'Cn,r,m' requested n coincident points for each of m points.  Either there is no points or m (%d) is greater than the number of points (%d).\n", coincidenttotal, numpoints);
    +    qh_errexit_rbox(qh, qh_ERRinput);
    +  }
    +  if (coincidenttotal == 0)
    +    coincidenttotal= numpoints;
    +
    +  /* ============= print header with total points =============== */
    +  if (issimplex || ismesh)
    +    totpoints= numpoints;
    +  else if (issimplex2)
    +    totpoints= numpoints+dim+1;
    +  else if (isregular) {
    +    totpoints= numpoints;
    +    if (dim == 2) {
    +        if (islens)
    +          totpoints += numpoints - 2;
    +    }else if (dim == 3) {
    +        if (islens)
    +          totpoints += 2 * numpoints;
    +      else if (isgap)
    +        totpoints += 1 + numpoints;
    +      else
    +        totpoints += 2;
    +    }
    +  }else
    +    totpoints= numpoints + isaxis;
    +  totpoints += cubesize + diamondsize + addpoints;
    +  totpoints += coincidentpoints*coincidenttotal;
    +
    +  /* ============= seed randoms =============== */
    +  if (istime == 0) {
    +    for (s=command; *s; s++) {
    +      if (issimplex2 && *s == 'y') /* make 'y' same seed as 'x' */
    +        i= 'x';
    +      else
    +        i= *s;
    +      seed= 11*seed + i;
    +    }
    +  }else if (israndom) {
    +    seed= (int)time(&timedata);
    +    sprintf(seedbuf, " t%d", seed);  /* appends an extra t, not worth removing */
    +    strncat(command, seedbuf, sizeof(command)-strlen(command)-1);
    +    t= strstr(command, " t ");
    +    if (t)
    +      strcpy(t+1, t+3); /* remove " t " */
    +  } /* else, seed explicitly set to n */
    +  qh_RANDOMseed_(qh, seed);
    +
    +  /* ============= print header =============== */
    +
    +  if (iscdd)
    +      qh_fprintf_rbox(qh, qh->fout, 9391, "%s\nbegin\n        %d %d %s\n",
    +      NOcommand ? "" : command,
    +      totpoints, dim+1,
    +      qh->rbox_isinteger ? "integer" : "real");
    +  else if (NOcommand)
    +      qh_fprintf_rbox(qh, qh->fout, 9392, "%d\n%d\n", dim, totpoints);
    +  else
    +      /* qh_fprintf_rbox special cases 9393 to append 'command' to the RboxPoints.comment() */
    +      qh_fprintf_rbox(qh, qh->fout, 9393, "%d %s\n%d\n", dim, command, totpoints);
    +
    +  /* ============= explicit points =============== */
    +  if ((s= first_point)) {
    +    while (s && *s) { /* 'P' */
    +      count= 0;
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      while (*++s) {
    +        qh_out1(qh, qh_strtod(s, &s));
    +        count++;
    +        if (isspace(*s) || !*s)
    +          break;
    +        if (*s != ',') {
    +          qh_fprintf_rbox(qh, qh->ferr, 6194, "rbox error: missing comma after coordinate in %s\n\n", s);
    +          qh_errexit_rbox(qh, qh_ERRinput);
    +        }
    +      }
    +      if (count < dim) {
    +        for (k=dim-count; k--; )
    +          qh_out1(qh, 0.0);
    +      }else if (count > dim) {
    +        qh_fprintf_rbox(qh, qh->ferr, 6195, "rbox error: %d coordinates instead of %d coordinates in %s\n\n",
    +                  count, dim, s);
    +        qh_errexit_rbox(qh, qh_ERRinput);
    +      }
    +      qh_fprintf_rbox(qh, qh->fout, 9394, "\n");
    +      while ((s= strchr(s, 'P'))) {
    +        if (isspace(s[-1]))
    +          break;
    +      }
    +    }
    +  }
    +
    +  /* ============= simplex distribution =============== */
    +  if (issimplex+issimplex2) {
    +    if (!(simplex= (double*)qh_malloc( dim * (dim+1) * sizeof(double)))) {
    +      qh_fprintf_rbox(qh, qh->ferr, 6196, "rbox error: insufficient memory for simplex\n");
    +      qh_errexit_rbox(qh, qh_ERRmem); /* qh_ERRmem */
    +    }
    +    simplexp= simplex;
    +    if (isregular) {
    +      for (i=0; ifout, 9395, "\n");
    +      }
    +    }
    +    for (j=0; jferr, 6197, "rbox error: regular points can be used only in 2-d and 3-d\n\n");
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    if (!isaxis || radius == 0.0) {
    +      isaxis= 1;
    +      radius= 1.0;
    +    }
    +    if (dim == 3) {
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, 0.0, 0.0, -box);
    +      if (!isgap) {
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out3n(qh, 0.0, 0.0, box);
    +      }
    +    }
    +    angle= 0.0;
    +    anglediff= 2.0 * PI/numpoints;
    +    for (i=0; i < numpoints; i++) {
    +      angle += anglediff;
    +      x= radius * cos(angle);
    +      y= radius * sin(angle);
    +      if (dim == 2) {
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out2n(qh, x*box, y*box);
    +      }else {
    +        norm= sqrt(1.0 + x*x + y*y);
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out3n(qh, box*x/norm, box*y/norm, box/norm);
    +        if (isgap) {
    +          x *= 1-gap;
    +          y *= 1-gap;
    +          norm= sqrt(1.0 + x*x + y*y);
    +          if (iscdd)
    +            qh_out1(qh, 1.0);
    +          qh_out3n(qh, box*x/norm, box*y/norm, box/norm);
    +        }
    +      }
    +    }
    +  }
    +  /* ============= regular points for 'r Ln D2' =============== */
    +  else if (isregular && islens && dim == 2) {
    +    double cos_0;
    +
    +    angle= lensangle;
    +    anglediff= 2 * lensangle/(numpoints - 1);
    +    cos_0= cos(lensangle);
    +    for (i=0; i < numpoints; i++, angle -= anglediff) {
    +      x= radius * sin(angle);
    +      y= radius * (cos(angle) - cos_0);
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out2n(qh, x*box, y*box);
    +      if (i != 0 && i != numpoints - 1) {
    +        if (iscdd)
    +          qh_out1(qh, 1.0);
    +        qh_out2n(qh, x*box, -y*box);
    +      }
    +    }
    +  }
    +  /* ============= regular points for 'r Ln D3' =============== */
    +  else if (isregular && islens && dim != 2) {
    +    if (dim != 3) {
    +      qh_free(simplex);
    +      qh_fprintf_rbox(qh, qh->ferr, 6198, "rbox error: regular points can be used only in 2-d and 3-d\n\n");
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    angle= 0.0;
    +    anglediff= 2* PI/numpoints;
    +    if (!isgap) {
    +      isgap= 1;
    +      gap= 0.5;
    +    }
    +    offset= sqrt(radius * radius - (1-gap)*(1-gap)) - lensbase;
    +    for (i=0; i < numpoints; i++, angle += anglediff) {
    +      x= cos(angle);
    +      y= sin(angle);
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, box*x, box*y, 0.0);
    +      x *= 1-gap;
    +      y *= 1-gap;
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, box*x, box*y, box * offset);
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      qh_out3n(qh, box*x, box*y, -box * offset);
    +    }
    +  }
    +  /* ============= apex of 'Zn' distribution + gendim =============== */
    +  else {
    +    if (isaxis) {
    +      gendim= dim-1;
    +      if (iscdd)
    +        qh_out1(qh, 1.0);
    +      for (j=0; j < gendim; j++)
    +        qh_out1(qh, 0.0);
    +      qh_out1(qh, -box);
    +      qh_fprintf_rbox(qh, qh->fout, 9398, "\n");
    +    }else if (islens)
    +      gendim= dim-1;
    +    else
    +      gendim= dim;
    +    /* ============= generate random point in unit cube =============== */
    +    for (i=0; i < numpoints; i++) {
    +      norm= 0.0;
    +      for (j=0; j < gendim; j++) {
    +        randr= qh_RANDOMint;
    +        coord[j]= 2.0 * randr/randmax - 1.0;
    +        norm += coord[j] * coord[j];
    +      }
    +      norm= sqrt(norm);
    +      /* ============= dim-1 point of 'Zn' distribution ========== */
    +      if (isaxis) {
    +        if (!isgap) {
    +          isgap= 1;
    +          gap= 1.0;
    +        }
    +        randr= qh_RANDOMint;
    +        rangap= 1.0 - gap * randr/randmax;
    +        factor= radius * rangap / norm;
    +        for (j=0; jferr, 6199, "rbox error: spiral distribution is available only in 3d\n\n");
    +          qh_errexit_rbox(qh, qh_ERRinput);
    +        }
    +        coord[0]= cos(2*PI*i/(numpoints - 1));
    +        coord[1]= sin(2*PI*i/(numpoints - 1));
    +        coord[2]= 2.0*(double)i/(double)(numpoints-1) - 1.0;
    +      /* ============= point of 's' distribution =============== */
    +      }else if (issphere) {
    +        factor= 1.0/norm;
    +        if (iswidth) {
    +          randr= qh_RANDOMint;
    +          factor *= 1.0 - width * randr/randmax;
    +        }
    +        for (j=0; j randmax/2)
    +          coord[dim-1]= -coord[dim-1];
    +      /* ============= project 'Wn' point toward boundary =============== */
    +      }else if (iswidth && !issphere) {
    +        j= qh_RANDOMint % gendim;
    +        if (coord[j] < 0)
    +          coord[j]= -1.0 - coord[j] * width;
    +        else
    +          coord[j]= 1.0 - coord[j] * width;
    +      }
    +      /* ============= scale point to box =============== */
    +      for (k=0; k=0; k--) {
    +        if (j & ( 1 << k))
    +          qh_out1(qh, cube);
    +        else
    +          qh_out1(qh, -cube);
    +      }
    +      qh_fprintf_rbox(qh, qh->fout, 9400, "\n");
    +    }
    +  }
    +
    +  /* ============= write diamond vertices =============== */
    +  if (adddiamond) {
    +    for (j=0; j=0; k--) {
    +        if (j/2 != k)
    +          qh_out1(qh, 0.0);
    +        else if (j & 0x1)
    +          qh_out1(qh, diamond);
    +        else
    +          qh_out1(qh, -diamond);
    +      }
    +      qh_fprintf_rbox(qh, qh->fout, 9401, "\n");
    +    }
    +  }
    +
    +  if (iscdd)
    +    qh_fprintf_rbox(qh, qh->fout, 9402, "end\nhull\n");
    +
    +  /* same code for error exit and normal return */
    +  qh_free(simplex);
    +  return qh_ERRnone;
    +} /* rboxpoints */
    +
    +/*------------------------------------------------
    +outxxx - output functions for qh_rboxpoints
    +*/
    +int qh_roundi(qhT *qh, double a) {
    +  if (a < 0.0) {
    +    if (a - 0.5 < INT_MIN) {
    +      qh_fprintf_rbox(qh, qh->ferr, 6200, "rbox input error: negative coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    return (int)(a - 0.5);
    +  }else {
    +    if (a + 0.5 > INT_MAX) {
    +      qh_fprintf_rbox(qh, qh->ferr, 6201, "rbox input error: coordinate %2.2g is too large.  Reduce 'Bn'\n", a);
    +      qh_errexit_rbox(qh, qh_ERRinput);
    +    }
    +    return (int)(a + 0.5);
    +  }
    +} /* qh_roundi */
    +
    +void qh_out1(qhT *qh, double a) {
    +
    +  if (qh->rbox_isinteger)
    +    qh_fprintf_rbox(qh, qh->fout, 9403, "%d ", qh_roundi(qh, a+qh->rbox_out_offset));
    +  else
    +    qh_fprintf_rbox(qh, qh->fout, 9404, qh_REAL_1, a+qh->rbox_out_offset);
    +} /* qh_out1 */
    +
    +void qh_out2n(qhT *qh, double a, double b) {
    +
    +  if (qh->rbox_isinteger)
    +    qh_fprintf_rbox(qh, qh->fout, 9405, "%d %d\n", qh_roundi(qh, a+qh->rbox_out_offset), qh_roundi(qh, b+qh->rbox_out_offset));
    +  else
    +    qh_fprintf_rbox(qh, qh->fout, 9406, qh_REAL_2n, a+qh->rbox_out_offset, b+qh->rbox_out_offset);
    +} /* qh_out2n */
    +
    +void qh_out3n(qhT *qh, double a, double b, double c) {
    +
    +  if (qh->rbox_isinteger)
    +    qh_fprintf_rbox(qh, qh->fout, 9407, "%d %d %d\n", qh_roundi(qh, a+qh->rbox_out_offset), qh_roundi(qh, b+qh->rbox_out_offset), qh_roundi(qh, c+qh->rbox_out_offset));
    +  else
    +    qh_fprintf_rbox(qh, qh->fout, 9408, qh_REAL_3n, a+qh->rbox_out_offset, b+qh->rbox_out_offset, c+qh->rbox_out_offset);
    +} /* qh_out3n */
    +
    +void qh_outcoord(qhT *qh, int iscdd, double *coord, int dim) {
    +    double *p= coord;
    +    int k;
    +
    +    if (iscdd)
    +      qh_out1(qh, 1.0);
    +    for (k=0; k < dim; k++)
    +      qh_out1(qh, *(p++));
    +    qh_fprintf_rbox(qh, qh->fout, 9396, "\n");
    +} /* qh_outcoord */
    +
    +void qh_outcoincident(qhT *qh, int coincidentpoints, double radius, int iscdd, double *coord, int dim) {
    +  double *p;
    +  double randr, delta;
    +  int i,k;
    +  double randmax= qh_RANDOMmax;
    +
    +  for (i= 0; ifout, 9410, "\n");
    +  }
    +} /* qh_outcoincident */
    +
    +/*------------------------------------------------
    +   Only called from qh_rboxpoints or qh_fprintf_rbox
    +   qh_fprintf_rbox is only called from qh_rboxpoints
    +*/
    +void qh_errexit_rbox(qhT *qh, int exitcode)
    +{
    +    longjmp(qh->rbox_errexit, exitcode);
    +} /* qh_errexit_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/stat_r.c b/xs/src/qhull/src/libqhull_r/stat_r.c
    new file mode 100644
    index 0000000000..0f3ff0d3d4
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/stat_r.c
    @@ -0,0 +1,682 @@
    +/*
      ---------------------------------
    +
    +   stat_r.c
    +   contains all statistics that are collected for qhull
    +
    +   see qh-stat_r.htm and stat_r.h
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/stat_r.c#5 $$Change: 2062 $
    +   $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "qhull_ra.h"
    +
    +/*========== functions in alphabetic order ================*/
    +
    +/*---------------------------------
    +
    +  qh_allstatA()
    +    define statistics in groups of 20
    +
    +  notes:
    +    (otherwise, 'gcc -O2' uses too much memory)
    +    uses qhstat.next
    +*/
    +void qh_allstatA(qhT *qh) {
    +
    +   /* zdef_(type,name,doc,average) */
    +  zzdef_(zdoc, Zdoc2, "precision statistics", -1);
    +  zdef_(zinc, Znewvertex, NULL, -1);
    +  zdef_(wadd, Wnewvertex, "ave. distance of a new vertex to a facet(!0s)", Znewvertex);
    +  zzdef_(wmax, Wnewvertexmax, "max. distance of a new vertex to a facet", -1);
    +  zdef_(wmax, Wvertexmax, "max. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wvertexmin, "min. distance of an output vertex to a facet", -1);
    +  zdef_(wmin, Wmindenom, "min. denominator in hyperplane computation", -1);
    +
    +  qh->qhstat.precision= qh->qhstat.next;  /* call qh_precision for each of these */
    +  zzdef_(zdoc, Zdoc3, "precision problems (corrected unless 'Q0' or an error)", -1);
    +  zzdef_(zinc, Zcoplanarridges, "coplanar half ridges in output", -1);
    +  zzdef_(zinc, Zconcaveridges, "concave half ridges in output", -1);
    +  zzdef_(zinc, Zflippedfacets, "flipped facets", -1);
    +  zzdef_(zinc, Zcoplanarhorizon, "coplanar horizon facets for new vertices", -1);
    +  zzdef_(zinc, Zcoplanarpart, "coplanar points during partitioning", -1);
    +  zzdef_(zinc, Zminnorm, "degenerate hyperplanes recomputed with gaussian elimination", -1);
    +  zzdef_(zinc, Znearlysingular, "nearly singular or axis-parallel hyperplanes", -1);
    +  zzdef_(zinc, Zback0, "zero divisors during back substitute", -1);
    +  zzdef_(zinc, Zgauss0, "zero divisors during gaussian elimination", -1);
    +  zzdef_(zinc, Zmultiridge, "ridges with multiple neighbors", -1);
    +}
    +void qh_allstatB(qhT *qh) {
    +  zzdef_(zdoc, Zdoc1, "summary information", -1);
    +  zdef_(zinc, Zvertices, "number of vertices in output", -1);
    +  zdef_(zinc, Znumfacets, "number of facets in output", -1);
    +  zdef_(zinc, Znonsimplicial, "number of non-simplicial facets in output", -1);
    +  zdef_(zinc, Znowsimplicial, "number of simplicial facets that were merged", -1);
    +  zdef_(zinc, Znumridges, "number of ridges in output", -1);
    +  zdef_(zadd, Znumridges, "average number of ridges per facet", Znumfacets);
    +  zdef_(zmax, Zmaxridges, "maximum number of ridges", -1);
    +  zdef_(zadd, Znumneighbors, "average number of neighbors per facet", Znumfacets);
    +  zdef_(zmax, Zmaxneighbors, "maximum number of neighbors", -1);
    +  zdef_(zadd, Znumvertices, "average number of vertices per facet", Znumfacets);
    +  zdef_(zmax, Zmaxvertices, "maximum number of vertices", -1);
    +  zdef_(zadd, Znumvneighbors, "average number of neighbors per vertex", Zvertices);
    +  zdef_(zmax, Zmaxvneighbors, "maximum number of neighbors", -1);
    +  zdef_(wadd, Wcpu, "cpu seconds for qhull after input", -1);
    +  zdef_(zinc, Ztotvertices, "vertices created altogether", -1);
    +  zzdef_(zinc, Zsetplane, "facets created altogether", -1);
    +  zdef_(zinc, Ztotridges, "ridges created altogether", -1);
    +  zdef_(zinc, Zpostfacets, "facets before post merge", -1);
    +  zdef_(zadd, Znummergetot, "average merges per facet(at most 511)", Znumfacets);
    +  zdef_(zmax, Znummergemax, "  maximum merges for a facet(at most 511)", -1);
    +  zdef_(zinc, Zangle, NULL, -1);
    +  zdef_(wadd, Wangle, "average angle(cosine) of facet normals for all ridges", Zangle);
    +  zdef_(wmax, Wanglemax, "  maximum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wmin, Wanglemin, "  minimum angle(cosine) of facet normals across a ridge", -1);
    +  zdef_(wadd, Wareatot, "total area of facets", -1);
    +  zdef_(wmax, Wareamax, "  maximum facet area", -1);
    +  zdef_(wmin, Wareamin, "  minimum facet area", -1);
    +}
    +void qh_allstatC(qhT *qh) {
    +  zdef_(zdoc, Zdoc9, "build hull statistics", -1);
    +  zzdef_(zinc, Zprocessed, "points processed", -1);
    +  zzdef_(zinc, Zretry, "retries due to precision problems", -1);
    +  zdef_(wmax, Wretrymax, "  max. random joggle", -1);
    +  zdef_(zmax, Zmaxvertex, "max. vertices at any one time", -1);
    +  zdef_(zinc, Ztotvisible, "ave. visible facets per iteration", Zprocessed);
    +  zdef_(zinc, Zinsidevisible, "  ave. visible facets without an horizon neighbor", Zprocessed);
    +  zdef_(zadd, Zvisfacettot,  "  ave. facets deleted per iteration", Zprocessed);
    +  zdef_(zmax, Zvisfacetmax,  "    maximum", -1);
    +  zdef_(zadd, Zvisvertextot, "ave. visible vertices per iteration", Zprocessed);
    +  zdef_(zmax, Zvisvertexmax, "    maximum", -1);
    +  zdef_(zinc, Ztothorizon, "ave. horizon facets per iteration", Zprocessed);
    +  zdef_(zadd, Znewfacettot,  "ave. new or merged facets per iteration", Zprocessed);
    +  zdef_(zmax, Znewfacetmax,  "    maximum(includes initial simplex)", -1);
    +  zdef_(wadd, Wnewbalance, "average new facet balance", Zprocessed);
    +  zdef_(wadd, Wnewbalance2, "  standard deviation", -1);
    +  zdef_(wadd, Wpbalance, "average partition balance", Zpbalance);
    +  zdef_(wadd, Wpbalance2, "  standard deviation", -1);
    +  zdef_(zinc, Zpbalance, "  number of trials", -1);
    +  zdef_(zinc, Zsearchpoints, "searches of all points for initial simplex", -1);
    +  zdef_(zinc, Zdetsimplex, "determinants computed(area & initial hull)", -1);
    +  zdef_(zinc, Znoarea, "determinants not computed because vertex too low", -1);
    +  zdef_(zinc, Znotmax, "points ignored(!above max_outside)", -1);
    +  zdef_(zinc, Znotgood, "points ignored(!above a good facet)", -1);
    +  zdef_(zinc, Znotgoodnew, "points ignored(didn't create a good new facet)", -1);
    +  zdef_(zinc, Zgoodfacet, "good facets found", -1);
    +  zzdef_(zinc, Znumvisibility, "distance tests for facet visibility", -1);
    +  zdef_(zinc, Zdistvertex, "distance tests to report minimum vertex", -1);
    +  zzdef_(zinc, Ztotcheck, "points checked for facets' outer planes", -1);
    +  zzdef_(zinc, Zcheckpart, "  ave. distance tests per check", Ztotcheck);
    +}
    +void qh_allstatD(qhT *qh) {
    +  zdef_(zinc, Zvisit, "resets of visit_id", -1);
    +  zdef_(zinc, Zvvisit, "  resets of vertex_visit", -1);
    +  zdef_(zmax, Zvisit2max, "  max visit_id/2", -1);
    +  zdef_(zmax, Zvvisit2max, "  max vertex_visit/2", -1);
    +
    +  zdef_(zdoc, Zdoc4, "partitioning statistics(see previous for outer planes)", -1);
    +  zzdef_(zadd, Zdelvertextot, "total vertices deleted", -1);
    +  zdef_(zmax, Zdelvertexmax, "    maximum vertices deleted per iteration", -1);
    +  zdef_(zinc, Zfindbest, "calls to findbest", -1);
    +  zdef_(zadd, Zfindbesttot, " ave. facets tested", Zfindbest);
    +  zdef_(zmax, Zfindbestmax, " max. facets tested", -1);
    +  zdef_(zadd, Zfindcoplanar, " ave. coplanar search", Zfindbest);
    +  zdef_(zinc, Zfindnew, "calls to findbestnew", -1);
    +  zdef_(zadd, Zfindnewtot, " ave. facets tested", Zfindnew);
    +  zdef_(zmax, Zfindnewmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindnewjump, " ave. clearly better", Zfindnew);
    +  zdef_(zinc, Zfindnewsharp, " calls due to qh_sharpnewfacets", -1);
    +  zdef_(zinc, Zfindhorizon, "calls to findhorizon", -1);
    +  zdef_(zadd, Zfindhorizontot, " ave. facets tested", Zfindhorizon);
    +  zdef_(zmax, Zfindhorizonmax, " max. facets tested", -1);
    +  zdef_(zinc, Zfindjump,       " ave. clearly better", Zfindhorizon);
    +  zdef_(zinc, Zparthorizon, " horizon facets better than bestfacet", -1);
    +  zdef_(zinc, Zpartangle, "angle tests for repartitioned coplanar points", -1);
    +  zdef_(zinc, Zpartflip, "  repartitioned coplanar points for flipped orientation", -1);
    +}
    +void qh_allstatE(qhT *qh) {
    +  zdef_(zinc, Zpartinside, "inside points", -1);
    +  zdef_(zinc, Zpartnear, "  inside points kept with a facet", -1);
    +  zdef_(zinc, Zcoplanarinside, "  inside points that were coplanar with a facet", -1);
    +  zdef_(zinc, Zbestlower, "calls to findbestlower", -1);
    +  zdef_(zinc, Zbestlowerv, "  with search of vertex neighbors", -1);
    +  zdef_(zinc, Zbestlowerall, "  with rare search of all facets", -1);
    +  zdef_(zmax, Zbestloweralln, "  facets per search of all facets", -1);
    +  zdef_(wadd, Wmaxout, "difference in max_outside at final check", -1);
    +  zzdef_(zinc, Zpartitionall, "distance tests for initial partition", -1);
    +  zdef_(zinc, Ztotpartition, "partitions of a point", -1);
    +  zzdef_(zinc, Zpartition, "distance tests for partitioning", -1);
    +  zzdef_(zinc, Zdistcheck, "distance tests for checking flipped facets", -1);
    +  zzdef_(zinc, Zdistconvex, "distance tests for checking convexity", -1);
    +  zdef_(zinc, Zdistgood, "distance tests for checking good point", -1);
    +  zdef_(zinc, Zdistio, "distance tests for output", -1);
    +  zdef_(zinc, Zdiststat, "distance tests for statistics", -1);
    +  zdef_(zinc, Zdistplane, "total number of distance tests", -1);
    +  zdef_(zinc, Ztotpartcoplanar, "partitions of coplanar points or deleted vertices", -1);
    +  zzdef_(zinc, Zpartcoplanar, "   distance tests for these partitions", -1);
    +  zdef_(zinc, Zcomputefurthest, "distance tests for computing furthest", -1);
    +}
    +void qh_allstatE2(qhT *qh) {
    +  zdef_(zdoc, Zdoc5, "statistics for matching ridges", -1);
    +  zdef_(zinc, Zhashlookup, "total lookups for matching ridges of new facets", -1);
    +  zdef_(zinc, Zhashtests, "average number of tests to match a ridge", Zhashlookup);
    +  zdef_(zinc, Zhashridge, "total lookups of subridges(duplicates and boundary)", -1);
    +  zdef_(zinc, Zhashridgetest, "average number of tests per subridge", Zhashridge);
    +  zdef_(zinc, Zdupsame, "duplicated ridges in same merge cycle", -1);
    +  zdef_(zinc, Zdupflip, "duplicated ridges with flipped facets", -1);
    +
    +  zdef_(zdoc, Zdoc6, "statistics for determining merges", -1);
    +  zdef_(zinc, Zangletests, "angles computed for ridge convexity", -1);
    +  zdef_(zinc, Zbestcentrum, "best merges used centrum instead of vertices",-1);
    +  zzdef_(zinc, Zbestdist, "distance tests for best merge", -1);
    +  zzdef_(zinc, Zcentrumtests, "distance tests for centrum convexity", -1);
    +  zzdef_(zinc, Zdistzero, "distance tests for checking simplicial convexity", -1);
    +  zdef_(zinc, Zcoplanarangle, "coplanar angles in getmergeset", -1);
    +  zdef_(zinc, Zcoplanarcentrum, "coplanar centrums in getmergeset", -1);
    +  zdef_(zinc, Zconcaveridge, "concave ridges in getmergeset", -1);
    +}
    +void qh_allstatF(qhT *qh) {
    +  zdef_(zdoc, Zdoc7, "statistics for merging", -1);
    +  zdef_(zinc, Zpremergetot, "merge iterations", -1);
    +  zdef_(zadd, Zmergeinittot, "ave. initial non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergeinitmax, "  maximum", -1);
    +  zdef_(zadd, Zmergesettot, "  ave. additional non-convex ridges per iteration", Zpremergetot);
    +  zdef_(zadd, Zmergesetmax, "  maximum additional in one pass", -1);
    +  zdef_(zadd, Zmergeinittot2, "initial non-convex ridges for post merging", -1);
    +  zdef_(zadd, Zmergesettot2, "  additional non-convex ridges", -1);
    +  zdef_(wmax, Wmaxoutside, "max distance of vertex or coplanar point above facet(w/roundoff)", -1);
    +  zdef_(wmin, Wminvertex, "max distance of merged vertex below facet(or roundoff)", -1);
    +  zdef_(zinc, Zwidefacet, "centrums frozen due to a wide merge", -1);
    +  zdef_(zinc, Zwidevertices, "centrums frozen due to extra vertices", -1);
    +  zzdef_(zinc, Ztotmerge, "total number of facets or cycles of facets merged", -1);
    +  zdef_(zinc, Zmergesimplex, "merged a simplex", -1);
    +  zdef_(zinc, Zonehorizon, "simplices merged into coplanar horizon", -1);
    +  zzdef_(zinc, Zcyclehorizon, "cycles of facets merged into coplanar horizon", -1);
    +  zzdef_(zadd, Zcyclefacettot, "  ave. facets per cycle", Zcyclehorizon);
    +  zdef_(zmax, Zcyclefacetmax, "  max. facets", -1);
    +  zdef_(zinc, Zmergeintohorizon, "new facets merged into horizon", -1);
    +  zdef_(zinc, Zmergenew, "new facets merged", -1);
    +  zdef_(zinc, Zmergehorizon, "horizon facets merged into new facets", -1);
    +  zdef_(zinc, Zmergevertex, "vertices deleted by merging", -1);
    +  zdef_(zinc, Zcyclevertex, "vertices deleted by merging into coplanar horizon", -1);
    +  zdef_(zinc, Zdegenvertex, "vertices deleted by degenerate facet", -1);
    +  zdef_(zinc, Zmergeflipdup, "merges due to flipped facets in duplicated ridge", -1);
    +  zdef_(zinc, Zneighbor, "merges due to redundant neighbors", -1);
    +  zdef_(zadd, Ztestvneighbor, "non-convex vertex neighbors", -1);
    +}
    +void qh_allstatG(qhT *qh) {
    +  zdef_(zinc, Zacoplanar, "merges due to angle coplanar facets", -1);
    +  zdef_(wadd, Wacoplanartot, "  average merge distance", Zacoplanar);
    +  zdef_(wmax, Wacoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zcoplanar, "merges due to coplanar facets", -1);
    +  zdef_(wadd, Wcoplanartot, "  average merge distance", Zcoplanar);
    +  zdef_(wmax, Wcoplanarmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zconcave, "merges due to concave facets", -1);
    +  zdef_(wadd, Wconcavetot, "  average merge distance", Zconcave);
    +  zdef_(wmax, Wconcavemax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zavoidold, "coplanar/concave merges due to avoiding old merge", -1);
    +  zdef_(wadd, Wavoidoldtot, "  average merge distance", Zavoidold);
    +  zdef_(wmax, Wavoidoldmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zdegen, "merges due to degenerate facets", -1);
    +  zdef_(wadd, Wdegentot, "  average merge distance", Zdegen);
    +  zdef_(wmax, Wdegenmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zflipped, "merges due to removing flipped facets", -1);
    +  zdef_(wadd, Wflippedtot, "  average merge distance", Zflipped);
    +  zdef_(wmax, Wflippedmax, "  maximum merge distance", -1);
    +  zdef_(zinc, Zduplicate, "merges due to duplicated ridges", -1);
    +  zdef_(wadd, Wduplicatetot, "  average merge distance", Zduplicate);
    +  zdef_(wmax, Wduplicatemax, "  maximum merge distance", -1);
    +}
    +void qh_allstatH(qhT *qh) {
    +  zdef_(zdoc, Zdoc8, "renamed vertex statistics", -1);
    +  zdef_(zinc, Zrenameshare, "renamed vertices shared by two facets", -1);
    +  zdef_(zinc, Zrenamepinch, "renamed vertices in a pinched facet", -1);
    +  zdef_(zinc, Zrenameall, "renamed vertices shared by multiple facets", -1);
    +  zdef_(zinc, Zfindfail, "rename failures due to duplicated ridges", -1);
    +  zdef_(zinc, Zdupridge, "  duplicate ridges detected", -1);
    +  zdef_(zinc, Zdelridge, "deleted ridges due to renamed vertices", -1);
    +  zdef_(zinc, Zdropneighbor, "dropped neighbors due to renamed vertices", -1);
    +  zdef_(zinc, Zdropdegen, "degenerate facets due to dropped neighbors", -1);
    +  zdef_(zinc, Zdelfacetdup, "  facets deleted because of no neighbors", -1);
    +  zdef_(zinc, Zremvertex, "vertices removed from facets due to no ridges", -1);
    +  zdef_(zinc, Zremvertexdel, "  deleted", -1);
    +  zdef_(zinc, Zintersectnum, "vertex intersections for locating redundant vertices", -1);
    +  zdef_(zinc, Zintersectfail, "intersections failed to find a redundant vertex", -1);
    +  zdef_(zinc, Zintersect, "intersections found redundant vertices", -1);
    +  zdef_(zadd, Zintersecttot, "   ave. number found per vertex", Zintersect);
    +  zdef_(zmax, Zintersectmax, "   max. found for a vertex", -1);
    +  zdef_(zinc, Zvertexridge, NULL, -1);
    +  zdef_(zadd, Zvertexridgetot, "  ave. number of ridges per tested vertex", Zvertexridge);
    +  zdef_(zmax, Zvertexridgemax, "  max. number of ridges per tested vertex", -1);
    +
    +  zdef_(zdoc, Zdoc10, "memory usage statistics(in bytes)", -1);
    +  zdef_(zadd, Zmemfacets, "for facets and their normals, neighbor and vertex sets", -1);
    +  zdef_(zadd, Zmemvertices, "for vertices and their neighbor sets", -1);
    +  zdef_(zadd, Zmempoints, "for input points, outside and coplanar sets, and qhT",-1);
    +  zdef_(zadd, Zmemridges, "for ridges and their vertex sets", -1);
    +} /* allstat */
    +
    +void qh_allstatI(qhT *qh) {
    +  qh->qhstat.vridges= qh->qhstat.next;
    +  zzdef_(zdoc, Zdoc11, "Voronoi ridge statistics", -1);
    +  zzdef_(zinc, Zridge, "non-simplicial Voronoi vertices for all ridges", -1);
    +  zzdef_(wadd, Wridge, "  ave. distance to ridge", Zridge);
    +  zzdef_(wmax, Wridgemax, "  max. distance to ridge", -1);
    +  zzdef_(zinc, Zridgemid, "bounded ridges", -1);
    +  zzdef_(wadd, Wridgemid, "  ave. distance of midpoint to ridge", Zridgemid);
    +  zzdef_(wmax, Wridgemidmax, "  max. distance of midpoint to ridge", -1);
    +  zzdef_(zinc, Zridgeok, "bounded ridges with ok normal", -1);
    +  zzdef_(wadd, Wridgeok, "  ave. angle to ridge", Zridgeok);
    +  zzdef_(wmax, Wridgeokmax, "  max. angle to ridge", -1);
    +  zzdef_(zinc, Zridge0, "bounded ridges with near-zero normal", -1);
    +  zzdef_(wadd, Wridge0, "  ave. angle to ridge", Zridge0);
    +  zzdef_(wmax, Wridge0max, "  max. angle to ridge", -1);
    +
    +  zdef_(zdoc, Zdoc12, "Triangulation statistics(Qt)", -1);
    +  zdef_(zinc, Ztricoplanar, "non-simplicial facets triangulated", -1);
    +  zdef_(zadd, Ztricoplanartot, "  ave. new facets created(may be deleted)", Ztricoplanar);
    +  zdef_(zmax, Ztricoplanarmax, "  max. new facets created", -1);
    +  zdef_(zinc, Ztrinull, "null new facets deleted(duplicated vertex)", -1);
    +  zdef_(zinc, Ztrimirror, "mirrored pairs of new facets deleted(same vertices)", -1);
    +  zdef_(zinc, Ztridegen, "degenerate new facets in output(same ridge)", -1);
    +} /* allstat */
    +
    +/*---------------------------------
    +
    +  qh_allstatistics()
    +    reset printed flag for all statistics
    +*/
    +void qh_allstatistics(qhT *qh) {
    +  int i;
    +
    +  for(i=ZEND; i--; )
    +    qh->qhstat.printed[i]= False;
    +} /* allstatistics */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_collectstatistics()
    +    collect statistics for qh.facet_list
    +
    +*/
    +void qh_collectstatistics(qhT *qh) {
    +  facetT *facet, *neighbor, **neighborp;
    +  vertexT *vertex, **vertexp;
    +  realT dotproduct, dist;
    +  int sizneighbors, sizridges, sizvertices, i;
    +
    +  qh->old_randomdist= qh->RANDOMdist;
    +  qh->RANDOMdist= False;
    +  zval_(Zmempoints)= qh->num_points * qh->normal_size + sizeof(qhT);
    +  zval_(Zmemfacets)= 0;
    +  zval_(Zmemridges)= 0;
    +  zval_(Zmemvertices)= 0;
    +  zval_(Zangle)= 0;
    +  wval_(Wangle)= 0.0;
    +  zval_(Znumridges)= 0;
    +  zval_(Znumfacets)= 0;
    +  zval_(Znumneighbors)= 0;
    +  zval_(Znumvertices)= 0;
    +  zval_(Znumvneighbors)= 0;
    +  zval_(Znummergetot)= 0;
    +  zval_(Znummergemax)= 0;
    +  zval_(Zvertices)= qh->num_vertices - qh_setsize(qh, qh->del_vertices);
    +  if (qh->MERGING || qh->APPROXhull || qh->JOGGLEmax < REALmax/2)
    +    wmax_(Wmaxoutside, qh->max_outside);
    +  if (qh->MERGING)
    +    wmin_(Wminvertex, qh->min_vertex);
    +  FORALLfacets
    +    facet->seen= False;
    +  if (qh->DELAUNAY) {
    +    FORALLfacets {
    +      if (facet->upperdelaunay != qh->UPPERdelaunay)
    +        facet->seen= True; /* remove from angle statistics */
    +    }
    +  }
    +  FORALLfacets {
    +    if (facet->visible && qh->NEWfacets)
    +      continue;
    +    sizvertices= qh_setsize(qh, facet->vertices);
    +    sizneighbors= qh_setsize(qh, facet->neighbors);
    +    sizridges= qh_setsize(qh, facet->ridges);
    +    zinc_(Znumfacets);
    +    zadd_(Znumvertices, sizvertices);
    +    zmax_(Zmaxvertices, sizvertices);
    +    zadd_(Znumneighbors, sizneighbors);
    +    zmax_(Zmaxneighbors, sizneighbors);
    +    zadd_(Znummergetot, facet->nummerge);
    +    i= facet->nummerge; /* avoid warnings */
    +    zmax_(Znummergemax, i);
    +    if (!facet->simplicial) {
    +      if (sizvertices == qh->hull_dim) {
    +        zinc_(Znowsimplicial);
    +      }else {
    +        zinc_(Znonsimplicial);
    +      }
    +    }
    +    if (sizridges) {
    +      zadd_(Znumridges, sizridges);
    +      zmax_(Zmaxridges, sizridges);
    +    }
    +    zadd_(Zmemfacets, sizeof(facetT) + qh->normal_size + 2*sizeof(setT)
    +       + SETelemsize * (sizneighbors + sizvertices));
    +    if (facet->ridges) {
    +      zadd_(Zmemridges,
    +         sizeof(setT) + SETelemsize * sizridges + sizridges *
    +         (sizeof(ridgeT) + sizeof(setT) + SETelemsize * (qh->hull_dim-1))/2);
    +    }
    +    if (facet->outsideset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(qh, facet->outsideset));
    +    if (facet->coplanarset)
    +      zadd_(Zmempoints, sizeof(setT) + SETelemsize * qh_setsize(qh, facet->coplanarset));
    +    if (facet->seen) /* Delaunay upper envelope */
    +      continue;
    +    facet->seen= True;
    +    FOREACHneighbor_(facet) {
    +      if (neighbor == qh_DUPLICATEridge || neighbor == qh_MERGEridge
    +          || neighbor->seen || !facet->normal || !neighbor->normal)
    +        continue;
    +      dotproduct= qh_getangle(qh, facet->normal, neighbor->normal);
    +      zinc_(Zangle);
    +      wadd_(Wangle, dotproduct);
    +      wmax_(Wanglemax, dotproduct)
    +      wmin_(Wanglemin, dotproduct)
    +    }
    +    if (facet->normal) {
    +      FOREACHvertex_(facet->vertices) {
    +        zinc_(Zdiststat);
    +        qh_distplane(qh, vertex->point, facet, &dist);
    +        wmax_(Wvertexmax, dist);
    +        wmin_(Wvertexmin, dist);
    +      }
    +    }
    +  }
    +  FORALLvertices {
    +    if (vertex->deleted)
    +      continue;
    +    zadd_(Zmemvertices, sizeof(vertexT));
    +    if (vertex->neighbors) {
    +      sizneighbors= qh_setsize(qh, vertex->neighbors);
    +      zadd_(Znumvneighbors, sizneighbors);
    +      zmax_(Zmaxvneighbors, sizneighbors);
    +      zadd_(Zmemvertices, sizeof(vertexT) + SETelemsize * sizneighbors);
    +    }
    +  }
    +  qh->RANDOMdist= qh->old_randomdist;
    +} /* collectstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_initstatistics(qh)
    +    initialize statistics
    +
    +  notes:
    +  NOerrors -- qh_initstatistics can not use qh_errexit(), qh_fprintf, or qh.ferr
    +  On first call, only qhmem.ferr is defined.  qh_memalloc is not setup.
    +  Also invoked by QhullQh().
    +*/
    +void qh_initstatistics(qhT *qh) {
    +  int i;
    +  realT realx;
    +  int intx;
    +
    +  qh->qhstat.next= 0;
    +  qh_allstatA(qh);
    +  qh_allstatB(qh);
    +  qh_allstatC(qh);
    +  qh_allstatD(qh);
    +  qh_allstatE(qh);
    +  qh_allstatE2(qh);
    +  qh_allstatF(qh);
    +  qh_allstatG(qh);
    +  qh_allstatH(qh);
    +  qh_allstatI(qh);
    +  if (qh->qhstat.next > (int)sizeof(qh->qhstat.id)) {
    +    qh_fprintf(qh, qh->qhmem.ferr, 6184, "qhull error (qh_initstatistics): increase size of qhstat.id[].\n\
    +      qhstat.next %d should be <= sizeof(qh->qhstat.id) %d\n", qh->qhstat.next, (int)sizeof(qh->qhstat.id));
    +#if 0 /* for locating error, Znumridges should be duplicated */
    +    for(i=0; i < ZEND; i++) {
    +      int j;
    +      for(j=i+1; j < ZEND; j++) {
    +        if (qh->qhstat.id[i] == qh->qhstat.id[j]) {
    +          qh_fprintf(qh, qh->qhmem.ferr, 6185, "qhull error (qh_initstatistics): duplicated statistic %d at indices %d and %d\n",
    +              qh->qhstat.id[i], i, j);
    +        }
    +      }
    +    }
    +#endif
    +    qh_exit(qh_ERRqhull);  /* can not use qh_errexit() */
    +  }
    +  qh->qhstat.init[zinc].i= 0;
    +  qh->qhstat.init[zadd].i= 0;
    +  qh->qhstat.init[zmin].i= INT_MAX;
    +  qh->qhstat.init[zmax].i= INT_MIN;
    +  qh->qhstat.init[wadd].r= 0;
    +  qh->qhstat.init[wmin].r= REALmax;
    +  qh->qhstat.init[wmax].r= -REALmax;
    +  for(i=0; i < ZEND; i++) {
    +    if (qh->qhstat.type[i] > ZTYPEreal) {
    +      realx= qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].r;
    +      qh->qhstat.stats[i].r= realx;
    +    }else if (qh->qhstat.type[i] != zdoc) {
    +      intx= qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].i;
    +      qh->qhstat.stats[i].i= intx;
    +    }
    +  }
    +} /* initstatistics */
    +
    +/*---------------------------------
    +
    +  qh_newstats(qh, )
    +    returns True if statistics for zdoc
    +
    +  returns:
    +    next zdoc
    +*/
    +boolT qh_newstats(qhT *qh, int idx, int *nextindex) {
    +  boolT isnew= False;
    +  int start, i;
    +
    +  if (qh->qhstat.type[qh->qhstat.id[idx]] == zdoc)
    +    start= idx+1;
    +  else
    +    start= idx;
    +  for(i= start; i < qh->qhstat.next && qh->qhstat.type[qh->qhstat.id[i]] != zdoc; i++) {
    +    if (!qh_nostatistic(qh, qh->qhstat.id[i]) && !qh->qhstat.printed[qh->qhstat.id[i]])
    +        isnew= True;
    +  }
    +  *nextindex= i;
    +  return isnew;
    +} /* newstats */
    +
    +/*---------------------------------
    +
    +  qh_nostatistic(qh, index )
    +    true if no statistic to print
    +*/
    +boolT qh_nostatistic(qhT *qh, int i) {
    +
    +  if ((qh->qhstat.type[i] > ZTYPEreal
    +       &&qh->qhstat.stats[i].r == qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].r)
    +      || (qh->qhstat.type[i] < ZTYPEreal
    +          &&qh->qhstat.stats[i].i == qh->qhstat.init[(unsigned char)(qh->qhstat.type[i])].i))
    +    return True;
    +  return False;
    +} /* nostatistic */
    +
    +#if qh_KEEPstatistics
    +/*---------------------------------
    +
    +  qh_printallstatistics(qh, fp, string )
    +    print all statistics with header 'string'
    +*/
    +void qh_printallstatistics(qhT *qh, FILE *fp, const char *string) {
    +
    +  qh_allstatistics(qh);
    +  qh_collectstatistics(qh);
    +  qh_printstatistics(qh, fp, string);
    +  qh_memstatistics(qh, fp);
    +}
    +
    +
    +/*---------------------------------
    +
    +  qh_printstatistics(qh, fp, string )
    +    print statistics to a file with header 'string'
    +    skips statistics with qhstat.printed[] (reset with qh_allstatistics)
    +
    +  see:
    +    qh_printallstatistics()
    +*/
    +void qh_printstatistics(qhT *qh, FILE *fp, const char *string) {
    +  int i, k;
    +  realT ave;
    +
    +  if (qh->num_points != qh->num_vertices) {
    +    wval_(Wpbalance)= 0;
    +    wval_(Wpbalance2)= 0;
    +  }else
    +    wval_(Wpbalance2)= qh_stddev(zval_(Zpbalance), wval_(Wpbalance),
    +                                 wval_(Wpbalance2), &ave);
    +  wval_(Wnewbalance2)= qh_stddev(zval_(Zprocessed), wval_(Wnewbalance),
    +                                 wval_(Wnewbalance2), &ave);
    +  qh_fprintf(qh, fp, 9350, "\n\
    +%s\n\
    + qhull invoked by: %s | %s\n%s with options:\n%s\n", string, qh->rbox_command,
    +     qh->qhull_command, qh_version, qh->qhull_options);
    +  qh_fprintf(qh, fp, 9351, "\nprecision constants:\n\
    + %6.2g max. abs. coordinate in the (transformed) input('Qbd:n')\n\
    + %6.2g max. roundoff error for distance computation('En')\n\
    + %6.2g max. roundoff error for angle computations\n\
    + %6.2g min. distance for outside points ('Wn')\n\
    + %6.2g min. distance for visible facets ('Vn')\n\
    + %6.2g max. distance for coplanar facets ('Un')\n\
    + %6.2g max. facet width for recomputing centrum and area\n\
    +",
    +  qh->MAXabs_coord, qh->DISTround, qh->ANGLEround, qh->MINoutside,
    +        qh->MINvisible, qh->MAXcoplanar, qh->WIDEfacet);
    +  if (qh->KEEPnearinside)
    +    qh_fprintf(qh, fp, 9352, "\
    + %6.2g max. distance for near-inside points\n", qh->NEARinside);
    +  if (qh->premerge_cos < REALmax/2) qh_fprintf(qh, fp, 9353, "\
    + %6.2g max. cosine for pre-merge angle\n", qh->premerge_cos);
    +  if (qh->PREmerge) qh_fprintf(qh, fp, 9354, "\
    + %6.2g radius of pre-merge centrum\n", qh->premerge_centrum);
    +  if (qh->postmerge_cos < REALmax/2) qh_fprintf(qh, fp, 9355, "\
    + %6.2g max. cosine for post-merge angle\n", qh->postmerge_cos);
    +  if (qh->POSTmerge) qh_fprintf(qh, fp, 9356, "\
    + %6.2g radius of post-merge centrum\n", qh->postmerge_centrum);
    +  qh_fprintf(qh, fp, 9357, "\
    + %6.2g max. distance for merging two simplicial facets\n\
    + %6.2g max. roundoff error for arithmetic operations\n\
    + %6.2g min. denominator for divisions\n\
    +  zero diagonal for Gauss: ", qh->ONEmerge, REALepsilon, qh->MINdenom);
    +  for(k=0; k < qh->hull_dim; k++)
    +    qh_fprintf(qh, fp, 9358, "%6.2e ", qh->NEARzero[k]);
    +  qh_fprintf(qh, fp, 9359, "\n\n");
    +  for(i=0 ; i < qh->qhstat.next; )
    +    qh_printstats(qh, fp, i, &i);
    +} /* printstatistics */
    +#endif /* qh_KEEPstatistics */
    +
    +/*---------------------------------
    +
    +  qh_printstatlevel(qh, fp, id )
    +    print level information for a statistic
    +
    +  notes:
    +    nop if id >= ZEND, printed, or same as initial value
    +*/
    +void qh_printstatlevel(qhT *qh, FILE *fp, int id) {
    +#define NULLfield "       "
    +
    +  if (id >= ZEND || qh->qhstat.printed[id])
    +    return;
    +  if (qh->qhstat.type[id] == zdoc) {
    +    qh_fprintf(qh, fp, 9360, "%s\n", qh->qhstat.doc[id]);
    +    return;
    +  }
    +  if (qh_nostatistic(qh, id) || !qh->qhstat.doc[id])
    +    return;
    +  qh->qhstat.printed[id]= True;
    +  if (qh->qhstat.count[id] != -1
    +      && qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i == 0)
    +    qh_fprintf(qh, fp, 9361, " *0 cnt*");
    +  else if (qh->qhstat.type[id] >= ZTYPEreal && qh->qhstat.count[id] == -1)
    +    qh_fprintf(qh, fp, 9362, "%7.2g", qh->qhstat.stats[id].r);
    +  else if (qh->qhstat.type[id] >= ZTYPEreal && qh->qhstat.count[id] != -1)
    +    qh_fprintf(qh, fp, 9363, "%7.2g", qh->qhstat.stats[id].r/ qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i);
    +  else if (qh->qhstat.type[id] < ZTYPEreal && qh->qhstat.count[id] == -1)
    +    qh_fprintf(qh, fp, 9364, "%7d", qh->qhstat.stats[id].i);
    +  else if (qh->qhstat.type[id] < ZTYPEreal && qh->qhstat.count[id] != -1)
    +    qh_fprintf(qh, fp, 9365, "%7.3g", (realT) qh->qhstat.stats[id].i / qh->qhstat.stats[(unsigned char)(qh->qhstat.count[id])].i);
    +  qh_fprintf(qh, fp, 9366, " %s\n", qh->qhstat.doc[id]);
    +} /* printstatlevel */
    +
    +
    +/*---------------------------------
    +
    +  qh_printstats(qh, fp, index, nextindex )
    +    print statistics for a zdoc group
    +
    +  returns:
    +    next zdoc if non-null
    +*/
    +void qh_printstats(qhT *qh, FILE *fp, int idx, int *nextindex) {
    +  int j, nexti;
    +
    +  if (qh_newstats(qh, idx, &nexti)) {
    +    qh_fprintf(qh, fp, 9367, "\n");
    +    for (j=idx; jqhstat.id[j]);
    +  }
    +  if (nextindex)
    +    *nextindex= nexti;
    +} /* printstats */
    +
    +#if qh_KEEPstatistics
    +
    +/*---------------------------------
    +
    +  qh_stddev(num, tot, tot2, ave )
    +    compute the standard deviation and average from statistics
    +
    +    tot2 is the sum of the squares
    +  notes:
    +    computes r.m.s.:
    +      (x-ave)^2
    +      == x^2 - 2x tot/num +   (tot/num)^2
    +      == tot2 - 2 tot tot/num + tot tot/num
    +      == tot2 - tot ave
    +*/
    +realT qh_stddev(int num, realT tot, realT tot2, realT *ave) {
    +  realT stddev;
    +
    +  *ave= tot/num;
    +  stddev= sqrt(tot2/num - *ave * *ave);
    +  return stddev;
    +} /* stddev */
    +
    +#endif /* qh_KEEPstatistics */
    +
    +#if !qh_KEEPstatistics
    +void    qh_collectstatistics(qhT *qh) {}
    +void    qh_printallstatistics(qhT *qh, FILE *fp, char *string) {};
    +void    qh_printstatistics(qhT *qh, FILE *fp, char *string) {}
    +#endif
    +
    diff --git a/xs/src/qhull/src/libqhull_r/stat_r.h b/xs/src/qhull/src/libqhull_r/stat_r.h
    new file mode 100644
    index 0000000000..75e7d10578
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/stat_r.h
    @@ -0,0 +1,533 @@
    +/*
      ---------------------------------
    +
    +   stat_r.h
    +     contains all statistics that are collected for qhull
    +
    +   see qh-stat_r.htm and stat_r.c
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/libqhull_r/stat_r.h#5 $$Change: 2079 $
    +   $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +
    +   recompile qhull if you change this file
    +
    +   Integer statistics are Z* while real statistics are W*.
    +
    +   define MAYdebugx to call a routine at every statistic event
    +
    +*/
    +
    +#ifndef qhDEFstat
    +#define qhDEFstat 1
    +
    +/* Depends on realT.  Do not include libqhull_r to avoid circular dependency */
    +
    +#ifndef DEFqhT
    +#define DEFqhT 1
    +typedef struct qhT qhT;         /* Defined by libqhull_r.h */
    +#endif
    +
    +#ifndef DEFqhstatT
    +#define DEFqhstatT 1
    +typedef struct qhstatT qhstatT; /* Defined here */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_KEEPstatistics
    +    0 turns off statistic gathering (except zzdef/zzinc/zzadd/zzval/wwval)
    +*/
    +#ifndef qh_KEEPstatistics
    +#define qh_KEEPstatistics 1
    +#endif
    +
    +/*---------------------------------
    +
    +  Zxxx for integers, Wxxx for reals
    +
    +  notes:
    +    be sure that all statistics are defined in stat_r.c
    +      otherwise initialization may core dump
    +    can pick up all statistics by:
    +      grep '[zw].*_[(][ZW]' *.c >z.x
    +    remove trailers with query">-
    +    remove leaders with  query-replace-regexp [ ^I]+  (
    +*/
    +#if qh_KEEPstatistics
    +enum qh_statistics {     /* alphabetical after Z/W */
    +    Zacoplanar,
    +    Wacoplanarmax,
    +    Wacoplanartot,
    +    Zangle,
    +    Wangle,
    +    Wanglemax,
    +    Wanglemin,
    +    Zangletests,
    +    Wareatot,
    +    Wareamax,
    +    Wareamin,
    +    Zavoidold,
    +    Wavoidoldmax,
    +    Wavoidoldtot,
    +    Zback0,
    +    Zbestcentrum,
    +    Zbestdist,
    +    Zbestlower,
    +    Zbestlowerall,
    +    Zbestloweralln,
    +    Zbestlowerv,
    +    Zcentrumtests,
    +    Zcheckpart,
    +    Zcomputefurthest,
    +    Zconcave,
    +    Wconcavemax,
    +    Wconcavetot,
    +    Zconcaveridges,
    +    Zconcaveridge,
    +    Zcoplanar,
    +    Wcoplanarmax,
    +    Wcoplanartot,
    +    Zcoplanarangle,
    +    Zcoplanarcentrum,
    +    Zcoplanarhorizon,
    +    Zcoplanarinside,
    +    Zcoplanarpart,
    +    Zcoplanarridges,
    +    Wcpu,
    +    Zcyclefacetmax,
    +    Zcyclefacettot,
    +    Zcyclehorizon,
    +    Zcyclevertex,
    +    Zdegen,
    +    Wdegenmax,
    +    Wdegentot,
    +    Zdegenvertex,
    +    Zdelfacetdup,
    +    Zdelridge,
    +    Zdelvertextot,
    +    Zdelvertexmax,
    +    Zdetsimplex,
    +    Zdistcheck,
    +    Zdistconvex,
    +    Zdistgood,
    +    Zdistio,
    +    Zdistplane,
    +    Zdiststat,
    +    Zdistvertex,
    +    Zdistzero,
    +    Zdoc1,
    +    Zdoc2,
    +    Zdoc3,
    +    Zdoc4,
    +    Zdoc5,
    +    Zdoc6,
    +    Zdoc7,
    +    Zdoc8,
    +    Zdoc9,
    +    Zdoc10,
    +    Zdoc11,
    +    Zdoc12,
    +    Zdropdegen,
    +    Zdropneighbor,
    +    Zdupflip,
    +    Zduplicate,
    +    Wduplicatemax,
    +    Wduplicatetot,
    +    Zdupridge,
    +    Zdupsame,
    +    Zflipped,
    +    Wflippedmax,
    +    Wflippedtot,
    +    Zflippedfacets,
    +    Zfindbest,
    +    Zfindbestmax,
    +    Zfindbesttot,
    +    Zfindcoplanar,
    +    Zfindfail,
    +    Zfindhorizon,
    +    Zfindhorizonmax,
    +    Zfindhorizontot,
    +    Zfindjump,
    +    Zfindnew,
    +    Zfindnewmax,
    +    Zfindnewtot,
    +    Zfindnewjump,
    +    Zfindnewsharp,
    +    Zgauss0,
    +    Zgoodfacet,
    +    Zhashlookup,
    +    Zhashridge,
    +    Zhashridgetest,
    +    Zhashtests,
    +    Zinsidevisible,
    +    Zintersect,
    +    Zintersectfail,
    +    Zintersectmax,
    +    Zintersectnum,
    +    Zintersecttot,
    +    Zmaxneighbors,
    +    Wmaxout,
    +    Wmaxoutside,
    +    Zmaxridges,
    +    Zmaxvertex,
    +    Zmaxvertices,
    +    Zmaxvneighbors,
    +    Zmemfacets,
    +    Zmempoints,
    +    Zmemridges,
    +    Zmemvertices,
    +    Zmergeflipdup,
    +    Zmergehorizon,
    +    Zmergeinittot,
    +    Zmergeinitmax,
    +    Zmergeinittot2,
    +    Zmergeintohorizon,
    +    Zmergenew,
    +    Zmergesettot,
    +    Zmergesetmax,
    +    Zmergesettot2,
    +    Zmergesimplex,
    +    Zmergevertex,
    +    Wmindenom,
    +    Wminvertex,
    +    Zminnorm,
    +    Zmultiridge,
    +    Znearlysingular,
    +    Zneighbor,
    +    Wnewbalance,
    +    Wnewbalance2,
    +    Znewfacettot,
    +    Znewfacetmax,
    +    Znewvertex,
    +    Wnewvertex,
    +    Wnewvertexmax,
    +    Znoarea,
    +    Znonsimplicial,
    +    Znowsimplicial,
    +    Znotgood,
    +    Znotgoodnew,
    +    Znotmax,
    +    Znumfacets,
    +    Znummergemax,
    +    Znummergetot,
    +    Znumneighbors,
    +    Znumridges,
    +    Znumvertices,
    +    Znumvisibility,
    +    Znumvneighbors,
    +    Zonehorizon,
    +    Zpartangle,
    +    Zpartcoplanar,
    +    Zpartflip,
    +    Zparthorizon,
    +    Zpartinside,
    +    Zpartition,
    +    Zpartitionall,
    +    Zpartnear,
    +    Zpbalance,
    +    Wpbalance,
    +    Wpbalance2,
    +    Zpostfacets,
    +    Zpremergetot,
    +    Zprocessed,
    +    Zremvertex,
    +    Zremvertexdel,
    +    Zrenameall,
    +    Zrenamepinch,
    +    Zrenameshare,
    +    Zretry,
    +    Wretrymax,
    +    Zridge,
    +    Wridge,
    +    Wridgemax,
    +    Zridge0,
    +    Wridge0,
    +    Wridge0max,
    +    Zridgemid,
    +    Wridgemid,
    +    Wridgemidmax,
    +    Zridgeok,
    +    Wridgeok,
    +    Wridgeokmax,
    +    Zsearchpoints,
    +    Zsetplane,
    +    Ztestvneighbor,
    +    Ztotcheck,
    +    Ztothorizon,
    +    Ztotmerge,
    +    Ztotpartcoplanar,
    +    Ztotpartition,
    +    Ztotridges,
    +    Ztotvertices,
    +    Ztotvisible,
    +    Ztricoplanar,
    +    Ztricoplanarmax,
    +    Ztricoplanartot,
    +    Ztridegen,
    +    Ztrimirror,
    +    Ztrinull,
    +    Wvertexmax,
    +    Wvertexmin,
    +    Zvertexridge,
    +    Zvertexridgetot,
    +    Zvertexridgemax,
    +    Zvertices,
    +    Zvisfacettot,
    +    Zvisfacetmax,
    +    Zvisit,
    +    Zvisit2max,
    +    Zvisvertextot,
    +    Zvisvertexmax,
    +    Zvvisit,
    +    Zvvisit2max,
    +    Zwidefacet,
    +    Zwidevertices,
    +    ZEND};
    +
    +/*---------------------------------
    +
    +  Zxxx/Wxxx statistics that remain defined if qh_KEEPstatistics=0
    +
    +  notes:
    +    be sure to use zzdef, zzinc, etc. with these statistics (no double checking!)
    +*/
    +#else
    +enum qh_statistics {     /* for zzdef etc. macros */
    +  Zback0,
    +  Zbestdist,
    +  Zcentrumtests,
    +  Zcheckpart,
    +  Zconcaveridges,
    +  Zcoplanarhorizon,
    +  Zcoplanarpart,
    +  Zcoplanarridges,
    +  Zcyclefacettot,
    +  Zcyclehorizon,
    +  Zdelvertextot,
    +  Zdistcheck,
    +  Zdistconvex,
    +  Zdistzero,
    +  Zdoc1,
    +  Zdoc2,
    +  Zdoc3,
    +  Zdoc11,
    +  Zflippedfacets,
    +  Zgauss0,
    +  Zminnorm,
    +  Zmultiridge,
    +  Znearlysingular,
    +  Wnewvertexmax,
    +  Znumvisibility,
    +  Zpartcoplanar,
    +  Zpartition,
    +  Zpartitionall,
    +  Zprocessed,
    +  Zretry,
    +  Zridge,
    +  Wridge,
    +  Wridgemax,
    +  Zridge0,
    +  Wridge0,
    +  Wridge0max,
    +  Zridgemid,
    +  Wridgemid,
    +  Wridgemidmax,
    +  Zridgeok,
    +  Wridgeok,
    +  Wridgeokmax,
    +  Zsetplane,
    +  Ztotcheck,
    +  Ztotmerge,
    +    ZEND};
    +#endif
    +
    +/*---------------------------------
    +
    +  ztype
    +    the type of a statistic sets its initial value.
    +
    +  notes:
    +    The type should be the same as the macro for collecting the statistic
    +*/
    +enum ztypes {zdoc,zinc,zadd,zmax,zmin,ZTYPEreal,wadd,wmax,wmin,ZTYPEend};
    +
    +/*========== macros and constants =============*/
    +
    +/*----------------------------------
    +
    +  MAYdebugx
    +    define as maydebug() to be called frequently for error trapping
    +*/
    +#define MAYdebugx
    +
    +/*----------------------------------
    +
    +  zzdef_, zdef_( type, name, doc, -1)
    +    define a statistic (assumes 'qhstat.next= 0;')
    +
    +  zdef_( type, name, doc, count)
    +    define an averaged statistic
    +    printed as name/count
    +*/
    +#define zzdef_(stype,name,string,cnt) qh->qhstat.id[qh->qhstat.next++]=name; \
    +   qh->qhstat.doc[name]= string; qh->qhstat.count[name]= cnt; qh->qhstat.type[name]= stype
    +#if qh_KEEPstatistics
    +#define zdef_(stype,name,string,cnt) qh->qhstat.id[qh->qhstat.next++]=name; \
    +   qh->qhstat.doc[name]= string; qh->qhstat.count[name]= cnt; qh->qhstat.type[name]= stype
    +#else
    +#define zdef_(type,name,doc,count)
    +#endif
    +
    +/*----------------------------------
    +
    +  zzinc_( name ), zinc_( name)
    +    increment an integer statistic
    +*/
    +#define zzinc_(id) {MAYdebugx; qh->qhstat.stats[id].i++;}
    +#if qh_KEEPstatistics
    +#define zinc_(id) {MAYdebugx; qh->qhstat.stats[id].i++;}
    +#else
    +#define zinc_(id) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzadd_( name, value ), zadd_( name, value ), wadd_( name, value )
    +    add value to an integer or real statistic
    +*/
    +#define zzadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].i += (val);}
    +#define wwadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].r += (val);}
    +#if qh_KEEPstatistics
    +#define zadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].i += (val);}
    +#define wadd_(id, val) {MAYdebugx; qh->qhstat.stats[id].r += (val);}
    +#else
    +#define zadd_(id, val) {}
    +#define wadd_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zzval_( name ), zval_( name ), wwval_( name )
    +    set or return value of a statistic
    +*/
    +#define zzval_(id) ((qh->qhstat.stats[id]).i)
    +#define wwval_(id) ((qh->qhstat.stats[id]).r)
    +#if qh_KEEPstatistics
    +#define zval_(id) ((qh->qhstat.stats[id]).i)
    +#define wval_(id) ((qh->qhstat.stats[id]).r)
    +#else
    +#define zval_(id) qh->qhstat.tempi
    +#define wval_(id) qh->qhstat.tempr
    +#endif
    +
    +/*----------------------------------
    +
    +  zmax_( id, val ), wmax_( id, value )
    +    maximize id with val
    +*/
    +#define wwmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].r,(val));}
    +#if qh_KEEPstatistics
    +#define zmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].i,(val));}
    +#define wmax_(id, val) {MAYdebugx; maximize_(qh->qhstat.stats[id].r,(val));}
    +#else
    +#define zmax_(id, val) {}
    +#define wmax_(id, val) {}
    +#endif
    +
    +/*----------------------------------
    +
    +  zmin_( id, val ), wmin_( id, value )
    +    minimize id with val
    +*/
    +#if qh_KEEPstatistics
    +#define zmin_(id, val) {MAYdebugx; minimize_(qh->qhstat.stats[id].i,(val));}
    +#define wmin_(id, val) {MAYdebugx; minimize_(qh->qhstat.stats[id].r,(val));}
    +#else
    +#define zmin_(id, val) {}
    +#define wmin_(id, val) {}
    +#endif
    +
    +/*================== stat_r.h types ==============*/
    +
    +
    +/*----------------------------------
    +
    +  intrealT
    +    union of integer and real, used for statistics
    +*/
    +typedef union intrealT intrealT;    /* union of int and realT */
    +union intrealT {
    +    int i;
    +    realT r;
    +};
    +
    +/*----------------------------------
    +
    +  qhstat
    +    Data structure for statistics, similar to qh and qhrbox
    +
    +    Allocated as part of qhT (libqhull_r.h)
    +*/
    +
    +struct qhstatT {
    +  intrealT   stats[ZEND];     /* integer and real statistics */
    +  unsigned   char id[ZEND+10]; /* id's in print order */
    +  const char *doc[ZEND];       /* array of documentation strings */
    +  short int  count[ZEND];     /* -1 if none, else index of count to use */
    +  char       type[ZEND];      /* type, see ztypes above */
    +  char       printed[ZEND];   /* true, if statistic has been printed */
    +  intrealT   init[ZTYPEend];  /* initial values by types, set initstatistics */
    +
    +  int        next;            /* next index for zdef_ */
    +  int        precision;       /* index for precision problems */
    +  int        vridges;         /* index for Voronoi ridges */
    +  int        tempi;
    +  realT      tempr;
    +};
    +
    +/*========== function prototypes ===========*/
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +void    qh_allstatA(qhT *qh);
    +void    qh_allstatB(qhT *qh);
    +void    qh_allstatC(qhT *qh);
    +void    qh_allstatD(qhT *qh);
    +void    qh_allstatE(qhT *qh);
    +void    qh_allstatE2(qhT *qh);
    +void    qh_allstatF(qhT *qh);
    +void    qh_allstatG(qhT *qh);
    +void    qh_allstatH(qhT *qh);
    +void    qh_allstatI(qhT *qh);
    +void    qh_allstatistics(qhT *qh);
    +void    qh_collectstatistics(qhT *qh);
    +void    qh_initstatistics(qhT *qh);
    +boolT   qh_newstats(qhT *qh, int idx, int *nextindex);
    +boolT   qh_nostatistic(qhT *qh, int i);
    +void    qh_printallstatistics(qhT *qh, FILE *fp, const char *string);
    +void    qh_printstatistics(qhT *qh, FILE *fp, const char *string);
    +void    qh_printstatlevel(qhT *qh, FILE *fp, int id);
    +void    qh_printstats(qhT *qh, FILE *fp, int idx, int *nextindex);
    +realT   qh_stddev(int num, realT tot, realT tot2, realT *ave);
    +
    +#ifdef __cplusplus
    +} /* extern "C" */
    +#endif
    +
    +#endif   /* qhDEFstat */
    diff --git a/xs/src/qhull/src/libqhull_r/user_r.c b/xs/src/qhull/src/libqhull_r/user_r.c
    new file mode 100644
    index 0000000000..bf7ed1d8d6
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/user_r.c
    @@ -0,0 +1,527 @@
    +/*
      ---------------------------------
    +
    +   user.c
    +   user redefinable functions
    +
    +   see user2_r.c for qh_fprintf, qh_malloc, qh_free
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   see libqhull_r.h for data structures, macros, and user-callable functions.
    +
    +   see user_eg.c, user_eg2.c, and unix.c for examples.
    +
    +   see user.h for user-definable constants
    +
    +      use qh_NOmem in mem_r.h to turn off memory management
    +      use qh_NOmerge in user.h to turn off facet merging
    +      set qh_KEEPstatistics in user.h to 0 to turn off statistics
    +
    +   This is unsupported software.  You're welcome to make changes,
    +   but you're on your own if something goes wrong.  Use 'Tc' to
    +   check frequently.  Usually qhull will report an error if
    +   a data structure becomes inconsistent.  If so, it also reports
    +   the last point added to the hull, e.g., 102.  You can then trace
    +   the execution of qhull with "T4P102".
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +
    +   Qhull-template is a template for calling qhull from within your application
    +
    +   if you recompile and load this module, then user.o will not be loaded
    +   from qhull.a
    +
    +   you can add additional quick allocation sizes in qh_user_memsizes
    +
    +   if the other functions here are redefined to not use qh_print...,
    +   then io.o will not be loaded from qhull.a.  See user_eg_r.c for an
    +   example.  We recommend keeping io.o for the extra debugging
    +   information it supplies.
    +*/
    +
    +#include "qhull_ra.h"
    +
    +#include 
    +
    +/*---------------------------------
    +
    +  Qhull-template
    +    Template for calling qhull from inside your program
    +
    +  returns:
    +    exit code(see qh_ERR... in libqhull_r.h)
    +    all memory freed
    +
    +  notes:
    +    This can be called any number of times.
    +
    +*/
    +#if 0
    +{
    +  int dim;                  /* dimension of points */
    +  int numpoints;            /* number of points */
    +  coordT *points;           /* array of coordinates for each point */
    +  boolT ismalloc;           /* True if qhull should free points in qh_freeqhull() or reallocation */
    +  char flags[]= "qhull Tv"; /* option flags for qhull, see qh_opt.htm */
    +  FILE *outfile= stdout;    /* output from qh_produce_output(qh)
    +                               use NULL to skip qh_produce_output(qh) */
    +  FILE *errfile= stderr;    /* error messages from qhull code */
    +  int exitcode;             /* 0 if no error from qhull */
    +  facetT *facet;            /* set by FORALLfacets */
    +  int curlong, totlong;     /* memory remaining after qh_memfreeshort */
    +
    +  qhT qh_qh;                /* Qhull's data structure.  First argument of most calls */
    +  qhT *qh= &qh_qh;          /* Alternatively -- qhT *qh= (qhT*)malloc(sizeof(qhT)) */
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  qh_zero(qh, errfile);
    +
    +  /* initialize dim, numpoints, points[], ismalloc here */
    +  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh->facet_list' contains the convex hull */
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf(qh, errfile, 7068, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +}
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_new_qhull(qh, dim, numpoints, points, ismalloc, qhull_cmd, outfile, errfile )
    +    Run qhull and return results in qh.
    +    Returns exitcode (0 if no errors).
    +    Before first call, either call qh_zero(qh, errfile), or set qh to all zero.
    +
    +  notes:
    +    do not modify points until finished with results.
    +      The qhull data structure contains pointers into the points array.
    +    do not call qhull functions before qh_new_qhull().
    +      The qhull data structure is not initialized until qh_new_qhull().
    +    do not call qh_init_A (global_r.c)
    +
    +    Default errfile is stderr, outfile may be null
    +    qhull_cmd must start with "qhull "
    +    projects points to a new point array for Delaunay triangulations ('d' and 'v')
    +    transforms points into a new point array for halfspace intersection ('H')
    +
    +  see:
    +    Qhull-template at the beginning of this file.
    +    An example of using qh_new_qhull is user_eg_r.c
    +*/
    +int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc,
    +                char *qhull_cmd, FILE *outfile, FILE *errfile) {
    +  /* gcc may issue a "might be clobbered" warning for dim, points, and ismalloc [-Wclobbered].
    +     These parameters are not referenced after a longjmp() and hence not clobbered.
    +     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
    +  int exitcode, hulldim;
    +  boolT new_ismalloc;
    +  coordT *new_points;
    +
    +  if(!errfile){
    +    errfile= stderr;
    +  }
    +  if (!qh->qhmem.ferr) {
    +    qh_meminit(qh, errfile);
    +  } else {
    +    qh_memcheck(qh);
    +  }
    +  if (strncmp(qhull_cmd, "qhull ", (size_t)6)) {
    +    qh_fprintf(qh, errfile, 6186, "qhull error (qh_new_qhull): start qhull_cmd argument with \"qhull \"\n");
    +    return qh_ERRinput;
    +  }
    +  qh_initqhull_start(qh, NULL, outfile, errfile);
    +  trace1((qh, qh->ferr, 1044, "qh_new_qhull: build new Qhull for %d %d-d points with %s\n", numpoints, dim, qhull_cmd));
    +  exitcode = setjmp(qh->errexit);
    +  if (!exitcode)
    +  {
    +    qh->NOerrexit = False;
    +    qh_initflags(qh, qhull_cmd);
    +    if (qh->DELAUNAY)
    +      qh->PROJECTdelaunay= True;
    +    if (qh->HALFspace) {
    +      /* points is an array of halfspaces,
    +         the last coordinate of each halfspace is its offset */
    +      hulldim= dim-1;
    +      qh_setfeasible(qh, hulldim);
    +      new_points= qh_sethalfspace_all(qh, dim, numpoints, points, qh->feasible_point);
    +      new_ismalloc= True;
    +      if (ismalloc)
    +        qh_free(points);
    +    }else {
    +      hulldim= dim;
    +      new_points= points;
    +      new_ismalloc= ismalloc;
    +    }
    +    qh_init_B(qh, new_points, numpoints, hulldim, new_ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    if (outfile) {
    +      qh_produce_output(qh);
    +    }else {
    +      qh_prepare_output(qh);
    +    }
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +  }
    +  qh->NOerrexit = True;
    +  return exitcode;
    +} /* new_qhull */
    +
    +/*---------------------------------
    +
    +  qh_errexit(qh, exitcode, facet, ridge )
    +    report and exit from an error
    +    report facet and ridge if non-NULL
    +    reports useful information such as last point processed
    +    set qh.FORCEoutput to print neighborhood of facet
    +
    +  see:
    +    qh_errexit2() in libqhull_r.c for printing 2 facets
    +
    +  design:
    +    check for error within error processing
    +    compute qh.hulltime
    +    print facet and ridge (if any)
    +    report commandString, options, qh.furthest_id
    +    print summary and statistics (including precision statistics)
    +    if qh_ERRsingular
    +      print help text for singular data set
    +    exit program via long jump (if defined) or exit()
    +*/
    +void qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge) {
    +
    +  if (qh->ERREXITcalled) {
    +    qh_fprintf(qh, qh->ferr, 8126, "\nqhull error while processing previous error.  Exit program\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh->ERREXITcalled= True;
    +  if (!qh->QHULLfinished)
    +    qh->hulltime= qh_CPUclock - qh->hulltime;
    +  qh_errprint(qh, "ERRONEOUS", facet, NULL, ridge, NULL);
    +  qh_fprintf(qh, qh->ferr, 8127, "\nWhile executing: %s | %s\n", qh->rbox_command, qh->qhull_command);
    +  qh_fprintf(qh, qh->ferr, 8128, "Options selected for Qhull %s:\n%s\n", qh_version, qh->qhull_options);
    +  if (qh->furthest_id >= 0) {
    +    qh_fprintf(qh, qh->ferr, 8129, "Last point added to hull was p%d.", qh->furthest_id);
    +    if (zzval_(Ztotmerge))
    +      qh_fprintf(qh, qh->ferr, 8130, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh->QHULLfinished)
    +      qh_fprintf(qh, qh->ferr, 8131, "\nQhull has finished constructing the hull.");
    +    else if (qh->POSTmerging)
    +      qh_fprintf(qh, qh->ferr, 8132, "\nQhull has started post-merging.");
    +    qh_fprintf(qh, qh->ferr, 8133, "\n");
    +  }
    +  if (qh->FORCEoutput && (qh->QHULLfinished || (!facet && !ridge)))
    +    qh_produce_output(qh);
    +  else if (exitcode != qh_ERRinput) {
    +    if (exitcode != qh_ERRsingular && zzval_(Zsetplane) > qh->hull_dim+1) {
    +      qh_fprintf(qh, qh->ferr, 8134, "\nAt error exit:\n");
    +      qh_printsummary(qh, qh->ferr);
    +      if (qh->PRINTstatistics) {
    +        qh_collectstatistics(qh);
    +        qh_printstatistics(qh, qh->ferr, "at error exit");
    +        qh_memstatistics(qh, qh->ferr);
    +      }
    +    }
    +    if (qh->PRINTprecision)
    +      qh_printstats(qh, qh->ferr, qh->qhstat.precision, NULL);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  else if (exitcode == qh_ERRsingular)
    +    qh_printhelp_singular(qh, qh->ferr);
    +  else if (exitcode == qh_ERRprec && !qh->PREmerge)
    +    qh_printhelp_degenerate(qh, qh->ferr);
    +  if (qh->NOerrexit) {
    +    qh_fprintf(qh, qh->ferr, 6187, "qhull error while ending program, or qh->NOerrexit not cleared after setjmp(). Exit program with error.\n");
    +    qh_exit(qh_ERRqhull);
    +  }
    +  qh->ERREXITcalled= False;
    +  qh->NOerrexit= True;
    +  qh->ALLOWrestart= False;  /* longjmp will undo qh_build_withrestart */
    +  longjmp(qh->errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*---------------------------------
    +
    +  qh_errprint(qh, fp, string, atfacet, otherfacet, atridge, atvertex )
    +    prints out the information of facets and ridges to fp
    +    also prints neighbors and geomview output
    +
    +  notes:
    +    except for string, any parameter may be NULL
    +*/
    +void qh_errprint(qhT *qh, const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +  int i;
    +
    +  if (atfacet) {
    +    qh_fprintf(qh, qh->ferr, 8135, "%s FACET:\n", string);
    +    qh_printfacet(qh, qh->ferr, atfacet);
    +  }
    +  if (otherfacet) {
    +    qh_fprintf(qh, qh->ferr, 8136, "%s OTHER FACET:\n", string);
    +    qh_printfacet(qh, qh->ferr, otherfacet);
    +  }
    +  if (atridge) {
    +    qh_fprintf(qh, qh->ferr, 8137, "%s RIDGE:\n", string);
    +    qh_printridge(qh, qh->ferr, atridge);
    +    if (atridge->top && atridge->top != atfacet && atridge->top != otherfacet)
    +      qh_printfacet(qh, qh->ferr, atridge->top);
    +    if (atridge->bottom
    +        && atridge->bottom != atfacet && atridge->bottom != otherfacet)
    +      qh_printfacet(qh, qh->ferr, atridge->bottom);
    +    if (!atfacet)
    +      atfacet= atridge->top;
    +    if (!otherfacet)
    +      otherfacet= otherfacet_(atridge, atfacet);
    +  }
    +  if (atvertex) {
    +    qh_fprintf(qh, qh->ferr, 8138, "%s VERTEX:\n", string);
    +    qh_printvertex(qh, qh->ferr, atvertex);
    +  }
    +  if (qh->fout && qh->FORCEoutput && atfacet && !qh->QHULLfinished && !qh->IStracing) {
    +    qh_fprintf(qh, qh->ferr, 8139, "ERRONEOUS and NEIGHBORING FACETS to output\n");
    +    for (i=0; i < qh_PRINTEND; i++)  /* use fout for geomview output */
    +      qh_printneighborhood(qh, qh->fout, qh->PRINTout[i], atfacet, otherfacet,
    +                            !qh_ALL);
    +  }
    +} /* errprint */
    +
    +
    +/*---------------------------------
    +
    +  qh_printfacetlist(qh, fp, facetlist, facets, printall )
    +    print all fields for a facet list and/or set of facets to fp
    +    if !printall,
    +      only prints good facets
    +
    +  notes:
    +    also prints all vertices
    +*/
    +void qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  qh_printbegin(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);
    +  FORALLfacet_(facetlist)
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);
    +  FOREACHfacet_(facets)
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);
    +  qh_printend(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);
    +} /* printfacetlist */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_degenerate(qh, fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(qhT *qh, FILE *fp) {
    +
    +  if (qh->MERGEexact || qh->PREmerge || qh->JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh, fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh->DELAUNAY && !qh->SCALElast && qh->MAXabs_coord > 1e4)
    +      qh_fprintf(qh, fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh->DELAUNAY && !qh->ATinfinity)
    +      qh_fprintf(qh, fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(qh, fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh->DISTround);
    +    qh_fprintf(qh, fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/*---------------------------------
    +
    +  qh_printhelp_narrowhull(qh, minangle )
    +    Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle) {
    +
    +    qh_fprintf(qh, fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +Is the input lower dimensional (e.g., on a plane in 3-d)?  Qhull may\n\
    +produce a wide facet.  Options 'QbB' (scale to unit box) or 'Qbb' (scale\n\
    +last coordinate) may remove this warning.  Use 'Pp' to skip this warning.\n\
    +See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/*---------------------------------
    +
    +  qh_printhelp_singular(qh, fp )
    +    prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(qhT *qh, FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(qh, fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh->hull_dim);
    +  qh_printvertexlist(qh, fp, "", qh->facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(qh, fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh->DISTround);
    +  qh_printpointid(qh, fp, "center point", qh->hull_dim, qh->interior_point, qh_IDunknown);
    +  qh_fprintf(qh, fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(qh, fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9380, " p%d", qh_pointid(qh, vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh, qh->interior_point, facet, &dist);
    +    qh_fprintf(qh, fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh->HALFspace)
    +      qh_fprintf(qh, fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(qh, fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh->hull_dim >= qh_INITIALmax)
    +      qh_fprintf(qh, fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(qh, fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh->hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh->num_points, coord= qh->first_point+k; i--; coord += qh->hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(qh, fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh->DISTround);
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull_r.h).\n");
    +#endif
    +    qh_fprintf(qh, fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +  }
    +} /* printhelp_singular */
    +
    +/*---------------------------------
    +
    +  qh_user_memsizes(qh)
    +    allocate up to 10 additional, quick allocation sizes
    +
    +  notes:
    +    increase maximum number of allocations in qh_initqhull_mem()
    +*/
    +void qh_user_memsizes(qhT *qh) {
    +
    +  QHULL_UNUSED(qh)
    +  /* qh_memsize(qh, size); */
    +} /* user_memsizes */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/user_r.h b/xs/src/qhull/src/libqhull_r/user_r.h
    new file mode 100644
    index 0000000000..7cca65a804
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/user_r.h
    @@ -0,0 +1,882 @@
    +/*
      ---------------------------------
    +
    +   user.h
    +   user redefinable constants
    +
    +   for each source file, user_r.h is included first
    +
    +   see qh-user_r.htm.  see COPYING for copyright information.
    +
    +   See user_r.c for sample code.
    +
    +   before reading any code, review libqhull_r.h for data structure definitions
    +
    +Sections:
    +   ============= qhull library constants ======================
    +   ============= data types and configuration macros ==========
    +   ============= performance related constants ================
    +   ============= memory constants =============================
    +   ============= joggle constants =============================
    +   ============= conditional compilation ======================
    +   ============= -merge constants- ============================
    +
    +Code flags --
    +  NOerrors -- the code does not call qh_errexit()
    +  WARN64 -- the code may be incompatible with 64-bit pointers
    +
    +*/
    +
    +#include 
    +
    +#ifndef qhDEFuser
    +#define qhDEFuser 1
    +
    +/* Derived from Qt's corelib/global/qglobal.h */
    +#if !defined(SAG_COM) && !defined(__CYGWIN__) && (defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__))
    +#   define QHULL_OS_WIN
    +#elif defined(__MWERKS__) && defined(__INTEL__) /* Metrowerks discontinued before the release of Intel Macs */
    +#   define QHULL_OS_WIN
    +#endif
    +
    +/*============================================================*/
    +/*============= qhull library constants ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  FILENAMElen -- max length for TI and TO filenames
    +
    +*/
    +
    +#define qh_FILENAMElen 500
    +
    +/*----------------------------------
    +
    +  msgcode -- Unique message codes for qh_fprintf
    +
    +  If add new messages, assign these values and increment in user.h and user_r.h
    +  See QhullError.h for 10000 errors.
    +
    +  def counters =  [27, 1048, 2059, 3026, 4068, 5003,
    +     6273, 7081, 8147, 9411, 10000, 11029]
    +
    +  See: qh_ERR* [libqhull_r.h]
    +*/
    +
    +#define MSG_TRACE0 0
    +#define MSG_TRACE1 1000
    +#define MSG_TRACE2 2000
    +#define MSG_TRACE3 3000
    +#define MSG_TRACE4 4000
    +#define MSG_TRACE5 5000
    +#define MSG_ERROR  6000   /* errors written to qh.ferr */
    +#define MSG_WARNING 7000
    +#define MSG_STDERR  8000  /* log messages Written to qh.ferr */
    +#define MSG_OUTPUT  9000
    +#define MSG_QHULL_ERROR 10000 /* errors thrown by QhullError.cpp (QHULLlastError is in QhullError.h) */
    +#define MSG_FIXUP  11000  /* FIXUP QH11... */
    +#define MSG_MAXLEN  3000 /* qh_printhelp_degenerate() in user.c */
    +
    +
    +/*----------------------------------
    +
    +  qh_OPTIONline -- max length of an option line 'FO'
    +*/
    +#define qh_OPTIONline 80
    +
    +/*============================================================*/
    +/*============= data types and configuration macros ==========*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  realT
    +    set the size of floating point numbers
    +
    +  qh_REALdigits
    +    maximimum number of significant digits
    +
    +  qh_REAL_1, qh_REAL_2n, qh_REAL_3n
    +    format strings for printf
    +
    +  qh_REALmax, qh_REALmin
    +    maximum and minimum (near zero) values
    +
    +  qh_REALepsilon
    +    machine roundoff.  Maximum roundoff error for addition and multiplication.
    +
    +  notes:
    +   Select whether to store floating point numbers in single precision (float)
    +   or double precision (double).
    +
    +   Use 'float' to save about 8% in time and 25% in space.  This is particularly
    +   helpful if high-d where convex hulls are space limited.  Using 'float' also
    +   reduces the printed size of Qhull's output since numbers have 8 digits of
    +   precision.
    +
    +   Use 'double' when greater arithmetic precision is needed.  This is needed
    +   for Delaunay triangulations and Voronoi diagrams when you are not merging
    +   facets.
    +
    +   If 'double' gives insufficient precision, your data probably includes
    +   degeneracies.  If so you should use facet merging (done by default)
    +   or exact arithmetic (see imprecision section of manual, qh-impre.htm).
    +   You may also use option 'Po' to force output despite precision errors.
    +
    +   You may use 'long double', but many format statements need to be changed
    +   and you may need a 'long double' square root routine.  S. Grundmann
    +   (sg@eeiwzb.et.tu-dresden.de) has done this.  He reports that the code runs
    +   much slower with little gain in precision.
    +
    +   WARNING: on some machines,    int f(){realT a= REALmax;return (a == REALmax);}
    +      returns False.  Use (a > REALmax/2) instead of (a == REALmax).
    +
    +   REALfloat =   1      all numbers are 'float' type
    +             =   0      all numbers are 'double' type
    +*/
    +#define REALfloat 1
    +
    +#if (REALfloat == 1)
    +#define realT float
    +#define REALmax FLT_MAX
    +#define REALmin FLT_MIN
    +#define REALepsilon FLT_EPSILON
    +#define qh_REALdigits 8   /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.8g "
    +#define qh_REAL_2n "%6.8g %6.8g\n"
    +#define qh_REAL_3n "%6.8g %6.8g %6.8g\n"
    +
    +#elif (REALfloat == 0)
    +#define realT double
    +#define REALmax DBL_MAX
    +#define REALmin DBL_MIN
    +#define REALepsilon DBL_EPSILON
    +#define qh_REALdigits 16    /* maximum number of significant digits */
    +#define qh_REAL_1 "%6.16g "
    +#define qh_REAL_2n "%6.16g %6.16g\n"
    +#define qh_REAL_3n "%6.16g %6.16g %6.16g\n"
    +
    +#else
    +#error unknown float option
    +#endif
    +
    +/*----------------------------------
    +
    +  countT
    +    The type for counts and identifiers (e.g., the number of points, vertex identifiers)
    +    Currently used by C++ code-only.  Decided against using it for setT because most sets are small.
    +
    +    Defined as 'int' for C-code compatibility and QH11026
    +
    +    FIXUP QH11026 countT may be defined as a unsigned value, but several code issues need to be solved first.  See countT in Changes.txt
    +*/
    +
    +#ifndef DEFcountT
    +#define DEFcountT 1
    +typedef int countT;
    +#endif
    +#define COUNTmax 0x7fffffff
    +
    +
    +/*----------------------------------
    +
    +  qh_CPUclock
    +    define the clock() function for reporting the total time spent by Qhull
    +    returns CPU ticks as a 'long int'
    +    qh_CPUclock is only used for reporting the total time spent by Qhull
    +
    +  qh_SECticks
    +    the number of clock ticks per second
    +
    +  notes:
    +    looks for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or assumes microseconds
    +    to define a custom clock, set qh_CLOCKtype to 0
    +
    +    if your system does not use clock() to return CPU ticks, replace
    +    qh_CPUclock with the corresponding function.  It is converted
    +    to 'unsigned long' to prevent wrap-around during long runs.  By default,
    +     defines clock_t as 'long'
    +
    +   Set qh_CLOCKtype to
    +
    +     1          for CLOCKS_PER_SEC, CLOCKS_PER_SECOND, or microsecond
    +                Note:  may fail if more than 1 hour elapsed time
    +
    +     2          use qh_clock() with POSIX times() (see global_r.c)
    +*/
    +#define qh_CLOCKtype 1  /* change to the desired number */
    +
    +#if (qh_CLOCKtype == 1)
    +
    +#if defined(CLOCKS_PER_SECOND)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SECOND
    +
    +#elif defined(CLOCKS_PER_SEC)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLOCKS_PER_SEC
    +
    +#elif defined(CLK_TCK)
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks CLK_TCK
    +
    +#else
    +#define qh_CPUclock    ((unsigned long)clock())  /* return CPU clock */
    +#define qh_SECticks 1E6
    +#endif
    +
    +#elif (qh_CLOCKtype == 2)
    +#define qh_CPUclock    qh_clock()  /* return CPU clock */
    +#define qh_SECticks 100
    +
    +#else /* qh_CLOCKtype == ? */
    +#error unknown clock option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_RANDOMtype, qh_RANDOMmax, qh_RANDOMseed
    +    define random number generator
    +
    +    qh_RANDOMint generates a random integer between 0 and qh_RANDOMmax.
    +    qh_RANDOMseed sets the random number seed for qh_RANDOMint
    +
    +  Set qh_RANDOMtype (default 5) to:
    +    1       for random() with 31 bits (UCB)
    +    2       for rand() with RAND_MAX or 15 bits (system 5)
    +    3       for rand() with 31 bits (Sun)
    +    4       for lrand48() with 31 bits (Solaris)
    +    5       for qh_rand(qh) with 31 bits (included with Qhull, requires 'qh')
    +
    +  notes:
    +    Random numbers are used by rbox to generate point sets.  Random
    +    numbers are used by Qhull to rotate the input ('QRn' option),
    +    simulate a randomized algorithm ('Qr' option), and to simulate
    +    roundoff errors ('Rn' option).
    +
    +    Random number generators differ between systems.  Most systems provide
    +    rand() but the period varies.  The period of rand() is not critical
    +    since qhull does not normally use random numbers.
    +
    +    The default generator is Park & Miller's minimal standard random
    +    number generator [CACM 31:1195 '88].  It is included with Qhull.
    +
    +    If qh_RANDOMmax is wrong, qhull will report a warning and Geomview
    +    output will likely be invisible.
    +*/
    +#define qh_RANDOMtype 5   /* *** change to the desired number *** */
    +
    +#if (qh_RANDOMtype == 1)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, random()/MAX */
    +#define qh_RANDOMint random()
    +#define qh_RANDOMseed_(qh, seed) srandom(seed);
    +
    +#elif (qh_RANDOMtype == 2)
    +#ifdef RAND_MAX
    +#define qh_RANDOMmax ((realT)RAND_MAX)
    +#else
    +#define qh_RANDOMmax ((realT)32767)   /* 15 bits (System 5) */
    +#endif
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(qh, seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 3)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, Sun */
    +#define qh_RANDOMint  rand()
    +#define qh_RANDOMseed_(qh, seed) srand((unsigned)seed);
    +
    +#elif (qh_RANDOMtype == 4)
    +#define qh_RANDOMmax ((realT)0x7fffffffUL)  /* 31 bits, lrand38()/MAX */
    +#define qh_RANDOMint lrand48()
    +#define qh_RANDOMseed_(qh, seed) srand48(seed);
    +
    +#elif (qh_RANDOMtype == 5)  /* 'qh' is an implicit parameter */
    +#define qh_RANDOMmax ((realT)2147483646UL)  /* 31 bits, qh_rand/MAX */
    +#define qh_RANDOMint qh_rand(qh)
    +#define qh_RANDOMseed_(qh, seed) qh_srand(qh, seed);
    +/* unlike rand(), never returns 0 */
    +
    +#else
    +#error: unknown random option
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_ORIENTclock
    +    0 for inward pointing normals by Geomview convention
    +*/
    +#define qh_ORIENTclock 0
    +
    +
    +/*============================================================*/
    +/*============= joggle constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +qh_JOGGLEdefault
    +default qh.JOGGLEmax is qh.DISTround * qh_JOGGLEdefault
    +
    +notes:
    +rbox s r 100 | qhull QJ1e-15 QR0 generates 90% faults at distround 7e-16
    +rbox s r 100 | qhull QJ1e-14 QR0 generates 70% faults
    +rbox s r 100 | qhull QJ1e-13 QR0 generates 35% faults
    +rbox s r 100 | qhull QJ1e-12 QR0 generates 8% faults
    +rbox s r 100 | qhull QJ1e-11 QR0 generates 1% faults
    +rbox s r 100 | qhull QJ1e-10 QR0 generates 0% faults
    +rbox 1000 W0 | qhull QJ1e-12 QR0 generates 86% faults
    +rbox 1000 W0 | qhull QJ1e-11 QR0 generates 20% faults
    +rbox 1000 W0 | qhull QJ1e-10 QR0 generates 2% faults
    +the later have about 20 points per facet, each of which may interfere
    +
    +pick a value large enough to avoid retries on most inputs
    +*/
    +#define qh_JOGGLEdefault 30000.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEincrease
    +factor to increase qh.JOGGLEmax on qh_JOGGLEretry or qh_JOGGLEagain
    +*/
    +#define qh_JOGGLEincrease 10.0
    +
    +/*----------------------------------
    +
    +qh_JOGGLEretry
    +if ZZretry = qh_JOGGLEretry, increase qh.JOGGLEmax
    +
    +notes:
    +try twice at the original value in case of bad luck the first time
    +*/
    +#define qh_JOGGLEretry 2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEagain
    +every following qh_JOGGLEagain, increase qh.JOGGLEmax
    +
    +notes:
    +1 is OK since it's already failed qh_JOGGLEretry times
    +*/
    +#define qh_JOGGLEagain 1
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxincrease
    +maximum qh.JOGGLEmax due to qh_JOGGLEincrease
    +relative to qh.MAXwidth
    +
    +notes:
    +qh.joggleinput will retry at this value until qh_JOGGLEmaxretry
    +*/
    +#define qh_JOGGLEmaxincrease 1e-2
    +
    +/*----------------------------------
    +
    +qh_JOGGLEmaxretry
    +stop after qh_JOGGLEmaxretry attempts
    +*/
    +#define qh_JOGGLEmaxretry 100
    +
    +/*============================================================*/
    +/*============= performance related constants ================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_HASHfactor
    +    total hash slots / used hash slots.  Must be at least 1.1.
    +
    +  notes:
    +    =2 for at worst 50% occupancy for qh.hash_table and normally 25% occupancy
    +*/
    +#define qh_HASHfactor 2
    +
    +/*----------------------------------
    +
    +  qh_VERIFYdirect
    +    with 'Tv' verify all points against all facets if op count is smaller
    +
    +  notes:
    +    if greater, calls qh_check_bestdist() instead
    +*/
    +#define qh_VERIFYdirect 1000000
    +
    +/*----------------------------------
    +
    +  qh_INITIALsearch
    +     if qh_INITIALmax, search points up to this dimension
    +*/
    +#define qh_INITIALsearch 6
    +
    +/*----------------------------------
    +
    +  qh_INITIALmax
    +    if dim >= qh_INITIALmax, use min/max coordinate points for initial simplex
    +
    +  notes:
    +    from points with non-zero determinants
    +    use option 'Qs' to override (much slower)
    +*/
    +#define qh_INITIALmax 8
    +
    +/*============================================================*/
    +/*============= memory constants =============================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  qh_MEMalign
    +    memory alignment for qh_meminitbuffers() in global_r.c
    +
    +  notes:
    +    to avoid bus errors, memory allocation must consider alignment requirements.
    +    malloc() automatically takes care of alignment.   Since mem_r.c manages
    +    its own memory, we need to explicitly specify alignment in
    +    qh_meminitbuffers().
    +
    +    A safe choice is sizeof(double).  sizeof(float) may be used if doubles
    +    do not occur in data structures and pointers are the same size.  Be careful
    +    of machines (e.g., DEC Alpha) with large pointers.
    +
    +    If using gcc, best alignment is [fmax_() is defined in geom_r.h]
    +              #define qh_MEMalign fmax_(__alignof__(realT),__alignof__(void *))
    +*/
    +#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
    +
    +/*----------------------------------
    +
    +  qh_MEMbufsize
    +    size of additional memory buffers
    +
    +  notes:
    +    used for qh_meminitbuffers() in global_r.c
    +*/
    +#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
    +
    +/*----------------------------------
    +
    +  qh_MEMinitbuf
    +    size of initial memory buffer
    +
    +  notes:
    +    use for qh_meminitbuffers() in global_r.c
    +*/
    +#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
    +
    +/*----------------------------------
    +
    +  qh_INFINITE
    +    on output, indicates Voronoi center at infinity
    +*/
    +#define qh_INFINITE  -10.101
    +
    +/*----------------------------------
    +
    +  qh_DEFAULTbox
    +    default box size (Geomview expects 0.5)
    +
    +  qh_DEFAULTbox
    +    default box size for integer coorindate (rbox only)
    +*/
    +#define qh_DEFAULTbox 0.5
    +#define qh_DEFAULTzbox 1e6
    +
    +/*============================================================*/
    +/*============= conditional compilation ======================*/
    +/*============================================================*/
    +
    +/*----------------------------------
    +
    +  __cplusplus
    +    defined by C++ compilers
    +
    +  __MSC_VER
    +    defined by Microsoft Visual C++
    +
    +  __MWERKS__ && __INTEL__
    +    defined by Metrowerks when compiling for Windows (not Intel-based Macintosh)
    +
    +  __MWERKS__ && __POWERPC__
    +    defined by Metrowerks when compiling for PowerPC-based Macintosh
    +
    +  __STDC__
    +    defined for strict ANSI C
    +*/
    +
    +/*----------------------------------
    +
    +  qh_COMPUTEfurthest
    +    compute furthest distance to an outside point instead of storing it with the facet
    +    =1 to compute furthest
    +
    +  notes:
    +    computing furthest saves memory but costs time
    +      about 40% more distance tests for partitioning
    +      removes facet->furthestdist
    +*/
    +#define qh_COMPUTEfurthest 0
    +
    +/*----------------------------------
    +
    +  qh_KEEPstatistics
    +    =0 removes most of statistic gathering and reporting
    +
    +  notes:
    +    if 0, code size is reduced by about 4%.
    +*/
    +#define qh_KEEPstatistics 1
    +
    +/*----------------------------------
    +
    +  qh_MAXoutside
    +    record outer plane for each facet
    +    =1 to record facet->maxoutside
    +
    +  notes:
    +    this takes a realT per facet and slightly slows down qhull
    +    it produces better outer planes for geomview output
    +*/
    +#define qh_MAXoutside 1
    +
    +/*----------------------------------
    +
    +  qh_NOmerge
    +    disables facet merging if defined
    +
    +  notes:
    +    This saves about 10% space.
    +
    +    Unless 'Q0'
    +      qh_NOmerge sets 'QJ' to avoid precision errors
    +
    +    #define qh_NOmerge
    +
    +  see:
    +    qh_NOmem in mem_r.c
    +
    +    see user_r.c/user_eg.c for removing io_r.o
    +*/
    +
    +/*----------------------------------
    +
    +  qh_NOtrace
    +    no tracing if defined
    +
    +  notes:
    +    This saves about 5% space.
    +
    +    #define qh_NOtrace
    +*/
    +
    +#if 0  /* sample code */
    +    exitcode= qh_new_qhull(qhT *qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +    qh_freeqhull(qhT *qh, !qh_ALL); /* frees long memory used by second call */
    +    qh_memfreeshort(qhT *qh, &curlong, &totlong);  /* frees short memory and memory allocator */
    +#endif
    +
    +/*----------------------------------
    +
    +  qh_QUICKhelp
    +    =1 to use abbreviated help messages, e.g., for degenerate inputs
    +*/
    +#define qh_QUICKhelp    0
    +
    +/*============================================================*/
    +/*============= -merge constants- ============================*/
    +/*============================================================*/
    +/*
    +   These constants effect facet merging.  You probably will not need
    +   to modify them.  They effect the performance of facet merging.
    +*/
    +
    +/*----------------------------------
    +
    +  qh_DIMmergeVertex
    +    max dimension for vertex merging (it is not effective in high-d)
    +*/
    +#define qh_DIMmergeVertex 6
    +
    +/*----------------------------------
    +
    +  qh_DIMreduceBuild
    +     max dimension for vertex reduction during build (slow in high-d)
    +*/
    +#define qh_DIMreduceBuild 5
    +
    +/*----------------------------------
    +
    +  qh_BESTcentrum
    +     if > 2*dim+n vertices, qh_findbestneighbor() tests centrums (faster)
    +     else, qh_findbestneighbor() tests all vertices (much better merges)
    +
    +  qh_BESTcentrum2
    +     if qh_BESTcentrum2 * DIM3 + BESTcentrum < #vertices tests centrums
    +*/
    +#define qh_BESTcentrum 20
    +#define qh_BESTcentrum2 2
    +
    +/*----------------------------------
    +
    +  qh_BESTnonconvex
    +    if > dim+n neighbors, qh_findbestneighbor() tests nonconvex ridges.
    +
    +  notes:
    +    It is needed because qh_findbestneighbor is slow for large facets
    +*/
    +#define qh_BESTnonconvex 15
    +
    +/*----------------------------------
    +
    +  qh_MAXnewmerges
    +    if >n newmerges, qh_merge_nonconvex() calls qh_reducevertices_centrums.
    +
    +  notes:
    +    It is needed because postmerge can merge many facets at once
    +*/
    +#define qh_MAXnewmerges 2
    +
    +/*----------------------------------
    +
    +  qh_MAXnewcentrum
    +    if <= dim+n vertices (n approximates the number of merges),
    +      reset the centrum in qh_updatetested() and qh_mergecycle_facets()
    +
    +  notes:
    +    needed to reduce cost and because centrums may move too much if
    +    many vertices in high-d
    +*/
    +#define qh_MAXnewcentrum 5
    +
    +/*----------------------------------
    +
    +  qh_COPLANARratio
    +    for 3-d+ merging, qh.MINvisible is n*premerge_centrum
    +
    +  notes:
    +    for non-merging, it's DISTround
    +*/
    +#define qh_COPLANARratio 3
    +
    +/*----------------------------------
    +
    +  qh_DISToutside
    +    When is a point clearly outside of a facet?
    +    Stops search in qh_findbestnew or qh_partitionall
    +    qh_findbest uses qh.MINoutside since since it is only called if no merges.
    +
    +  notes:
    +    'Qf' always searches for best facet
    +    if !qh.MERGING, same as qh.MINoutside.
    +    if qh_USEfindbestnew, increase value since neighboring facets may be ill-behaved
    +      [Note: Zdelvertextot occurs normally with interior points]
    +            RBOX 1000 s Z1 G1e-13 t1001188774 | QHULL Tv
    +    When there is a sharp edge, need to move points to a
    +    clearly good facet; otherwise may be lost in another partitioning.
    +    if too big then O(n^2) behavior for partitioning in cone
    +    if very small then important points not processed
    +    Needed in qh_partitionall for
    +      RBOX 1000 s Z1 G1e-13 t1001032651 | QHULL Tv
    +    Needed in qh_findbestnew for many instances of
    +      RBOX 1000 s Z1 G1e-13 t | QHULL Tv
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_DISToutside ((qh_USEfindbestnew ? 2 : 1) * \
    +     fmax_((qh->MERGING ? 2 : 1)*qh->MINoutside, qh->max_outside))
    +
    +/*----------------------------------
    +
    +  qh_RATIOnearinside
    +    ratio of qh.NEARinside to qh.ONEmerge for retaining inside points for
    +    qh_check_maxout().
    +
    +  notes:
    +    This is overkill since do not know the correct value.
    +    It effects whether 'Qc' reports all coplanar points
    +    Not used for 'd' since non-extreme points are coplanar
    +*/
    +#define qh_RATIOnearinside 5
    +
    +/*----------------------------------
    +
    +  qh_SEARCHdist
    +    When is a facet coplanar with the best facet?
    +    qh_findbesthorizon: all coplanar facets of the best facet need to be searched.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_SEARCHdist ((qh_USEfindbestnew ? 2 : 1) * \
    +      (qh->max_outside + 2 * qh->DISTround + fmax_( qh->MINvisible, qh->MAXcoplanar)));
    +
    +/*----------------------------------
    +
    +  qh_USEfindbestnew
    +     Always use qh_findbestnew for qh_partitionpoint, otherwise use
    +     qh_findbestnew if merged new facet or sharpnewfacets.
    +
    +  See:
    +    qh_DISToutside -- when is a point clearly outside of a facet
    +    qh_SEARCHdist -- when is facet coplanar with the best facet?
    +    qh_USEfindbestnew -- when to use qh_findbestnew for qh_partitionpoint()
    +*/
    +#define qh_USEfindbestnew (zzval_(Ztotmerge) > 50)
    +
    +/*----------------------------------
    +
    +  qh_WIDEcoplanar
    +    n*MAXcoplanar or n*MINvisible for a WIDEfacet
    +
    +    if vertex is further than qh.WIDEfacet from the hyperplane
    +    then its ridges are not counted in computing the area, and
    +    the facet's centrum is frozen.
    +
    +  notes:
    +   qh.WIDEfacet= max(qh.MAXoutside,qh_WIDEcoplanar*qh.MAXcoplanar,
    +      qh_WIDEcoplanar * qh.MINvisible);
    +*/
    +#define qh_WIDEcoplanar 6
    +
    +/*----------------------------------
    +
    +  qh_WIDEduplicate
    +    Merge ratio for errexit from qh_forcedmerges due to duplicate ridge
    +    Override with option Q12 no-wide-duplicate
    +
    +    Notes:
    +      Merging a duplicate ridge can lead to very wide facets.
    +      A future release of qhull will avoid duplicate ridges by removing duplicate sub-ridges from the horizon
    +*/
    +#define qh_WIDEduplicate 100
    +
    +/*----------------------------------
    +
    +  qh_MAXnarrow
    +    max. cosine in initial hull that sets qh.NARROWhull
    +
    +  notes:
    +    If qh.NARROWhull, the initial partition does not make
    +    coplanar points.  If narrow, a coplanar point can be
    +    coplanar to two facets of opposite orientations and
    +    distant from the exact convex hull.
    +
    +    Conservative estimate.  Don't actually see problems until it is -1.0
    +*/
    +#define qh_MAXnarrow -0.99999999
    +
    +/*----------------------------------
    +
    +  qh_WARNnarrow
    +    max. cosine in initial hull to warn about qh.NARROWhull
    +
    +  notes:
    +    this is a conservative estimate.
    +    Don't actually see problems until it is -1.0.  See qh-impre.htm
    +*/
    +#define qh_WARNnarrow -0.999999999999999
    +
    +/*----------------------------------
    +
    +  qh_ZEROdelaunay
    +    a zero Delaunay facet occurs for input sites coplanar with their convex hull
    +    the last normal coefficient of a zero Delaunay facet is within
    +        qh_ZEROdelaunay * qh.ANGLEround of 0
    +
    +  notes:
    +    qh_ZEROdelaunay does not allow for joggled input ('QJ').
    +
    +    You can avoid zero Delaunay facets by surrounding the input with a box.
    +
    +    Use option 'PDk:-n' to explicitly define zero Delaunay facets
    +      k= dimension of input sites (e.g., 3 for 3-d Delaunay triangulation)
    +      n= the cutoff for zero Delaunay facets (e.g., 'PD3:-1e-12')
    +*/
    +#define qh_ZEROdelaunay 2
    +
    +/*============================================================*/
    +/*============= Microsoft DevStudio ==========================*/
    +/*============================================================*/
    +
    +/*
    +   Finding Memory Leaks Using the CRT Library
    +   https://msdn.microsoft.com/en-us/library/x98tx3cf(v=vs.100).aspx
    +
    +   Reports enabled in qh_lib_check for Debug window and stderr
    +
    +   From 2005=>msvcr80d, 2010=>msvcr100d, 2012=>msvcr110d
    +
    +   Watch: {,,msvcr80d.dll}_crtBreakAlloc  Value from {n} in the leak report
    +   _CrtSetBreakAlloc(689); // qh_lib_check() [global_r.c]
    +
    +   Examples
    +     http://free-cad.sourceforge.net/SrcDocu/d2/d7f/MemDebug_8cpp_source.html
    +     https://github.com/illlust/Game/blob/master/library/MemoryLeak.cpp
    +*/
    +#if 0   /* off (0) by default for QHULL_CRTDBG */
    +#define QHULL_CRTDBG
    +#endif
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)
    +#define _CRTDBG_MAP_ALLOC
    +#include 
    +#include 
    +#endif
    +
    +#endif /* qh_DEFuser */
    +
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/usermem_r.c b/xs/src/qhull/src/libqhull_r/usermem_r.c
    new file mode 100644
    index 0000000000..3297b03185
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/usermem_r.c
    @@ -0,0 +1,94 @@
    +/*
      ---------------------------------
    +
    +   usermem_r.c
    +   qh_exit(), qh_free(), and qh_malloc()
    +
    +   See README.txt.
    +
    +   If you redefine one of these functions you must redefine all of them.
    +   If you recompile and load this file, then usermem.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull_r.h for data structures, macros, and user-callable functions.
    +   See user_r.c for qhull-related, redefinable functions
    +   see user_r.h for user-definable constants
    +   See userprintf_r.c for qh_fprintf and userprintf_rbox_r.c for qh_fprintf_rbox
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull_r.h"
    +
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_exit( exitcode )
    +    exit program
    +
    +  notes:
    +    qh_exit() is called when qh_errexit() and longjmp() are not available.
    +
    +    This is the only use of exit() in Qhull
    +    To replace qh_exit with 'throw', see libqhullcpp/usermem_r-cpp.cpp
    +*/
    +void qh_exit(int exitcode) {
    +    exit(exitcode);
    +} /* exit */
    +
    +/*---------------------------------
    +
    +  qh_fprintf_stderr( msgcode, format, list of args )
    +    fprintf to stderr with msgcode (non-zero)
    +
    +  notes:
    +    qh_fprintf_stderr() is called when qh->ferr is not defined, usually due to an initialization error
    +    
    +    It is typically followed by qh_errexit().
    +
    +    Redefine this function to avoid using stderr
    +
    +    Use qh_fprintf [userprintf_r.c] for normal printing
    +*/
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +      fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +
    +/*---------------------------------
    +
    +  qh_free(qhT *qh, mem )
    +    free memory
    +
    +  notes:
    +    same as free()
    +    No calls to qh_errexit() 
    +*/
    +void qh_free(void *mem) {
    +    free(mem);
    +} /* free */
    +
    +/*---------------------------------
    +
    +    qh_malloc( mem )
    +      allocate memory
    +
    +    notes:
    +      same as malloc()
    +*/
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +} /* malloc */
    +
    +
    diff --git a/xs/src/qhull/src/libqhull_r/userprintf_r.c b/xs/src/qhull/src/libqhull_r/userprintf_r.c
    new file mode 100644
    index 0000000000..6004491a1c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/userprintf_r.c
    @@ -0,0 +1,65 @@
    +/*
      ---------------------------------
    +
    +   userprintf_r.c
    +   qh_fprintf()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf_r.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull_r.h for data structures, macros, and user-callable functions.
    +   See user_r.c for qhull-related, redefinable functions
    +   see user_r.h for user-definable constants
    +   See usermem_r.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf(qh, fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib_r.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf via qh_errexit()
    +     may be called for errors in qh_initstatistics and qh_meminit
    +*/
    +
    +void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        if(!qh){
    +            qh_fprintf_stderr(6241, "userprintf_r.c: fp and qh not defined for qh_fprintf '%s'", fmt);
    +            qh_exit(qhmem_ERRqhull);  /* can not use qh_errexit() */
    +        }
    +        /* could use qh->qhmem.ferr, but probably better to be cautious */
    +        qh_fprintf_stderr(6232, "Qhull internal error (userprintf_r.c): fp is 0.  Wrong qh_fprintf called.\n");
    +        qh_errexit(qh, 6232, NULL, NULL);
    +    }
    +    va_start(args, fmt);
    +    if (qh && qh->ANNOTATEoutput) {
    +      fprintf(fp, "[QH%.4d]", msgcode);
    +    }else if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR ) {
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    }
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +
    +    /* Place debugging traps here. Use with option 'Tn' */
    +
    +} /* qh_fprintf */
    +
    diff --git a/xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c b/xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c
    new file mode 100644
    index 0000000000..1e721a22ae
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhull_r/userprintf_rbox_r.c
    @@ -0,0 +1,53 @@
    +/*
      ---------------------------------
    +
    +   userprintf_rbox_r.c
    +   qh_fprintf_rbox()
    +
    +   see README.txt  see COPYING.txt for copyright information.
    +
    +   If you recompile and load this file, then userprintf_rbox_r.o will not be loaded
    +   from qhull.a or qhull.lib
    +
    +   See libqhull_r.h for data structures, macros, and user-callable functions.
    +   See user_r.c for qhull-related, redefinable functions
    +   see user_r.h for user-definable constants
    +   See usermem_r.c for qh_exit(), qh_free(), and qh_malloc()
    +   see Qhull.cpp and RboxPoints.cpp for examples.
    +
    +   Please report any errors that you fix to qhull@qhull.org
    +*/
    +
    +#include "libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +   qh_fprintf_rbox(qh, fp, msgcode, format, list of args )
    +     print arguments to *fp according to format
    +     Use qh_fprintf_rbox() for rboxlib_r.c
    +
    +   notes:
    +     same as fprintf()
    +     fgets() is not trapped like fprintf()
    +     exit qh_fprintf_rbox via qh_errexit_rbox()
    +*/
    +
    +void qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    if (!fp) {
    +        qh_fprintf_stderr(6231, "Qhull internal error (userprintf_rbox_r.c): fp is 0.  Wrong qh_fprintf_rbox called.\n");
    +        qh_errexit_rbox(qh, 6231);
    +    }
    +    if (msgcode >= MSG_ERROR && msgcode < MSG_STDERR)
    +      fprintf(fp, "QH%.4d ", msgcode);
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +} /* qh_fprintf_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/Coordinates.cpp b/xs/src/qhull/src/libqhullcpp/Coordinates.cpp
    new file mode 100644
    index 0000000000..806b438aba
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/Coordinates.cpp
    @@ -0,0 +1,198 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/Coordinates.cpp#4 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/Coordinates.h"
    +
    +#include "libqhullcpp/functionObjects.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//! Coordinates -- vector of coordT (normally double)
    +
    +#//!\name Constructor
    +
    +#//!\name Element access
    +
    +// Inefficient without result-value-optimization or implicitly shared object
    +Coordinates Coordinates::
    +mid(countT idx, countT length) const
    +{
    +    countT newLength= length;
    +    if(length<0 || idx+length > count()){
    +        newLength= count()-idx;
    +    }
    +    Coordinates result;
    +    if(newLength>0){
    +        std::copy(begin()+idx, begin()+(idx+newLength), std::back_inserter(result));
    +    }
    +    return result;
    +}//mid
    +
    +coordT Coordinates::
    +value(countT idx, const coordT &defaultValue) const
    +{
    +    return ((idx < 0 || idx >= count()) ? defaultValue : (*this)[idx]);
    +}//value
    +
    +#//!\name GetSet
    +
    +Coordinates Coordinates::
    +operator+(const Coordinates &other) const
    +{
    +    Coordinates result(*this);
    +    std::copy(other.begin(), other.end(), std::back_inserter(result));
    +    return result;
    +}//operator+
    +
    +Coordinates & Coordinates::
    +operator+=(const Coordinates &other)
    +{
    +    if(&other==this){
    +        Coordinates clone(other);
    +        std::copy(clone.begin(), clone.end(), std::back_inserter(*this));
    +    }else{
    +        std::copy(other.begin(), other.end(), std::back_inserter(*this));
    +    }
    +    return *this;
    +}//operator+=
    +
    +#//!\name Read-write
    +
    +void Coordinates::
    +append(int pointDimension, coordT *c)
    +{
    +    if(c){
    +        coordT *p= c;
    +        for(int i= 0; i(i-begin())); // WARN64 coordinate index
    +            }
    +            ++i;
    +        }
    +    }
    +    return -1;
    +}//indexOf
    +
    +countT Coordinates::
    +lastIndexOf(const coordT &t, countT from) const
    +{
    +    if(from<0){
    +        from += count();
    +    }else if(from>=count()){
    +        from= count()-1;
    +    }
    +    if(from>=0){
    +        const_iterator i= begin()+from+1;
    +        while(i-- != constBegin()){
    +            if(*i==t){
    +                return (static_cast(i-begin())); // WARN64 coordinate index
    +            }
    +        }
    +    }
    +    return -1;
    +}//lastIndexOf
    +
    +void Coordinates::
    +removeAll(const coordT &t)
    +{
    +    MutableCoordinatesIterator i(*this);
    +    while(i.findNext(t)){
    +        i.remove();
    +    }
    +}//removeAll
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::istream;
    +using std::ostream;
    +using std::string;
    +using std::ws;
    +using orgQhull::Coordinates;
    +
    +ostream &
    +operator<<(ostream &os, const Coordinates &cs)
    +{
    +    Coordinates::const_iterator c= cs.begin();
    +    for(countT i=cs.count(); i--; ){
    +        os << *c++ << " ";
    +    }
    +    return os;
    +}//operator<<
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/Coordinates.h b/xs/src/qhull/src/libqhullcpp/Coordinates.h
    new file mode 100644
    index 0000000000..df8bd11386
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/Coordinates.h
    @@ -0,0 +1,303 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/Coordinates.h#6 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHCOORDINATES_H
    +#define QHCOORDINATES_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullIterator.h"
    +
    +#include  // ptrdiff_t, size_t
    +#include 
    +// Requires STL vector class.  Can use with another vector class such as QList.
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! An std::vector of point coordinates independent of dimension
    +    //! Used by PointCoordinates for RboxPoints and by Qhull for feasiblePoint
    +    //! A QhullPoint refers to previously allocated coordinates
    +    class Coordinates;
    +    class MutableCoordinatesIterator;
    +
    +class Coordinates {
    +
    +private:
    +#//!\name Fields
    +    std::vector coordinate_array;
    +
    +public:
    +#//!\name Subtypes
    +
    +    class const_iterator;
    +    class iterator;
    +    typedef iterator Iterator;
    +    typedef const_iterator ConstIterator;
    +
    +    typedef coordT              value_type;
    +    typedef const value_type   *const_pointer;
    +    typedef const value_type &  const_reference;
    +    typedef value_type *        pointer;
    +    typedef value_type &        reference;
    +    typedef ptrdiff_t           difference_type;
    +    typedef countT              size_type;
    +
    +#//!\name Construct
    +                        Coordinates() {};
    +    explicit            Coordinates(const std::vector &other) : coordinate_array(other) {}
    +                        Coordinates(const Coordinates &other) : coordinate_array(other.coordinate_array) {}
    +    Coordinates &       operator=(const Coordinates &other) { coordinate_array= other.coordinate_array; return *this; }
    +    Coordinates &       operator=(const std::vector &other) { coordinate_array= other; return *this; }
    +                        ~Coordinates() {}
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const { return coordinate_array; }
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList       toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +    countT              count() const { return static_cast(size()); }
    +    coordT *            data() { return isEmpty() ? 0 : &at(0); }
    +    const coordT *      data() const { return const_cast(isEmpty() ? 0 : &at(0)); }
    +    bool                isEmpty() const { return coordinate_array.empty(); }
    +    bool                operator==(const Coordinates &other) const  { return coordinate_array==other.coordinate_array; }
    +    bool                operator!=(const Coordinates &other) const  { return coordinate_array!=other.coordinate_array; }
    +    size_t              size() const { return coordinate_array.size(); }
    +
    +#//!\name Element access
    +    coordT &            at(countT idx) { return coordinate_array.at(idx); }
    +    const coordT &      at(countT idx) const { return coordinate_array.at(idx); }
    +    coordT &            back() { return coordinate_array.back(); }
    +    const coordT &      back() const { return coordinate_array.back(); }
    +    coordT &            first() { return front(); }
    +    const coordT &      first() const { return front(); }
    +    coordT &            front() { return coordinate_array.front(); }
    +    const coordT &      front() const { return coordinate_array.front(); }
    +    coordT &            last() { return back(); }
    +    const coordT &      last() const { return back(); }
    +    Coordinates         mid(countT idx, countT length= -1) const; //!<\todo countT -1 indicates
    +    coordT &            operator[](countT idx) { return coordinate_array.operator[](idx); }
    +    const coordT &      operator[](countT idx) const { return coordinate_array.operator[](idx); }
    +    coordT              value(countT idx, const coordT &defaultValue) const;
    +
    +#//!\name Iterator
    +    iterator            begin() { return iterator(coordinate_array.begin()); }
    +    const_iterator      begin() const { return const_iterator(coordinate_array.begin()); }
    +    const_iterator      constBegin() const { return begin(); }
    +    const_iterator      constEnd() const { return end(); }
    +    iterator            end() { return iterator(coordinate_array.end()); }
    +    const_iterator      end() const { return const_iterator(coordinate_array.end()); }
    +
    +#//!\name GetSet
    +    Coordinates         operator+(const Coordinates &other) const;
    +
    +#//!\name Modify
    +    void                append(int pointDimension, coordT *c);
    +    void                append(const coordT &c) { push_back(c); }
    +    void                clear() { coordinate_array.clear(); }
    +    iterator            erase(iterator idx) { return iterator(coordinate_array.erase(idx.base())); }
    +    iterator            erase(iterator beginIterator, iterator endIterator) { return iterator(coordinate_array.erase(beginIterator.base(), endIterator.base())); }
    +    void                insert(countT before, const coordT &c) { insert(begin()+before, c); }
    +    iterator            insert(iterator before, const coordT &c) { return iterator(coordinate_array.insert(before.base(), c)); }
    +    void                move(countT from, countT to) { insert(to, takeAt(from)); }
    +    Coordinates &       operator+=(const Coordinates &other);
    +    Coordinates &       operator+=(const coordT &c) { append(c); return *this; }
    +    Coordinates &       operator<<(const Coordinates &other) { return *this += other; }
    +    Coordinates &       operator<<(const coordT &c) { return *this += c; }
    +    void                pop_back() { coordinate_array.pop_back(); }
    +    void                pop_front() { removeFirst(); }
    +    void                prepend(const coordT &c) { insert(begin(), c); }
    +    void                push_back(const coordT &c) { coordinate_array.push_back(c); }
    +    void                push_front(const coordT &c) { insert(begin(), c); }
    +                        //removeAll below
    +    void                removeAt(countT idx) { erase(begin()+idx); }
    +    void                removeFirst() { erase(begin()); }
    +    void                removeLast() { erase(--end()); }
    +    void                replace(countT idx, const coordT &c) { (*this)[idx]= c; }
    +    void                reserve(countT i) { coordinate_array.reserve(i); }
    +    void                swap(countT idx, countT other);
    +    coordT              takeAt(countT idx);
    +    coordT              takeFirst() { return takeAt(0); }
    +    coordT              takeLast();
    +
    +#//!\name Search
    +    bool                contains(const coordT &t) const;
    +    countT              count(const coordT &t) const;
    +    countT              indexOf(const coordT &t, countT from = 0) const;
    +    countT              lastIndexOf(const coordT &t, countT from = -1) const;
    +    void                removeAll(const coordT &t);
    +
    +#//!\name Coordinates::iterator -- from QhullPoints, forwarding to coordinate_array
    +    // before const_iterator for conversion with comparison operators
    +    // Reviewed corelib/tools/qlist.h and corelib/tools/qvector.h w/o QT_STRICT_ITERATORS
    +    class iterator {
    +
    +    private:
    +        std::vector::iterator i;
    +        friend class const_iterator;
    +
    +    public:
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef coordT      value_type;
    +        typedef value_type *pointer;
    +        typedef value_type &reference;
    +        typedef ptrdiff_t   difference_type;
    +
    +                        iterator() {}
    +                        iterator(const iterator &other) { i= other.i; }
    +        explicit        iterator(const std::vector::iterator &vi) { i= vi; }
    +        iterator &      operator=(const iterator &other) { i= other.i; return *this; }
    +        std::vector::iterator &base() { return i; }
    +        coordT &        operator*() const { return *i; }
    +        // No operator->() when the base type is double
    +        coordT &        operator[](countT idx) const { return i[idx]; }
    +
    +        bool            operator==(const iterator &other) const { return i==other.i; }
    +        bool            operator!=(const iterator &other) const { return i!=other.i; }
    +        bool            operator<(const iterator &other) const { return i(const iterator &other) const { return i>other.i; }
    +        bool            operator>=(const iterator &other) const { return i>=other.i; }
    +              // reinterpret_cast to break circular dependency
    +        bool            operator==(const Coordinates::const_iterator &other) const { return *this==reinterpret_cast(other); }
    +        bool            operator!=(const Coordinates::const_iterator &other) const { return *this!=reinterpret_cast(other); }
    +        bool            operator<(const Coordinates::const_iterator &other) const { return *this(other); }
    +        bool            operator<=(const Coordinates::const_iterator &other) const { return *this<=reinterpret_cast(other); }
    +        bool            operator>(const Coordinates::const_iterator &other) const { return *this>reinterpret_cast(other); }
    +        bool            operator>=(const Coordinates::const_iterator &other) const { return *this>=reinterpret_cast(other); }
    +
    +        iterator &      operator++() { ++i; return *this; }
    +        iterator        operator++(int) { return iterator(i++); }
    +        iterator &      operator--() { --i; return *this; }
    +        iterator        operator--(int) { return iterator(i--); }
    +        iterator &      operator+=(countT idx) { i += idx; return *this; }
    +        iterator &      operator-=(countT idx) { i -= idx; return *this; }
    +        iterator        operator+(countT idx) const { return iterator(i+idx); }
    +        iterator        operator-(countT idx) const { return iterator(i-idx); }
    +        difference_type operator-(iterator other) const { return i-other.i; }
    +    };//Coordinates::iterator
    +
    +#//!\name Coordinates::const_iterator
    +    class const_iterator {
    +
    +    private:
    +        std::vector::const_iterator i;
    +
    +    public:
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef coordT            value_type;
    +        typedef const value_type *pointer;
    +        typedef const value_type &reference;
    +        typedef ptrdiff_t         difference_type;
    +
    +                        const_iterator() {}
    +                        const_iterator(const const_iterator &other) { i= other.i; }
    +                        const_iterator(const iterator &o) : i(o.i) {}
    +        explicit        const_iterator(const std::vector::const_iterator &vi) { i= vi; }
    +        const_iterator &operator=(const const_iterator &other) { i= other.i; return *this; }
    +        const coordT &  operator*() const { return *i; }
    +        // No operator->() when the base type is double
    +        const coordT &  operator[](countT idx) const { return i[idx]; }
    +
    +        bool            operator==(const const_iterator &other) const { return i==other.i; }
    +        bool            operator!=(const const_iterator &other) const { return i!=other.i; }
    +        bool            operator<(const const_iterator &other) const { return i(const const_iterator &other) const { return i>other.i; }
    +        bool            operator>=(const const_iterator &other) const { return i>=other.i; }
    +
    +        const_iterator & operator++() { ++i; return *this; } 
    +        const_iterator  operator++(int) { return const_iterator(i++); }
    +        const_iterator & operator--() { --i; return *this; }
    +        const_iterator  operator--(int) { return const_iterator(i--); }
    +        const_iterator & operator+=(countT idx) { i += idx; return *this; }
    +        const_iterator & operator-=(countT idx) { i -= idx; return *this; }
    +        const_iterator  operator+(countT idx) const { return const_iterator(i+idx); }
    +        const_iterator  operator-(countT idx) const { return const_iterator(i-idx); }
    +        difference_type operator-(const_iterator other) const { return i-other.i; }
    +    };//Coordinates::const_iterator
    +
    +};//Coordinates
    +
    +//class CoordinatesIterator
    +//QHULL_DECLARE_SEQUENTIAL_ITERATOR(Coordinates, coordT)
    +
    +class CoordinatesIterator
    +{
    +    typedef Coordinates::const_iterator const_iterator;
    +
    +private:
    +    const Coordinates * c;
    +    const_iterator      i;
    +
    +public:
    +                        CoordinatesIterator(const Coordinates &container): c(&container), i(c->constBegin()) {}
    +    CoordinatesIterator &operator=(const Coordinates &container) { c= &container; i= c->constBegin(); return *this; }
    +                        ~CoordinatesIterator() {}
    +
    +    bool                findNext(const coordT &t) { while (i != c->constEnd()) if(*i++ == t){ return true;} return false; }
    +    bool                findPrevious(const coordT &t) { while (i != c->constBegin())if (*(--i) == t){ return true;} return false;  }
    +    bool                hasNext() const { return i != c->constEnd(); }
    +    bool                hasPrevious() const { return i != c->constBegin(); }
    +    const coordT &      next() { return *i++; }
    +    const coordT &      previous() { return *--i; }
    +    const coordT &      peekNext() const { return *i; }
    +    const coordT &      peekPrevious() const { const_iterator p= i; return *--p; }
    +    void                toFront() { i= c->constBegin(); }
    +    void                toBack() { i= c->constEnd(); }
    +};//CoordinatesIterator
    +
    +//class MutableCoordinatesIterator
    +//QHULL_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR(Coordinates, coordT)
    +class MutableCoordinatesIterator
    +{
    +    typedef Coordinates::iterator iterator;
    +    typedef Coordinates::const_iterator const_iterator;
    +
    +private:
    +    Coordinates *       c;
    +    iterator            i;
    +    iterator            n;
    +    bool                item_exists() const { return const_iterator(n) != c->constEnd(); }
    +
    +public:
    +                        MutableCoordinatesIterator(Coordinates &container) : c(&container) { i= c->begin(); n= c->end(); }
    +    MutableCoordinatesIterator &operator=(Coordinates &container) { c= &container; i= c->begin(); n= c->end(); return *this; }
    +                        ~MutableCoordinatesIterator() {}
    +
    +    bool                findNext(const coordT &t) { while(c->constEnd()!=const_iterator(n= i)){ if(*i++==t){ return true;}} return false; }
    +    bool                findPrevious(const coordT &t) { while(c->constBegin()!=const_iterator(i)){ if(*(n= --i)== t){ return true;}} n= c->end(); return false;  }
    +    bool                hasNext() const { return (c->constEnd()!=const_iterator(i)); }
    +    bool                hasPrevious() const { return (c->constBegin()!=const_iterator(i)); }
    +    void                insert(const coordT &t) { n= i= c->insert(i, t); ++i; }
    +    coordT &            next() { n= i++; return *n; }
    +    coordT &            peekNext() const { return *i; }
    +    coordT &            peekPrevious() const { iterator p= i; return *--p; }
    +    coordT &            previous() { n= --i; return *n; }
    +    void                remove() { if(c->constEnd()!=const_iterator(n)){ i= c->erase(n); n= c->end();} }
    +    void                setValue(const coordT &t) const { if(c->constEnd()!=const_iterator(n)){ *n= t;} }
    +    void                toFront() { i= c->begin(); n= c->end(); }
    +    void                toBack() { i= c->end(); n= i; }
    +    coordT &            value() { QHULL_ASSERT(item_exists()); return *n; }
    +    const coordT &      value() const { QHULL_ASSERT(item_exists()); return *n; }
    +};//MutableCoordinatesIterator
    +
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::Coordinates &c);
    +
    +#endif // QHCOORDINATES_H
    diff --git a/xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp b/xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp
    new file mode 100644
    index 0000000000..a5b71e901d
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/PointCoordinates.cpp
    @@ -0,0 +1,348 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/PointCoordinates.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/PointCoordinates.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include 
    +#include 
    +
    +using std::istream;
    +using std::string;
    +using std::ws;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//! PointCoordinates -- vector of PointCoordinates
    +
    +#//!\name Constructors
    +
    +PointCoordinates::
    +PointCoordinates()
    +: QhullPoints()
    +, point_coordinates()
    +, describe_points()
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const std::string &aComment)
    +: QhullPoints()
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(int pointDimension, const std::string &aComment)
    +: QhullPoints()
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +}
    +
    +//! Qhull and QhullQh constructors are the same
    +PointCoordinates::
    +PointCoordinates(const Qhull &q)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points()
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const Qhull &q, const std::string &aComment)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c)
    +: QhullPoints(q)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +    append(coordinatesCount, c);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points()
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh, const std::string &aComment)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c)
    +: QhullPoints(qqh)
    +, point_coordinates()
    +, describe_points(aComment)
    +{
    +    setDimension(pointDimension);
    +    append(coordinatesCount, c);
    +}
    +
    +PointCoordinates::
    +PointCoordinates(const PointCoordinates &other)
    +: QhullPoints(other)
    +, point_coordinates(other.point_coordinates)
    +, describe_points(other.describe_points)
    +{
    +    makeValid();  // Update point_first and point_end
    +}
    +
    +PointCoordinates & PointCoordinates::
    +operator=(const PointCoordinates &other)
    +{
    +    QhullPoints::operator=(other);
    +    point_coordinates= other.point_coordinates;
    +    describe_points= other.describe_points;
    +    makeValid(); // Update point_first and point_end
    +    return *this;
    +}//operator=
    +
    +PointCoordinates::
    +~PointCoordinates()
    +{ }
    +
    +#//!\name GetSet
    +
    +void PointCoordinates::
    +checkValid() const
    +{
    +    if(getCoordinates().data()!=data()
    +    || getCoordinates().count()!=coordinateCount()){
    +        throw QhullError(10060, "Qhull error: first point (%x) is not PointCoordinates.data() or count (%d) is not PointCoordinates.count (%d)", coordinateCount(), getCoordinates().count(), 0.0, data());
    +    }
    +}//checkValid
    +
    +void PointCoordinates::
    +setDimension(int i)
    +{
    +    if(i<0){
    +        throw QhullError(10062, "Qhull error: can not set PointCoordinates dimension to %d", i);
    +    }
    +    int currentDimension=QhullPoints::dimension();
    +    if(currentDimension!=0 && i!=currentDimension){
    +        throw QhullError(10063, "Qhull error: can not change PointCoordinates dimension (from %d to %d)", currentDimension, i);
    +    }
    +    QhullPoints::setDimension(i);
    +}//setDimension
    +
    +#//!\name Foreach
    +
    +Coordinates::ConstIterator PointCoordinates::
    +beginCoordinates(countT pointIndex) const
    +{
    +    return point_coordinates.begin()+indexOffset(pointIndex);
    +}
    +
    +Coordinates::Iterator PointCoordinates::
    +beginCoordinates(countT pointIndex)
    +{
    +    return point_coordinates.begin()+indexOffset(pointIndex);
    +}
    +
    +#//!\name Methods
    +
    +void PointCoordinates::
    +append(countT coordinatesCount, const coordT *c)
    +{
    +    if(coordinatesCount<=0){
    +        return;
    +    }
    +    if(includesCoordinates(c)){
    +        throw QhullError(10065, "Qhull error: can not append a subset of PointCoordinates to itself.  The coordinates for point %d may move.", indexOf(c, QhullError::NOthrow));
    +    }
    +    reserveCoordinates(coordinatesCount);
    +    std::copy(c, c+coordinatesCount, std::back_inserter(point_coordinates));
    +    makeValid();
    +}//append coordT
    +
    +void PointCoordinates::
    +append(const PointCoordinates &other)
    +{
    +    setDimension(other.dimension());
    +    append(other.coordinateCount(), other.data());
    +}//append PointCoordinates
    +
    +void PointCoordinates::
    +append(const QhullPoint &p)
    +{
    +    setDimension(p.dimension());
    +    append(p.dimension(), p.coordinates());
    +}//append QhullPoint
    +
    +void PointCoordinates::
    +appendComment(const std::string &s){
    +    if(char c= s[0] && describe_points.empty()){
    +        if(c=='-' || isdigit(c)){
    +            throw QhullError(10028, "Qhull argument error: comments can not start with a number or minus, %s", 0, 0, 0.0, s.c_str());
    +        }
    +    }
    +    describe_points += s;
    +}//appendComment
    +
    +//! Read PointCoordinates from istream.  First two numbers are dimension and count.  A non-digit starts a rboxCommand.
    +//! Overwrites describe_points.  See qh_readpoints [io.c]
    +void PointCoordinates::
    +appendPoints(istream &in)
    +{
    +    int inDimension;
    +    countT inCount;
    +    in >> ws >> inDimension >> ws;
    +    if(!in.good()){
    +        in.clear();
    +        string remainder;
    +        getline(in, remainder);
    +        throw QhullError(10005, "Qhull error: input did not start with dimension or count -- %s", 0, 0, 0, remainder.c_str());
    +    }
    +    char c= (char)in.peek();
    +    if(c!='-' && !isdigit(c)){         // Comments start with a non-digit
    +        getline(in, describe_points);
    +        in >> ws;
    +    }
    +    in >> inCount >> ws;
    +    if(!in.good()){
    +        in.clear();
    +        string remainder;
    +        getline(in, remainder);
    +        throw QhullError(10009, "Qhull error: input did not start with dimension and count -- %d %s", inDimension, 0, 0, remainder.c_str());
    +    }
    +    c= (char)in.peek();
    +    if(c!='-' && !isdigit(c)){         // Comments start with a non-digit
    +        getline(in, describe_points);
    +        in >> ws;
    +    }
    +    if(inCount> p >> ws;
    +        if(in.fail()){
    +            in.clear();
    +            string remainder;
    +            getline(in, remainder);
    +            throw QhullError(10008, "Qhull error: failed to read coordinate %d  of point %d\n   %s", coordinatesCount % inDimension, coordinatesCount/inDimension, 0, remainder.c_str());
    +        }else{
    +            point_coordinates.push_back(p);
    +            coordinatesCount++;
    +        }
    +    }
    +    if(coordinatesCount != inCount*inDimension){
    +        if(coordinatesCount%inDimension==0){
    +            throw QhullError(10006, "Qhull error: expected %d %d-d PointCoordinates but read %i PointCoordinates", int(inCount), inDimension, 0.0, int(coordinatesCount/inDimension));
    +        }else{
    +            throw QhullError(10012, "Qhull error: expected %d %d-d PointCoordinates but read %i PointCoordinates plus %f extra coordinates", inCount, inDimension, float(coordinatesCount%inDimension), coordinatesCount/inDimension);
    +        }
    +    }
    +    makeValid();
    +}//appendPoints istream
    +
    +PointCoordinates PointCoordinates::
    +operator+(const PointCoordinates &other) const
    +{
    +    PointCoordinates pc= *this;
    +    pc << other;
    +    return pc;
    +}//operator+
    +
    +void PointCoordinates::
    +reserveCoordinates(countT newCoordinates)
    +{
    +    // vector::reserve is not const
    +    point_coordinates.reserve((countT)point_coordinates.size()+newCoordinates); // WARN64
    +    makeValid();
    +}//reserveCoordinates
    +
    +#//!\name Helpers
    +
    +countT PointCoordinates::
    +indexOffset(countT i) const {
    +    countT n= i*dimension();
    +    countT coordinatesCount= point_coordinates.count();
    +    if(i<0 || n>coordinatesCount){
    +        throw QhullError(10061, "Qhull error: point_coordinates is too short (%d) for point %d", coordinatesCount, i);
    +    }
    +    return n;
    +}
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +
    +using orgQhull::Coordinates;
    +using orgQhull::PointCoordinates;
    +
    +ostream&
    +operator<<(ostream &os, const PointCoordinates &p)
    +{
    +    p.checkValid();
    +    countT count= p.count();
    +    int dimension= p.dimension();
    +    string comment= p.comment();
    +    if(comment.empty()){
    +        os << dimension << endl;
    +    }else{
    +        os << dimension << " " << comment << endl;
    +    }
    +    os << count << endl;
    +    Coordinates::ConstIterator c= p.beginCoordinates();
    +    for(countT i=0; i
    +#include 
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullPoints with Coordinates and description
    +    //! Inherited by RboxPoints
    +    class PointCoordinates;
    +
    +class PointCoordinates : public QhullPoints {
    +
    +private:
    +#//!\name Fields
    +    Coordinates         point_coordinates;      //! std::vector of point coordinates
    +                                                //! may have extraCoordinates()
    +    std::string         describe_points;          //! Comment describing PointCoordinates
    +
    +public:
    +#//!\name Construct
    +    //! QhullPoint, PointCoordinates, and QhullPoints have similar constructors
    +    //! If Qhull/QhullQh is not initialized, then dimension()==0                        PointCoordinates();
    +                        PointCoordinates();
    +    explicit            PointCoordinates(const std::string &aComment);
    +                        PointCoordinates(int pointDimension, const std::string &aComment);
    +                        //! Qhull/QhullQh used for dimension() and QhullPoint equality
    +    explicit            PointCoordinates(const Qhull &q);
    +                        PointCoordinates(const Qhull &q, const std::string &aComment);
    +                        PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment);
    +                        PointCoordinates(const Qhull &q, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c); // may be invalid
    +                        //! Use append() and appendPoints() for Coordinates and vector
    +    explicit            PointCoordinates(QhullQh *qqh);
    +                        PointCoordinates(QhullQh *qqh, const std::string &aComment);
    +                        PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment);
    +                        PointCoordinates(QhullQh *qqh, int pointDimension, const std::string &aComment, countT coordinatesCount, const coordT *c); // may be invalid
    +                        //! Use append() and appendPoints() for Coordinates and vector
    +                        PointCoordinates(const PointCoordinates &other);
    +    PointCoordinates &  operator=(const PointCoordinates &other);
    +                        ~PointCoordinates();
    +
    +#//!\name Convert
    +    //! QhullPoints coordinates, constData, data, count, size
    +#ifndef QHULL_NO_STL
    +    void                append(const std::vector &otherCoordinates) { if(!otherCoordinates.empty()){ append((int)otherCoordinates.size(), &otherCoordinates[0]); } }
    +    std::vector toStdVector() const { return point_coordinates.toStdVector(); }
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    void                append(const QList &pointCoordinates) { if(!pointCoordinates.isEmpty()){ append(pointCoordinates.count(), &pointCoordinates[0]); } }
    +    QList       toQList() const { return point_coordinates.toQList(); }
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +    //! See QhullPoints for coordinates, coordinateCount, dimension, empty, isEmpty, ==, !=
    +    void                checkValid() const;
    +    std::string         comment() const { return describe_points; }
    +    void                makeValid() { defineAs(point_coordinates.count(), point_coordinates.data()); }
    +    const Coordinates & getCoordinates() const { return point_coordinates; }
    +    void                setComment(const std::string &s) { describe_points= s; }
    +    void                setDimension(int i);
    +
    +private:
    +    //! disable QhullPoints.defineAs()
    +    void                defineAs(countT coordinatesCount, coordT *c) { QhullPoints::defineAs(coordinatesCount, c); }
    +public:
    +
    +#//!\name ElementAccess
    +    //! See QhullPoints for at, back, first, front, last, mid, [], value
    +
    +#//!\name Foreach
    +    //! See QhullPoints for begin, constBegin, end
    +    Coordinates::ConstIterator  beginCoordinates() const { return point_coordinates.begin(); }
    +    Coordinates::Iterator       beginCoordinates() { return point_coordinates.begin(); }
    +    Coordinates::ConstIterator  beginCoordinates(countT pointIndex) const;
    +    Coordinates::Iterator       beginCoordinates(countT pointIndex);
    +    Coordinates::ConstIterator  endCoordinates() const { return point_coordinates.end(); }
    +    Coordinates::Iterator       endCoordinates() { return point_coordinates.end(); }
    +
    +#//!\name Search
    +    //! See QhullPoints for contains, count, indexOf, lastIndexOf
    +
    +#//!\name GetSet
    +    PointCoordinates    operator+(const PointCoordinates &other) const;
    +
    +#//!\name Modify
    +    //FIXUP QH11001: Add clear() and other modify operators from Coordinates.h.  Include QhullPoint::operator=()
    +    void                append(countT coordinatesCount, const coordT *c);  //! Dimension previously defined
    +    void                append(const coordT &c) { append(1, &c); } //! Dimension previously defined
    +    void                append(const QhullPoint &p);
    +    //! See convert for std::vector and QList
    +    void                append(const Coordinates &c) { append(c.count(), c.data()); }
    +    void                append(const PointCoordinates &other);
    +    void                appendComment(const std::string &s);
    +    void                appendPoints(std::istream &in);
    +    PointCoordinates &  operator+=(const PointCoordinates &other) { append(other); return *this; }
    +    PointCoordinates &  operator+=(const coordT &c) { append(c); return *this; }
    +    PointCoordinates &  operator+=(const QhullPoint &p) { append(p); return *this; }
    +    PointCoordinates &  operator<<(const PointCoordinates &other) { return *this += other; }
    +    PointCoordinates &  operator<<(const coordT &c) { return *this += c; }
    +    PointCoordinates &  operator<<(const QhullPoint &p) { return *this += p; }
    +    // reserve() is non-const
    +    void                reserveCoordinates(countT newCoordinates);
    +
    +#//!\name Helpers
    +private:
    +    int                 indexOffset(int i) const;
    +
    +};//PointCoordinates
    +
    +// No references to QhullPoint.  Prevents use of QHULL_DECLARE_SEQUENTIAL_ITERATOR(PointCoordinates, QhullPoint)
    +class PointCoordinatesIterator
    +{
    +    typedef PointCoordinates::const_iterator const_iterator;
    +
    +private:
    +    const PointCoordinates *c;
    +    const_iterator      i;
    +
    +public:
    +                        PointCoordinatesIterator(const PointCoordinates &container) : c(&container), i(c->constBegin()) {}
    +                        PointCoordinatesIterator &operator=(const PointCoordinates &container) { c = &container; i = c->constBegin(); return *this; }
    +
    +    void                toFront() { i = c->constBegin(); }
    +    void                toBack() { i = c->constEnd(); }
    +    bool                hasNext() const { return i != c->constEnd(); }
    +    const QhullPoint    next() { return *i++; }
    +    const QhullPoint    peekNext() const { return *i; }
    +    bool                hasPrevious() const { return i != c->constBegin(); }
    +    const QhullPoint    previous() { return *--i; }
    +    const QhullPoint    peekPrevious() const { const_iterator p = i; return *--p; }
    +    bool                findNext(const QhullPoint &t) { while(i != c->constEnd()){ if (*i++ == t) return true;} return false; }
    +    bool                findPrevious(const QhullPoint &t) { while(i != c->constBegin()){ if (*(--i) == t) return true;} return false;  }
    +};//CoordinatesIterator
    +
    +// FIXUP QH11002:  Add MutablePointCoordinatesIterator after adding modify operators
    +\
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &          operator<<(std::ostream &os, const orgQhull::PointCoordinates &p);
    +
    +#endif // QHPOINTCOORDINATES_H
    diff --git a/xs/src/qhull/src/libqhullcpp/Qhull.cpp b/xs/src/qhull/src/libqhullcpp/Qhull.cpp
    new file mode 100644
    index 0000000000..7124a15cdc
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/Qhull.cpp
    @@ -0,0 +1,352 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/Qhull.cpp#4 $$Change: 2078 $
    +** $DateTime: 2016/02/07 16:53:56 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! Qhull -- invoke qhull from C++
    +#//! Compile libqhull_r and Qhull together due to use of setjmp/longjmp()
    +
    +#include "libqhullcpp/Qhull.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullQh.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +
    +#include 
    +
    +using std::cerr;
    +using std::string;
    +using std::vector;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Global variables
    +
    +const char s_unsupported_options[]=" Fd TI ";
    +const char s_not_output_options[]= " Fd TI A C d E H P Qb QbB Qbb Qc Qf Qg Qi Qm QJ Qr QR Qs Qt Qv Qx Qz Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 Q10 Q11 R Tc TC TM TP TR Tv TV TW U v V W ";
    +
    +#//!\name Constructor, destructor, etc.
    +Qhull::
    +Qhull()
    +: qh_qh(0)
    +, origin_point()
    +, run_called(false)
    +, feasible_point()
    +{
    +    allocateQhullQh();
    +}//Qhull
    +
    +//! Invokes Qhull on rboxPoints
    +//! Same as runQhull()
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +Qhull::
    +Qhull(const RboxPoints &rboxPoints, const char *qhullCommand2)
    +: qh_qh(0)
    +, origin_point()
    +, run_called(false)
    +, feasible_point()
    +{
    +    allocateQhullQh();
    +    runQhull(rboxPoints, qhullCommand2);
    +}//Qhull rbox
    +
    +//! Invokes Qhull on a set of input points
    +//! Same as runQhull()
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +Qhull::
    +Qhull(const char *inputComment2, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand2)
    +: qh_qh(0)
    +, origin_point()
    +, run_called(false)
    +, feasible_point()
    +{
    +    allocateQhullQh();
    +    runQhull(inputComment2, pointDimension, pointCount, pointCoordinates, qhullCommand2);
    +}//Qhull points
    +
    +void Qhull::
    +allocateQhullQh()
    +{
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +    qh_qh= new QhullQh;
    +    void *p= qh_qh;
    +    void *p2= static_cast(qh_qh);
    +    char *s= static_cast(p);
    +    char *s2= static_cast(p2);
    +    if(s!=s2){
    +        throw QhullError(10074, "Qhull error: QhullQh at a different address than base type QhT (%d bytes).  Please report compiler to qhull.org", int(s2-s));
    +    }
    +}//allocateQhullQh
    +
    +Qhull::
    +~Qhull() throw()
    +{
    +    // Except for cerr, does not throw errors
    +    if(qh_qh->hasQhullMessage()){
    +        cerr<< "\nQhull output at end\n"; //FIXUP QH11005: where should error and log messages go on ~Qhull?
    +        cerr<< qh_qh->qhullMessage();
    +        qh_qh->clearQhullMessage();
    +    }
    +    delete qh_qh;
    +    qh_qh= 0;
    +}//~Qhull
    +
    +#//!\name GetSet
    +
    +void Qhull::
    +checkIfQhullInitialized()
    +{
    +    if(!initialized()){ // qh_initqhull_buffers() not called
    +        throw QhullError(10023, "Qhull error: checkIfQhullInitialized failed.  Call runQhull() first.");
    +    }
    +}//checkIfQhullInitialized
    +
    +//! Return feasiblePoint for halfspace intersection
    +//! If called before runQhull(), then it returns the value from setFeasiblePoint.  qh.feasible_string overrides this value if it is defined.
    +Coordinates Qhull::
    +feasiblePoint() const
    +{
    +    Coordinates result;
    +    if(qh_qh->feasible_point){
    +        result.append(qh_qh->hull_dim, qh_qh->feasible_point);
    +    }else{
    +        result= feasible_point;
    +    }
    +    return result;
    +}//feasiblePoint
    +
    +//! Return origin point for qh.input_dim
    +QhullPoint Qhull::
    +inputOrigin()
    +{
    +    QhullPoint result= origin();
    +    result.setDimension(qh_qh->input_dim);
    +    return result;
    +}//inputOrigin
    +
    +#//!\name GetValue
    +
    +double Qhull::
    +area(){
    +    checkIfQhullInitialized();
    +    if(!qh_qh->hasAreaVolume){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_getarea(qh_qh, qh_qh->facet_list);
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +    return qh_qh->totarea;
    +}//area
    +
    +double Qhull::
    +volume(){
    +    checkIfQhullInitialized();
    +    if(!qh_qh->hasAreaVolume){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_getarea(qh_qh, qh_qh->facet_list);
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +    return qh_qh->totvol;
    +}//volume
    +
    +#//!\name Foreach
    +
    +//! Define QhullVertex::neighborFacets().
    +//! Automatically called if merging facets or computing the Voronoi diagram.
    +//! Noop if called multiple times.
    +void Qhull::
    +defineVertexNeighborFacets(){
    +    checkIfQhullInitialized();
    +    if(!qh_qh->hasAreaVolume){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_vertexneighbors(qh_qh);
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +}//defineVertexNeighborFacets
    +
    +QhullFacetList Qhull::
    +facetList() const{
    +    return QhullFacetList(beginFacet(), endFacet());
    +}//facetList
    +
    +QhullPoints Qhull::
    +points() const
    +{
    +    return QhullPoints(qh_qh, qh_qh->hull_dim, qh_qh->num_points*qh_qh->hull_dim, qh_qh->first_point);
    +}//points
    +
    +QhullPointSet Qhull::
    +otherPoints() const
    +{
    +    return QhullPointSet(qh_qh, qh_qh->other_points);
    +}//otherPoints
    +
    +//! Return vertices of the convex hull.
    +QhullVertexList Qhull::
    +vertexList() const{
    +    return QhullVertexList(beginVertex(), endVertex());
    +}//vertexList
    +
    +#//!\name Methods
    +
    +void Qhull::
    +outputQhull()
    +{
    +    checkIfQhullInitialized();
    +    QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +        qh_produce_output2(qh_qh);
    +    }
    +    qh_qh->NOerrexit= true;
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +}//outputQhull
    +
    +void Qhull::
    +outputQhull(const char *outputflags)
    +{
    +    checkIfQhullInitialized();
    +    string cmd(" "); // qh_checkflags skips first word
    +    cmd += outputflags;
    +    char *command= const_cast(cmd.c_str());
    +    QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +        qh_clear_outputflags(qh_qh);
    +        char *s = qh_qh->qhull_command + strlen(qh_qh->qhull_command) + 1; //space
    +        strncat(qh_qh->qhull_command, command, sizeof(qh_qh->qhull_command)-strlen(qh_qh->qhull_command)-1);
    +        qh_checkflags(qh_qh, command, const_cast(s_not_output_options));
    +        qh_initflags(qh_qh, s);
    +        qh_initqhull_outputflags(qh_qh);
    +        if(qh_qh->KEEPminArea < REALmax/2
    +           || (0 != qh_qh->KEEParea + qh_qh->KEEPmerge + qh_qh->GOODvertex
    +                    + qh_qh->GOODthreshold + qh_qh->GOODpoint + qh_qh->SPLITthresholds)){
    +            facetT *facet;
    +            qh_qh->ONLYgood= False;
    +            FORALLfacet_(qh_qh->facet_list) {
    +                facet->good= True;
    +            }
    +            qh_prepare_output(qh_qh);
    +        }
    +        qh_produce_output2(qh_qh);
    +        if(qh_qh->VERIFYoutput && !qh_qh->STOPpoint && !qh_qh->STOPcone){
    +            qh_check_points(qh_qh);
    +        }
    +    }
    +    qh_qh->NOerrexit= true;
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +}//outputQhull
    +
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +void Qhull::
    +runQhull(const RboxPoints &rboxPoints, const char *qhullCommand2)
    +{
    +    runQhull(rboxPoints.comment().c_str(), rboxPoints.dimension(), rboxPoints.count(), &*rboxPoints.coordinates(), qhullCommand2);
    +}//runQhull, RboxPoints
    +
    +//! pointCoordinates is a array of points, input sites ('d' or 'v'), or halfspaces with offset last ('H')
    +//! Derived from qh_new_qhull [user.c]
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +//! For qhull commands, see http://www.qhull.org/html/qhull.htm or html/qhull.htm
    +void Qhull::
    +runQhull(const char *inputComment, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand)
    +{
    +  /* gcc may issue a "might be clobbered" warning for pointDimension and pointCoordinates [-Wclobbered].
    +     These parameters are not referenced after a longjmp() and hence not clobbered.
    +     See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */
    +    if(run_called){
    +        throw QhullError(10027, "Qhull error: runQhull called twice.  Only one call allowed.");
    +    }
    +    run_called= true;
    +    string s("qhull ");
    +    s += qhullCommand;
    +    char *command= const_cast(s.c_str());
    +    /************* Expansion of QH_TRY_ for debugging
    +    int QH_TRY_status;
    +    if(qh_qh->NOerrexit){
    +        qh_qh->NOerrexit= False;
    +        QH_TRY_status= setjmp(qh_qh->errexit);
    +    }else{
    +        QH_TRY_status= QH_TRY_ERROR;
    +    }
    +    if(!QH_TRY_status){
    +    *************/
    +    QH_TRY_(qh_qh){ // no object creation -- destructors are skipped on longjmp()
    +        qh_checkflags(qh_qh, command, const_cast(s_unsupported_options));
    +        qh_initflags(qh_qh, command);
    +        *qh_qh->rbox_command= '\0';
    +        strncat( qh_qh->rbox_command, inputComment, sizeof(qh_qh->rbox_command)-1);
    +        if(qh_qh->DELAUNAY){
    +            qh_qh->PROJECTdelaunay= True;   // qh_init_B() calls qh_projectinput()
    +        }
    +        pointT *newPoints= const_cast(pointCoordinates);
    +        int newDimension= pointDimension;
    +        int newIsMalloc= False;
    +        if(qh_qh->HALFspace){
    +            --newDimension;
    +            initializeFeasiblePoint(newDimension);
    +            newPoints= qh_sethalfspace_all(qh_qh, pointDimension, pointCount, newPoints, qh_qh->feasible_point);
    +            newIsMalloc= True;
    +        }
    +        qh_init_B(qh_qh, newPoints, pointCount, newDimension, newIsMalloc);
    +        qh_qhull(qh_qh);
    +        qh_check_output(qh_qh);
    +        qh_prepare_output(qh_qh);
    +        if(qh_qh->VERIFYoutput && !qh_qh->STOPpoint && !qh_qh->STOPcone){
    +            qh_check_points(qh_qh);
    +        }
    +    }
    +    qh_qh->NOerrexit= true;
    +    for(int k= qh_qh->hull_dim; k--; ){  // Do not move into QH_TRY block.  It may throw an error
    +        origin_point << 0.0;
    +    }
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +}//runQhull
    +
    +#//!\name Helpers -- be careful of allocating C++ objects due to setjmp/longjmp() error handling by qh_... routines
    +
    +//! initialize qh.feasible_point for half-space intersection
    +//! Sets from qh.feasible_string if available, otherwise from Qhull::feasible_point
    +//! called only once from runQhull(), otherwise it leaks memory (the same as qh_setFeasible)
    +void Qhull::
    +initializeFeasiblePoint(int hulldim)
    +{
    +    if(qh_qh->feasible_string){
    +        qh_setfeasible(qh_qh, hulldim);
    +    }else{
    +        if(feasible_point.isEmpty()){
    +            qh_fprintf(qh_qh, qh_qh->ferr, 6209, "qhull error: missing feasible point for halfspace intersection.  Use option 'Hn,n' or Qhull::setFeasiblePoint before runQhull()\n");
    +            qh_errexit(qh_qh, qh_ERRmem, NULL, NULL);
    +        }
    +        if(feasible_point.size()!=(size_t)hulldim){
    +            qh_fprintf(qh_qh, qh_qh->ferr, 6210, "qhull error: dimension of feasiblePoint should be %d.  It is %u", hulldim, feasible_point.size());
    +            qh_errexit(qh_qh, qh_ERRmem, NULL, NULL);
    +        }
    +        if (!(qh_qh->feasible_point= (coordT*)qh_malloc(hulldim * sizeof(coordT)))) {
    +            qh_fprintf(qh_qh, qh_qh->ferr, 6202, "qhull error: insufficient memory for feasible point\n");
    +            qh_errexit(qh_qh, qh_ERRmem, NULL, NULL);
    +        }
    +        coordT *t= qh_qh->feasible_point;
    +        // No qh_... routines after here -- longjmp() ignores destructor
    +        for(Coordinates::ConstIterator p=feasible_point.begin(); p.  It could be rewritten for another vector class such as QList
    +   #define QHULL_USES_QT
    +      Supply conversions to QT
    +      qhulltest requires QT.  It is defined in RoadTest.h
    +
    +  #define QHULL_ASSERT
    +      Defined by QhullError.h
    +      It invokes assert()
    +*/
    +
    +#//!\name Used here
    +    class QhullFacetList;
    +    class QhullPoints;
    +    class QhullQh;
    +    class RboxPoints;
    +
    +#//!\name Defined here
    +    class Qhull;
    +
    +//! Interface to Qhull from C++
    +class Qhull {
    +
    +private:
    +#//!\name Members and friends
    +    QhullQh *           qh_qh;          //! qhT for this instance
    +    Coordinates         origin_point;   //! origin for qh_qh->hull_dim.  Set by runQhull()
    +    bool                run_called;     //! True at start of runQhull.  Errors if call again.
    +    Coordinates         feasible_point;  //! feasible point for half-space intersection (alternative to qh.feasible_string for qh.feasible_point)
    +
    +public:
    +#//!\name Constructors
    +                        Qhull();      //!< call runQhull() next
    +                        Qhull(const RboxPoints &rboxPoints, const char *qhullCommand2);
    +                        Qhull(const char *inputComment2, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand2);
    +                        ~Qhull() throw();
    +private:                //! Disable copy constructor and assignment.  Qhull owns QhullQh.
    +                        Qhull(const Qhull &);
    +    Qhull &             operator=(const Qhull &);
    +
    +private:
    +    void                allocateQhullQh();
    +
    +public:
    +
    +#//!\name GetSet
    +    void                checkIfQhullInitialized();
    +    int                 dimension() const { return qh_qh->input_dim; } //!< Dimension of input and result
    +    void                disableOutputStream() { qh_qh->disableOutputStream(); }
    +    void                enableOutputStream() { qh_qh->enableOutputStream(); }
    +    countT              facetCount() const { return qh_qh->num_facets; }
    +    Coordinates         feasiblePoint() const; 
    +    int                 hullDimension() const { return qh_qh->hull_dim; } //!< Dimension of the computed hull
    +    bool                hasOutputStream() const { return qh_qh->hasOutputStream(); }
    +    bool                initialized() const { return (qh_qh->hull_dim>0); }
    +    const char *        inputComment() const { return qh_qh->rbox_command; }
    +    QhullPoint          inputOrigin();
    +                        //! non-const due to QhullPoint
    +    QhullPoint          origin() { QHULL_ASSERT(initialized()); return QhullPoint(qh_qh, origin_point.data()); }
    +    QhullQh *           qh() const { return qh_qh; };
    +    const char *        qhullCommand() const { return qh_qh->qhull_command; }
    +    const char *        rboxCommand() const { return qh_qh->rbox_command; }
    +    int                 rotateRandom() const { return qh_qh->ROTATErandom; } //!< Return QRn for repeating QR0 runs
    +    void                setFeasiblePoint(const Coordinates &c) { feasible_point= c; } //!< Sets qh.feasible_point via initializeFeasiblePoint
    +    countT              vertexCount() const { return qh_qh->num_vertices; }
    +
    +#//!\name Delegated to QhullQh
    +    double              angleEpsilon() const { return qh_qh->angleEpsilon(); } //!< Epsilon for hyperplane angle equality
    +    void                appendQhullMessage(const std::string &s) { qh_qh->appendQhullMessage(s); }
    +    void                clearQhullMessage() { qh_qh->clearQhullMessage(); }
    +    double              distanceEpsilon() const { return qh_qh->distanceEpsilon(); } //!< Epsilon for distance to hyperplane
    +    double              factorEpsilon() const { return qh_qh->factorEpsilon(); }  //!< Factor for angleEpsilon and distanceEpsilon
    +    std::string         qhullMessage() const { return qh_qh->qhullMessage(); }
    +    bool                hasQhullMessage() const { return qh_qh->hasQhullMessage(); }
    +    int                 qhullStatus() const { return qh_qh->qhullStatus(); }
    +    void                setErrorStream(std::ostream *os) { qh_qh->setErrorStream(os); }
    +    void                setFactorEpsilon(double a) { qh_qh->setFactorEpsilon(a); }
    +    void                setOutputStream(std::ostream *os) { qh_qh->setOutputStream(os); }
    +
    +#//!\name ForEach
    +    QhullFacet          beginFacet() const { return QhullFacet(qh_qh, qh_qh->facet_list); }
    +    QhullVertex         beginVertex() const { return QhullVertex(qh_qh, qh_qh->vertex_list); }
    +    void                defineVertexNeighborFacets(); //!< Automatically called if merging facets or Voronoi diagram
    +    QhullFacet          endFacet() const { return QhullFacet(qh_qh, qh_qh->facet_tail); }
    +    QhullVertex         endVertex() const { return QhullVertex(qh_qh, qh_qh->vertex_tail); }
    +    QhullFacetList      facetList() const;
    +    QhullFacet          firstFacet() const { return beginFacet(); }
    +    QhullVertex         firstVertex() const { return beginVertex(); }
    +    QhullPoints         points() const;
    +    QhullPointSet       otherPoints() const;
    +                        //! Same as points().coordinates()
    +    coordT *            pointCoordinateBegin() const { return qh_qh->first_point; }
    +    coordT *            pointCoordinateEnd() const { return qh_qh->first_point + qh_qh->num_points*qh_qh->hull_dim; }
    +    QhullVertexList     vertexList() const;
    +
    +#//!\name Methods
    +    double              area();
    +    void                outputQhull();
    +    void                outputQhull(const char * outputflags);
    +    void                runQhull(const RboxPoints &rboxPoints, const char *qhullCommand2);
    +    void                runQhull(const char *inputComment2, int pointDimension, int pointCount, const realT *pointCoordinates, const char *qhullCommand2);
    +    double              volume();
    +
    +#//!\name Helpers
    +private:
    +    void                initializeFeasiblePoint(int hulldim);
    +};//Qhull
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLCPP_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullError.h b/xs/src/qhull/src/libqhullcpp/QhullError.h
    new file mode 100644
    index 0000000000..08d50aa0ff
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullError.h
    @@ -0,0 +1,62 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullError.h#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLERROR_H
    +#define QHULLERROR_H
    +
    +#include "libqhullcpp/RoadError.h"
    +// No dependencies on libqhull
    +
    +#ifndef QHULL_ASSERT
    +#define QHULL_ASSERT assert
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullError -- std::exception class for Qhull
    +    class QhullError;
    +
    +class QhullError : public RoadError {
    +
    +public:
    +#//!\name Constants
    +    enum {
    +        QHULLfirstError= 10000, //MSG_QHULL_ERROR in Qhull's user.h
    +        QHULLlastError= 10078,
    +        NOthrow= 1 //! For flag to indexOf()
    +    };
    +
    +#//!\name Constructors
    +    // default constructors
    +    QhullError() : RoadError() {};
    +    QhullError(const QhullError &other) : RoadError(other) {}
    +    QhullError(int code, const std::string &message) : RoadError(code, message) {};
    +    QhullError(int code, const char *fmt) : RoadError(code, fmt) {};
    +    QhullError(int code, const char *fmt, int d) : RoadError(code, fmt, d) {};
    +    QhullError(int code, const char *fmt, int d, int d2) : RoadError(code, fmt, d, d2) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f) : RoadError(code, fmt, d, d2, f) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, const char *s) : RoadError(code, fmt, d, d2, f, s) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, const void *x) : RoadError(code, fmt, d, d2, f, x) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, int i) : RoadError(code, fmt, d, d2, f, i) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, long long i) : RoadError(code, fmt, d, d2, f, i) {};
    +    QhullError(int code, const char *fmt, int d, int d2, float f, double e) : RoadError(code, fmt, d, d2, f, e) {};
    +    QhullError &operator=(const QhullError &other) { this->RoadError::operator=(other); return *this; }
    +    ~QhullError() throw() {}
    +
    +};//class QhullError
    +
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +inline std::ostream &operator<<(std::ostream &os, const orgQhull::QhullError &e) { return os << e.what(); }
    +
    +#endif // QHULLERROR_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacet.cpp b/xs/src/qhull/src/libqhullcpp/QhullFacet.cpp
    new file mode 100644
    index 0000000000..40d3828a4c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacet.cpp
    @@ -0,0 +1,519 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacet.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullFacet -- Qhull's facet structure, facetT, as a C++ class
    +
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullPointSet.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +
    +#include 
    +
    +using std::endl;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +facetT QhullFacet::
    +s_empty_facet= {0,0,0,0,{0},
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0,0,
    +        0,0,0,0};
    +
    +#//!\name Constructors
    +
    +QhullFacet::
    +QhullFacet(const Qhull &q) 
    +: qh_facet(&s_empty_facet)
    +, qh_qh(q.qh())
    +{
    +}
    +
    +QhullFacet::
    +QhullFacet(const Qhull &q, facetT *f) 
    +: qh_facet(f ? f : &s_empty_facet)
    +, qh_qh(q.qh())
    +{
    +}
    +
    +#//!\name GetSet
    +
    +//! Return voronoi center or facet centrum.  Derived from qh_printcenter [io_r.c]
    +//! if printFormat=qh_PRINTtriangles and qh.DELAUNAY, returns centrum of a Delaunay facet
    +//! Sets center if needed
    +//! Code duplicated for PrintCenter and getCenter
    +//! Returns QhullPoint() if none or qh_INFINITE
    +QhullPoint QhullFacet::
    +getCenter(qh_PRINT printFormat)
    +{
    +    if(!qh_qh){
    +        // returns QhullPoint()
    +    }else if(qh_qh->CENTERtype==qh_ASvoronoi){
    +        if(!qh_facet->normal || !qh_facet->upperdelaunay || !qh_qh->ATinfinity){
    +            if(!qh_facet->center){
    +                QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +                    qh_facet->center= qh_facetcenter(qh_qh, qh_facet->vertices);
    +                }
    +                qh_qh->NOerrexit= true;
    +                qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +            }
    +            return QhullPoint(qh_qh, qh_qh->hull_dim-1, qh_facet->center);
    +        }
    +    }else if(qh_qh->CENTERtype==qh_AScentrum){
    +        volatile int numCoords= qh_qh->hull_dim;
    +        if(printFormat==qh_PRINTtriangles && qh_qh->DELAUNAY){
    +            numCoords--;
    +        }
    +        if(!qh_facet->center){
    +            QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +                qh_facet->center= qh_getcentrum(qh_qh, getFacetT());
    +            }
    +            qh_qh->NOerrexit= true;
    +            qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +        }
    +        return QhullPoint(qh_qh, numCoords, qh_facet->center);
    +    }
    +    return QhullPoint();
    + }//getCenter
    +
    +//! Return innerplane clearly below the vertices
    +//! from io_r.c[qh_PRINTinner]
    +QhullHyperplane QhullFacet::
    +innerplane() const{
    +    QhullHyperplane h;
    +    if(qh_qh){
    +        realT inner;
    +        // Does not error, TRY_QHULL_ not needed
    +        qh_outerinner(qh_qh, const_cast(getFacetT()), NULL, &inner);
    +        h= hyperplane();
    +        h.setOffset(h.offset()-inner); //inner is negative
    +    }
    +    return h;
    +}//innerplane
    +
    +//! Return outerplane clearly above all points
    +//! from io_r.c[qh_PRINTouter]
    +QhullHyperplane QhullFacet::
    +outerplane() const{
    +    QhullHyperplane h;
    +    if(qh_qh){
    +        realT outer;
    +        // Does not error, TRY_QHULL_ not needed
    +        qh_outerinner(qh_qh, const_cast(getFacetT()), &outer, NULL);
    +        h= hyperplane();
    +        h.setOffset(h.offset()-outer); //outer is positive
    +    }
    +    return h;
    +}//outerplane
    +
    +//! Set by qh_triangulate for option 'Qt'.
    +//! Errors if tricoplanar and facetArea() or qh_getarea() called first.
    +QhullFacet QhullFacet::
    +tricoplanarOwner() const
    +{
    +    if(qh_facet->tricoplanar){
    +        if(qh_facet->isarea){
    +            throw QhullError(10018, "Qhull error: facetArea() or qh_getarea() previously called.  triCoplanarOwner() is not available.");
    +        }
    +        return QhullFacet(qh_qh, qh_facet->f.triowner);
    +    }
    +    return QhullFacet(qh_qh); 
    +}//tricoplanarOwner
    +
    +QhullPoint QhullFacet::
    +voronoiVertex()
    +{
    +    if(qh_qh && qh_qh->CENTERtype!=qh_ASvoronoi){
    +          throw QhullError(10052, "Error: QhullFacet.voronoiVertex() requires option 'v' (qh_ASvoronoi)");
    +    }
    +    return getCenter();
    +}//voronoiVertex
    +
    +#//!\name Value
    +
    +//! Disables tricoplanarOwner()
    +double QhullFacet::
    +facetArea()
    +{
    +    if(qh_qh && !qh_facet->isarea){
    +        QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +            qh_facet->f.area= qh_facetarea(qh_qh, qh_facet);
    +            qh_facet->isarea= True;
    +        }
    +        qh_qh->NOerrexit= true;
    +        qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    }
    +    return qh_facet->f.area;
    +}//facetArea
    +
    +#//!\name Foreach
    +
    +QhullPointSet QhullFacet::
    +coplanarPoints() const
    +{
    +    return QhullPointSet(qh_qh, qh_facet->coplanarset);
    +}//coplanarPoints
    +
    +QhullFacetSet QhullFacet::
    +neighborFacets() const
    +{
    +    return QhullFacetSet(qh_qh, qh_facet->neighbors);
    +}//neighborFacets
    +
    +QhullPointSet QhullFacet::
    +outsidePoints() const
    +{
    +    return QhullPointSet(qh_qh, qh_facet->outsideset);
    +}//outsidePoints
    +
    +QhullRidgeSet QhullFacet::
    +ridges() const
    +{
    +    return QhullRidgeSet(qh_qh, qh_facet->ridges);
    +}//ridges
    +
    +QhullVertexSet QhullFacet::
    +vertices() const
    +{
    +    return QhullVertexSet(qh_qh, qh_facet->vertices);
    +}//vertices
    +
    +}//namespace orgQhull
    +
    +#//!\name operator<<
    +
    +using std::ostream;
    +
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetSet;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullPointSet;
    +using orgQhull::QhullRidge;
    +using orgQhull::QhullRidgeSet;
    +using orgQhull::QhullSetBase;
    +using orgQhull::QhullVertexSet;
    +
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintFacet &pr)
    +{
    +    os << pr.message;
    +    QhullFacet f= *pr.facet;
    +    if(f.getFacetT()==0){ // Special values from set iterator
    +        os << " NULLfacet" << endl;
    +        return os;
    +    }
    +    if(f.getFacetT()==qh_MERGEridge){
    +        os << " MERGEridge" << endl;
    +        return os;
    +    }
    +    if(f.getFacetT()==qh_DUPLICATEridge){
    +        os << " DUPLICATEridge" << endl;
    +        return os;
    +    }
    +    os << f.printHeader();
    +    if(!f.ridges().isEmpty()){
    +        os << f.printRidges();
    +    }
    +    return os;
    +}//operator<< PrintFacet
    +
    +//! Print Voronoi center or facet centrum to stream.  Same as qh_printcenter [_r.]
    +//! Code duplicated for PrintCenter and getCenter
    +//! Sets center if needed
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintCenter &pr)
    +{
    +    facetT *f= pr.facet->getFacetT();
    +    if(pr.facet->qh()->CENTERtype!=qh_ASvoronoi && pr.facet->qh()->CENTERtype!=qh_AScentrum){
    +        return os;
    +    }
    +    if (pr.message){
    +        os << pr.message;
    +    }
    +    int numCoords;
    +    if(pr.facet->qh()->CENTERtype==qh_ASvoronoi){
    +        numCoords= pr.facet->qh()->hull_dim-1;
    +        if(!f->normal || !f->upperdelaunay || !pr.facet->qh()->ATinfinity){
    +            if(!f->center){
    +                f->center= qh_facetcenter(pr.facet->qh(), f->vertices);
    +            }
    +            for(int k=0; kcenter[k] << " "; // FIXUP QH11010 qh_REAL_1
    +            }
    +        }else{
    +            for(int k=0; kqh()->hull_dim;
    +        if(pr.print_format==qh_PRINTtriangles && pr.facet->qh()->DELAUNAY){
    +            numCoords--;
    +        }
    +        if(!f->center){
    +            f->center= qh_getcentrum(pr.facet->qh(), f);
    +        }
    +        for(int k=0; kcenter[k] << " "; // FIXUP QH11010 qh_REAL_1
    +        }
    +    }
    +    if(pr.print_format==qh_PRINTgeom && numCoords==2){
    +        os << " 0";
    +    }
    +    os << endl;
    +    return os;
    +}//operator<< PrintCenter
    +
    +//! Print flags for facet to stream.  Space prefix.  From qh_printfacetheader [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintFlags &p)
    +{
    +    const facetT *f= p.facet->getFacetT();
    +    if(p.message){
    +        os << p.message;
    +    }
    +
    +    os << (p.facet->isTopOrient() ? " top" : " bottom");
    +    if(p.facet->isSimplicial()){
    +        os << " simplicial";
    +    }
    +    if(p.facet->isTriCoplanar()){
    +        os << " tricoplanar";
    +    }
    +    if(p.facet->isUpperDelaunay()){
    +        os << " upperDelaunay";
    +    }
    +    if(f->visible){
    +        os << " visible";
    +    }
    +    if(f->newfacet){
    +        os << " new";
    +    }
    +    if(f->tested){
    +        os << " tested";
    +    }
    +    if(!f->good){
    +        os << " notG";
    +    }
    +    if(f->seen){
    +        os << " seen";
    +    }
    +    if(f->coplanar){
    +        os << " coplanar";
    +    }
    +    if(f->mergehorizon){
    +        os << " mergehorizon";
    +    }
    +    if(f->keepcentrum){
    +        os << " keepcentrum";
    +    }
    +    if(f->dupridge){
    +        os << " dupridge";
    +    }
    +    if(f->mergeridge && !f->mergeridge2){
    +        os << " mergeridge1";
    +    }
    +    if(f->mergeridge2){
    +        os << " mergeridge2";
    +    }
    +    if(f->newmerge){
    +        os << " newmerge";
    +    }
    +    if(f->flipped){
    +        os << " flipped";
    +    }
    +    if(f->notfurthest){
    +        os << " notfurthest";
    +    }
    +    if(f->degenerate){
    +        os << " degenerate";
    +    }
    +    if(f->redundant){
    +        os << " redundant";
    +    }
    +    os << endl;
    +    return os;
    +}//operator<< PrintFlags
    +
    +//! Print header for facet to stream. Space prefix.  From qh_printfacetheader [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintHeader &pr)
    +{
    +    QhullFacet facet= *pr.facet;
    +    facetT *f= facet.getFacetT();
    +    os << "- f" << facet.id() << endl;
    +    os << facet.printFlags("    - flags:");
    +    if(f->isarea){
    +        os << "    - area: " << f->f.area << endl; //FIXUP QH11010 2.2g
    +    }else if(pr.facet->qh()->NEWfacets && f->visible && f->f.replace){
    +        os << "    - replacement: f" << f->f.replace->id << endl;
    +    }else if(f->newfacet){
    +        if(f->f.samecycle && f->f.samecycle != f){
    +            os << "    - shares same visible/horizon as f" << f->f.samecycle->id << endl;
    +        }
    +    }else if(f->tricoplanar /* !isarea */){
    +        if(f->f.triowner){
    +            os << "    - owner of normal & centrum is facet f" << f->f.triowner->id << endl;
    +        }
    +    }else if(f->f.newcycle){
    +        os << "    - was horizon to f" << f->f.newcycle->id << endl;
    +    }
    +    if(f->nummerge){
    +        os << "    - merges: " << f->nummerge << endl;
    +    }
    +    os << facet.hyperplane().print("    - normal: ", "\n    - offset: "); // FIXUP QH11010 %10.7g
    +    if(pr.facet->qh()->CENTERtype==qh_ASvoronoi || f->center){
    +        os << facet.printCenter(qh_PRINTfacets, "    - center: ");
    +    }
    +#if qh_MAXoutside
    +    if(f->maxoutside > pr.facet->qh()->DISTround){
    +        os << "    - maxoutside: " << f->maxoutside << endl; //FIXUP QH11010 %10.7g
    +    }
    +#endif
    +    QhullPointSet ps= facet.outsidePoints();
    +    if(!ps.isEmpty()){
    +        QhullPoint furthest= ps.last();
    +        if (ps.size() < 6) {
    +            os << "    - outside set(furthest p" << furthest.id() << "):" << endl;
    +            for(QhullPointSet::iterator i=ps.begin(); i!=ps.end(); ++i){
    +                QhullPoint p= *i;
    +                os << p.print("     ");
    +            }
    +        }else if(ps.size()<21){
    +            os << ps.print("    - outside set:");
    +        }else{
    +            os << "    - outside set:  " << ps.size() << " points.";
    +            os << furthest.print("  Furthest");
    +        }
    +#if !qh_COMPUTEfurthest
    +        os << "    - furthest distance= " << f->furthestdist << endl; //FIXUP QH11010 %2.2g
    +#endif
    +    }
    +    QhullPointSet cs= facet.coplanarPoints();
    +    if(!cs.isEmpty()){
    +        QhullPoint furthest= cs.last();
    +        if (cs.size() < 6) {
    +            os << "    - coplanar set(furthest p" << furthest.id() << "):" << endl;
    +            for(QhullPointSet::iterator i=cs.begin(); i!=cs.end(); ++i){
    +                QhullPoint p= *i;
    +                os << p.print("     ");
    +            }
    +        }else if(cs.size()<21){
    +            os << cs.print("    - coplanar set:");
    +        }else{
    +            os << "    - coplanar set:  " << cs.size() << " points.";
    +            os << furthest.print("  Furthest");
    +        }
    +        // FIXUP QH11027 Can/should zinc_(Zdistio) be called from C++ interface
    +        double d= facet.distance(furthest);
    +        os << "      furthest distance= " << d << endl; //FIXUP QH11010 %2.2g
    +    }
    +    QhullVertexSet vs= facet.vertices();
    +    if(!vs.isEmpty()){
    +        os << vs.print("    - vertices:");
    +    }
    +    QhullFacetSet fs= facet.neighborFacets();
    +    fs.selectAll();
    +    if(!fs.isEmpty()){
    +        os << fs.printIdentifiers("    - neighboring facets:");
    +    }
    +    return os;
    +}//operator<< PrintHeader
    +
    +
    +//! Print ridges of facet to stream.  Same as qh_printfacetridges [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacet::PrintRidges &pr)
    +{
    +    const QhullFacet facet= *pr.facet;
    +    facetT *f= facet.getFacetT();
    +    QhullRidgeSet rs= facet.ridges();
    +    if(!rs.isEmpty()){
    +        if(f->visible && pr.facet->qh()->NEWfacets){
    +            os << "    - ridges(ids may be garbage):";
    +            for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +                QhullRidge r= *i;
    +                os << " r" << r.id();
    +            }
    +            os << endl;
    +        }else{
    +            os << "    - ridges:" << endl;
    +        }
    +
    +        // Keep track of printed ridges
    +        for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +            QhullRidge r= *i;
    +            r.getRidgeT()->seen= false;
    +        }
    +        int ridgeCount= 0;
    +        if(facet.dimension()==3){
    +            for(QhullRidge r= rs.first(); !r.getRidgeT()->seen; r= r.nextRidge3d(facet)){
    +                r.getRidgeT()->seen= true;
    +                os << r.print("");
    +                ++ridgeCount;
    +                if(!r.hasNextRidge3d(facet)){
    +                    break;
    +                }
    +            }
    +        }else {
    +            QhullFacetSet ns(facet.neighborFacets());
    +            for(QhullFacetSet::iterator i=ns.begin(); i!=ns.end(); ++i){
    +                QhullFacet neighbor= *i;
    +                QhullRidgeSet nrs(neighbor.ridges());
    +                for(QhullRidgeSet::iterator j=nrs.begin(); j!=nrs.end(); ++j){
    +                    QhullRidge r= *j;
    +                    if(r.otherFacet(neighbor)==facet){
    +                        r.getRidgeT()->seen= true;
    +                        os << r.print("");
    +                        ridgeCount++;
    +                    }
    +                }
    +            }
    +        }
    +        if(ridgeCount!=rs.count()){
    +            os << "     - all ridges:";
    +            for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +                QhullRidge r= *i;
    +                os << " r" << r.id();
    +            }
    +            os << endl;
    +        }
    +        for(QhullRidgeSet::iterator i=rs.begin(); i!=rs.end(); ++i){
    +            QhullRidge r= *i;
    +            if(!r.getRidgeT()->seen){
    +                os << r.print("");
    +            }
    +        }
    +    }
    +    return os;
    +}//operator<< PrintRidges
    +
    +// "No conversion" error if defined inline
    +ostream &
    +operator<<(ostream &os, QhullFacet &f)
    +{
    +    os << f.print("");
    +    return os;
    +}//<< QhullFacet
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacet.h b/xs/src/qhull/src/libqhullcpp/QhullFacet.h
    new file mode 100644
    index 0000000000..ae4f008fd2
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacet.h
    @@ -0,0 +1,151 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacet.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLFACET_H
    +#define QHULLFACET_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullHyperplane.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullPointSet.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Coordinates;
    +    class Qhull;
    +    class QhullFacetSet;
    +    class QhullRidge;
    +    class QhullVertex;
    +    class QhullVertexSet;
    +
    +#//!\name Defined here
    +    class QhullFacet;
    +    typedef QhullSet  QhullRidgeSet;
    +
    +//! A QhullFacet is the C++ equivalent to Qhull's facetT*
    +class QhullFacet {
    +
    +#//!\name Defined here
    +public:
    +    typedef facetT *   base_type;  // for QhullVertexSet
    +
    +private:
    +#//!\name Fields -- no additions (QhullFacetSet of facetT*)
    +    facetT *            qh_facet;  //!< Corresponding facetT, may be 0 for corner cases (e.g., *facetSet.end()==0) and tricoplanarOwner()
    +    QhullQh *           qh_qh;     //!< QhullQh/qhT for facetT, may be 0
    +
    +#//!\name Class objects
    +    static facetT       s_empty_facet; // needed for shallow copy
    +
    +public:
    +#//!\name Constructors
    +                        QhullFacet() : qh_facet(&s_empty_facet), qh_qh(0) {}
    +    explicit            QhullFacet(const Qhull &q);
    +                        QhullFacet(const Qhull &q, facetT *f);
    +    explicit            QhullFacet(QhullQh *qqh) : qh_facet(&s_empty_facet), qh_qh(qqh) {}
    +                        QhullFacet(QhullQh *qqh, facetT *f) : qh_facet(f ? f : &s_empty_facet), qh_qh(qqh) {}
    +                        // Creates an alias.  Does not copy QhullFacet.  Needed for return by value and parameter passing
    +                        QhullFacet(const QhullFacet &other) : qh_facet(other.qh_facet ? other.qh_facet : &s_empty_facet), qh_qh(other.qh_qh) {}
    +                        // Creates an alias.  Does not copy QhullFacet.  Needed for vector
    +    QhullFacet &        operator=(const QhullFacet &other) { qh_facet= other.qh_facet ? other.qh_facet : &s_empty_facet; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullFacet() {}
    +
    +
    +#//!\name GetSet
    +    int                 dimension() const { return (qh_qh ? qh_qh->hull_dim : 0); }
    +    QhullPoint          getCenter() { return getCenter(qh_PRINTpoints); }
    +    QhullPoint          getCenter(qh_PRINT printFormat);
    +    facetT *            getBaseT() const { return getFacetT(); } //!< For QhullSet
    +                        // Do not define facetT().  It conflicts with return type facetT*
    +    facetT *            getFacetT() const { return qh_facet; }
    +    QhullHyperplane     hyperplane() const { return QhullHyperplane(qh_qh, dimension(), qh_facet->normal, qh_facet->offset); }
    +    countT              id() const { return (qh_facet ? qh_facet->id : (int)qh_IDunknown); }
    +    QhullHyperplane     innerplane() const;
    +    bool                isValid() const { return qh_qh && qh_facet && qh_facet != &s_empty_facet; }
    +    bool                isGood() const { return qh_facet && qh_facet->good; }
    +    bool                isSimplicial() const { return qh_facet && qh_facet->simplicial; }
    +    bool                isTopOrient() const { return qh_facet && qh_facet->toporient; }
    +    bool                isTriCoplanar() const { return qh_facet && qh_facet->tricoplanar; }
    +    bool                isUpperDelaunay() const { return qh_facet && qh_facet->upperdelaunay; }
    +    QhullFacet          next() const { return QhullFacet(qh_qh, qh_facet->next); }
    +    bool                operator==(const QhullFacet &other) const { return qh_facet==other.qh_facet; }
    +    bool                operator!=(const QhullFacet &other) const { return !operator==(other); }
    +    QhullHyperplane     outerplane() const;
    +    QhullFacet          previous() const { return QhullFacet(qh_qh, qh_facet->previous); }
    +    QhullQh *           qh() const { return qh_qh; }
    +    QhullFacet          tricoplanarOwner() const;
    +    QhullPoint          voronoiVertex();
    +
    +#//!\name value
    +    //! Undefined if c.size() != dimension()
    +    double              distance(const Coordinates &c) const { return distance(c.data()); }
    +    double              distance(const pointT *p) const { return distance(QhullPoint(qh_qh, const_cast(p))); }
    +    double              distance(const QhullPoint &p) const { return hyperplane().distance(p); }
    +    double              facetArea();
    +
    +#//!\name foreach
    +    // Can not inline.  Otherwise circular reference
    +    QhullPointSet       coplanarPoints() const;
    +    QhullFacetSet       neighborFacets() const;
    +    QhullPointSet       outsidePoints() const;
    +    QhullRidgeSet       ridges() const;
    +    QhullVertexSet      vertices() const;
    +
    +#//!\name IO
    +    struct PrintCenter{
    +        QhullFacet *    facet;  // non-const due to facet.center()
    +        const char *    message;
    +        qh_PRINT        print_format;
    +                        PrintCenter(QhullFacet &f, qh_PRINT printFormat, const char * s) : facet(&f), message(s), print_format(printFormat){}
    +    };//PrintCenter
    +    PrintCenter         printCenter(qh_PRINT printFormat, const char *message) { return PrintCenter(*this, printFormat, message); }
    +
    +    struct PrintFacet{
    +        QhullFacet *    facet;  // non-const due to f->center()
    +        const char *    message;
    +        explicit        PrintFacet(QhullFacet &f, const char * s) : facet(&f), message(s) {}
    +    };//PrintFacet
    +    PrintFacet          print(const char *message) { return PrintFacet(*this, message); }
    +
    +    struct PrintFlags{
    +        const QhullFacet *facet;
    +        const char *    message;
    +                        PrintFlags(const QhullFacet &f, const char *s) : facet(&f), message(s) {}
    +    };//PrintFlags
    +    PrintFlags          printFlags(const char *message) const { return PrintFlags(*this, message); }
    +
    +    struct PrintHeader{
    +        QhullFacet *    facet;  // non-const due to f->center()
    +                        PrintHeader(QhullFacet &f) : facet(&f) {}
    +    };//PrintHeader
    +    PrintHeader         printHeader() { return PrintHeader(*this); }
    +
    +    struct PrintRidges{
    +        const QhullFacet *facet;
    +                        PrintRidges(QhullFacet &f) : facet(&f) {}
    +    };//PrintRidges
    +    PrintRidges         printRidges() { return PrintRidges(*this); }
    +
    +};//class QhullFacet
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintFacet &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintCenter &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintFlags &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintHeader &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacet::PrintRidges &pr);
    +std::ostream &operator<<(std::ostream &os, orgQhull::QhullFacet &f); // non-const due to qh_getcenter()
    +
    +#endif // QHULLFACET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp b/xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp
    new file mode 100644
    index 0000000000..9e6ddfe9ec
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacetList.cpp
    @@ -0,0 +1,174 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacetList.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullFacetList -- Qhull's linked facets, as a C++ class
    +
    +#include "libqhullcpp/QhullFacetList.h"
    +
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullVertex.h"
    +
    +using std::string;
    +using std::vector;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +QhullFacetList::
    +QhullFacetList(const Qhull &q, facetT *b, facetT *e ) 
    +: QhullLinkedList(QhullFacet(q, b), QhullFacet(q, e))
    +, select_all(false)
    +{
    +}
    +
    +#//!\name Conversions
    +
    +// See qt_qhull.cpp for QList conversions
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullFacetList::
    +toStdVector() const
    +{
    +    QhullLinkedListIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.push_back(f);
    +        }
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#ifndef QHULL_NO_STL
    +//! Same as PrintVertices
    +std::vector QhullFacetList::
    +vertices_toStdVector() const
    +{
    +    std::vector vs;
    +    QhullVertexSet qvs(qh(), first().getFacetT(), 0, isSelectAll());
    +
    +    for(QhullVertexSet::iterator i=qvs.begin(); i!=qvs.end(); ++i){
    +        vs.push_back(*i);
    +    }
    +    return vs;
    +}//vertices_toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +bool QhullFacetList::
    +contains(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullLinkedList::contains(facet);
    +    }
    +    for(QhullFacetList::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//contains
    +
    +int QhullFacetList::
    +count() const
    +{
    +    if(isSelectAll()){
    +        return QhullLinkedList::count();
    +    }
    +    int counter= 0;
    +    for(QhullFacetList::const_iterator i=begin(); i != end(); ++i){
    +        if((*i).isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +int QhullFacetList::
    +count(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullLinkedList::count(facet);
    +    }
    +    int counter= 0;
    +    for(QhullFacetList::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetList;
    +using orgQhull::QhullVertex;
    +using orgQhull::QhullVertexSet;
    +
    +ostream &
    +operator<<(ostream &os, const QhullFacetList::PrintFacetList &pr)
    +{
    +    os << pr.print_message;
    +    QhullFacetList fs= *pr.facet_list;
    +    os << "Vertices for " << fs.count() << " facets" << endl;
    +    os << fs.printVertices();
    +    os << fs.printFacets();
    +    return os;
    +}//operator<<
    +
    +//! Print facet list to stream.  From qh_printafacet [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullFacetList::PrintFacets &pr)
    +{
    +    for(QhullFacetList::const_iterator i= pr.facet_list->begin(); i != pr.facet_list->end(); ++i){
    +        QhullFacet f= *i;
    +        if(pr.facet_list->isSelectAll() || f.isGood()){
    +            os << f.print("");
    +        }
    +    }
    +    return os;
    +}//printFacets
    +
    +//! Print vertices of good faces in facet list to stream.  From qh_printvertexlist [io_r.c]
    +//! Same as vertices_toStdVector
    +ostream &
    +operator<<(ostream &os, const QhullFacetList::PrintVertices &pr)
    +{
    +    QhullVertexSet vs(pr.facet_list->qh(), pr.facet_list->first().getFacetT(), NULL, pr.facet_list->isSelectAll());
    +    for(QhullVertexSet::iterator i=vs.begin(); i!=vs.end(); ++i){
    +        QhullVertex v= *i;
    +        os << v.print("");
    +    }
    +    return os;
    +}//printVertices
    +
    +std::ostream &
    +operator<<(ostream &os, const QhullFacetList &fs)
    +{
    +    os << fs.printFacets();
    +    return os;
    +}//QhullFacetList
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacetList.h b/xs/src/qhull/src/libqhullcpp/QhullFacetList.h
    new file mode 100644
    index 0000000000..e61e568403
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacetList.h
    @@ -0,0 +1,106 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacetList.h#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLFACETLIST_H
    +#define QHULLFACETLIST_H
    +
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#include 
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullFacet;
    +    class QhullQh;
    +
    +#//!\name Defined here
    +    //! QhullFacetList -- List of QhullFacet/facetT, as a C++ class.  
    +    //!\see QhullFacetSet.h
    +    class QhullFacetList;
    +    //! QhullFacetListIterator -- if(f.isGood()){ ... }
    +    typedef QhullLinkedListIterator QhullFacetListIterator;
    +
    +class QhullFacetList : public QhullLinkedList {
    +
    +#//!\name  Fields
    +private:
    +    bool                select_all;   //! True if include bad facets.  Default is false.
    +
    +#//!\name Constructors
    +public:
    +                        QhullFacetList(const Qhull &q, facetT *b, facetT *e);
    +                        QhullFacetList(QhullQh *qqh, facetT *b, facetT *e);
    +                        QhullFacetList(QhullFacet b, QhullFacet e) : QhullLinkedList(b, e), select_all(false) {}
    +                        //Copy constructor copies pointer but not contents.  Needed for return by value and parameter passing.
    +                        QhullFacetList(const QhullFacetList &other) : QhullLinkedList(*other.begin(), *other.end()), select_all(other.select_all) {}
    +    QhullFacetList &    operator=(const QhullFacetList &other) { QhullLinkedList::operator =(other); select_all= other.select_all; return *this; }
    +                        ~QhullFacetList() {}
    +
    +private:                //!Disable default constructor.  See QhullLinkedList
    +                    QhullFacetList();
    +public:
    +
    +#//!\name Conversion
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +    std::vector vertices_toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +    QList  vertices_toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +                        //! Filtered by facet.isGood().  May be 0 when !isEmpty().
    +    countT              count() const;
    +    bool                contains(const QhullFacet &f) const;
    +    countT              count(const QhullFacet &f) const;
    +    bool                isSelectAll() const { return select_all; }
    +    QhullQh *           qh() const { return first().qh(); }
    +    void                selectAll() { select_all= true; }
    +    void                selectGood() { select_all= false; }
    +                        //!< operator==() does not depend on isGood()
    +
    +#//!\name IO
    +    struct PrintFacetList{
    +        const QhullFacetList *facet_list;
    +        const char *    print_message;   //!< non-null message
    +                        PrintFacetList(const QhullFacetList &fl, const char *message) : facet_list(&fl), print_message(message) {}
    +    };//PrintFacetList
    +    PrintFacetList      print(const char *message) const  { return PrintFacetList(*this, message); }
    +
    +    struct PrintFacets{
    +        const QhullFacetList *facet_list;
    +                        PrintFacets(const QhullFacetList &fl) : facet_list(&fl) {}
    +    };//PrintFacets
    +    PrintFacets         printFacets() const { return PrintFacets(*this); }
    +
    +    struct PrintVertices{
    +        const QhullFacetList *facet_list;
    +                        PrintVertices(const QhullFacetList &fl) : facet_list(&fl) {}
    +    };//PrintVertices
    +    PrintVertices       printVertices() const { return PrintVertices(*this); }
    +};//class QhullFacetList
    +
    +}//namespace orgQhull
    +
    +#//!\name == Global namespace =========================================
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList::PrintFacetList &p);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList::PrintFacets &p);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList::PrintVertices &p);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetList &fs);
    +
    +#endif // QHULLFACETLIST_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp b/xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp
    new file mode 100644
    index 0000000000..d30c21e26a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullFacetSet.cpp
    @@ -0,0 +1,147 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullFacetSet.cpp#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullFacetSet -- Qhull's linked facets, as a C++ class
    +
    +#include "libqhullcpp/QhullFacetSet.h"
    +
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullVertex.h"
    +
    +#ifndef QHULL_NO_STL
    +using std::vector;
    +#endif
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversions
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullFacetSet::
    +toStdVector() const
    +{
    +    QhullSetIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.push_back(f);
    +        }
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +bool QhullFacetSet::
    +contains(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullSet::contains(facet);
    +    }
    +    for(QhullFacetSet::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//contains
    +
    +int QhullFacetSet::
    +count() const
    +{
    +    if(isSelectAll()){
    +        return QhullSet::count();
    +    }
    +    int counter= 0;
    +    for(QhullFacetSet::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f.isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +int QhullFacetSet::
    +count(const QhullFacet &facet) const
    +{
    +    if(isSelectAll()){
    +        return QhullSet::count(facet);
    +    }
    +    int counter= 0;
    +    for(QhullFacetSet::const_iterator i=begin(); i != end(); ++i){
    +        QhullFacet f= *i;
    +        if(f==facet && f.isGood()){
    +            counter++;
    +        }
    +    }
    +    return counter;
    +}//count
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetSet;
    +
    +ostream &
    +operator<<(ostream &os, const QhullFacetSet &fs)
    +{
    +    os << fs.print("");
    +    return os;
    +}//<begin(); i!=p.facet_set->end(); ++i){
    +        const QhullFacet f= *i;
    +        if(f.getFacetT()==qh_MERGEridge){
    +            os << " MERGE";
    +        }else if(f.getFacetT()==qh_DUPLICATEridge){
    +            os << " DUP";
    +        }else if(p.facet_set->isSelectAll() || f.isGood()){
    +            os << " f" << f.id();
    +        }
    +    }
    +    os << endl;
    +    return os;
    +}//<
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +
    +#//!\name Defined here
    +    //! QhullFacetSet -- a set of Qhull facets, as a C++ class.  See QhullFacetList.h
    +    class QhullFacetSet;
    +    typedef QhullSetIterator QhullFacetSetIterator;
    +
    +class QhullFacetSet : public QhullSet {
    +
    +#//!\name Defined here
    +public:
    +    typedef facetT *   base_type;  // for QhullVertexSet
    +
    +private:
    +#//!\name Fields
    +    bool                select_all;   //! True if include bad facets.  Default is false.
    +
    +public:
    +#//!\name Constructor
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        QhullFacetSet(const Qhull &q, setT *s) : QhullSet(q, s), select_all(false) {}
    +                        QhullFacetSet(QhullQh *qqh, setT *s) : QhullSet(qqh, s), select_all(false) {}
    +                        //!Copy constructor copies pointers but not contents.  Needed for return by value and parameter passing.
    +                        QhullFacetSet(const QhullFacetSet &other) : QhullSet(other), select_all(other.select_all) {}
    +                        //!Assignment copies pointers but not contents.
    +    QhullFacetSet &     operator=(const QhullFacetSet &other) { QhullSet::operator=(other); select_all= other.select_all; return *this; }
    +
    +private:
    +                        //!Disable default constructor.  See QhullSetBase
    +                        QhullFacetSet();
    +public:
    +
    +#//!\name Conversion
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +                        //! Filtered by facet.isGood().  May be 0 when !isEmpty().
    +    countT              count() const;
    +    bool                contains(const QhullFacet &f) const;
    +    countT              count(const QhullFacet &f) const;
    +    bool                isSelectAll() const { return select_all; }
    +                        //! operator==() does not depend on isGood()
    +    void                selectAll() { select_all= true; }
    +    void                selectGood() { select_all= false; }
    +
    +#//!\name IO
    +    // Not same as QhullFacetList#IO.  A QhullFacetSet is a component of a QhullFacetList.
    +
    +    struct PrintFacetSet{
    +        const QhullFacetSet *facet_set;
    +        const char *    print_message;  //!< non-null message
    +                        PrintFacetSet(const char *message, const QhullFacetSet *s) : facet_set(s), print_message(message) {}
    +    };//PrintFacetSet
    +    const PrintFacetSet print(const char *message) const { return PrintFacetSet(message, this); }
    +
    +    struct PrintIdentifiers{
    +        const QhullFacetSet *facet_set;
    +        const char *    print_message;  //!< non-null message
    +                        PrintIdentifiers(const char *message, const QhullFacetSet *s) : facet_set(s), print_message(message) {}
    +    };//PrintIdentifiers
    +    PrintIdentifiers    printIdentifiers(const char *message) const { return PrintIdentifiers(message, this); }
    +
    +};//class QhullFacetSet
    +
    +}//namespace orgQhull
    +
    +#//!\name == Global namespace =========================================
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetSet &fs);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetSet::PrintFacetSet &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullFacetSet::PrintIdentifiers &p);
    +
    +#endif // QHULLFACETSET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp
    new file mode 100644
    index 0000000000..ed5cc4bae1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.cpp
    @@ -0,0 +1,187 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullHyperplane.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/QhullHyperplane.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include 
    +
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +QhullHyperplane::
    +QhullHyperplane(const Qhull &q) 
    +: hyperplane_coordinates(0)
    +, qh_qh(q.qh())
    +, hyperplane_offset(0.0)
    +, hyperplane_dimension(0)
    +{
    +}
    +
    +QhullHyperplane::
    +QhullHyperplane(const Qhull &q, int hyperplaneDimension, coordT *c, coordT hyperplaneOffset) 
    +: hyperplane_coordinates(c)
    +, qh_qh(q.qh())
    +, hyperplane_offset(hyperplaneOffset)
    +, hyperplane_dimension(hyperplaneDimension)
    +{
    +}
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversions
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullHyperplane::
    +toStdVector() const
    +{
    +    QhullHyperplaneIterator i(*this);
    +    std::vector fs;
    +    while(i.hasNext()){
    +        fs.push_back(i.next());
    +    }
    +    fs.push_back(hyperplane_offset);
    +    return fs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +//! Return true if equal
    +//! If qh_qh defined, tests qh.distanceEpsilon and qh.angleEpsilon
    +//! otherwise, tests equal coordinates and offset
    +bool QhullHyperplane::
    +operator==(const QhullHyperplane &other) const
    +{
    +    if(hyperplane_dimension!=other.hyperplane_dimension || !hyperplane_coordinates || !other.hyperplane_coordinates){
    +        return false;
    +    }
    +    double d= fabs(hyperplane_offset-other.hyperplane_offset);
    +    if(d > (qh_qh ? qh_qh->distanceEpsilon() : 0.0)){
    +        return false;
    +    }
    +    double angle= hyperplaneAngle(other);
    +
    +    double a= fabs(angle-1.0);
    +    if(a > (qh_qh ? qh_qh->angleEpsilon() : 0.0)){
    +        return false;
    +    }
    +    return true;
    +}//operator==
    +
    +#//!\name Methods
    +
    +//! Return distance from point to hyperplane.
    +//!   If greater than zero, the point is above the facet (i.e., outside).
    +// qh_distplane [geom_r.c], QhullFacet::distance, and QhullHyperplane::distance are copies
    +//    Does not support RANDOMdist or logging
    +double QhullHyperplane::
    +distance(const QhullPoint &p) const
    +{
    +    const coordT *point= p.coordinates();
    +    int dim= p.dimension();
    +    QHULL_ASSERT(dim==dimension());
    +    const coordT *normal= coordinates();
    +    double dist;
    +
    +    switch (dim){
    +  case 2:
    +      dist= offset() + point[0] * normal[0] + point[1] * normal[1];
    +      break;
    +  case 3:
    +      dist= offset() + point[0] * normal[0] + point[1] * normal[1] + point[2] * normal[2];
    +      break;
    +  case 4:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3];
    +      break;
    +  case 5:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4];
    +      break;
    +  case 6:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5];
    +      break;
    +  case 7:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6];
    +      break;
    +  case 8:
    +      dist= offset()+point[0]*normal[0]+point[1]*normal[1]+point[2]*normal[2]+point[3]*normal[3]+point[4]*normal[4]+point[5]*normal[5]+point[6]*normal[6]+point[7]*normal[7];
    +      break;
    +  default:
    +      dist= offset();
    +      for (int k=dim; k--; )
    +          dist += *point++ * *normal++;
    +      break;
    +    }
    +    return dist;
    +}//distance
    +
    +double QhullHyperplane::
    +hyperplaneAngle(const QhullHyperplane &other) const
    +{
    +    volatile realT result= 0.0;
    +    QH_TRY_(qh_qh){ // no object creation -- destructors skipped on longjmp()
    +        result= qh_getangle(qh_qh, hyperplane_coordinates, other.hyperplane_coordinates);
    +    }
    +    qh_qh->NOerrexit= true;
    +    qh_qh->maybeThrowQhullMessage(QH_TRY_status);
    +    return result;
    +}//hyperplaneAngle
    +
    +double QhullHyperplane::
    +norm() const {
    +    double d= 0.0;
    +    const coordT *c= coordinates();
    +    for (int k=dimension(); k--; ){
    +        d += *c * *c;
    +        ++c;
    +    }
    +    return sqrt(d);
    +}//norm
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::ostream;
    +using orgQhull::QhullHyperplane;
    +
    +#//!\name GetSet<<
    +
    +ostream &
    +operator<<(ostream &os, const QhullHyperplane &p)
    +{
    +    os << p.print("");
    +    return os;
    +}
    +
    +ostream &
    +operator<<(ostream &os, const QhullHyperplane::PrintHyperplane &pr)
    +{
    +    os << pr.print_message;
    +    QhullHyperplane p= *pr.hyperplane;
    +    const realT *c= p.coordinates();
    +    for(int k=p.dimension(); k--; ){
    +        realT r= *c++;
    +        if(pr.print_message){
    +            os << " " << r; // FIXUP QH11010 %8.4g
    +        }else{
    +            os << " " << r; // FIXUP QH11010 qh_REAL_1
    +        }
    +    }
    +    os << pr.hyperplane_offset_message << " " << p.offset();
    +    os << std::endl;
    +    return os;
    +}//PrintHyperplane
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullHyperplane.h b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.h
    new file mode 100644
    index 0000000000..2868ce5c99
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullHyperplane.h
    @@ -0,0 +1,123 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullHyperplane.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHHYPERPLANE_H
    +#define QHHYPERPLANE_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullIterator.h"
    +#include "libqhullcpp/QhullQh.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullPoint;
    +
    +#//!\name Defined here
    +    //! QhullHyperplane as an offset, dimension, and pointer to coordinates
    +    class QhullHyperplane;
    +    //! Java-style iterator for QhullHyperplane coordinates
    +    class QhullHyperplaneIterator;
    +
    +class QhullHyperplane { // Similar to QhullPoint
    +public:
    +#//!\name Subtypes
    +    typedef const coordT *                  iterator;
    +    typedef const coordT *                  const_iterator;
    +    typedef QhullHyperplane::iterator       Iterator;
    +    typedef QhullHyperplane::const_iterator ConstIterator;
    +
    +private:
    +#//!\name Fields
    +    coordT *            hyperplane_coordinates;  //!< Normal to hyperplane.   facetT.normal is normalized to 1.0
    +    QhullQh *           qh_qh;                  //!< qhT for distanceEpsilon() in operator==
    +    coordT              hyperplane_offset;      //!< Distance from hyperplane to origin
    +    int                 hyperplane_dimension;   //!< Dimension of hyperplane
    +
    +#//!\name Construct
    +public:
    +                        QhullHyperplane() : hyperplane_coordinates(0), qh_qh(0), hyperplane_offset(0.0), hyperplane_dimension(0) {}
    +    explicit            QhullHyperplane(const Qhull &q);
    +                        QhullHyperplane(const Qhull &q, int hyperplaneDimension, coordT *c, coordT hyperplaneOffset);
    +    explicit            QhullHyperplane(QhullQh *qqh) : hyperplane_coordinates(0), qh_qh(qqh), hyperplane_offset(0.0), hyperplane_dimension(0) {}
    +                        QhullHyperplane(QhullQh *qqh, int hyperplaneDimension, coordT *c, coordT hyperplaneOffset) : hyperplane_coordinates(c), qh_qh(qqh), hyperplane_offset(hyperplaneOffset), hyperplane_dimension(hyperplaneDimension) {}
    +                        // Creates an alias.  Does not copy the hyperplane's coordinates.  Needed for return by value and parameter passing.
    +                        QhullHyperplane(const QhullHyperplane &other)  : hyperplane_coordinates(other.hyperplane_coordinates), qh_qh(other.qh_qh), hyperplane_offset(other.hyperplane_offset), hyperplane_dimension(other.hyperplane_dimension) {}
    +                        // Creates an alias.  Does not copy the hyperplane's coordinates.  Needed for vector
    +    QhullHyperplane &   operator=(const QhullHyperplane &other) { hyperplane_coordinates= other.hyperplane_coordinates; qh_qh= other.qh_qh; hyperplane_offset= other.hyperplane_offset; hyperplane_dimension= other.hyperplane_dimension; return *this; }
    +                        ~QhullHyperplane() {}
    +
    +#//!\name Conversions --
    +//! Includes offset at end
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList       toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +public:
    +    const coordT *      coordinates() const { return hyperplane_coordinates; }
    +    coordT *            coordinates() { return hyperplane_coordinates; }
    +    void                defineAs(int hyperplaneDimension, coordT *c, coordT hyperplaneOffset) { QHULL_ASSERT(hyperplaneDimension>=0); hyperplane_coordinates= c; hyperplane_dimension= hyperplaneDimension; hyperplane_offset= hyperplaneOffset; }
    +    //! Creates an alias to other using the same qh_qh
    +    void                defineAs(QhullHyperplane &other) { hyperplane_coordinates= other.coordinates(); hyperplane_dimension= other.dimension();  hyperplane_offset= other.offset(); }
    +    int                 dimension() const { return hyperplane_dimension; }
    +    bool                isValid() const { return hyperplane_coordinates!=0 && hyperplane_dimension>0; }
    +    coordT              offset() const { return hyperplane_offset; }
    +    bool                operator==(const QhullHyperplane &other) const;
    +    bool                operator!=(const QhullHyperplane &other) const { return !operator==(other); }
    +    const coordT &      operator[](int idx) const { QHULL_ASSERT(idx>=0 && idx=0 && idx
    +#include 
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! Only QHULL_DECLARE_SEQUENTIAL_ITERATOR is used in libqhullcpp.  The others need further development
    +    //! QHULL_DECLARE_SEQUENTIAL_ITERATOR(C) -- Declare a Java-style iterator
    +    //! QHULL_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR(C) -- Declare a mutable Java-style iterator
    +    //! QHULL_DECLARE_SET_ITERATOR(C) -- Declare a set iterator
    +    //! QHULL_DECLARE_MUTABLE_SET_ITERATOR(C) -- Declare a mutable set iterator
    +    //! Derived from Qt/core/tools/qiterator.h and qset_r.h/FOREACHsetelement_()
    +
    +// Stores C* as done in Mutable...  Assumes the container is not deleted.
    +// C::const_iterator is an STL-style iterator that returns T&
    +#define QHULL_DECLARE_SEQUENTIAL_ITERATOR(C, T) \
    +    \
    +    class C##Iterator \
    +    { \
    +        typedef C::const_iterator const_iterator; \
    +        const C *c; \
    +        const_iterator i; \
    +        public: \
    +        inline C##Iterator(const C &container) \
    +        : c(&container), i(c->constBegin()) {} \
    +        inline C##Iterator &operator=(const C &container) \
    +        { c = &container; i = c->constBegin(); return *this; } \
    +        inline void toFront() { i = c->constBegin(); } \
    +        inline void toBack() { i = c->constEnd(); } \
    +        inline bool hasNext() const { return i != c->constEnd(); } \
    +        inline const T &next() { return *i++; } \
    +        inline const T &peekNext() const { return *i; } \
    +        inline bool hasPrevious() const { return i != c->constBegin(); } \
    +        inline const T &previous() { return *--i; } \
    +        inline const T &peekPrevious() const { const_iterator p = i; return *--p; } \
    +        inline bool findNext(const T &t) \
    +        { while (i != c->constEnd()) if (*i++ == t) return true; return false; } \
    +        inline bool findPrevious(const T &t) \
    +        { while (i != c->constBegin()) if (*(--i) == t) return true; \
    +        return false;  } \
    +    };//C##Iterator
    +
    +// Remove setShareable() from Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR
    +// Uses QHULL_ASSERT (assert.h)
    +// Duplicated in MutablePointIterator without insert or remove
    +// Not used in libqhullcpp.  See Coordinates.h
    +#define QHULL_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR(C, T) \
    +    class Mutable##C##Iterator \
    +    { \
    +        typedef C::iterator iterator; \
    +        typedef C::const_iterator const_iterator; \
    +        C *c; \
    +        iterator i, n; \
    +        inline bool item_exists() const { return const_iterator(n) != c->constEnd(); } \
    +        public: \
    +        inline Mutable##C##Iterator(C &container) \
    +        : c(&container) \
    +        { i = c->begin(); n = c->end(); } \
    +        inline ~Mutable##C##Iterator() \
    +        {} \
    +        inline Mutable##C##Iterator &operator=(C &container) \
    +        { c = &container; \
    +        i = c->begin(); n = c->end(); return *this; } \
    +        inline void toFront() { i = c->begin(); n = c->end(); } \
    +        inline void toBack() { i = c->end(); n = i; } \
    +        inline bool hasNext() const { return c->constEnd() != const_iterator(i); } \
    +        inline T &next() { n = i++; return *n; } \
    +        inline T &peekNext() const { return *i; } \
    +        inline bool hasPrevious() const { return c->constBegin() != const_iterator(i); } \
    +        inline T &previous() { n = --i; return *n; } \
    +        inline T &peekPrevious() const { iterator p = i; return *--p; } \
    +        inline void remove() \
    +        { if (c->constEnd() != const_iterator(n)) { i = c->erase(n); n = c->end(); } } \
    +        inline void setValue(const T &t) const { if (c->constEnd() != const_iterator(n)) *n = t; } \
    +        inline T &value() { QHULL_ASSERT(item_exists()); return *n; } \
    +        inline const T &value() const { QHULL_ASSERT(item_exists()); return *n; } \
    +        inline void insert(const T &t) { n = i = c->insert(i, t); ++i; } \
    +        inline bool findNext(const T &t) \
    +        { while (c->constEnd() != const_iterator(n = i)) if (*i++ == t) return true; return false; } \
    +        inline bool findPrevious(const T &t) \
    +        { while (c->constBegin() != const_iterator(i)) if (*(n = --i) == t) return true; \
    +        n = c->end(); return false;  } \
    +    };//Mutable##C##Iterator
    +
    +// Not used in libqhullcpp.
    +#define QHULL_DECLARE_SET_ITERATOR(C) \
    +\
    +    template  \
    +    class Qhull##C##Iterator \
    +    { \
    +        typedef typename Qhull##C::const_iterator const_iterator; \
    +        Qhull##C c; \
    +        const_iterator i; \
    +    public: \
    +        inline Qhull##C##Iterator(const Qhull##C &container) \
    +        : c(container), i(c.constBegin()) {} \
    +        inline Qhull##C##Iterator &operator=(const Qhull##C &container) \
    +        { c = container; i = c.constBegin(); return *this; } \
    +        inline void toFront() { i = c.constBegin(); } \
    +        inline void toBack() { i = c.constEnd(); } \
    +        inline bool hasNext() const { return i != c.constEnd(); } \
    +        inline const T &next() { return *i++; } \
    +        inline const T &peekNext() const { return *i; } \
    +        inline bool hasPrevious() const { return i != c.constBegin(); } \
    +        inline const T &previous() { return *--i; } \
    +        inline const T &peekPrevious() const { const_iterator p = i; return *--p; } \
    +        inline bool findNext(const T &t) \
    +        { while (i != c.constEnd()) if (*i++ == t) return true; return false; } \
    +        inline bool findPrevious(const T &t) \
    +        { while (i != c.constBegin()) if (*(--i) == t) return true; \
    +        return false;  } \
    +    };//Qhull##C##Iterator
    +
    +// Not used in libqhullcpp.
    +#define QHULL_DECLARE_MUTABLE_SET_ITERATOR(C) \
    +\
    +template  \
    +class QhullMutable##C##Iterator \
    +{ \
    +    typedef typename Qhull##C::iterator iterator; \
    +    typedef typename Qhull##C::const_iterator const_iterator; \
    +    Qhull##C *c; \
    +    iterator i, n; \
    +    inline bool item_exists() const { return const_iterator(n) != c->constEnd(); } \
    +public: \
    +    inline Mutable##C##Iterator(Qhull##C &container) \
    +        : c(&container) \
    +    { c->setSharable(false); i = c->begin(); n = c->end(); } \
    +    inline ~Mutable##C##Iterator() \
    +    { c->setSharable(true); } \
    +    inline Mutable##C##Iterator &operator=(Qhull##C &container) \
    +    { c->setSharable(true); c = &container; c->setSharable(false); \
    +      i = c->begin(); n = c->end(); return *this; } \
    +    inline void toFront() { i = c->begin(); n = c->end(); } \
    +    inline void toBack() { i = c->end(); n = i; } \
    +    inline bool hasNext() const { return c->constEnd() != const_iterator(i); } \
    +    inline T &next() { n = i++; return *n; } \
    +    inline T &peekNext() const { return *i; } \
    +    inline bool hasPrevious() const { return c->constBegin() != const_iterator(i); } \
    +    inline T &previous() { n = --i; return *n; } \
    +    inline T &peekPrevious() const { iterator p = i; return *--p; } \
    +    inline void remove() \
    +    { if (c->constEnd() != const_iterator(n)) { i = c->erase(n); n = c->end(); } } \
    +    inline void setValue(const T &t) const { if (c->constEnd() != const_iterator(n)) *n = t; } \
    +    inline T &value() { Q_ASSERT(item_exists()); return *n; } \
    +    inline const T &value() const { Q_ASSERT(item_exists()); return *n; } \
    +    inline void insert(const T &t) { n = i = c->insert(i, t); ++i; } \
    +    inline bool findNext(const T &t) \
    +    { while (c->constEnd() != const_iterator(n = i)) if (*i++ == t) return true; return false; } \
    +    inline bool findPrevious(const T &t) \
    +    { while (c->constBegin() != const_iterator(i)) if (*(n = --i) == t) return true; \
    +      n = c->end(); return false;  } \
    +};//QhullMutable##C##Iterator
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLITERATOR_H
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullLinkedList.h b/xs/src/qhull/src/libqhullcpp/QhullLinkedList.h
    new file mode 100644
    index 0000000000..d4caf52c18
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullLinkedList.h
    @@ -0,0 +1,388 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullLinkedList.h#7 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLLINKEDLIST_H
    +#define QHULLLINKEDLIST_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +#include   // ptrdiff_t, size_t
    +
    +#ifdef QHULL_USES_QT
    +#include 
    +#endif
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#include 
    +#include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullLinkedList -- A linked list modeled on QLinkedList.
    +    //!   T is an opaque type with T(B *b), b=t.getBaseT(), t=t.next(), and t=t.prev().  The end node is a sentinel.
    +    //!   QhullQh/qhT owns the contents.
    +    //!   QhullLinkedList does not define erase(), clear(), removeFirst(), removeLast(), pop_back(), pop_front(), fromStdList()
    +    //!   Derived from Qt/core/tools/qlinkedlist.h and libqhull_r.h/FORALLfacets_()
    +    //! QhullLinkedList::const_iterator -- STL-style iterator
    +    //! QhullLinkedList::iterator -- STL-style iterator
    +    //! QhullLinkedListIterator -- Java-style iterator
    +    //!   Derived from Qt/core/tools/qiterator.h
    +    //!   Works with Qt's foreach keyword [Qt/src/corelib/global/qglobal.h]
    +
    +template 
    +class QhullLinkedList
    +{
    +#//!\name Defined here
    +public:
    +    class const_iterator;
    +    class iterator;
    +    typedef const_iterator  ConstIterator;
    +    typedef iterator    Iterator;
    +    typedef ptrdiff_t   difference_type;
    +    typedef countT      size_type;
    +    typedef T           value_type;
    +    typedef const value_type *const_pointer;
    +    typedef const value_type &const_reference;
    +    typedef value_type *pointer;
    +    typedef value_type &reference;
    +
    +#//!\name Fields
    +private:
    +    T                   begin_node;
    +    T                   end_node;     //! Sentinel node at end of list
    +
    +#//!\name Constructors
    +public:
    +                        QhullLinkedList(T b, T e) : begin_node(b), end_node(e) {}
    +                        //! Copy constructor copies begin_node and end_node, but not the list elements.  Needed for return by value and parameter passing.
    +                        QhullLinkedList(const QhullLinkedList &other) : begin_node(other.begin_node), end_node(other.end_node) {}
    +                        //! Copy assignment copies begin_node and end_node, but not the list elements.
    +                        QhullLinkedList & operator=(const QhullLinkedList &other) { begin_node= other.begin_node; end_node= other.end_node; return *this; }
    +                        ~QhullLinkedList() {}
    +
    +private:
    +                        //!disabled since a sentinel must be allocated as the private type
    +                        QhullLinkedList() {}
    +
    +public:
    +
    +#//!\name Conversions
    +#ifndef QHULL_NO_STL
    +    std::vector      toStdVector() const;
    +#endif
    +#ifdef QHULL_USES_QT
    +    QList            toQList() const;
    +#endif
    +
    +#//!\name GetSet
    +    countT              count() const;
    +                        //count(t) under #//!\name Search
    +    bool                isEmpty() const { return (begin_node==end_node); }
    +    bool                operator==(const QhullLinkedList &o) const;
    +    bool                operator!=(const QhullLinkedList &o) const { return !operator==(o); }
    +    size_t              size() const { return count(); }
    +
    +#//!\name Element access
    +    //! For back() and last(), return T instead of T& (T is computed)
    +    const T             back() const { return last(); }
    +    T                   back() { return last(); }
    +    const T &           first() const { QHULL_ASSERT(!isEmpty()); return begin_node; }
    +    T &                 first() { QHULL_ASSERT(!isEmpty()); return begin_node; }
    +    const T &           front() const { return first(); }
    +    T &                 front() { return first(); }
    +    const T             last() const { QHULL_ASSERT(!isEmpty()); return *--end(); }
    +    T                   last() { QHULL_ASSERT(!isEmpty()); return *--end(); }
    +
    +#//!\name Modify -- Allocation of opaque types not implemented.
    +
    +#//!\name Search
    +    bool                contains(const T &t) const;
    +    countT              count(const T &t) const;
    +
    +#//!\name Iterator
    +    iterator            begin() { return begin_node; }
    +    const_iterator      begin() const { return begin_node; }
    +    const_iterator      constBegin() const { return begin_node; }
    +    const_iterator      constEnd() const { return end_node; }
    +    iterator            end() { return end_node; }
    +    const_iterator      end() const { return end_node; }
    +
    +    class iterator {
    +
    +    private:
    +        T               i;
    +        friend class const_iterator;
    +
    +    public:
    +        typedef std::bidirectional_iterator_tag  iterator_category;
    +        typedef T           value_type;
    +        typedef value_type *pointer;
    +        typedef value_type &reference;
    +        typedef ptrdiff_t   difference_type;
    +
    +                        iterator() : i() {}
    +                        iterator(const T &t) : i(t) {}  //!< Automatic conversion to iterator
    +                        iterator(const iterator &o) : i(o.i) {}
    +        iterator &      operator=(const iterator &o) { i= o.i; return *this; }
    +
    +        const T &       operator*() const { return i; }
    +        T &             operator*() { return i; }
    +        // Do not define operator[]
    +        const T *       operator->() const { return &i; }
    +        T *             operator->() { return &i; }
    +        bool            operator==(const iterator &o) const { return i == o.i; }
    +        bool            operator!=(const iterator &o) const { return !operator==(o); }
    +        bool            operator==(const const_iterator &o) const { return i==reinterpret_cast(o).i; }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +        iterator &      operator++() { i= i.next(); return *this; }
    +        iterator        operator++(int) { iterator o= i; i= i.next(); return o; }
    +        iterator &      operator--() { i= i.previous(); return *this; }
    +        iterator        operator--(int) { iterator o= i; i= i.previous(); return o; }
    +        iterator        operator+(int j) const;
    +        iterator        operator-(int j) const { return operator+(-j); }
    +        iterator &      operator+=(int j) { return (*this= *this + j); }
    +        iterator &      operator-=(int j) { return (*this= *this - j); }
    +    };//QhullLinkedList::iterator
    +
    +    class const_iterator {
    +
    +    private:
    +        T               i;
    +
    +    public:
    +        typedef std::bidirectional_iterator_tag  iterator_category;
    +        typedef T                 value_type;
    +        typedef const value_type *pointer;
    +        typedef const value_type &reference;
    +        typedef ptrdiff_t         difference_type;
    +
    +                        const_iterator() : i() {}
    +                        const_iterator(const T &t) : i(t) {}  //!< Automatic conversion to const_iterator
    +                        const_iterator(const iterator &o) : i(o.i) {}
    +                        const_iterator(const const_iterator &o) : i(o.i) {}
    +        const_iterator &operator=(const const_iterator &o) { i= o.i; return *this; }
    +
    +        const T &       operator*() const { return i; }
    +        const T *       operator->() const { return i; }
    +        bool            operator==(const const_iterator &o) const { return i == o.i; }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +                        // No comparisons or iterator diff
    +        const_iterator &operator++() { i= i.next(); return *this; }
    +        const_iterator  operator++(int) { const_iterator o= i; i= i.next(); return o; }
    +        const_iterator &operator--() { i= i.previous(); return *this; }
    +        const_iterator  operator--(int) { const_iterator o= i; i= i.previous(); return o; }
    +        const_iterator  operator+(int j) const;
    +        const_iterator  operator-(int j) const { return operator+(-j); }
    +        const_iterator &operator+=(int j) { return (*this= *this + j); }
    +        const_iterator &operator-=(int j) { return (*this= *this - j); }
    +    };//QhullLinkedList::const_iterator
    +
    +};//QhullLinkedList
    +
    +template 
    +class QhullLinkedListIterator // FIXUP QH11016 define QhullMutableLinkedListIterator
    +{
    +    typedef typename QhullLinkedList::const_iterator const_iterator;
    +    const QhullLinkedList *c;
    +    const_iterator      i;
    +
    +public:
    +                        QhullLinkedListIterator(const QhullLinkedList &container) : c(&container), i(c->constBegin()) {}
    +    QhullLinkedListIterator & operator=(const QhullLinkedList &container) { c= &container; i= c->constBegin(); return *this; }
    +    bool                findNext(const T &t);
    +    bool                findPrevious(const T &t);
    +    bool                hasNext() const { return i != c->constEnd(); }
    +    bool                hasPrevious() const { return i != c->constBegin(); }
    +    T                   next() { return *i++; }
    +    T                   peekNext() const { return *i; }
    +    T                   peekPrevious() const { const_iterator p= i; return *--p; }
    +    T                   previous() { return *--i; }
    +    void                toFront() { i= c->constBegin(); }
    +    void                toBack() { i= c->constEnd(); }
    +};//QhullLinkedListIterator
    +
    +#//!\name == Definitions =========================================
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +template 
    +std::vector QhullLinkedList::
    +toStdVector() const
    +{
    +    std::vector tmp;
    +    std::copy(constBegin(), constEnd(), std::back_inserter(tmp));
    +    return tmp;
    +}//toStdVector
    +#endif
    +
    +#ifdef QHULL_USES_QT
    +template 
    +QList  QhullLinkedList::
    +toQList() const
    +{
    +    QhullLinkedListIterator i(*this);
    +    QList ls;
    +    while(i.hasNext()){
    +        ls.append(i.next());
    +    }
    +    return ls;
    +}//toQList
    +#endif
    +
    +#//!\name GetSet
    +
    +template 
    +countT QhullLinkedList::
    +count() const
    +{
    +    const_iterator i= begin_node;
    +    countT c= 0;
    +    while(i != end_node){
    +        c++;
    +        i++;
    +    }
    +    return c;
    +}//count
    +
    +#//!\name Search
    +
    +template 
    +bool QhullLinkedList::
    +contains(const T &t) const
    +{
    +    const_iterator i= begin_node;
    +    while(i != end_node){
    +        if(i==t){
    +            return true;
    +        }
    +        i++;
    +    }
    +    return false;
    +}//contains
    +
    +template 
    +countT QhullLinkedList::
    +count(const T &t) const
    +{
    +    const_iterator i= begin_node;
    +    countT c= 0;
    +    while(i != end_node){
    +        if(i==t){
    +            c++;
    +        }
    +        i++;
    +    }
    +    return c;
    +}//count
    +
    +template 
    +bool QhullLinkedList::
    +operator==(const QhullLinkedList &l) const
    +{
    +    if(begin_node==l.begin_node){
    +        return (end_node==l.end_node);
    +    }
    +    T i= begin_node;
    +    T il= l.begin_node;
    +    while(i != end_node){
    +        if(i != il){
    +            return false;
    +        }
    +        i= static_cast(i.next());
    +        il= static_cast(il.next());
    +    }
    +    if(il != l.end_node){
    +        return false;
    +    }
    +    return true;
    +}//operator==
    +
    +#//!\name Iterator
    +
    +template 
    +typename QhullLinkedList::iterator  QhullLinkedList::iterator::
    +operator+(int j) const
    +{
    +    T n= i;
    +    if(j>0){
    +        while(j--){
    +            n= n.next();
    +        }
    +    }else{
    +        while(j++){
    +            n= n.previous();
    +        }
    +    }
    +    return iterator(n);
    +}//operator+
    +
    +template 
    +typename QhullLinkedList::const_iterator  QhullLinkedList::const_iterator::
    +operator+(int j) const
    +{
    +    T n= i;
    +    if(j>0){
    +        while(j--){
    +            n= n.next();
    +        }
    +    }else{
    +        while(j++){
    +            n= n.previous();
    +        }
    +    }
    +    return const_iterator(n);
    +}//operator+
    +
    +#//!\name QhullLinkedListIterator
    +
    +template 
    +bool QhullLinkedListIterator::
    +findNext(const T &t)
    +{
    +    while(i != c->constEnd()){
    +        if (*i++ == t){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +template 
    +bool QhullLinkedListIterator::
    +findPrevious(const T &t)
    +{
    +    while(i!=c->constBegin()){
    +        if(*(--i)==t){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +template 
    +std::ostream &
    +operator<<(std::ostream &os, const orgQhull::QhullLinkedList &qs)
    +{
    +    typename orgQhull::QhullLinkedList::const_iterator i;
    +    for(i= qs.begin(); i != qs.end(); ++i){
    +        os << *i;
    +    }
    +    return os;
    +}//operator<<
    +
    +#endif // QHULLLINKEDLIST_H
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPoint.cpp b/xs/src/qhull/src/libqhullcpp/QhullPoint.cpp
    new file mode 100644
    index 0000000000..f5e9124609
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPoint.cpp
    @@ -0,0 +1,203 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPoint.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +
    +QhullPoint::
    +QhullPoint(const Qhull &q) 
    +: point_coordinates(0)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +}//QhullPoint
    +
    +QhullPoint::
    +QhullPoint(const Qhull &q, coordT *c) 
    +: point_coordinates(c)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +    QHULL_ASSERT(q.hullDimension()>0);
    +}//QhullPoint dim, coordT
    +
    +QhullPoint::
    +QhullPoint(const Qhull &q, int pointDimension, coordT *c) 
    +: point_coordinates(c)
    +, qh_qh(q.qh())
    +, point_dimension(pointDimension)
    +{
    +}//QhullPoint dim, coordT
    +
    +//! QhullPoint of Coordinates with point_dimension==c.count()
    +QhullPoint::
    +QhullPoint(const Qhull &q, Coordinates &c) 
    +: point_coordinates(c.data())
    +, qh_qh(q.qh())
    +, point_dimension(c.count())
    +{
    +}//QhullPoint Coordinates
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversion
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullPoint::
    +toStdVector() const
    +{
    +    QhullPointIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        vs.push_back(i.next());
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +//! QhullPoint is equal if it has the same address and dimension
    +//! If !qh_qh, returns true if dimension and coordinates are equal
    +//! If qh_qh, returns true if the distance between points is less than qh_qh->distanceEpsilon()
    +//!\todo Compares distance with distance-to-hyperplane (distanceEpsilon).   Is that correct?
    +bool QhullPoint::
    +operator==(const QhullPoint &other) const
    +{
    +    if(point_dimension!=other.point_dimension){
    +        return false;
    +    }
    +    const coordT *c= point_coordinates;
    +    const coordT *c2= other.point_coordinates;
    +    if(c==c2){
    +        return true;
    +    }
    +    if(!c || !c2){
    +        return false;
    +    }
    +    if(!qh_qh || qh_qh->hull_dim==0){
    +        for(int k= point_dimension; k--; ){
    +            if(*c++ != *c2++){
    +                return false;
    +            }
    +        }
    +        return true;
    +    }
    +    double dist2= 0.0;
    +    for(int k= point_dimension; k--; ){
    +        double diff= *c++ - *c2++;
    +        dist2 += diff*diff;
    +    }
    +    dist2= sqrt(dist2);
    +    return (dist2 < qh_qh->distanceEpsilon());
    +}//operator==
    +
    +#//!\name Methods
    +
    +//! Return distance between two points.
    +double QhullPoint::
    +distance(const QhullPoint &p) const
    +{
    +    const coordT *c= point_coordinates;
    +    const coordT *c2= p.point_coordinates;
    +    int dim= point_dimension;
    +    if(dim!=p.point_dimension){
    +        throw QhullError(10075, "QhullPoint error: Expecting dimension %d for distance().  Got %d", dim, p.point_dimension);
    +    }
    +    if(!c || !c2){
    +        throw QhullError(10076, "QhullPoint error: Cannot compute distance() for undefined point");
    +    }
    +    double dist;
    +
    +    switch(dim){
    +  case 2:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]);
    +      break;
    +  case 3:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]);
    +      break;
    +  case 4:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]);
    +      break;
    +  case 5:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]);
    +      break;
    +  case 6:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]) + (c[5]-c2[5])*(c[5]-c2[5]);
    +      break;
    +  case 7:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]) + (c[5]-c2[5])*(c[5]-c2[5]) + (c[6]-c2[6])*(c[6]-c2[6]);
    +      break;
    +  case 8:
    +      dist= (c[0]-c2[0])*(c[0]-c2[0]) + (c[1]-c2[1])*(c[1]-c2[1]) + (c[2]-c2[2])*(c[2]-c2[2]) + (c[3]-c2[3])*(c[3]-c2[3]) + (c[4]-c2[4])*(c[4]-c2[4]) + (c[5]-c2[5])*(c[5]-c2[5]) + (c[6]-c2[6])*(c[6]-c2[6]) + (c[7]-c2[7])*(c[7]-c2[7]);
    +      break;
    +  default:
    +      dist= 0.0;
    +      for(int k=dim; k--; ){
    +          dist += (*c - *c2) * (*c - *c2);
    +          ++c;
    +          ++c2;
    +      }
    +      break;
    +    }
    +    return sqrt(dist);
    +}//distance
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +
    +//! Same as qh_printpointid [io.c]
    +ostream &
    +operator<<(ostream &os, const QhullPoint::PrintPoint &pr)
    +{
    +    QhullPoint p= *pr.point; 
    +    countT i= p.id();
    +    if(pr.point_message){
    +        if(*pr.point_message){
    +            os << pr.point_message << " ";
    +        }
    +        if(pr.with_identifier && (i!=qh_IDunknown) && (i!=qh_IDnone)){
    +            os << "p" << i << ": ";
    +        }
    +    }
    +    const realT *c= p.coordinates();
    +    for(int k=p.dimension(); k--; ){
    +        realT r= *c++;
    +        if(pr.point_message){
    +            os << " " << r; // FIXUP QH11010 %8.4g
    +        }else{
    +            os << " " << r; // FIXUP QH11010 qh_REAL_1
    +        }
    +    }
    +    os << std::endl;
    +    return os;
    +}//printPoint
    +
    +ostream & 
    +operator<<(ostream &os, const QhullPoint &p)
    +{
    +    os << p.print(""); 
    +    return os;
    +}//operator<<
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPoint.h b/xs/src/qhull/src/libqhullcpp/QhullPoint.h
    new file mode 100644
    index 0000000000..17f94ab364
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPoint.h
    @@ -0,0 +1,136 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPoint.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHPOINT_H
    +#define QHPOINT_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullIterator.h"
    +#include "libqhullcpp/QhullQh.h"
    +#include "libqhullcpp/Coordinates.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    class QhullPoint;  //!<  QhullPoint as a pointer and dimension to shared memory
    +    class QhullPointIterator; //!< Java-style iterator for QhullPoint coordinates
    +
    +#//!\name Used here
    +    class Qhull;
    +
    +//! A QhullPoint is a dimension and an array of coordinates.
    +//! With Qhull/QhullQh, a QhullPoint has an identifier.  Point equality is relative to qh.distanceEpsilon
    +class QhullPoint {
    +
    +#//!\name Iterators
    +public:
    +    typedef coordT *                    base_type;  // for QhullPointSet
    +    typedef const coordT *              iterator;
    +    typedef const coordT *              const_iterator;
    +    typedef QhullPoint::iterator        Iterator;
    +    typedef QhullPoint::const_iterator  ConstIterator;
    +
    +#//!\name Fields
    +protected: // For QhullPoints::iterator, QhullPoints::const_iterator
    +    coordT *            point_coordinates;  //!< Pointer to first coordinate,   0 if undefined
    +    QhullQh *           qh_qh;              //!< qhT for this instance of Qhull.  0 if undefined.
    +                                            //!< operator==() returns true if points within sqrt(qh_qh->distanceEpsilon())
    +                                            //!< If !qh_qh, id() is -3, and operator==() requires equal coordinates
    +    int                 point_dimension;    //!< Default dimension is qh_qh->hull_dim
    +public:
    +
    +#//!\name Constructors
    +    //! QhullPoint, PointCoordinates, and QhullPoints have similar constructors
    +    //! If Qhull/QhullQh is not initialized, then QhullPoint.dimension() is zero unless explicitly set
    +    //! Cannot define QhullPoints(int pointDimension) since it is ambiguous with QhullPoints(QhullQh *qqh)
    +                        QhullPoint() : point_coordinates(0), qh_qh(0), point_dimension(0) {}
    +                        QhullPoint(int pointDimension, coordT *c) : point_coordinates(c), qh_qh(0), point_dimension(pointDimension) { QHULL_ASSERT(pointDimension>0); }
    +    explicit            QhullPoint(const Qhull &q);
    +                        QhullPoint(const Qhull &q, coordT *c);
    +                        QhullPoint(const Qhull &q, Coordinates &c);
    +                        QhullPoint(const Qhull &q, int pointDimension, coordT *c);
    +    explicit            QhullPoint(QhullQh *qqh) : point_coordinates(0), qh_qh(qqh), point_dimension(qqh->hull_dim) {}
    +                        QhullPoint(QhullQh *qqh, coordT *c) : point_coordinates(c), qh_qh(qqh), point_dimension(qqh->hull_dim) { QHULL_ASSERT(qqh->hull_dim>0); }
    +                        QhullPoint(QhullQh *qqh, Coordinates &c) : point_coordinates(c.data()), qh_qh(qqh), point_dimension(c.count()) {}
    +                        QhullPoint(QhullQh *qqh, int pointDimension, coordT *c) : point_coordinates(c), qh_qh(qqh), point_dimension(pointDimension) {}
    +                        //! Creates an alias.  Does not make a deep copy of the point.  Needed for return by value and parameter passing.
    +                        QhullPoint(const QhullPoint &other) : point_coordinates(other.point_coordinates), qh_qh(other.qh_qh), point_dimension(other.point_dimension) {}
    +                        //! Creates an alias.  Does not make a deep copy of the point.  Needed for vector
    +    QhullPoint &        operator=(const QhullPoint &other) { point_coordinates= other.point_coordinates; qh_qh= other.qh_qh; point_dimension= other.point_dimension; return *this; }
    +                        ~QhullPoint() {}
    +
    +
    +#//!\name Conversions
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList       toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +public:
    +    const coordT *      coordinates() const { return point_coordinates; }  //!< 0 if undefined
    +    coordT *            coordinates() { return point_coordinates; }        //!< 0 if undefined
    +    void                defineAs(coordT *c) { QHULL_ASSERT(point_dimension>0); point_coordinates= c; }
    +    void                defineAs(int pointDimension, coordT *c) { QHULL_ASSERT(pointDimension>=0); point_coordinates= c; point_dimension= pointDimension; }
    +    void                defineAs(QhullPoint &other) { point_coordinates= other.point_coordinates; qh_qh= other.qh_qh; point_dimension= other.point_dimension; }
    +    int                 dimension() const { return point_dimension; }
    +    coordT *            getBaseT() const { return point_coordinates; } // for QhullPointSet
    +    countT              id() const { return qh_pointid(qh_qh, point_coordinates); } // NOerrors
    +    bool                isValid() const { return (point_coordinates!=0 && point_dimension>0); };
    +    bool                operator==(const QhullPoint &other) const;
    +    bool                operator!=(const QhullPoint &other) const { return ! operator==(other); }
    +    const coordT &      operator[](int idx) const { QHULL_ASSERT(point_coordinates!=0 && idx>=0 && idx=0 && idx
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +// Implemented via QhullSet.h
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullPointSet;
    +using orgQhull::QhullPointSetIterator;
    +
    +ostream &
    +operator<<(ostream &os, const QhullPointSet::PrintIdentifiers &pr)
    +{
    +    os << pr.print_message;
    +    const QhullPointSet s= *pr.point_set;
    +    QhullPointSetIterator i(s);
    +    while(i.hasNext()){
    +        if(i.hasPrevious()){
    +            os << " ";
    +        }
    +        const QhullPoint point= i.next();
    +        countT id= point.id();
    +        os << "p" << id;
    +
    +    }
    +    os << endl;
    +    return os;
    +}//PrintIdentifiers
    +
    +ostream &
    +operator<<(ostream &os, const QhullPointSet::PrintPointSet &pr)
    +{
    +    os << pr.print_message;
    +    const QhullPointSet s= *pr.point_set;
    +    for(QhullPointSet::const_iterator i=s.begin(); i != s.end(); ++i){
    +        const QhullPoint point= *i;
    +        os << point;
    +    }
    +    return os;
    +}//printPointSet
    +
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPointSet.h b/xs/src/qhull/src/libqhullcpp/QhullPointSet.h
    new file mode 100644
    index 0000000000..8562e170ea
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPointSet.h
    @@ -0,0 +1,77 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPointSet.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLPOINTSET_H
    +#define QHULLPOINTSET_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullPoint;
    +
    +#//!\name Defined here
    +    //! QhullPointSet -- a set of coordinate pointers with input dimension
    +    // with const_iterator and iterator
    +    class QhullPointSet;
    +
    +class QhullPointSet : public QhullSet {
    +
    +private:
    +#//!\name Fields
    +    // no fields
    +public:
    +
    +#//!\name Construct
    +                        QhullPointSet(const Qhull &q, setT *s) : QhullSet(q, s) {}
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        QhullPointSet(QhullQh *qqh, setT *s) : QhullSet(qqh, s) {}
    +                        //Copy constructor copies pointer but not contents.  Needed for return by value and parameter passing.
    +                        QhullPointSet(const QhullPointSet &other) : QhullSet(other) {}
    +                        //!Assignment copies pointers but not contents.
    +    QhullPointSet &     operator=(const QhullPointSet &other) { QhullSet::operator=(other); return *this; }
    +                        ~QhullPointSet() {}
    +
    +                        //!Default constructor disabled.
    +private:
    +                        QhullPointSet();
    +public:
    +
    +#//!\name IO
    +    struct PrintIdentifiers{
    +        const QhullPointSet *point_set;
    +        const char *    print_message; //!< non-null message
    +        PrintIdentifiers(const char *message, const QhullPointSet *s) : point_set(s), print_message(message) {}
    +    };//PrintIdentifiers
    +    PrintIdentifiers printIdentifiers(const char *message) const { return PrintIdentifiers(message, this); }
    +
    +    struct PrintPointSet{
    +        const QhullPointSet *point_set;
    +        const char *    print_message;  //!< non-null message
    +        PrintPointSet(const char *message, const QhullPointSet &s) : point_set(&s), print_message(message) {}
    +    };//PrintPointSet
    +    PrintPointSet       print(const char *message) const { return PrintPointSet(message, *this); }
    +
    +};//QhullPointSet
    +
    +typedef QhullSetIterator  QhullPointSetIterator;
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullPointSet::PrintIdentifiers &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullPointSet::PrintPointSet &pr);
    +
    +#endif // QHULLPOINTSET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullPoints.cpp b/xs/src/qhull/src/libqhullcpp/QhullPoints.cpp
    new file mode 100644
    index 0000000000..2320b5007a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullPoints.cpp
    @@ -0,0 +1,320 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullPoints.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/QhullPoints.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructors
    +
    +QhullPoints::
    +QhullPoints(const Qhull &q)
    +: point_first(0)
    +, point_end(0)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +}//QhullPoints Qhull
    +
    +QhullPoints::
    +QhullPoints(const Qhull &q, countT coordinateCount2, coordT *c)
    +: point_first(c)
    +, point_end(c+coordinateCount2)
    +, qh_qh(q.qh())
    +, point_dimension(q.hullDimension())
    +{
    +    QHULL_ASSERT(q.hullDimension());
    +    QHULL_ASSERT(coordinateCount2>=0);
    +}//QhullPoints Qhull dim
    +
    +QhullPoints::
    +QhullPoints(const Qhull &q, int pointDimension, countT coordinateCount2, coordT *c)
    +: point_first(c)
    +, point_end(c+coordinateCount2)
    +, qh_qh(q.qh())
    +, point_dimension(pointDimension)
    +{
    +    QHULL_ASSERT(pointDimension>=0);
    +    QHULL_ASSERT(coordinateCount2>=0);
    +}//QhullPoints Qhull dim coordT
    +
    +QhullPoints::
    +QhullPoints(QhullQh *qqh, int pointDimension, countT coordinateCount2, coordT *c)
    +: point_first(c)
    +, point_end(c+coordinateCount2)
    +, qh_qh(qqh)
    +, point_dimension(pointDimension)
    +{
    +    QHULL_ASSERT(pointDimension>=0);
    +    QHULL_ASSERT(coordinateCount2>=0);
    +}//QhullPoints QhullQh dim coordT
    +
    +#//!\name Conversions
    +// See qt-qhull.cpp for QList conversion
    +
    +#ifndef QHULL_NO_STL
    +std::vector QhullPoints::
    +toStdVector() const
    +{
    +    QhullPointsIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        vs.push_back(i.next());
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#//!\name GetSet
    +
    +countT QhullPoints::
    +extraCoordinatesCount() const
    +{
    +    if(point_dimension>0){
    +        return (countT)((point_end-point_first)%(size_t)point_dimension);
    +    }
    +    return 0;
    +}//extraCoordinatesCount
    +
    +//! QhullPoints is equal if the same address, or if the coordinates are identical
    +//! Use QhullPoint.operator==() for DISTround equality
    +bool QhullPoints::
    +operator==(const QhullPoints &other) const
    +{
    +    if((point_end-point_first) != (other.point_end-other.point_first)){
    +        return false;
    +    }
    +    if(point_dimension!=other.point_dimension){
    +        return false;
    +    }
    +    if(point_first==other.point_first){
    +        return true;
    +    }
    +    if(!qh_qh || qh_qh->hull_dim==0){
    +        const coordT *c= point_first;
    +        const coordT *c2= other.point_first;
    +        while(chull_dim : 0);
    +    point_first= 0;
    +    point_end= 0;
    +}//resetQhullQh
    +
    +QhullPoint QhullPoints::
    +value(countT idx) const
    +{
    +    QhullPoint p(qh_qh);
    +    if(idx>=0 && idx=0 && idx=n){
    +        n= 0;
    +    }else if(length<0 || idx+length>=n){
    +        n -= idx;
    +    }else{
    +        n -= idx+length;
    +    }
    +    return QhullPoints(qh_qh, point_dimension, n*point_dimension, point_first+idx*point_dimension);
    +}//mid
    +
    +#//!\name QhullPointsIterator
    +
    +bool QhullPointsIterator::
    +findNext(const QhullPoint &p)
    +{
    +    while(i!=ps->constEnd()){
    +        if(*i++ == p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +bool QhullPointsIterator::
    +findPrevious(const QhullPoint &p)
    +{
    +    while(i!=ps->constBegin()){
    +        if(*--i == p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findPrevious
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullPoints;
    +using orgQhull::QhullPointsIterator;
    +
    +ostream &
    +operator<<(ostream &os, const QhullPoints &p)
    +{
    +    QhullPointsIterator i(p);
    +    while(i.hasNext()){
    +        os << i.next();
    +    }
    +    return os;
    +}//operator<  // ptrdiff_t, size_t
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    class QhullPoints;          //!< One or more points Coordinate pointers with dimension and iterators
    +    class QhullPointsIterator;  //!< Java-style iterator
    +
    +//! QhullPoints are an array of QhullPoint as pointers into an array of coordinates.
    +//! For Qhull/QhullQh, QhullPoints must use hull_dim.  Can change QhullPoint to input_dim if needed for Delaunay input site
    +class QhullPoints {
    +
    +private:
    +#//!\name Fields
    +    coordT *            point_first; //!< First coordinate of an array of points of point_dimension
    +    coordT *            point_end;   //!< End of point coordinates (end>=first).  Trailing coordinates ignored
    +    QhullQh *           qh_qh;       //!< Maybe initialized NULL to allow ownership by RboxPoints
    +                                     //!< qh_qh used for QhullPoint() and qh_qh->hull_dim in constructor
    +    int                 point_dimension;  //!< Dimension, >=0
    +
    +public:
    +#//!\name Subtypes
    +    class const_iterator;
    +    class iterator;
    +    typedef QhullPoints::const_iterator ConstIterator;
    +    typedef QhullPoints::iterator       Iterator;
    +
    +#//!\name Construct
    +    //! QhullPoint, PointCoordinates, and QhullPoints have similar constructors
    +    //! If Qhull/QhullQh is not initialized, then QhullPoints.dimension() is zero unless explicitly set
    +    //! Cannot define QhullPoints(int pointDimension) since it is ambiguous with QhullPoints(QhullQh *qqh)
    +                        QhullPoints() : point_first(0), point_end(0), qh_qh(0), point_dimension(0) { }
    +                        QhullPoints(int pointDimension, countT coordinateCount2, coordT *c) : point_first(c), point_end(c+coordinateCount2), qh_qh(0), point_dimension(pointDimension) { QHULL_ASSERT(pointDimension>=0); }
    +    explicit            QhullPoints(const Qhull &q);
    +                        QhullPoints(const Qhull &q, countT coordinateCount2, coordT *c);
    +                        QhullPoints(const Qhull &q, int pointDimension, countT coordinateCount2, coordT *c);
    +    explicit            QhullPoints(QhullQh *qqh) : point_first(0), point_end(0), qh_qh(qqh), point_dimension(qqh ? qqh->hull_dim : 0) { }
    +                        QhullPoints(QhullQh *qqh, countT coordinateCount2, coordT *c) : point_first(c), point_end(c+coordinateCount2), qh_qh(qqh), point_dimension(qqh ? qqh->hull_dim : 0) { QHULL_ASSERT(qqh && qqh->hull_dim>0); }
    +                        QhullPoints(QhullQh *qqh, int pointDimension, countT coordinateCount2, coordT *c);
    +                        //! Copy constructor copies pointers but not contents.  Needed for return by value and parameter passing.
    +                        QhullPoints(const QhullPoints &other)  : point_first(other.point_first), point_end(other.point_end), qh_qh(other.qh_qh), point_dimension(other.point_dimension) {}
    +    QhullPoints &       operator=(const QhullPoints &other) { point_first= other.point_first; point_end= other.point_end; qh_qh= other.qh_qh; point_dimension= other.point_dimension; return *this; }
    +                        ~QhullPoints() {}
    +
    +public:
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name GetSet
    +    // Constructs QhullPoint.  Cannot return reference.
    +    const QhullPoint    at(countT idx) const { /* point_first==0 caught by point_end assert */ coordT *p= point_first+idx*point_dimension; QHULL_ASSERT(p=0 && coordinatesCount>=0 && c!=0); point_first= c; point_end= c+coordinatesCount; point_dimension= pointDimension; }
    +    void                defineAs(countT coordinatesCount, coordT *c) { QHULL_ASSERT((point_dimension>0 && coordinatesCount>=0 && c!=0) || (c==0 && coordinatesCount==0)); point_first= c; point_end= c+coordinatesCount; }
    +    void                defineAs(const QhullPoints &other) { point_first= other.point_first; point_end= other.point_end; qh_qh= other.qh_qh; point_dimension= other.point_dimension; }
    +    int                 dimension() const { return point_dimension; }
    +    ConstIterator       end() const { return ConstIterator(qh_qh, point_dimension, point_end); }
    +    Iterator            end() { return Iterator(qh_qh, point_dimension, point_end); }
    +    coordT *            extraCoordinates() const { return extraCoordinatesCount() ? (point_end-extraCoordinatesCount()) : 0; }
    +    countT              extraCoordinatesCount() const;  // WARN64
    +    // Constructs QhullPoint.  Cannot return reference.
    +    const QhullPoint    first() const { return QhullPoint(qh_qh, point_dimension, point_first); }
    +    QhullPoint          first() { return QhullPoint(qh_qh, point_dimension, point_first); }
    +    // Constructs QhullPoint.  Cannot return reference.
    +    const QhullPoint    front() const { return first(); }
    +    QhullPoint          front() { return first(); }
    +    bool                includesCoordinates(const coordT *c) const { return c>=point_first && c(other)); return *this; }
    +
    +        // Need 'const QhullPoint' to maintain const
    +        const QhullPoint & operator*() const { return *this; }
    +        QhullPoint &    operator*() { return *this; }
    +        const QhullPoint * operator->() const { return this; }
    +        QhullPoint *    operator->() { return this; }
    +        // value instead of reference since advancePoint() modifies self
    +        QhullPoint      operator[](countT idx) const { QhullPoint result= *this; result.advancePoint(idx); return result; }
    +        bool            operator==(const iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return (point_coordinates==o.point_coordinates && point_dimension==o.point_dimension); }
    +        bool            operator!=(const iterator &o) const { return !operator==(o); }
    +        bool            operator<(const iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates < o.point_coordinates; }
    +        bool            operator<=(const iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates <= o.point_coordinates; }
    +        bool            operator>(const iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates > o.point_coordinates; }
    +        bool            operator>=(const iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates >= o.point_coordinates; }
    +        // reinterpret_cast to break circular dependency
    +        bool            operator==(const QhullPoints::const_iterator &o) const { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return (point_coordinates==reinterpret_cast(o).point_coordinates && point_dimension==reinterpret_cast(o).point_dimension); }
    +        bool            operator!=(const QhullPoints::const_iterator &o) const { return !operator==(reinterpret_cast(o)); }
    +        bool            operator<(const QhullPoints::const_iterator &o) const  { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates < reinterpret_cast(o).point_coordinates; }
    +        bool            operator<=(const QhullPoints::const_iterator &o) const { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates <= reinterpret_cast(o).point_coordinates; }
    +        bool            operator>(const QhullPoints::const_iterator &o) const  { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates > reinterpret_cast(o).point_coordinates; }
    +        bool            operator>=(const QhullPoints::const_iterator &o) const { QHULL_ASSERT(qh_qh==reinterpret_cast(o).qh_qh); return point_coordinates >= reinterpret_cast(o).point_coordinates; }
    +        iterator &      operator++() { advancePoint(1); return *this; }
    +        iterator        operator++(int) { iterator n= *this; operator++(); return iterator(n); }
    +        iterator &      operator--() { advancePoint(-1); return *this; }
    +        iterator        operator--(int) { iterator n= *this; operator--(); return iterator(n); }
    +        iterator &      operator+=(countT idx) { advancePoint(idx); return *this; }
    +        iterator &      operator-=(countT idx) { advancePoint(-idx); return *this; }
    +        iterator        operator+(countT idx) const { iterator n= *this; n.advancePoint(idx); return n; }
    +        iterator        operator-(countT idx) const { iterator n= *this; n.advancePoint(-idx); return n; }
    +        difference_type operator-(iterator o) const { QHULL_ASSERT(qh_qh==o.qh_qh && point_dimension==o.point_dimension); return (point_dimension ? (point_coordinates-o.point_coordinates)/point_dimension : 0); }
    +    };//QhullPoints::iterator
    +
    +#//!\name QhullPoints::const_iterator
    +    //!\todo FIXUP QH11018 const_iterator same as iterator.  SHould have a common definition
    +    class const_iterator : public QhullPoint {
    +
    +    public:
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef QhullPoint          value_type;
    +        typedef const value_type *  pointer;
    +        typedef const value_type &  reference;
    +        typedef ptrdiff_t           difference_type;
    +
    +                        const_iterator(const QhullPoints::iterator &o) : QhullPoint(*o) {}
    +        explicit        const_iterator(const QhullPoints &ps) : QhullPoint(ps.qh(), ps.dimension(), ps.coordinates()) {}
    +                        const_iterator(const int pointDimension, coordT *c): QhullPoint(pointDimension, c) {}
    +                        const_iterator(const Qhull &q, coordT *c): QhullPoint(q, c) {}
    +                        const_iterator(const Qhull &q, int pointDimension, coordT *c): QhullPoint(q, pointDimension, c) {}
    +                        const_iterator(QhullQh *qqh, coordT *c): QhullPoint(qqh, c) {}
    +                        const_iterator(QhullQh *qqh, int pointDimension, coordT *c): QhullPoint(qqh, pointDimension, c) {}
    +                        const_iterator(const const_iterator &o) : QhullPoint(*o) {}
    +        const_iterator &operator=(const const_iterator &o) { defineAs(const_cast(o)); return *this; }
    +
    +        // value/non-const since advancePoint(1), etc. modifies self
    +        const QhullPoint & operator*() const { return *this; }
    +        const QhullPoint * operator->() const { return this; }
    +        // value instead of reference since advancePoint() modifies self
    +        const QhullPoint operator[](countT idx) const { QhullPoint n= *this; n.advancePoint(idx); return n; }
    +        bool            operator==(const const_iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return (point_coordinates==o.point_coordinates && point_dimension==o.point_dimension); }
    +        bool            operator!=(const const_iterator &o) const { return ! operator==(o); }
    +        bool            operator<(const const_iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates < o.point_coordinates; }
    +        bool            operator<=(const const_iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates <= o.point_coordinates; }
    +        bool            operator>(const const_iterator &o) const  { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates > o.point_coordinates; }
    +        bool            operator>=(const const_iterator &o) const { QHULL_ASSERT(qh_qh==o.qh_qh); return point_coordinates >= o.point_coordinates; }
    +        const_iterator &operator++() { advancePoint(1); return *this; }
    +        const_iterator  operator++(int) { const_iterator n= *this; operator++(); return const_iterator(n); }
    +        const_iterator &operator--() { advancePoint(-1); return *this; }
    +        const_iterator  operator--(int) { const_iterator n= *this; operator--(); return const_iterator(n); }
    +        const_iterator &operator+=(countT idx) { advancePoint(idx); return *this; }
    +        const_iterator &operator-=(countT idx) { advancePoint(-idx); return *this; }
    +        const_iterator  operator+(countT idx) const { const_iterator n= *this; n.advancePoint(idx); return n; }
    +        const_iterator  operator-(countT idx) const { const_iterator n= *this; n.advancePoint(-idx); return n; }
    +        difference_type operator-(const_iterator o) const { QHULL_ASSERT(qh_qh==o.qh_qh && point_dimension==o.point_dimension); return (point_dimension ? (point_coordinates-o.point_coordinates)/point_dimension : 0); }
    +    };//QhullPoints::const_iterator
    +
    +#//!\name IO
    +    struct PrintPoints{
    +        const QhullPoints  *points;
    +        const char *    point_message;
    +        bool            with_identifier;
    +        PrintPoints(const char *message, bool withIdentifier, const QhullPoints &ps) : points(&ps), point_message(message), with_identifier(withIdentifier) {}
    +    };//PrintPoints
    +    PrintPoints          print(const char *message) const { return PrintPoints(message, false, *this); }
    +    PrintPoints          printWithIdentifier(const char *message) const { return PrintPoints(message, true, *this); }
    +};//QhullPoints
    +
    +// Instead of QHULL_DECLARE_SEQUENTIAL_ITERATOR because next(),etc would return a reference to a temporary
    +class QhullPointsIterator
    +{
    +    typedef QhullPoints::const_iterator const_iterator;
    +
    +#//!\name Fields
    +private:
    +    const QhullPoints  *ps;
    +    const_iterator      i;
    +
    +public:
    +                        QhullPointsIterator(const QhullPoints &other) : ps(&other), i(ps->constBegin()) {}
    +    QhullPointsIterator &operator=(const QhullPoints &other) { ps = &other; i = ps->constBegin(); return *this; }
    +
    +    bool                findNext(const QhullPoint &t);
    +    bool                findPrevious(const QhullPoint &t);
    +    bool                hasNext() const { return i != ps->constEnd(); }
    +    bool                hasPrevious() const { return i != ps->constBegin(); }
    +    QhullPoint          next() { return *i++; }
    +    QhullPoint          peekNext() const { return *i; }
    +    QhullPoint          peekPrevious() const { const_iterator p = i; return *--p; }
    +    QhullPoint          previous() { return *--i; }
    +    void                toBack() { i = ps->constEnd(); }
    +    void                toFront() { i = ps->constBegin(); }
    +};//QhullPointsIterator
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &          operator<<(std::ostream &os, const orgQhull::QhullPoints &p);
    +std::ostream &          operator<<(std::ostream &os, const orgQhull::QhullPoints::PrintPoints &pr);
    +
    +#endif // QHULLPOINTS_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullQh.cpp b/xs/src/qhull/src/libqhullcpp/QhullQh.cpp
    new file mode 100644
    index 0000000000..3635337001
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullQh.cpp
    @@ -0,0 +1,237 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullQh.cpp#5 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullQh -- Qhull's global data structure, qhT, as a C++ class
    +
    +
    +#include "libqhullcpp/QhullQh.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullStat.h"
    +
    +#include 
    +#include 
    +
    +#include 
    +
    +using std::cerr;
    +using std::string;
    +using std::vector;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Global variables
    +const double QhullQh::
    +default_factor_epsilon= 1.0;
    +
    +#//!\name Constructor, destructor, etc.
    +
    +//! Derived from qh_new_qhull[user.c]
    +QhullQh::
    +QhullQh()
    +: qhull_status(qh_ERRnone)
    +, qhull_message()
    +, error_stream(0)
    +, output_stream(0)
    +, factor_epsilon(QhullQh::default_factor_epsilon)
    +, use_output_stream(false)
    +{
    +    // NOerrors: TRY_QHULL_ not needed since these routines do not call qh_errexit()
    +    qh_meminit(this, NULL);
    +    qh_initstatistics(this);
    +    qh_initqhull_start2(this, NULL, NULL, qh_FILEstderr);  // Initialize qhT
    +    this->ISqhullQh= True;
    +}//QhullQh
    +
    +QhullQh::
    +~QhullQh()
    +{
    +    checkAndFreeQhullMemory();
    +}//~QhullQh
    +
    +#//!\name Methods
    +
    +//! Check memory for internal consistency
    +//! Free global memory used by qh_initbuild and qh_buildhull
    +//! Zero the qhT data structure, except for memory (qhmemT) and statistics (qhstatT)
    +//! Check and free short memory (e.g., facetT)
    +//! Zero the qhmemT data structure
    +void QhullQh::
    +checkAndFreeQhullMemory()
    +{
    +#ifdef qh_NOmem
    +    qh_freeqhull(this, qh_ALL);
    +#else
    +    qh_memcheck(this);
    +    qh_freeqhull(this, !qh_ALL);
    +    countT curlong;
    +    countT totlong;
    +    qh_memfreeshort(this, &curlong, &totlong);
    +    if (curlong || totlong)
    +        throw QhullError(10026, "Qhull error: qhull did not free %d bytes of long memory (%d pieces).", totlong, curlong);
    +#endif
    +}//checkAndFreeQhullMemory
    +
    +#//!\name Messaging
    +
    +void QhullQh::
    +appendQhullMessage(const string &s)
    +{
    +    if(output_stream && use_output_stream && this->USEstdout){ 
    +        *output_stream << s;
    +    }else if(error_stream){
    +        *error_stream << s;
    +    }else{
    +        qhull_message += s;
    +    }
    +}//appendQhullMessage
    +
    +//! clearQhullMessage does not throw errors (~Qhull)
    +void QhullQh::
    +clearQhullMessage()
    +{
    +    qhull_status= qh_ERRnone;
    +    qhull_message.clear();
    +    RoadError::clearGlobalLog();
    +}//clearQhullMessage
    +
    +//! hasQhullMessage does not throw errors (~Qhull)
    +bool QhullQh::
    +hasQhullMessage() const
    +{
    +    return (!qhull_message.empty() || qhull_status!=qh_ERRnone);
    +    //FIXUP QH11006 -- inconsistent usage with Rbox.  hasRboxMessage just tests rbox_status.  No appendRboxMessage()
    +}
    +
    +void QhullQh::
    +maybeThrowQhullMessage(int exitCode)
    +{
    +    if(!NOerrexit){
    +        if(qhull_message.size()>0){
    +            qhull_message.append("\n");
    +        }
    +        if(exitCode || qhull_status==qh_ERRnone){
    +            qhull_status= 10073;
    +        }else{
    +            qhull_message.append("QH10073: ");
    +        }
    +        qhull_message.append("Cannot call maybeThrowQhullMessage() from QH_TRY_().  Or missing 'qh->NOerrexit=true;' after QH_TRY_(){...}.");
    +    }
    +    if(qhull_status==qh_ERRnone){
    +        qhull_status= exitCode;
    +    }
    +    if(qhull_status!=qh_ERRnone){
    +        QhullError e(qhull_status, qhull_message);
    +        clearQhullMessage();
    +        throw e; // FIXUP QH11007: copy constructor is expensive if logging
    +    }
    +}//maybeThrowQhullMessage
    +
    +void QhullQh::
    +maybeThrowQhullMessage(int exitCode, int noThrow)  throw()
    +{
    +    QHULL_UNUSED(noThrow);
    +
    +    if(qhull_status==qh_ERRnone){
    +        qhull_status= exitCode;
    +    }
    +    if(qhull_status!=qh_ERRnone){
    +        QhullError e(qhull_status, qhull_message);
    +        e.logErrorLastResort();
    +    }
    +}//maybeThrowQhullMessage
    +
    +//! qhullMessage does not throw errors (~Qhull)
    +std::string QhullQh::
    +qhullMessage() const
    +{
    +    if(qhull_message.empty() && qhull_status!=qh_ERRnone){
    +        return "qhull: no message for error.  Check cerr or error stream\n";
    +    }else{
    +        return qhull_message;
    +    }
    +}//qhullMessage
    +
    +int QhullQh::
    +qhullStatus() const
    +{
    +    return qhull_status;
    +}//qhullStatus
    +
    +void QhullQh::
    +setErrorStream(ostream *os)
    +{
    +    error_stream= os;
    +}//setErrorStream
    +
    +//! Updates use_output_stream
    +void QhullQh::
    +setOutputStream(ostream *os)
    +{
    +    output_stream= os;
    +    use_output_stream= (os!=0);
    +}//setOutputStream
    +
    +}//namespace orgQhull
    +
    +/*---------------------------------
    +
    +  qh_fprintf(qhT *qh, fp, msgcode, format, list of args )
    +    replaces qh_fprintf() in userprintf_r.c
    +
    +notes:
    +    only called from libqhull
    +    same as fprintf() and RboxPoints.qh_fprintf_rbox()
    +    fgets() is not trapped like fprintf()
    +    Do not throw errors from here.  Use qh_errexit;
    +*/
    +extern "C"
    +void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    using namespace orgQhull;
    +
    +    if(!qh->ISqhullQh){
    +        qh_fprintf_stderr(10025, "Qhull error: qh_fprintf called from a Qhull instance without QhullQh defined\n");
    +        qh_exit(10025);
    +    }
    +    QhullQh *qhullQh= static_cast(qh);
    +    va_start(args, fmt);
    +    if(msgcode=MSG_ERROR && msgcodeqhull_statusqhull_status>=MSG_WARNING){
    +                qhullQh->qhull_status= msgcode;
    +            }
    +        }
    +        char newMessage[MSG_MAXLEN];
    +        // RoadError will add the message tag
    +        vsnprintf(newMessage, sizeof(newMessage), fmt, args);
    +        qhullQh->appendQhullMessage(newMessage);
    +        va_end(args);
    +        return;
    +    }
    +    if(qhullQh->output_stream && qhullQh->use_output_stream){
    +        char newMessage[MSG_MAXLEN];
    +        vsnprintf(newMessage, sizeof(newMessage), fmt, args);
    +        *qhullQh->output_stream << newMessage;
    +        va_end(args);
    +        return;
    +    }
    +    // FIXUP QH11008: how do users trap messages and handle input?  A callback?
    +    char newMessage[MSG_MAXLEN];
    +    vsnprintf(newMessage, sizeof(newMessage), fmt, args);
    +    qhullQh->appendQhullMessage(newMessage);
    +    va_end(args);
    +} /* qh_fprintf */
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullQh.h b/xs/src/qhull/src/libqhullcpp/QhullQh.h
    new file mode 100644
    index 0000000000..c3b277ff0f
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullQh.h
    @@ -0,0 +1,110 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullQh.h#2 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLQH_H
    +#define QHULLQH_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +
    +#include 
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  /* interaction between '_setjmp' and C++ object destruction is non-portable */
    +/* setjmp should not be implemented with 'catch' */
    +#endif
    +
    +//! Use QH_TRY_ or QH_TRY_NOTHROW_ to call a libqhull_r routine that may invoke qh_errexit()
    +//! QH_TRY_(qh){...} qh->NOerrexit=true;
    +//! No object creation -- longjmp() skips object destructors
    +//! To test for error when done -- qh->maybeThrowQhullMessage(QH_TRY_status);
    +//! Use the same compiler for QH_TRY_, libqhullcpp, and libqhull_r.  setjmp() is not portable between compilers.
    +
    +#define QH_TRY_ERROR 10071
    +
    +#define QH_TRY_(qh) \
    +    int QH_TRY_status; \
    +    if(qh->NOerrexit){ \
    +        qh->NOerrexit= False; \
    +        QH_TRY_status= setjmp(qh->errexit); \
    +    }else{ \
    +        throw QhullError(QH_TRY_ERROR, "Cannot invoke QH_TRY_() from inside a QH_TRY_.  Or missing 'qh->NOerrexit=true' after previously called QH_TRY_(qh){...}"); \
    +    } \
    +    if(!QH_TRY_status)
    +
    +#define QH_TRY_NO_THROW_(qh) \
    +    int QH_TRY_status; \
    +    if(qh->NOerrexit){ \
    +        qh->NOerrexit= False; \
    +        QH_TRY_status= setjmp(qh->errexit); \
    +    }else{ \
    +        QH_TRY_status= QH_TRY_ERROR; \
    +    } \
    +    if(!QH_TRY_status)
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! QhullQh -- Qhull's global data structure, qhT, as a C++ class
    +    class QhullQh;
    +
    +//! POD type equivalent to qhT.  No virtual members
    +class QhullQh : public qhT {
    +
    +#//!\name Constants
    +
    +#//!\name Fields
    +private:
    +    int                 qhull_status;   //!< qh_ERRnone if valid
    +    std::string         qhull_message;  //!< Returned messages from libqhull_r
    +    std::ostream *      error_stream;   //!< overrides errorMessage, use appendQhullMessage()
    +    std::ostream *      output_stream;  //!< send output to stream
    +    double              factor_epsilon; //!< Factor to increase ANGLEround and DISTround for hyperplane equality
    +    bool                use_output_stream; //!< True if using output_stream
    +
    +    friend void         ::qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +
    +    static const double default_factor_epsilon;  //!< Default factor_epsilon is 1.0, never updated
    +
    +#//!\name Constructors
    +public:
    +                        QhullQh();
    +                        ~QhullQh();
    +private:
    +                        //!disable copy constructor and assignment
    +                        QhullQh(const QhullQh &);
    +    QhullQh &           operator=(const QhullQh &);
    +public:
    +
    +#//!\name GetSet
    +    double              factorEpsilon() const { return factor_epsilon; }
    +    void                setFactorEpsilon(double a) { factor_epsilon= a; }
    +    void                disableOutputStream() { use_output_stream= false; }
    +    void                enableOutputStream() { use_output_stream= true; }
    +
    +#//!\name Messaging
    +    void                appendQhullMessage(const std::string &s);
    +    void                clearQhullMessage();
    +    std::string         qhullMessage() const;
    +    bool                hasOutputStream() const { return use_output_stream; }
    +    bool                hasQhullMessage() const;
    +    void                maybeThrowQhullMessage(int exitCode);
    +    void                maybeThrowQhullMessage(int exitCode, int noThrow) throw();
    +    int                 qhullStatus() const;
    +    void                setErrorStream(std::ostream *os);
    +    void                setOutputStream(std::ostream *os);
    +
    +#//!\name Methods
    +    double              angleEpsilon() const { return this->ANGLEround*factor_epsilon; } //!< Epsilon for hyperplane angle equality
    +    void                checkAndFreeQhullMemory();
    +    double              distanceEpsilon() const { return this->DISTround*factor_epsilon; } //!< Epsilon for distance to hyperplane
    +
    +};//class QhullQh
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLQH_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullRidge.cpp b/xs/src/qhull/src/libqhullcpp/QhullRidge.cpp
    new file mode 100644
    index 0000000000..7a01812805
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullRidge.cpp
    @@ -0,0 +1,124 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullRidge.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullRidge -- Qhull's ridge structure, ridgeT, as a C++ class
    +
    +#include "libqhullcpp/QhullRidge.h"
    +
    +#include "libqhullcpp/QhullSets.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +ridgeT QhullRidge::
    +s_empty_ridge= {0,0,0,0,0,
    +                0,0};
    +
    +#//!\name Constructors
    +
    +QhullRidge::QhullRidge(const Qhull &q)
    +: qh_ridge(&s_empty_ridge)
    +, qh_qh(q.qh())
    +{
    +}//Default
    +
    +QhullRidge::QhullRidge(const Qhull &q, ridgeT *r)
    +: qh_ridge(r ? r : &s_empty_ridge)
    +, qh_qh(q.qh())
    +{
    +}//ridgeT
    +
    +#//!\name foreach
    +
    +//! Return True if nextRidge3d
    +//! Simplicial facets may have incomplete ridgeSets
    +//! Does not use qh_errexit()
    +bool QhullRidge::
    +hasNextRidge3d(const QhullFacet &f) const
    +{
    +    if(!qh_qh){
    +        return false;
    +    }
    +    vertexT *v= 0;
    +    // Does not call qh_errexit(), TRY_QHULL_ not needed
    +    ridgeT *ridge= qh_nextridge3d(getRidgeT(), f.getFacetT(), &v);
    +    return (ridge!=0);
    +}//hasNextRidge3d
    +
    +//! Return next ridge and optional vertex for a 3d facet and ridge
    +//! Does not use qh_errexit()
    +QhullRidge QhullRidge::
    +nextRidge3d(const QhullFacet &f, QhullVertex *nextVertex) const
    +{
    +    vertexT *v= 0;
    +    ridgeT *ridge= 0;
    +    if(qh_qh){
    +        // Does not call qh_errexit(), TRY_QHULL_ not needed
    +        ridge= qh_nextridge3d(getRidgeT(), f.getFacetT(), &v);
    +        if(!ridge){
    +            throw QhullError(10030, "Qhull error nextRidge3d:  missing next ridge for facet %d ridge %d.  Does facet contain ridge?", f.id(), id());
    +        }
    +    }
    +    if(nextVertex!=0){
    +        *nextVertex= QhullVertex(qh_qh, v);
    +    }
    +    return QhullRidge(qh_qh, ridge);
    +}//nextRidge3d
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullRidge;
    +using orgQhull::QhullVertex;
    +
    +ostream &
    +operator<<(ostream &os, const QhullRidge &r)
    +{
    +    os << r.print("");
    +    return os;
    +}//<< QhullRidge
    +
    +//! Duplicate of qh_printridge [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullRidge::PrintRidge &pr)
    +{
    +    if(*pr.print_message){
    +        os << pr.print_message << " ";
    +    }else{
    +        os << "     - ";
    +    }
    +    QhullRidge r= *pr.ridge;
    +    os << "r" << r.id();
    +    if(r.getRidgeT()->tested){
    +        os << " tested";
    +    }
    +    if(r.getRidgeT()->nonconvex){
    +        os << " nonconvex";
    +    }
    +    os << endl;
    +    os << r.vertices().print("           vertices:");
    +    if(r.getRidgeT()->top && r.getRidgeT()->bottom){
    +        os << "           between f" << r.topFacet().id() << " and f" << r.bottomFacet().id() << endl;
    +    }else if(r.getRidgeT()->top){
    +        os << "           top f" << r.topFacet().id() << endl;
    +    }else if(r.getRidgeT()->bottom){
    +        os << "           bottom f" << r.bottomFacet().id() << endl;
    +    }
    +
    +    return os;
    +}//<< PrintRidge
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullRidge.h b/xs/src/qhull/src/libqhullcpp/QhullRidge.h
    new file mode 100644
    index 0000000000..924340fb09
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullRidge.h
    @@ -0,0 +1,112 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullRidge.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLRIDGE_H
    +#define QHULLRIDGE_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullSet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +    class QhullVertex;
    +    class QhullVertexSet;
    +    class QhullFacet;
    +
    +#//!\name Defined here
    +    //! QhullRidge -- Qhull's ridge structure, ridgeT [libqhull.h], as a C++ class
    +    class QhullRidge;
    +    typedef QhullSet  QhullRidgeSet;
    +    typedef QhullSetIterator  QhullRidgeSetIterator;
    +    // see QhullSets.h for QhullRidgeSet and QhullRidgeSetIterator -- avoids circular references
    +
    +/************************
    +a ridge is hull_dim-1 simplex between two neighboring facets.  If the
    +facets are non-simplicial, there may be more than one ridge between
    +two facets.  E.G. a 4-d hypercube has two triangles between each pair
    +of neighboring facets.
    +
    +topological information:
    +    vertices            a set of vertices
    +    top,bottom          neighboring facets with orientation
    +
    +geometric information:
    +    tested              True if ridge is clearly convex
    +    nonconvex           True if ridge is non-convex
    +*/
    +
    +class QhullRidge {
    +
    +#//!\name Defined here
    +public:
    +    typedef ridgeT *   base_type;  // for QhullRidgeSet
    +
    +#//!\name Fields
    +private:
    +    ridgeT *            qh_ridge;  //!< Corresponding ridgeT, never 0
    +    QhullQh *           qh_qh;     //!< QhullQh/qhT for ridgeT, may be 0
    +
    +#//!\name Class objects
    +    static ridgeT       s_empty_ridge;
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name Constructors
    +                        QhullRidge() : qh_ridge(&s_empty_ridge), qh_qh(0) {}
    +    explicit            QhullRidge(const Qhull &q);
    +                        QhullRidge(const Qhull &q, ridgeT *r);
    +    explicit            QhullRidge(QhullQh *qqh) : qh_ridge(&s_empty_ridge), qh_qh(qqh) {}
    +                        QhullRidge(QhullQh *qqh, ridgeT *r) : qh_ridge(r ? r : &s_empty_ridge), qh_qh(qqh) {}
    +                        // Creates an alias.  Does not copy QhullRidge.  Needed for return by value and parameter passing
    +                        QhullRidge(const QhullRidge &other) : qh_ridge(other.qh_ridge), qh_qh(other.qh_qh) {}
    +                        // Creates an alias.  Does not copy QhullRidge.  Needed for vector
    +    QhullRidge &        operator=(const QhullRidge &other) { qh_ridge= other.qh_ridge; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullRidge() {}
    +
    +#//!\name GetSet
    +    QhullFacet          bottomFacet() const { return QhullFacet(qh_qh, qh_ridge->bottom); }
    +    int                 dimension() const { return ((qh_qh && qh_qh->hull_dim) ? qh_qh->hull_dim-1 : 0); }
    +    ridgeT *            getBaseT() const { return getRidgeT(); } //!< For QhullSet
    +    ridgeT *            getRidgeT() const { return qh_ridge; }
    +    countT              id() const { return qh_ridge->id; }
    +    bool                isValid() const { return (qh_qh && qh_ridge != &s_empty_ridge); }
    +    bool                operator==(const QhullRidge &other) const { return qh_ridge==other.qh_ridge; }
    +    bool                operator!=(const QhullRidge &other) const { return !operator==(other); }
    +    QhullFacet          otherFacet(const QhullFacet &f) const { return QhullFacet(qh_qh, (qh_ridge->top==f.getFacetT() ? qh_ridge->bottom : qh_ridge->top)); }
    +    QhullFacet          topFacet() const { return QhullFacet(qh_qh, qh_ridge->top); }
    +
    +#//!\name foreach
    +    bool                hasNextRidge3d(const QhullFacet &f) const;
    +    QhullRidge          nextRidge3d(const QhullFacet &f) const { return nextRidge3d(f, 0); }
    +    QhullRidge          nextRidge3d(const QhullFacet &f, QhullVertex *nextVertex) const;
    +    QhullVertexSet      vertices() const { return QhullVertexSet(qh_qh, qh_ridge->vertices); }
    +
    +#//!\name IO
    +
    +    struct PrintRidge{
    +        const QhullRidge *ridge;
    +        const char *    print_message;    //!< non-null message
    +                        PrintRidge(const char *message, const QhullRidge &r) : ridge(&r), print_message(message) {}
    +    };//PrintRidge
    +    PrintRidge          print(const char* message) const { return PrintRidge(message, *this); }
    +};//class QhullRidge
    +
    +}//namespace orgQhull
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullRidge &r);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullRidge::PrintRidge &pr);
    +
    +#endif // QHULLRIDGE_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullSet.cpp b/xs/src/qhull/src/libqhullcpp/QhullSet.cpp
    new file mode 100644
    index 0000000000..dfdc3c51f3
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullSet.cpp
    @@ -0,0 +1,62 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullSet.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullSet -- Qhull's set structure, setT, as a C++ class
    +
    +#include "libqhullcpp/QhullSet.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +
    +setT QhullSetBase::
    +s_empty_set;
    +
    +#//!\name Constructors
    +
    +QhullSetBase::
    +QhullSetBase(const Qhull &q, setT *s) 
    +: qh_set(s ? s : &s_empty_set)
    +, qh_qh(q.qh())
    +{
    +}
    +
    +#//!\name Class methods
    +
    +// Same code for qh_setsize [qset_r.c] and QhullSetBase::count [static]
    +countT QhullSetBase::
    +count(const setT *set)
    +{
    +    countT size;
    +    const setelemT *sizep;
    +
    +    if (!set){
    +        return(0);
    +    }
    +    sizep= SETsizeaddr_(set);
    +    if ((size= sizep->i)) {
    +        size--;
    +        if (size > set->maxsize) {
    +            // FIXUP QH11022 How to add additional output to a error? -- qh_setprint(qhmem.ferr, "set: ", set);
    +            throw QhullError(10032, "QhullSet internal error: current set size %d is greater than maximum size %d\n",
    +                size, set->maxsize);
    +        }
    +    }else{
    +        size= set->maxsize;
    +    }
    +    return size;
    +}//count
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullSet.h b/xs/src/qhull/src/libqhullcpp/QhullSet.h
    new file mode 100644
    index 0000000000..afb6b51d9f
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullSet.h
    @@ -0,0 +1,462 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullSet.h#6 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QhullSet_H
    +#define QhullSet_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullQh.h"
    +
    +#include   // ptrdiff_t, size_t
    +
    +#ifndef QHULL_NO_STL
    +#include 
    +#endif
    +
    +#ifdef QHULL_USES_QT
    + #include 
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class Qhull;
    +
    +#//!\name Defined here
    +    class QhullSetBase;  //! Base class for QhullSet
    +    //! QhullSet defined below
    +    //! QhullSetIterator defined below
    +    //! \see QhullPointSet, QhullLinkedList
    +
    +//! QhullSetBase is a wrapper for Qhull's setT of void* pointers
    +//! \see libqhull_r/qset.h
    +class QhullSetBase {
    +
    +private:
    +#//!\name Fields --
    +    setT *              qh_set;
    +    QhullQh *           qh_qh;             //! Provides access to setT memory allocator
    +
    +#//!\name Class objects
    +    static setT         s_empty_set;  //! Used if setT* is NULL
    +
    +public:
    +#//!\name Constructors
    +                        QhullSetBase(const Qhull &q, setT *s);
    +                        QhullSetBase(QhullQh *qqh, setT *s) : qh_set(s ? s : &s_empty_set), qh_qh(qqh) {}
    +                        //! Copy constructor copies the pointer but not the set.  Needed for return by value and parameter passing.
    +                        QhullSetBase(const QhullSetBase &other) : qh_set(other.qh_set), qh_qh(other.qh_qh) {}
    +    QhullSetBase &      operator=(const QhullSetBase &other) { qh_set= other.qh_set; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullSetBase() {}
    +
    +private:
    +                        //!disabled since memory allocation for QhullSet not defined
    +                        QhullSetBase() {}
    +public:
    +
    +#//!\name GetSet
    +    countT              count() const { return QhullSetBase::count(qh_set); }
    +    void                defineAs(setT *s) { qh_set= s ? s : &s_empty_set; } //!< Not type-safe since setT may contain any type
    +    void                forceEmpty() { qh_set= &s_empty_set; }
    +    setT *              getSetT() const { return qh_set; }
    +    bool                isEmpty() const { return SETempty_(qh_set); }
    +    QhullQh *           qh() const { return qh_qh; }
    +    setT **             referenceSetT() { return &qh_set; }
    +    size_t              size() const { return QhullSetBase::count(qh_set); }
    +
    +#//!\name Element
    +protected:
    +    void **             beginPointer() const { return &qh_set->e[0].p; }
    +    void **             elementPointer(countT idx) const { QHULL_ASSERT(idx>=0 && idxmaxsize); return &SETelem_(qh_set, idx); }
    +                        //! Always points to 0
    +    void **             endPointer() const { return qh_setendpointer(qh_set); }
    +
    +#//!\name Class methods
    +public:
    +    static countT       count(const setT *set);
    +    //s may be null
    +    static bool         isEmpty(const setT *s) { return SETempty_(s); }
    +};//QhullSetBase
    +
    +
    +//! QhullSet -- A read-only wrapper to Qhull's collection class, setT.
    +//!  QhullSet is similar to STL's  and Qt's QVector
    +//!  QhullSet is unrelated to STL and Qt's set and map types (e.g., QSet and QMap)
    +//!  T is a Qhull type that defines 'base_type' and getBaseT() (e.g., QhullFacet with base_type 'facetT *'
    +//!  A QhullSet does not own its contents -- erase(), clear(), removeFirst(), removeLast(), pop_back(), pop_front(), fromStdList() not defined
    +//!  QhullSetIterator is faster than STL-style iterator/const_iterator
    +//!  Qhull's FOREACHelement_() [qset_r.h] maybe more efficient than QhullSet.  It uses a NULL terminator instead of an end pointer.  STL requires an end pointer.
    +//!  Derived from QhullLinkedList.h and Qt/core/tools/qlist.h w/o QT_STRICT_ITERATORS
    +template 
    +class QhullSet : public QhullSetBase {
    +
    +private:
    +#//!\name Fields -- see QhullSetBase
    +
    +#//!\name Class objects
    +    static setT         s_empty_set;  //! Workaround for no setT allocator.  Used if setT* is NULL
    +
    +public:
    +#//!\name Defined here
    +    class iterator;
    +    class const_iterator;
    +    typedef typename QhullSet::iterator Iterator;
    +    typedef typename QhullSet::const_iterator ConstIterator;
    +
    +#//!\name Constructors
    +                        QhullSet(const Qhull &q, setT *s) : QhullSetBase(q, s) { }
    +                        QhullSet(QhullQh *qqh, setT *s) : QhullSetBase(qqh, s) { }
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        //Copy constructor copies pointer but not contents.  Needed for return by value.
    +                        QhullSet(const QhullSet &other) : QhullSetBase(other) {}
    +    QhullSet &       operator=(const QhullSet &other) { QhullSetBase::operator=(other); return *this; }
    +                        ~QhullSet() {}
    +
    +private:
    +                        //!Disable default constructor.  See QhullSetBase
    +                        QhullSet();
    +public:
    +
    +#//!\name Conversion
    +
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif
    +#ifdef QHULL_USES_QT
    +    QList toQList() const;
    +#endif
    +
    +#//!\name GetSet -- see QhullSetBase for count(), empty(), isEmpty(), size()
    +    using QhullSetBase::count;
    +    using QhullSetBase::isEmpty;
    +    // operator== defined for QhullSets of the same type
    +    bool                operator==(const QhullSet &other) const { return qh_setequal(getSetT(), other.getSetT()); }
    +    bool                operator!=(const QhullSet &other) const { return !operator==(other); }
    +
    +#//!\name Element access
    +    // Constructs T.  Cannot return reference.
    +    const T             at(countT idx) const { return operator[](idx); }
    +    // Constructs T.  Cannot return reference.
    +    const T             back() const { return last(); }
    +    T                   back() { return last(); }
    +    //! end element is NULL
    +    const typename T::base_type * constData() const { return reinterpret_cast(beginPointer()); }
    +    typename T::base_type *     data() { return reinterpret_cast(beginPointer()); }
    +    const typename T::base_type *data() const { return reinterpret_cast(beginPointer()); }
    +    typename T::base_type *     endData() { return reinterpret_cast(endPointer()); }
    +    const typename T::base_type * endData() const { return reinterpret_cast(endPointer()); }
    +    // Constructs T.  Cannot return reference.
    +    const T             first() const { QHULL_ASSERT(!isEmpty()); return T(qh(), *data()); }
    +    T                   first() { QHULL_ASSERT(!isEmpty()); return T(qh(), *data()); }
    +    // Constructs T.  Cannot return reference.
    +    const T             front() const { return first(); }
    +    T                   front() { return first(); }
    +    // Constructs T.  Cannot return reference.
    +    const T             last() const { QHULL_ASSERT(!isEmpty()); return T(qh(), *(endData()-1)); }
    +    T                   last() { QHULL_ASSERT(!isEmpty()); return T(qh(), *(endData()-1)); }
    +    // mid() not available.  No setT constructor
    +    // Constructs T.  Cannot return reference.
    +    const T             operator[](countT idx) const { const typename T::base_type *p= reinterpret_cast(elementPointer(idx)); QHULL_ASSERT(idx>=0 && p < endData()); return T(qh(), *p); }
    +    T                   operator[](countT idx) { typename T::base_type *p= reinterpret_cast(elementPointer(idx)); QHULL_ASSERT(idx>=0 && p < endData()); return T(qh(), *p); }
    +    const T             second() const { return operator[](1); }
    +    T                   second() { return operator[](1); }
    +    T                   value(countT idx) const;
    +    T                   value(countT idx, const T &defaultValue) const;
    +
    +#//!\name Read-write -- Not available, no setT constructor
    +
    +#//!\name iterator
    +    iterator            begin() { return iterator(qh(), reinterpret_cast(beginPointer())); }
    +    const_iterator      begin() const { return const_iterator(qh(), data()); }
    +    const_iterator      constBegin() const { return const_iterator(qh(), data()); }
    +    const_iterator      constEnd() const { return const_iterator(qh(), endData()); }
    +    iterator            end() { return iterator(qh(), endData()); }
    +    const_iterator      end() const { return const_iterator(qh(), endData()); }
    +
    +#//!\name Search
    +    bool                contains(const T &t) const;
    +    countT              count(const T &t) const;
    +    countT              indexOf(const T &t) const { /* no qh_qh */ return qh_setindex(getSetT(), t.getBaseT()); }
    +    countT              lastIndexOf(const T &t) const;
    +
    +    // before const_iterator for conversion with comparison operators
    +    class iterator {
    +        friend class const_iterator;
    +    private:
    +        typename T::base_type *  i;  // e.g., facetT**, first for debugger
    +        QhullQh *       qh_qh;
    +
    +    public:
    +        typedef ptrdiff_t       difference_type;
    +        typedef std::bidirectional_iterator_tag  iterator_category;
    +        typedef T               value_type;
    +
    +                        iterator(QhullQh *qqh, typename T::base_type *p) : i(p), qh_qh(qqh) {}
    +                        iterator(const iterator &o) : i(o.i), qh_qh(o.qh_qh) {}
    +        iterator &      operator=(const iterator &o) { i= o.i; qh_qh= o.qh_qh; return *this; }
    +
    +        // Constructs T.  Cannot return reference.  
    +        T               operator*() const { return T(qh_qh, *i); }
    +        //operator->() n/a, value-type
    +        // Constructs T.  Cannot return reference.  
    +        T               operator[](countT idx) const { return T(qh_qh, *(i+idx)); } //!< No error checking
    +        bool            operator==(const iterator &o) const { return i == o.i; }
    +        bool            operator!=(const iterator &o) const { return !operator==(o); }
    +        bool            operator==(const const_iterator &o) const { return (i==reinterpret_cast(o).i); }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +
    +        //! Assumes same point set
    +        countT          operator-(const iterator &o) const { return (countT)(i-o.i); } //WARN64
    +        bool            operator>(const iterator &o) const { return i>o.i; }
    +        bool            operator<=(const iterator &o) const { return !operator>(o); }
    +        bool            operator<(const iterator &o) const { return i=(const iterator &o) const { return !operator<(o); }
    +        bool            operator>(const const_iterator &o) const { return (i > reinterpret_cast(o).i); }
    +        bool            operator<=(const const_iterator &o) const { return !operator>(o); }
    +        bool            operator<(const const_iterator &o) const { return (i < reinterpret_cast(o).i); }
    +        bool            operator>=(const const_iterator &o) const { return !operator<(o); }
    +
    +        //! No error checking
    +        iterator &      operator++() { ++i; return *this; }
    +        iterator        operator++(int) { iterator o= *this; ++i; return o; }
    +        iterator &      operator--() { --i; return *this; }
    +        iterator        operator--(int) { iterator o= *this; --i; return o; }
    +        iterator        operator+(countT j) const { return iterator(qh_qh, i+j); }
    +        iterator        operator-(countT j) const { return operator+(-j); }
    +        iterator &      operator+=(countT j) { i += j; return *this; }
    +        iterator &      operator-=(countT j) { i -= j; return *this; }
    +    };//QhullPointSet::iterator
    +
    +    class const_iterator {
    +    private:
    +        const typename T::base_type *  i;  // e.g., const facetT**, first for debugger
    +        QhullQh *       qh_qh;
    +
    +    public:
    +        typedef ptrdiff_t       difference_type;
    +        typedef std::random_access_iterator_tag  iterator_category;
    +        typedef T               value_type;
    +
    +                        const_iterator(QhullQh *qqh, const typename T::base_type * p) : i(p), qh_qh(qqh) {}
    +                        const_iterator(const const_iterator &o) : i(o.i), qh_qh(o.qh_qh) {}
    +                        const_iterator(const iterator &o) : i(o.i), qh_qh(o.qh_qh) {}
    +        const_iterator &operator=(const const_iterator &o) { i= o.i; qh_qh= o.qh_qh; return *this; }
    +
    +        // Constructs T.  Cannot return reference.  Retaining 'const T' return type for consistency with QList/QVector
    +        const T         operator*() const { return T(qh_qh, *i); }
    +        const T         operator[](countT idx) const { return T(qh_qh, *(i+idx)); }  //!< No error checking
    +        //operator->() n/a, value-type
    +        bool            operator==(const const_iterator &o) const { return i == o.i; }
    +        bool            operator!=(const const_iterator &o) const { return !operator==(o); }
    +
    +        //! Assumes same point set
    +        countT          operator-(const const_iterator &o) { return (countT)(i-o.i); } //WARN64
    +        bool            operator>(const const_iterator &o) const { return i>o.i; }
    +        bool            operator<=(const const_iterator &o) const { return !operator>(o); }
    +        bool            operator<(const const_iterator &o) const { return i=(const const_iterator &o) const { return !operator<(o); }
    +
    +        //!< No error checking
    +        const_iterator &operator++() { ++i; return *this; }
    +        const_iterator  operator++(int) { const_iterator o= *this; ++i; return o; }
    +        const_iterator &operator--() { --i; return *this; }
    +        const_iterator  operator--(int) { const_iterator o= *this; --i; return o; }
    +        const_iterator  operator+(int j) const { return const_iterator(qh_qh, i+j); }
    +        const_iterator  operator-(int j) const { return operator+(-j); }
    +        const_iterator &operator+=(int j) { i += j; return *this; }
    +        const_iterator &operator-=(int j) { i -= j; return *this; }
    +    };//QhullPointSet::const_iterator
    +
    +};//class QhullSet
    +
    +
    +//! Faster then interator/const_iterator due to T::base_type
    +template 
    +class QhullSetIterator {
    +
    +#//!\name Subtypes
    +    typedef typename QhullSet::const_iterator const_iterator;
    +
    +private:
    +#//!\name Fields
    +    const typename T::base_type *  i;  // e.g., facetT**, first for debugger
    +    const typename T::base_type *  begin_i;  // must be initialized after i
    +    const typename T::base_type *  end_i;
    +    QhullQh *                qh_qh;
    +
    +public:
    +#//!\name Constructors
    +                        QhullSetIterator(const QhullSet &s) : i(s.data()), begin_i(i), end_i(s.endData()), qh_qh(s.qh()) {}
    +                        QhullSetIterator(const QhullSetIterator &o) : i(o.i), begin_i(o.begin_i), end_i(o.end_i), qh_qh(o.qh_qh) {}
    +    QhullSetIterator &operator=(const QhullSetIterator &o) { i= o.i; begin_i= o.begin_i; end_i= o.end_i; qh_qh= o.qh_qh; return *this; }
    +
    +#//!\name ReadOnly
    +    countT              countRemaining() { return (countT)(end_i-i); } // WARN64
    +
    +#//!\name Search
    +    bool                findNext(const T &t);
    +    bool                findPrevious(const T &t);
    +
    +#//!\name Foreach
    +    bool                hasNext() const { return i != end_i; }
    +    bool                hasPrevious() const { return i != begin_i; }
    +    T                   next() { return T(qh_qh, *i++); }
    +    T                   peekNext() const { return T(qh_qh, *i); }
    +    T                   peekPrevious() const { const typename T::base_type *p = i; return T(qh_qh, *--p); }
    +    T                   previous() { return T(qh_qh, *--i); }
    +    void                toBack() { i = end_i; }
    +    void                toFront() { i = begin_i; }
    +};//class QhullSetIterator
    +
    +#//!\name == Definitions =========================================
    +
    +#//!\name Conversions
    +
    +// See qt-qhull.cpp for QList conversion
    +
    +#ifndef QHULL_NO_STL
    +template 
    +std::vector QhullSet::
    +toStdVector() const
    +{
    +	typename QhullSet::const_iterator i = begin();
    +	typename QhullSet::const_iterator e = end();
    +    std::vector vs;
    +    while(i!=e){
    +        vs.push_back(*i++);
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +#ifdef QHULL_USES_QT
    +template 
    +QList QhullSet::
    +toQList() const
    +{
    +    QhullSet::const_iterator i= begin();
    +    QhullSet::const_iterator e= end();
    +    QList vs;
    +    while(i!=e){
    +        vs.append(*i++);
    +    }
    +    return vs;
    +}//toQList
    +#endif
    +
    +#//!\name Element
    +
    +template 
    +T QhullSet::
    +value(countT idx) const
    +{
    +    // Avoid call to qh_setsize() and assert in elementPointer()
    +    const typename T::base_type *p= reinterpret_cast(&SETelem_(getSetT(), idx));
    +    return (idx>=0 && p
    +T QhullSet::
    +value(countT idx, const T &defaultValue) const
    +{
    +    // Avoid call to qh_setsize() and assert in elementPointer()
    +    const typename T::base_type *p= reinterpret_cast(&SETelem_(getSetT(), idx));
    +    return (idx>=0 && p
    +bool QhullSet::
    +contains(const T &t) const
    +{
    +    setT *s= getSetT();
    +    void *p= t.getBaseT();  // contains() is not inline for better error reporting
    +    int result= qh_setin(s, p);
    +    return result!=0;
    +}//contains
    +
    +template 
    +countT QhullSet::
    +count(const T &t) const
    +{
    +    countT n= 0;
    +    const typename T::base_type *i= data();
    +    const typename T::base_type *e= endData();
    +    typename T::base_type p= t.getBaseT();
    +    while(i
    +countT QhullSet::
    +lastIndexOf(const T &t) const
    +{
    +    const typename T::base_type *b= data();
    +    const typename T::base_type *i= endData();
    +    typename T::base_type p= t.getBaseT();
    +    while(--i>=b){
    +        if(*i==p){
    +            break;
    +        }
    +    }
    +    return (countT)(i-b); // WARN64
    +}//lastIndexOf
    +
    +#//!\name QhullSetIterator
    +
    +template 
    +bool QhullSetIterator::
    +findNext(const T &t)
    +{
    +    typename T::base_type p= t.getBaseT();
    +    while(i!=end_i){
    +        if(*(++i)==p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findNext
    +
    +template 
    +bool QhullSetIterator::
    +findPrevious(const T &t)
    +{
    +    typename T::base_type p= t.getBaseT();
    +    while(i!=begin_i){
    +        if(*(--i)==p){
    +            return true;
    +        }
    +    }
    +    return false;
    +}//findPrevious
    +
    +}//namespace orgQhull
    +
    +
    +#//!\name == Global namespace =========================================
    +
    +template 
    +std::ostream &
    +operator<<(std::ostream &os, const orgQhull::QhullSet &qs)
    +{
    +    const typename T::base_type *i= qs.data();
    +    const typename T::base_type *e= qs.endData();
    +    while(i!=e){
    +        os << T(qs.qh(), *i++);
    +    }
    +    return os;
    +}//operator<<
    +
    +#endif // QhullSet_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullSets.h b/xs/src/qhull/src/libqhullcpp/QhullSets.h
    new file mode 100644
    index 0000000000..d0f200cbcf
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullSets.h
    @@ -0,0 +1,27 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullSets.h#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLSETS_H
    +#define QHULLSETS_H
    +
    +#include "libqhullcpp/QhullSet.h"
    +
    +namespace orgQhull {
    +
    +    //See: QhullFacetSet.h
    +    //See: QhullPointSet.h
    +    //See: QhullVertexSet.h
    +
    +    // Avoid circular references between QhullFacet, QhullRidge, and QhullVertex
    +    class QhullRidge;
    +    typedef QhullSet  QhullRidgeSet;
    +    typedef QhullSetIterator  QhullRidgeSetIterator;
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLSETS_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullStat.cpp b/xs/src/qhull/src/libqhullcpp/QhullStat.cpp
    new file mode 100644
    index 0000000000..c4fe6c4918
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullStat.cpp
    @@ -0,0 +1,42 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullStat.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullStat -- Qhull's global data structure, statT, as a C++ class
    +
    +#include "libqhullcpp/QhullStat.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +
    +#include 
    +#include 
    +
    +using std::cerr;
    +using std::string;
    +using std::vector;
    +using std::ostream;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Constructor, destructor, etc.
    +
    +//! If qh_QHpointer==0, invoke with placement new on qh_stat;
    +QhullStat::
    +QhullStat()
    +{
    +}//QhullStat
    +
    +QhullStat::
    +~QhullStat()
    +{
    +}//~QhullStat
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullStat.h b/xs/src/qhull/src/libqhullcpp/QhullStat.h
    new file mode 100644
    index 0000000000..54bde8fc79
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullStat.h
    @@ -0,0 +1,49 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullStat.h#2 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLSTAT_H
    +#define QHULLSTAT_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name defined here
    +    //! QhullStat -- Qhull's statistics, qhstatT, as a C++ class
    +    //! Statistics defined with zzdef_() control Qhull's behavior, summarize its result, and report precision problems.
    +    class QhullStat;
    +
    +class QhullStat : public qhstatT {
    +
    +private:
    +#//!\name Fields (empty) -- POD type equivalent to qhstatT.  No data or virtual members
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name class methods
    +
    +#//!\name constructor, assignment, destructor, invariant
    +                        QhullStat();
    +                        ~QhullStat();
    +
    +private:
    +    //!disable copy constructor and assignment
    +                        QhullStat(const QhullStat &);
    +    QhullStat &         operator=(const QhullStat &);
    +public:
    +
    +#//!\name Access
    +};//class QhullStat
    +
    +}//namespace orgQhull
    +
    +#endif // QHULLSTAT_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullVertex.cpp b/xs/src/qhull/src/libqhullcpp/QhullVertex.cpp
    new file mode 100644
    index 0000000000..fd7aef0893
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullVertex.cpp
    @@ -0,0 +1,112 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullVertex.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullVertex -- Qhull's vertex structure, vertexT, as a C++ class
    +
    +#include "libqhullcpp/QhullVertex.h"
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullFacet.h"
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  // interaction between '_setjmp' and C++ object destruction is non-portable
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class objects
    +vertexT QhullVertex::
    +s_empty_vertex= {0,0,0,0,0,
    +                 0,0,0,0,0,
    +                 0};
    +
    +#//!\name Constructors
    +
    +QhullVertex::QhullVertex(const Qhull &q)
    +: qh_vertex(&s_empty_vertex)
    +, qh_qh(q.qh())
    +{
    +}//Default
    +
    +QhullVertex::QhullVertex(const Qhull &q, vertexT *v)
    +: qh_vertex(v ? v : &s_empty_vertex)
    +, qh_qh(q.qh())
    +{
    +}//vertexT
    +
    +#//!\name foreach
    +
    +//! Return neighboring facets for a vertex
    +//! If neither merging nor Voronoi diagram, requires Qhull::defineVertexNeighborFacets() beforehand.
    +QhullFacetSet QhullVertex::
    +neighborFacets() const
    +{
    +    if(!neighborFacetsDefined()){
    +        throw QhullError(10034, "Qhull error: neighboring facets of vertex %d not defined.  Please call Qhull::defineVertexNeighborFacets() beforehand.", id());
    +    }
    +    return QhullFacetSet(qh_qh, qh_vertex->neighbors);
    +}//neighborFacets
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using std::string;
    +using std::vector;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetSet;
    +using orgQhull::QhullFacetSetIterator;
    +using orgQhull::QhullVertex;
    +
    +//! Duplicate of qh_printvertex [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullVertex::PrintVertex &pr)
    +{
    +    QhullVertex v= *pr.vertex;
    +    QhullPoint p= v.point();
    +    if(*pr.print_message){
    +        os << pr.print_message << " ";
    +    }else{
    +        os << "- ";
    +    }
    +    os << "p" << p.id() << " (v" << v.id() << "): ";
    +    const realT *c= p.coordinates();
    +    for(int k= p.dimension(); k--; ){
    +        os << " " << *c++; // FIXUP QH11010 %5.2g
    +    }
    +    if(v.getVertexT()->deleted){
    +        os << " deleted";
    +    }
    +    if(v.getVertexT()->delridge){
    +        os << " ridgedeleted";
    +    }
    +    os << endl;
    +    if(v.neighborFacetsDefined()){
    +        QhullFacetSetIterator i= v.neighborFacets();
    +        if(i.hasNext()){
    +            os << " neighborFacets:";
    +            countT count= 0;
    +            while(i.hasNext()){
    +                if(++count % 100 == 0){
    +                    os << endl << "     ";
    +                }
    +                QhullFacet f= i.next();
    +                os << " f" << f.id();
    +            }
    +            os << endl;
    +        }
    +    }
    +    return os;
    +}//<< PrintVertex
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullVertex.h b/xs/src/qhull/src/libqhullcpp/QhullVertex.h
    new file mode 100644
    index 0000000000..0137766db5
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullVertex.h
    @@ -0,0 +1,104 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullVertex.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHULLVERTEX_H
    +#define QHULLVERTEX_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/QhullSet.h"
    +
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +    class QhullFacetSet;
    +
    +#//!\name Defined here
    +    //! QhullVertex -- Qhull's vertex structure, vertexT [libqhull_r.h], as a C++ class
    +    class QhullVertex;
    +    typedef QhullLinkedList QhullVertexList;
    +    typedef QhullLinkedListIterator QhullVertexListIterator;
    +
    +
    +/*********************
    +  topological information:
    +    next,previous       doubly-linked list of all vertices
    +    neighborFacets           set of adjacent facets (only if qh.VERTEXneighbors)
    +
    +  geometric information:
    +    point               array of DIM coordinates
    +*/
    +
    +class QhullVertex {
    +
    +#//!\name Defined here
    +public:
    +    typedef vertexT *   base_type;  // for QhullVertexSet
    +
    +private:
    +#//!\name Fields
    +    vertexT *           qh_vertex;  //!< Corresponding vertexT, never 0
    +    QhullQh *           qh_qh;      //!< QhullQh/qhT for vertexT, may be 0
    +
    +#//!\name Class objects
    +    static vertexT      s_empty_vertex;  // needed for shallow copy
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name Constructors
    +                        QhullVertex() : qh_vertex(&s_empty_vertex), qh_qh(0) {}
    +    explicit            QhullVertex(const Qhull &q);
    +                        QhullVertex(const Qhull &q, vertexT *v);
    +    explicit            QhullVertex(QhullQh *qqh) : qh_vertex(&s_empty_vertex), qh_qh(qqh) {}
    +                        QhullVertex(QhullQh *qqh, vertexT *v) : qh_vertex(v ? v : &s_empty_vertex), qh_qh(qqh) {}
    +                        // Creates an alias.  Does not copy QhullVertex.  Needed for return by value and parameter passing
    +                        QhullVertex(const QhullVertex &other) : qh_vertex(other.qh_vertex), qh_qh(other.qh_qh) {}
    +                        // Creates an alias.  Does not copy QhullVertex.  Needed for vector
    +    QhullVertex &       operator=(const QhullVertex &other) { qh_vertex= other.qh_vertex; qh_qh= other.qh_qh; return *this; }
    +                        ~QhullVertex() {}
    +
    +#//!\name GetSet
    +    int                 dimension() const { return (qh_qh ? qh_qh->hull_dim : 0); }
    +    vertexT *           getBaseT() const { return getVertexT(); } //!< For QhullSet
    +    vertexT *           getVertexT() const { return qh_vertex; }
    +    countT              id() const { return qh_vertex->id; }
    +    bool                isValid() const { return (qh_qh && qh_vertex != &s_empty_vertex); }
    +                        //! True if defineVertexNeighborFacets() already called.  Auotomatically set for facet merging, Voronoi diagrams
    +    bool                neighborFacetsDefined() const { return qh_vertex->neighbors != 0; }
    +    QhullVertex         next() const { return QhullVertex(qh_qh, qh_vertex->next); }
    +    bool                operator==(const QhullVertex &other) const { return qh_vertex==other.qh_vertex; }
    +    bool                operator!=(const QhullVertex &other) const { return !operator==(other); }
    +    QhullPoint          point() const { return QhullPoint(qh_qh, qh_vertex->point); }
    +    QhullVertex         previous() const { return QhullVertex(qh_qh, qh_vertex->previous); }
    +    QhullQh *           qh() const { return qh_qh; }
    +
    +#//!\name foreach
    +    //See also QhullVertexList
    +    QhullFacetSet       neighborFacets() const;
    +
    +#//!\name IO
    +    struct PrintVertex{
    +        const QhullVertex *vertex;
    +        const char *    print_message;    //!< non-null message
    +                        PrintVertex(const char *message, const QhullVertex &v) : vertex(&v), print_message(message) {}
    +    };//PrintVertex
    +    PrintVertex         print(const char *message) const { return PrintVertex(message, *this); }
    +};//class QhullVertex
    +
    +}//namespace orgQhull
    +
    +#//!\name GLobal
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertex::PrintVertex &pr);
    +inline std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertex &v) { os << v.print(""); return os; }
    +
    +#endif // QHULLVERTEX_H
    diff --git a/xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp b/xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp
    new file mode 100644
    index 0000000000..00ba62d196
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/QhullVertexSet.cpp
    @@ -0,0 +1,161 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/QhullVertexSet.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! QhullVertexSet -- Qhull's linked Vertexs, as a C++ class
    +
    +#include "libqhullcpp/QhullVertexSet.h"
    +
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::string;
    +using std::vector;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4611)  /* interaction between '_setjmp' and C++ object destruction is non-portable */
    +                                    /* setjmp should not be implemented with 'catch' */
    +#endif
    +
    +namespace orgQhull {
    +
    +QhullVertexSet::
    +QhullVertexSet(const Qhull &q, facetT *facetlist, setT *facetset, bool allfacets)
    +: QhullSet(q.qh(), 0)
    +, qhsettemp_defined(false)
    +{
    +    QH_TRY_(q.qh()){ // no object creation -- destructors skipped on longjmp()
    +        setT *vertices= qh_facetvertices(q.qh(), facetlist, facetset, allfacets);
    +        defineAs(vertices);
    +        qhsettemp_defined= true;
    +    }
    +    q.qh()->NOerrexit= true;
    +    q.qh()->maybeThrowQhullMessage(QH_TRY_status);
    +}//QhullVertexSet facetlist facetset
    +
    +//! Return tempory QhullVertexSet of vertices for a list and/or a set of facets
    +//! Sets qhsettemp_defined (disallows copy constructor and assignment to prevent double-free)
    +QhullVertexSet::
    +QhullVertexSet(QhullQh *qqh, facetT *facetlist, setT *facetset, bool allfacets)
    +: QhullSet(qqh, 0)
    +, qhsettemp_defined(false)
    +{
    +    QH_TRY_(qh()){ // no object creation -- destructors skipped on longjmp()
    +        setT *vertices= qh_facetvertices(qh(), facetlist, facetset, allfacets);
    +        defineAs(vertices);
    +        qhsettemp_defined= true;
    +    }
    +    qh()->NOerrexit= true;
    +    qh()->maybeThrowQhullMessage(QH_TRY_status);
    +}//QhullVertexSet facetlist facetset
    +
    +//! Copy constructor for argument passing and returning a result
    +//! Only copies a pointer to the set.
    +//! Throws an error if qhsettemp_defined, otherwise have a double-free
    +//!\todo Convert QhullVertexSet to a shared pointer with reference counting
    +QhullVertexSet::
    +QhullVertexSet(const QhullVertexSet &other)
    +: QhullSet(other)
    +, qhsettemp_defined(false)
    +{
    +    if(other.qhsettemp_defined){
    +        throw QhullError(10077, "QhullVertexSet: Cannot use copy constructor since qhsettemp_defined (e.g., QhullVertexSet for a set and/or list of QhFacet).  Contains %d vertices", other.count());
    +    }
    +}//copy constructor
    +
    +//! Copy assignment only copies a pointer to the set.
    +//! Throws an error if qhsettemp_defined, otherwise have a double-free
    +QhullVertexSet & QhullVertexSet::
    +operator=(const QhullVertexSet &other)
    +{
    +    QhullSet::operator=(other);
    +    qhsettemp_defined= false;
    +    if(other.qhsettemp_defined){
    +        throw QhullError(10078, "QhullVertexSet: Cannot use copy constructor since qhsettemp_defined (e.g., QhullVertexSet for a set and/or list of QhFacet).  Contains %d vertices", other.count());
    +    }
    +    return *this;
    +}//assignment
    +
    +void QhullVertexSet::
    +freeQhSetTemp()
    +{
    +    if(qhsettemp_defined){
    +        qhsettemp_defined= false;
    +        QH_TRY_(qh()){ // no object creation -- destructors skipped on longjmp()
    +            qh_settempfree(qh(), referenceSetT()); // errors if not top of tempstack or if qhmem corrupted
    +        }
    +        qh()->NOerrexit= true;
    +        qh()->maybeThrowQhullMessage(QH_TRY_status, QhullError::NOthrow);
    +    }
    +}//freeQhSetTemp
    +
    +QhullVertexSet::
    +~QhullVertexSet()
    +{
    +    freeQhSetTemp();
    +}//~QhullVertexSet
    +
    +//FIXUP -- Move conditional, QhullVertexSet code to QhullVertexSet.cpp
    +#ifndef QHULL_NO_STL
    +std::vector QhullVertexSet::
    +toStdVector() const
    +{
    +    QhullSetIterator i(*this);
    +    std::vector vs;
    +    while(i.hasNext()){
    +        QhullVertex v= i.next();
    +        vs.push_back(v);
    +    }
    +    return vs;
    +}//toStdVector
    +#endif //QHULL_NO_STL
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +using std::endl;
    +using std::ostream;
    +using orgQhull::QhullPoint;
    +using orgQhull::QhullVertex;
    +using orgQhull::QhullVertexSet;
    +using orgQhull::QhullVertexSetIterator;
    +
    +//! Print Vertex identifiers to stream.  Space prefix.  From qh_printVertexheader [io_r.c]
    +ostream &
    +operator<<(ostream &os, const QhullVertexSet::PrintIdentifiers &pr)
    +{
    +    os << pr.print_message;
    +    for(QhullVertexSet::const_iterator i= pr.vertex_set->begin(); i!=pr.vertex_set->end(); ++i){
    +        const QhullVertex v= *i;
    +        os << " v" << v.id();
    +    }
    +    os << endl;
    +    return os;
    +}//<
    +
    +namespace orgQhull {
    +
    +#//!\name Used here
    +
    +#//!\name Defined here
    +    //! QhullVertexSet -- a set of Qhull Vertices, as a C++ class.
    +    //! See Qhull
    +    class QhullVertexSet;
    +    typedef QhullSetIterator QhullVertexSetIterator;
    +
    +class QhullVertexSet : public QhullSet {
    +
    +private:
    +#//!\name Fields
    +    bool                qhsettemp_defined;  //! Set was allocated with qh_settemp()
    +
    +public:
    +#//!\name Constructor
    +                        QhullVertexSet(const Qhull &q, setT *s) : QhullSet(q, s), qhsettemp_defined(false) {}
    +                        QhullVertexSet(const Qhull &q, facetT *facetlist, setT *facetset, bool allfacets);
    +                        //Conversion from setT* is not type-safe.  Implicit conversion for void* to T
    +                        QhullVertexSet(QhullQh *qqh, setT *s) : QhullSet(qqh, s), qhsettemp_defined(false) {}
    +                        QhullVertexSet(QhullQh *qqh, facetT *facetlist, setT *facetset, bool allfacets);
    +                        //Copy constructor and assignment copies pointer but not contents.  Throws error if qhsettemp_defined.  Needed for return by value.
    +                        QhullVertexSet(const QhullVertexSet &other);
    +    QhullVertexSet &    operator=(const QhullVertexSet &other);
    +                        ~QhullVertexSet();
    +
    +private:                //!Default constructor disabled.  Will implement allocation later
    +                        QhullVertexSet();
    +public:
    +
    +#//!\name Destructor
    +    void                freeQhSetTemp();
    +
    +#//!\name Conversion
    +#ifndef QHULL_NO_STL
    +    std::vector toStdVector() const;
    +#endif //QHULL_NO_STL
    +#ifdef QHULL_USES_QT
    +    QList   toQList() const;
    +#endif //QHULL_USES_QT
    +
    +#//!\name IO
    +    struct PrintVertexSet{
    +        const QhullVertexSet *vertex_set;
    +        const char *    print_message;     //!< non-null message
    +                        
    +                        PrintVertexSet(const char *message, const QhullVertexSet *s) : vertex_set(s), print_message(message) {}
    +    };//PrintVertexSet
    +    const PrintVertexSet print(const char *message) const { return PrintVertexSet(message, this); }
    +
    +    struct PrintIdentifiers{
    +        const QhullVertexSet *vertex_set;
    +        const char *    print_message;    //!< non-null message
    +                        PrintIdentifiers(const char *message, const QhullVertexSet *s) : vertex_set(s), print_message(message) {}
    +    };//PrintIdentifiers
    +    PrintIdentifiers    printIdentifiers(const char *message) const { return PrintIdentifiers(message, this); }
    +
    +};//class QhullVertexSet
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertexSet::PrintVertexSet &pr);
    +std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertexSet::PrintIdentifiers &p);
    +inline std::ostream &operator<<(std::ostream &os, const orgQhull::QhullVertexSet &vs) { os << vs.print(""); return os; }
    +
    +#endif // QHULLVERTEXSET_H
    diff --git a/xs/src/qhull/src/libqhullcpp/RboxPoints.cpp b/xs/src/qhull/src/libqhullcpp/RboxPoints.cpp
    new file mode 100644
    index 0000000000..d7acd9fce0
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RboxPoints.cpp
    @@ -0,0 +1,224 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RboxPoints.cpp#3 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include "libqhullcpp/RboxPoints.h"
    +
    +#include "libqhullcpp/QhullError.h"
    +
    +#include 
    +
    +using std::cerr;
    +using std::endl;
    +using std::istream;
    +using std::ostream;
    +using std::ostringstream;
    +using std::string;
    +using std::vector;
    +using std::ws;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#pragma warning( disable : 4996)  // function was declared deprecated(strcpy, localtime, etc.)
    +#endif
    +
    +namespace orgQhull {
    +
    +#//! RboxPoints -- generate random PointCoordinates for qhull (rbox)
    +
    +
    +#//!\name Constructors
    +RboxPoints::
    +RboxPoints()
    +: PointCoordinates("rbox")
    +, rbox_new_count(0)
    +, rbox_status(qh_ERRnone)
    +, rbox_message()
    +{
    +    allocateQhullQh();
    +}
    +
    +//! Allocate and generate points according to rboxCommand
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +//! Same as appendPoints()
    +RboxPoints::
    +RboxPoints(const char *rboxCommand)
    +: PointCoordinates("rbox")
    +, rbox_new_count(0)
    +, rbox_status(qh_ERRnone)
    +, rbox_message()
    +{
    +    allocateQhullQh();
    +    // rbox arguments added to comment() via qh_rboxpoints > qh_fprintf_rbox
    +    appendPoints(rboxCommand);
    +}
    +
    +RboxPoints::
    +~RboxPoints()
    +{
    +    delete qh();
    +    resetQhullQh(0);
    +}
    +
    +// RboxPoints and qh_rboxpoints has several fields in qhT (rbox_errexit..cpp_object)
    +// It shares last_random with qh_rand and qh_srand
    +// The other fields are unused
    +void RboxPoints::
    +allocateQhullQh()
    +{
    +    QHULL_LIB_CHECK /* Check for compatible library */
    +    resetQhullQh(new QhullQh);
    +}//allocateQhullQh
    +
    +#//!\name Messaging
    +
    +void RboxPoints::
    +clearRboxMessage()
    +{
    +    rbox_status= qh_ERRnone;
    +    rbox_message.clear();
    +}//clearRboxMessage
    +
    +std::string RboxPoints::
    +rboxMessage() const
    +{
    +    if(rbox_status!=qh_ERRnone){
    +        return rbox_message;
    +    }
    +    if(isEmpty()){
    +        return "rbox warning: no points generated\n";
    +    }
    +    return "rbox: OK\n";
    +}//rboxMessage
    +
    +int RboxPoints::
    +rboxStatus() const
    +{
    +    return rbox_status;
    +}
    +
    +bool RboxPoints::
    +hasRboxMessage() const
    +{
    +    return (rbox_status!=qh_ERRnone);
    +}
    +
    +#//!\name Methods
    +
    +//! Appends points as defined by rboxCommand
    +//! Appends rboxCommand to comment
    +//! For rbox commands, see http://www.qhull.org/html/rbox.htm or html/rbox.htm
    +void RboxPoints::
    +appendPoints(const char *rboxCommand)
    +{
    +    string s("rbox ");
    +    s += rboxCommand;
    +    char *command= const_cast(s.c_str());
    +    if(qh()->cpp_object){
    +        throw QhullError(10001, "Qhull error: conflicting user of cpp_object for RboxPoints::appendPoints() or corrupted qh_qh");
    +    }
    +    if(extraCoordinatesCount()!=0){
    +        throw QhullError(10067, "Qhull error: Extra coordinates (%d) prior to calling RboxPoints::appendPoints.  Was %s", extraCoordinatesCount(), 0, 0.0, comment().c_str());
    +    }
    +    countT previousCount= count();
    +    qh()->cpp_object= this;           // for qh_fprintf_rbox()
    +    int status= qh_rboxpoints(qh(), command);
    +    qh()->cpp_object= 0;
    +    if(rbox_status==qh_ERRnone){
    +        rbox_status= status;
    +    }
    +    if(rbox_status!=qh_ERRnone){
    +        throw QhullError(rbox_status, rbox_message);
    +    }
    +    if(extraCoordinatesCount()!=0){
    +        throw QhullError(10002, "Qhull error: extra coordinates (%d) for PointCoordinates (%x)", extraCoordinatesCount(), 0, 0.0, coordinates());
    +    }
    +    if(previousCount+newCount()!=count()){
    +        throw QhullError(10068, "Qhull error: rbox specified %d points but got %d points for command '%s'", newCount(), count()-previousCount, 0.0, comment().c_str());
    +    }
    +}//appendPoints
    +
    +}//namespace orgQhull
    +
    +#//!\name Global functions
    +
    +/*---------------------------------
    +
    +  qh_fprintf_rbox(qh, fp, msgcode, format, list of args )
    +    fp is ignored (replaces qh_fprintf_rbox() in userprintf_rbox.c)
    +    cpp_object == RboxPoints
    +
    +notes:
    +    only called from qh_rboxpoints()
    +    same as fprintf() and Qhull.qh_fprintf()
    +    fgets() is not trapped like fprintf()
    +    Do not throw errors from here.  Use qh_errexit_rbox;
    +    A similar technique can be used for qh_fprintf to capture all of its output
    +*/
    +extern "C"
    +void qh_fprintf_rbox(qhT *qh, FILE*, int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    using namespace orgQhull;
    +
    +    if(!qh->cpp_object){
    +        qh_errexit_rbox(qh, 10072);
    +    }
    +    RboxPoints *out= reinterpret_cast(qh->cpp_object);
    +    va_start(args, fmt);
    +    if(msgcoderbox_message += newMessage;
    +        if(out->rbox_statusrbox_status>=MSG_STDERR){
    +            out->rbox_status= msgcode;
    +        }
    +        va_end(args);
    +        return;
    +    }
    +    switch(msgcode){
    +    case 9391:
    +    case 9392:
    +        out->rbox_message += "RboxPoints error: options 'h', 'n' not supported.\n";
    +        qh_errexit_rbox(qh, 10010);
    +        /* never returns */
    +    case 9393:  // FIXUP QH11026 countT vs. int
    +        {
    +            int dimension= va_arg(args, int);
    +            string command(va_arg(args, char*));
    +            countT count= va_arg(args, countT);
    +            out->setDimension(dimension);
    +            out->appendComment(" \"");
    +            out->appendComment(command.substr(command.find(' ')+1));
    +            out->appendComment("\"");
    +            out->setNewCount(count);
    +            out->reservePoints();
    +        }
    +        break;
    +    case 9407:
    +        *out << va_arg(args, int);
    +        // fall through
    +    case 9405:
    +        *out << va_arg(args, int);
    +        // fall through
    +    case 9403:
    +        *out << va_arg(args, int);
    +        break;
    +    case 9408:
    +        *out << va_arg(args, double);
    +        // fall through
    +    case 9406:
    +        *out << va_arg(args, double);
    +        // fall through
    +    case 9404:
    +        *out << va_arg(args, double);
    +        break;
    +    }
    +    va_end(args);
    +} /* qh_fprintf_rbox */
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/RboxPoints.h b/xs/src/qhull/src/libqhullcpp/RboxPoints.h
    new file mode 100644
    index 0000000000..e8ec72b14a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RboxPoints.h
    @@ -0,0 +1,69 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RboxPoints.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef RBOXPOINTS_H
    +#define RBOXPOINTS_H
    +
    +#include "libqhull_r/qhull_ra.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/PointCoordinates.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! RboxPoints -- generate random PointCoordinates for Qhull
    +    class RboxPoints;
    +
    +class RboxPoints : public PointCoordinates {
    +
    +private:
    +#//!\name Fields and friends
    +                        //! PointCoordinates.qh() is owned by RboxPoints
    +    countT              rbox_new_count;     //! Number of points for PointCoordinates
    +    int                 rbox_status;    //! error status from rboxpoints.  qh_ERRnone if none.
    +    std::string         rbox_message;   //! stderr from rboxpoints
    +
    +    // '::' is required for friend references
    +    friend void ::qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... );
    +
    +public:
    +#//!\name Construct
    +                        RboxPoints();
    +    explicit            RboxPoints(const char *rboxCommand);
    +                        ~RboxPoints();
    +private:                // Disable copy constructor and assignment.  RboxPoints owns QhullQh.
    +                        RboxPoints(const RboxPoints &);
    +                        RboxPoints &operator=(const RboxPoints &);
    +private:
    +    void                allocateQhullQh();
    +
    +public:
    +#//!\name GetSet
    +    void                clearRboxMessage();
    +    countT              newCount() const { return rbox_new_count; }
    +    std::string         rboxMessage() const;
    +    int                 rboxStatus() const;
    +    bool                hasRboxMessage() const;
    +    void                setNewCount(countT pointCount) { QHULL_ASSERT(pointCount>=0); rbox_new_count= pointCount; }
    +
    +#//!\name Modify
    +    void                appendPoints(const char* rboxCommand);
    +    using               PointCoordinates::appendPoints;
    +    void                reservePoints() { reserveCoordinates((count()+newCount())*dimension()); }
    +};//class RboxPoints
    +
    +}//namespace orgQhull
    +
    +#endif // RBOXPOINTS_H
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadError.cpp b/xs/src/qhull/src/libqhullcpp/RoadError.cpp
    new file mode 100644
    index 0000000000..1d41ec1bc1
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadError.cpp
    @@ -0,0 +1,158 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadError.cpp#2 $$Change: 2066 $
    +** $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! RoadError -- All exceptions thrown by Qhull are RoadErrors
    +#//! Do not throw RoadError's from destructors.  Use e.logError() instead.
    +
    +#include "libqhullcpp/RoadError.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +using std::cerr;
    +using std::cout;
    +using std::string;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Class fields
    +
    +//! Identifies error messages from Qhull and Road for web searches.
    +//! See QhullError.h#QHULLlastError and user.h#MSG_ERROR
    +const char * RoadError::
    +ROADtag= "QH";
    +
    +std::ostringstream RoadError::
    +global_log;
    +
    +#//!\name Constructor
    +
    +RoadError::
    +RoadError()
    +: error_code(0)
    +, log_event()
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(const RoadError &other)
    +: error_code(other.error_code)
    +, log_event(other.log_event)
    +, error_message(other.error_message)
    +{
    +}//copy construct
    +
    +RoadError::
    +RoadError(int code, const std::string &message)
    +: error_code(code)
    +, log_event(message.c_str())
    +, error_message(log_event.toString(ROADtag, error_code))
    +{
    +    log_event.cstr_1= error_message.c_str(); // overwrites initial value
    +}
    +
    +RoadError::
    +RoadError(int code, const char *fmt)
    +: error_code(code)
    +, log_event(fmt)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d)
    +: error_code(code)
    +, log_event(fmt, d)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2)
    +: error_code(code)
    +, log_event(fmt, d, d2)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f)
    +: error_code(code)
    +, log_event(fmt, d, d2, f)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, const char *s)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, s)
    +, error_message(log_event.toString(ROADtag, code)) // char * may go out of scope
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, const void *x)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, x)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, int i)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, i)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, long long i)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, i)
    +, error_message()
    +{ }
    +
    +RoadError::
    +RoadError(int code, const char *fmt, int d, int d2, float f, double e)
    +: error_code(code)
    +, log_event(fmt, d, d2, f, e)
    +, error_message()
    +{ }
    +
    +RoadError & RoadError::
    +operator=(const RoadError &other)
    +{
    +    error_code= other.error_code;
    +    error_message= other.error_message;
    +    log_event= other.log_event;
    +    return *this;
    +}//operator=
    +
    +#//!\name Virtual
    +const char * RoadError::
    +what() const throw()
    +{
    +    if(error_message.empty()){
    +        error_message= log_event.toString(ROADtag, error_code);
    +    }
    +    return error_message.c_str();
    +}//what
    +
    +#//!\name Updates
    +
    +//! Log error instead of throwing it.
    +//! Not reentrant, so avoid using it if possible
    +//!\todo Redesign with a thread-local stream or a reentrant ostringstream
    +void RoadError::
    +logErrorLastResort() const
    +{
    +    global_log << what() << endl;
    +}//logError
    +
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadError.h b/xs/src/qhull/src/libqhullcpp/RoadError.h
    new file mode 100644
    index 0000000000..1c9f6cdd5a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadError.h
    @@ -0,0 +1,88 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadError.h#4 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef ROADERROR_H
    +#define ROADERROR_H
    +
    +#include "libqhull_r/user_r.h"  /* for QHULL_CRTDBG */
    +#include "libqhullcpp/RoadLogEvent.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! RoadError -- Report and log errors
    +    //!  See discussion in Saylan, G., "Practical C++ error handling in hybrid environments," Dr. Dobb's Journal, p. 50-55, March 2007.
    +    //!   He uses an auto_ptr to track a stringstream.  It constructs a string on the fly.  RoadError uses the copy constructor to transform RoadLogEvent into a string
    +    class RoadError;
    +
    +class RoadError : public std::exception {
    +
    +private:
    +#//!\name Fields
    +    int                 error_code;  //! Non-zero code (not logged), maybe returned as program status
    +    RoadLogEvent        log_event;   //! Format string w/ arguments
    +    mutable std::string error_message;  //! Formated error message.  Must be after log_event.
    +
    +#//!\name Class fields
    +    static const char  *  ROADtag;
    +    static std::ostringstream  global_log; //!< May be replaced with any ostream object
    +                                    //!< Not reentrant -- only used by RoadError::logErrorLastResort()
    +
    +public:
    +#//!\name Constants
    +
    +#//!\name Constructors
    +    RoadError();
    +    RoadError(const RoadError &other);  //! Called on throw, generates error_message
    +    RoadError(int code, const std::string &message);
    +    RoadError(int code, const char *fmt);
    +    RoadError(int code, const char *fmt, int d);
    +    RoadError(int code, const char *fmt, int d, int d2);
    +    RoadError(int code, const char *fmt, int d, int d2, float f);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, const char *s);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, const void *x);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, int i);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, long long i);
    +    RoadError(int code, const char *fmt, int d, int d2, float f, double e);
    +
    +    RoadError &         operator=(const RoadError &other);
    +                        ~RoadError() throw() {};
    +
    +#//!\name Class methods
    +
    +    static void         clearGlobalLog() { global_log.seekp(0); }
    +    static bool         emptyGlobalLog() { return global_log.tellp()<=0; }
    +    static const char  *stringGlobalLog() { return global_log.str().c_str(); }
    +
    +#//!\name Virtual
    +    virtual const char *what() const throw();
    +
    +#//!\name GetSet
    +    bool                isValid() const { return log_event.isValid(); }
    +    int                 errorCode() const { return error_code; };
    +   // FIXUP QH11021 should RoadError provide errorMessage().  Currently what()
    +    RoadLogEvent        roadLogEvent() const { return log_event; };
    +
    +#//!\name Update
    +    void                logErrorLastResort() const;
    +};//class RoadError
    +
    +}//namespace orgQhull
    +
    +#//!\name Global
    +
    +inline std::ostream &   operator<<(std::ostream &os, const orgQhull::RoadError &e) { return os << e.what(); }
    +
    +#endif // ROADERROR_H
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp
    new file mode 100644
    index 0000000000..9a9cf5960a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.cpp
    @@ -0,0 +1,122 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadLogEvent.cpp#3 $$Change: 2066 $
    +** $Date: 2016/01/18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#//! RoadLogEvent -- All exceptions thrown by Qhull are RoadErrors
    +
    +#include "libqhullcpp/RoadLogEvent.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::string;
    +
    +#ifdef _MSC_VER  // Microsoft Visual C++ -- warning level 4
    +#endif
    +
    +namespace orgQhull {
    +
    +#//!\name Conversion
    +string RoadLogEvent::
    +toString(const char *tag, int code) const
    +{
    +    ostringstream os;
    +    if(tag && code){
    +        os << tag << code;
    +        if(format_string){
    +            os << " ";
    +        }
    +    }
    +    if(!format_string){
    +        return os.str();
    +    }
    +    const char *s= format_string;
    +    int dCount= 0;  // Count of %d
    +    int fCount= 0;  // Count of %f
    +    char extraCode= '\0';
    +    while(*s){
    +        if(*s!='%'){
    +            os << *s++;
    +        }else{
    +            char c= *++s;
    +            s++;
    +            switch(c){
    +            case 'd':
    +                if(++dCount>2){
    +                    os << " ERROR_three_%d_in_format ";
    +                }else if(dCount==2){
    +                    os << int_2;
    +                }else{
    +                    os << int_1;
    +                }
    +                break;
    +            case 'e':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << double_1;
    +                }
    +                break;
    +            case 'f':
    +                if(++fCount>1){
    +                    os << " ERROR_two_%f_in_format ";
    +                }else{
    +                    os << float_1;
    +                }
    +                break;
    +            case 'i':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << int64_1;
    +                }
    +                break;
    +            case 's':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << cstr_1;
    +                }
    +                break;
    +            case 'u':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << "0x" << std::hex << int64_1 << std::dec;
    +                }
    +                break;
    +            case 'x':
    +                if(firstExtraCode(os, c, &extraCode)){
    +                    os << void_1;
    +                }
    +                break;
    +            case '%':
    +                os << c;
    +                break;
    +            default:
    +                os << " ERROR_%" << c << "_not_defined_in_format";
    +                break;
    +            }
    +        }
    +    }
    +    if(s[-1]!='\n'){
    +        os << endl;
    +    }
    +    return os.str();
    +}//toString
    +
    +#//!\name Class helpers (static)
    +
    +//! True if this char is the first extra code
    +bool RoadLogEvent::
    +firstExtraCode(std::ostream &os, char c, char *extraCode){
    +    if(*extraCode){
    +        os << " ERROR_%" << *extraCode << "_and_%" << c << "_in_format ";
    +        return false;
    +    }
    +    *extraCode= c;
    +    return true;
    +}//firstExtraCode
    +
    +}//namespace orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/RoadLogEvent.h b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.h
    new file mode 100644
    index 0000000000..7c4cfba0de
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/RoadLogEvent.h
    @@ -0,0 +1,77 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/RoadLogEvent.h#1 $$Change: 1981 $
    +** $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef ROADLOGEVENT_H
    +#define ROADLOGEVENT_H
    +
    +#include 
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +    //! RoadLogEvent -- Record an event for the RoadLog
    +    struct RoadLogEvent;
    +
    +struct RoadLogEvent {
    +
    +public:
    +#//!\name Fields
    +    const char *    format_string; //! Format string (a literal with format codes, for logging)
    +    int             int_1;       //! Integer argument (%d, for logging)
    +    int             int_2;       //! Integer argument (%d, for logging)
    +    float           float_1;     //! Float argument (%f, for logging)
    +    union {                      //! One additional argument (for logging)
    +        const char *cstr_1;      //!   Cstr argument (%s) -- type checked at construct-time
    +        const void *void_1;      //!   Void* argument (%x) -- Use upper-case codes for object types
    +        long long   int64_1;     //!   signed int64 (%i).  Ambiguous if unsigned is also defined.
    +        double      double_1;    //!   Double argument (%e)
    +    };
    +
    +#//!\name Constants
    +
    +#//!\name Constructors
    +    RoadLogEvent() : format_string(0), int_1(0), int_2(0), float_1(0), int64_1(0) {};
    +    explicit RoadLogEvent(const char *fmt) : format_string(fmt), int_1(0), int_2(0), float_1(0), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d) : format_string(fmt), int_1(d), int_2(0), float_1(0), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d, int d2) : format_string(fmt), int_1(d), int_2(d2), float_1(0), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f) : format_string(fmt), int_1(d), int_2(d2), float_1(f), int64_1(0) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, const char *s) : format_string(fmt), int_1(d), int_2(d2), float_1(f), cstr_1(s) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, const void *x) : format_string(fmt), int_1(d), int_2(d2), float_1(f), void_1(x) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, int i) : format_string(fmt), int_1(d), int_2(d2), float_1(f), int64_1(i) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, long long i) : format_string(fmt), int_1(d), int_2(d2), float_1(f), int64_1(i) {};
    +    RoadLogEvent(const char *fmt, int d, int d2, float f, double g) : format_string(fmt), int_1(d), int_2(d2), float_1(f), double_1(g) {};
    +    ~RoadLogEvent() {};
    +    //! Default copy constructor and assignment
    +
    +#//!\name GetSet
    +    bool                isValid() const { return format_string!=0; }
    +    int                 int1() const { return int_1; };
    +    int                 int2() const { return int_2; };
    +    float               float1() const { return float_1; };
    +    const char *        format() const { return format_string; };
    +    const char *        cstr1() const { return cstr_1; };
    +    const void *        void1() const { return void_1; };
    +    long long           int64() const { return int64_1; };
    +    double              double1() const { return double_1; };
    +
    +#//!\name Conversion
    +
    +    std::string        toString(const char* tag, int code) const;
    +
    +private:
    +#//!\name Class helpers
    +    static bool         firstExtraCode(std::ostream &os, char c, char *extraCode);
    +
    +
    +};//class RoadLogEvent
    +
    +}//namespace orgQhull
    +
    +#endif // ROADLOGEVENT_H
    diff --git a/xs/src/qhull/src/libqhullcpp/functionObjects.h b/xs/src/qhull/src/libqhullcpp/functionObjects.h
    new file mode 100644
    index 0000000000..3645c0713a
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/functionObjects.h
    @@ -0,0 +1,67 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/functionObjects.h#1 $$Change: 1981 $
    +** $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef QHFUNCTIONOBJECTS_H
    +#define QHFUNCTIONOBJECTS_H
    +
    +#include 
    +#include 
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +
    +    //! Sum of absolute values of the elements in a container
    +    class AbsoluteSumOf;
    +    //! Sum of the elements in a container
    +    class SumOf;
    +    //! Sum of squares of the elements in a container
    +    class SumSquaresOf;
    +
    +#//!\name Class
    +
    +//! Absolute sum of the elements in a container
    +class AbsoluteSumOf
    +{
    +private:
    +    double sum;
    +public:
    +    inline AbsoluteSumOf() : sum(0.0) {}
    +    inline void operator()(double v) { sum += fabs(v); }
    +    inline operator double() { return sum; }
    +};//AbsoluteSumOf
    +
    +//! Sum of the elements in a container
    +class SumOf
    +{
    +private:
    +    double sum;
    +public:
    +    inline SumOf() : sum(0.0) {}
    +    inline void operator()(double v) { sum += v; }
    +    inline operator double() { return sum; }
    +};//SumOf
    +
    +
    +//! Sum of squares of the elements in a container
    +class SumSquaresOf
    +{
    +private:
    +    double sum;
    +public:
    +    inline SumSquaresOf() : sum(0.0) {}
    +    inline void operator()(double v) { sum += v*v; }
    +    inline operator double() { return sum; }
    +};//SumSquaresOf
    +
    +
    +}//orgQhull
    +
    +
    +#endif //QHFUNCTIONOBJECTS_H
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/libqhullcpp.pro b/xs/src/qhull/src/libqhullcpp/libqhullcpp.pro
    new file mode 100644
    index 0000000000..89b967bef2
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/libqhullcpp.pro
    @@ -0,0 +1,71 @@
    +# -------------------------------------------------
    +# libqhullcpp.pro -- Qt project for Qhull cpp shared library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +TEMPLATE = lib
    +# Do not create libqhullcpp as a shared library.  Qhull C++ classes may change layout and size. 
    +CONFIG += staticlib warn_on
    +CONFIG -= qt rtti
    +build_pass:CONFIG(debug, debug|release):{
    +   TARGET = qhullcpp_d
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +   TARGET = qhullcpp
    +   OBJECTS_DIR = Release
    +}
    +MOC_DIR = moc
    +
    +INCLUDEPATH += ../../src
    +INCLUDEPATH += $$PWD # for MOC_DIR
    +
    +CONFIG += qhull_warn_shadow qhull_warn_conversion
    +
    +SOURCES += ../libqhullcpp/Coordinates.cpp
    +SOURCES += ../libqhullcpp/PointCoordinates.cpp
    +SOURCES += ../libqhullcpp/Qhull.cpp
    +SOURCES += ../libqhullcpp/QhullFacet.cpp
    +SOURCES += ../libqhullcpp/QhullFacetList.cpp
    +SOURCES += ../libqhullcpp/QhullFacetSet.cpp
    +SOURCES += ../libqhullcpp/QhullHyperplane.cpp
    +SOURCES += ../libqhullcpp/QhullPoint.cpp
    +SOURCES += ../libqhullcpp/QhullPoints.cpp
    +SOURCES += ../libqhullcpp/QhullPointSet.cpp
    +SOURCES += ../libqhullcpp/QhullQh.cpp
    +SOURCES += ../libqhullcpp/QhullRidge.cpp
    +SOURCES += ../libqhullcpp/QhullSet.cpp
    +SOURCES += ../libqhullcpp/QhullStat.cpp
    +SOURCES += ../libqhullcpp/QhullVertex.cpp
    +SOURCES += ../libqhullcpp/QhullVertexSet.cpp
    +SOURCES += ../libqhullcpp/RboxPoints.cpp
    +SOURCES += ../libqhullcpp/RoadError.cpp
    +SOURCES += ../libqhullcpp/RoadLogEvent.cpp
    +
    +HEADERS += ../libqhullcpp/Coordinates.h
    +HEADERS += ../libqhullcpp/functionObjects.h
    +HEADERS += ../libqhullcpp/PointCoordinates.h
    +HEADERS += ../libqhullcpp/Qhull.h
    +HEADERS += ../libqhullcpp/QhullError.h
    +HEADERS += ../libqhullcpp/QhullFacet.h
    +HEADERS += ../libqhullcpp/QhullFacetList.h
    +HEADERS += ../libqhullcpp/QhullFacetSet.h
    +HEADERS += ../libqhullcpp/QhullHyperplane.h
    +HEADERS += ../libqhullcpp/QhullIterator.h
    +HEADERS += ../libqhullcpp/QhullLinkedList.h
    +HEADERS += ../libqhullcpp/QhullPoint.h
    +HEADERS += ../libqhullcpp/QhullPoints.h
    +HEADERS += ../libqhullcpp/QhullPointSet.h
    +HEADERS += ../libqhullcpp/QhullQh.h
    +HEADERS += ../libqhullcpp/QhullRidge.h
    +HEADERS += ../libqhullcpp/QhullSet.h
    +HEADERS += ../libqhullcpp/QhullSets.h
    +HEADERS += ../libqhullcpp/QhullStat.h
    +HEADERS += ../libqhullcpp/QhullVertex.h
    +HEADERS += ../libqhullcpp/QhullVertexSet.h
    +HEADERS += ../libqhullcpp/RboxPoints.h
    +HEADERS += ../libqhullcpp/RoadError.h
    +HEADERS += ../libqhullcpp/RoadLogEvent.h
    diff --git a/xs/src/qhull/src/libqhullcpp/qt-qhull.cpp b/xs/src/qhull/src/libqhullcpp/qt-qhull.cpp
    new file mode 100644
    index 0000000000..895f591a85
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/qt-qhull.cpp
    @@ -0,0 +1,130 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/libqhullcpp/qt-qhull.cpp#1 $$Change: 1981 $
    +** $DateTime: 2015/09/28 20:26:32 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#include 
    +#include "qhulltest/RoadTest.h"
    +
    +#ifndef QHULL_USES_QT
    +#define QHULL_USES_QT 1
    +#endif
    +
    +#include "Coordinates.h"
    +#include "QhullFacetList.h"
    +#include "QhullFacetSet.h"
    +#include "QhullHyperplane.h"
    +#include "QhullPoint.h"
    +#include "QhullPoints.h"
    +#include "QhullPointSet.h"
    +#include "QhullVertex.h"
    +#include "QhullVertexSet.h"
    +
    +namespace orgQhull {
    +
    +#//!\name Conversions
    +
    +QList Coordinates::
    +toQList() const
    +{
    +    CoordinatesIterator i(*this);
    +    QList cs;
    +    while(i.hasNext()){
    +        cs.append(i.next());
    +    }
    +    return cs;
    +}//toQList
    +
    +QList QhullFacetList::
    +toQList() const
    +{
    +    QhullLinkedListIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.append(f);
    +        }
    +    }
    +    return vs;
    +}//toQList
    +
    +//! Same as PrintVertices
    +QList QhullFacetList::
    +vertices_toQList() const
    +{
    +    QList vs;
    +    QhullVertexSet qvs(qh(), first().getFacetT(), NULL, isSelectAll());
    +    for(QhullVertexSet::iterator i=qvs.begin(); i!=qvs.end(); ++i){
    +        vs.push_back(*i);
    +    }
    +    return vs;
    +}//vertices_toQList
    +
    +QList QhullFacetSet::
    +toQList() const
    +{
    +    QhullSetIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        QhullFacet f= i.next();
    +        if(isSelectAll() || f.isGood()){
    +            vs.append(f);
    +        }
    +    }
    +    return vs;
    +}//toQList
    +
    +#ifdef QHULL_USES_QT
    +QList QhullHyperplane::
    +toQList() const
    +{
    +    QhullHyperplaneIterator i(*this);
    +    QList fs;
    +    while(i.hasNext()){
    +        fs.append(i.next());
    +    }
    +    fs.append(hyperplane_offset);
    +    return fs;
    +}//toQList
    +#endif //QHULL_USES_QT
    +
    +QList QhullPoint::
    +toQList() const
    +{
    +    QhullPointIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        vs.append(i.next());
    +    }
    +    return vs;
    +}//toQList
    +
    +QList QhullPoints::
    +toQList() const
    +{
    +    QhullPointsIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        vs.append(i.next());
    +    }
    +    return vs;
    +}//toQList
    +
    +/******
    +QList QhullPointSet::
    +toQList() const
    +{
    +    QhullPointSetIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        vs.append(i.next());
    +    }
    +    return vs;
    +}//toQList
    +*/
    +}//orgQhull
    +
    diff --git a/xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp b/xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp
    new file mode 100644
    index 0000000000..bb9534d098
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullcpp/usermem_r-cpp.cpp
    @@ -0,0 +1,93 @@
    +/*
      ---------------------------------
    +
    +   usermem_r-cpp.cpp
    +
    +   Redefine qh_exit() as 'throw std::runtime_error("QH10003 ...")'
    +
    +   This file is not included in the Qhull builds.
    +
    +   qhull_r calls qh_exit() when qh_errexit() is not available.  For example,
    +   it calls qh_exit() if you linked the wrong qhull library.
    +
    +   The C++ interface avoids most of the calls to qh_exit().
    +
    +   If needed, include usermem_r-cpp.o before libqhullstatic_r.a.  You may need to
    +   override duplicate symbol errors (e.g. /FORCE:MULTIPLE for DevStudio).  It
    +   may produce a warning about throwing an error from C code.
    +*/
    +
    +extern "C" {
    +    void    qh_exit(int exitcode);
    +}
    +
    +#include 
    +#include 
    +
    +/*---------------------------------
    +
    +  qh_exit( exitcode )
    +    exit program
    +
    +  notes:
    +    same as exit()
    +*/
    +void qh_exit(int exitcode) {
    +    exitcode= exitcode;
    +    throw std::runtime_error("QH10003 Qhull error.  See stderr or errfile.");
    +} /* exit */
    +
    +/*---------------------------------
    +
    +  qh_fprintf_stderr( msgcode, format, list of args )
    +    fprintf to stderr with msgcode (non-zero)
    +
    +  notes:
    +    qh_fprintf_stderr() is called when qh->ferr is not defined, usually due to an initialization error
    +
    +    It is typically followed by qh_errexit().
    +
    +    Redefine this function to avoid using stderr
    +
    +    Use qh_fprintf [userprintf_r.c] for normal printing
    +*/
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +      fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +
    +/*---------------------------------
    +
    +  qh_free(qhT *qh, mem )
    +    free memory
    +
    +  notes:
    +    same as free()
    +    No calls to qh_errexit()
    +*/
    +void qh_free(void *mem) {
    +    free(mem);
    +} /* free */
    +
    +/*---------------------------------
    +
    +    qh_malloc( mem )
    +      allocate memory
    +
    +    notes:
    +      same as malloc()
    +*/
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +} /* malloc */
    +
    +
    diff --git a/xs/src/qhull/src/libqhullstatic/libqhullstatic.pro b/xs/src/qhull/src/libqhullstatic/libqhullstatic.pro
    new file mode 100644
    index 0000000000..1a516db73c
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullstatic/libqhullstatic.pro
    @@ -0,0 +1,19 @@
    +# -------------------------------------------------
    +# libqhullstatic.pro -- Qt project for Qhull static library
    +#   Built with qh_QHpointer=0.  See libqhullp.pro
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +include(../qhull-libqhull-src.pri)
    +
    +DESTDIR = ../../lib
    +TEMPLATE = lib
    +CONFIG += staticlib warn_on
    +CONFIG -= qt
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhullstatic_d
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhullstatic
    +    OBJECTS_DIR = Release
    +}
    diff --git a/xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro b/xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro
    new file mode 100644
    index 0000000000..2f5bf4d076
    --- /dev/null
    +++ b/xs/src/qhull/src/libqhullstatic_r/libqhullstatic_r.pro
    @@ -0,0 +1,21 @@
    +# -------------------------------------------------
    +# libqhullstatic_r.pro -- Qt project for Qhull static library
    +#
    +# It uses reeentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +DESTDIR = ../../lib
    +TEMPLATE = lib
    +CONFIG += staticlib warn_on
    +CONFIG -= qt
    +build_pass:CONFIG(debug, debug|release):{
    +    TARGET = qhullstatic_rd
    +    OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release):{
    +    TARGET = qhullstatic_r
    +    OBJECTS_DIR = Release
    +}
    +
    +include(../qhull-libqhull-src_r.pri)
    diff --git a/xs/src/qhull/src/qconvex/qconvex.c b/xs/src/qhull/src/qconvex/qconvex.c
    new file mode 100644
    index 0000000000..41bd666da1
    --- /dev/null
    +++ b/xs/src/qhull/src/qconvex/qconvex.c
    @@ -0,0 +1,326 @@
    +/*
      ---------------------------------
    +
    +   qconvex.c
    +      compute convex hulls using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qconvex
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qconvex.htm */
    +char hidden_options[]=" d v H Qbb Qf Qg Qm Qr Qu Qv Qx Qz TR E V Fp Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qconvex- compute the convex hull\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar point\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    n    - normals with offsets\n\
    +    o    - OFF file format (dim, points and facets; Voronoi regions)\n\
    +    p    - point coordinates \n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +    FI   - ID for each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest vertex for each coplanar point\n\
    +    FQ   - command used for qconvex\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      for output: #vertices, #facets,\n\
    +                                  #coplanar points, #non-simplicial facets\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0) \n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d, and 4-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qconvex- compute the convex hull.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qconvex.htm):\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (includes coplanar points if 'Qc')\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - report total area and volume\n\
    +    FS   - compute total area and volume\n\
    +    o    - OFF format (dim, n, points, facets)\n\
    +    G    - Geomview output (2-d, 3-d, and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c D2 | qconvex s n                    rbox c D2 | qconvex i\n\
    +    rbox c D2 | qconvex o                      rbox 1000 s | qconvex s Tv FA\n\
    +    rbox c d D2 | qconvex s Qc Fx              rbox y 1000 W0 | qconvex s n\n\
    +    rbox y 1000 W0 | qconvex s QJ              rbox d G1 D12 | qconvex QR0 FA Pp\n\
    +    rbox c D7 | qconvex FA TF1000\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary        facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoplanars     FCentrums      Fd_cdd_in\n\
    + FD_cdd_out     FFacet_xridge  Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh_vertex  Fouter         FOptions       FPoint_near\n\
    + FQhull         Fsummary       FSize          Fvertices      FVertex_ave\n\
    + Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   PFacet_area_keep Pgood        PGood_neighbors\n\
    + PMerge_keep    Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  QbB_scale_box  Qcoplanar      QGood_point    Qinterior\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit = False;
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("Qxact_merge", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qconvex/qconvex.pro b/xs/src/qhull/src/qconvex/qconvex.pro
    new file mode 100644
    index 0000000000..1bf631bff6
    --- /dev/null
    +++ b/xs/src/qhull/src/qconvex/qconvex.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qconvex.pro -- Qt project file for qconvex.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qconvex
    +
    +SOURCES += qconvex.c
    diff --git a/xs/src/qhull/src/qconvex/qconvex_r.c b/xs/src/qhull/src/qconvex/qconvex_r.c
    new file mode 100644
    index 0000000000..abf68ce37e
    --- /dev/null
    +++ b/xs/src/qhull/src/qconvex/qconvex_r.c
    @@ -0,0 +1,328 @@
    +/*
      ---------------------------------
    +
    +   qconvex.c
    +      compute convex hulls using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qconvex
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qconvex.htm */
    +char hidden_options[]=" d v H Qbb Qf Qg Qm Qr Qu Qv Qx Qz TR E V Fp Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qconvex- compute the convex hull\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar point\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    n    - normals with offsets\n\
    +    o    - OFF file format (dim, points and facets; Voronoi regions)\n\
    +    p    - point coordinates \n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +    FI   - ID for each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest vertex for each coplanar point\n\
    +    FQ   - command used for qconvex\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      for output: #vertices, #facets,\n\
    +                                  #coplanar points, #non-simplicial facets\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0) \n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d, and 4-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qconvex- compute the convex hull.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qconvex.htm):\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (includes coplanar points if 'Qc')\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - report total area and volume\n\
    +    FS   - compute total area and volume\n\
    +    o    - OFF format (dim, n, points, facets)\n\
    +    G    - Geomview output (2-d, 3-d, and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c D2 | qconvex s n                    rbox c D2 | qconvex i\n\
    +    rbox c D2 | qconvex o                      rbox 1000 s | qconvex s Tv FA\n\
    +    rbox c d D2 | qconvex s Qc Fx              rbox y 1000 W0 | qconvex s n\n\
    +    rbox y 1000 W0 | qconvex s QJ              rbox d G1 D12 | qconvex QR0 FA Pp\n\
    +    rbox c D7 | qconvex FA TF1000\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary        facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoplanars     FCentrums      Fd_cdd_in\n\
    + FD_cdd_out     FFacet_xridge  Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh_vertex  Fouter         FOptions       FPoint_near\n\
    + FQhull         Fsummary       FSize          Fvertices      FVertex_ave\n\
    + Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   PFacet_area_keep Pgood        PGood_neighbors\n\
    + PMerge_keep    Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  QbB_scale_box  Qcoplanar      QGood_point    Qinterior\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qdelaunay/qdelaun.c b/xs/src/qhull/src/qdelaunay/qdelaun.c
    new file mode 100644
    index 0000000000..9011d9fcc0
    --- /dev/null
    +++ b/xs/src/qhull/src/qdelaunay/qdelaun.c
    @@ -0,0 +1,315 @@
    +/*
      ---------------------------------
    +
    +   qdelaun.c
    +     compute Delaunay triangulations and furthest-point Delaunay
    +     triangulations using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qdelau_f.htm and qdelaun.htm */
    +char hidden_options[]=" d n v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V FC Fi Fo Ft Fp FV Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qdelaunay- compute the Delaunay triangulation\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - print Delaunay region if visible from point n, -n if not\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    m    - Mathematica output (2-d only, lifted to a paraboloid)\n\
    +    o    - OFF format (dim, points, and facets as a paraboloid)\n\
    +    p    - point coordinates (lifted to a paraboloid)\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each Delaunay region\n\
    +    FA   - compute total area for option 's'\n\
    +    Fc   - count plus coincident points for each Delaunay region\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each Delaunay region\n\
    +    Fm   - merge count for each Delaunay region (511 max)\n\
    +    FM   - Maple output (2-d only, lifted to a paraboloid)\n\
    +    Fn   - count plus neighboring region for each Delaunay region\n\
    +    FN   - count plus neighboring region for each point\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qdelaunay\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #vertices, #Delaunay regions,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2), tot area, 0\n\
    +    Fv   - count plus vertices for each Delaunay region\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d and 3-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc     - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - transparent outer ridges to view 3-d Delaunay\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Delaunay regions by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Delaunay regions (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Delaunay regions whose area is at least n\n\
    +    PG   - print neighbors of good regions (needs 'QGn' or 'QVn')\n\
    +    PMn  - keep n Delaunay regions with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qdelaunay- compute the Delaunay triangulation.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qdelaun.htm):\n\
    +    Qu   - furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    Fx   - extreme points (vertices of the convex hull)\n\
    +    o    - OFF format (shows the points lifted to a paraboloid)\n\
    +    G    - Geomview output (2-d and 3-d points lifted to a paraboloid)\n\
    +    m    - Mathematica output (2-d inputs lifted to a paraboloid)\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c P0 D2 | qdelaunay s o          rbox c P0 D2 | qdelaunay i\n\
    +    rbox c P0 D2 | qdelaunay Fv           rbox c P0 D2 | qdelaunay s Qu Fv\n\
    +    rbox c G1 d D2 | qdelaunay s i        rbox c G1 d D2 | qdelaunay Qt\n\
    +    rbox M3,4 z 100 D2 | qdelaunay s      rbox M3,4 z 100 D2 | qdelaunay s Qt\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    OFF_format     points_lifted  summary\n\
    + facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoincident    Fd_cdd_in      FD_cdd_out\n\
    + FF_dump_xridge FIDs           Fmerges        Fneighbors     FNeigh_vertex\n\
    + FOptions       FPoint_near    FQdelaun       Fsummary       FSize\n\
    + Fvertices      Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QGood_point    QJoggle        Qsearch_1st    Qtriangulate   QupperDelaunay\n\
    + QVertex_good   Qzinfinite\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit = False;
    +    qh_option("delaunay  Qbbound-last", NULL, NULL);
    +    qh DELAUNAY= True;     /* 'd'   */
    +    qh SCALElast= True;    /* 'Qbb' */
    +    qh KEEPcoplanar= True; /* 'Qc', to keep coplanars in 'p' */
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("Qxact_merge", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qdelaunay/qdelaun_r.c b/xs/src/qhull/src/qdelaunay/qdelaun_r.c
    new file mode 100644
    index 0000000000..0854b8bb97
    --- /dev/null
    +++ b/xs/src/qhull/src/qdelaunay/qdelaun_r.c
    @@ -0,0 +1,317 @@
    +/*
      ---------------------------------
    +
    +   qdelaun.c
    +     compute Delaunay triangulations and furthest-point Delaunay
    +     triangulations using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qdelau_f.htm and qdelaun.htm */
    +char hidden_options[]=" d n v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V FC Fi Fo Ft Fp FV Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qdelaunay- compute the Delaunay triangulation\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - print Delaunay region if visible from point n, -n if not\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for outside point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    m    - Mathematica output (2-d only, lifted to a paraboloid)\n\
    +    o    - OFF format (dim, points, and facets as a paraboloid)\n\
    +    p    - point coordinates (lifted to a paraboloid)\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each Delaunay region\n\
    +    FA   - compute total area for option 's'\n\
    +    Fc   - count plus coincident points for each Delaunay region\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each Delaunay region\n\
    +    Fm   - merge count for each Delaunay region (511 max)\n\
    +    FM   - Maple output (2-d only, lifted to a paraboloid)\n\
    +    Fn   - count plus neighboring region for each Delaunay region\n\
    +    FN   - count plus neighboring region for each point\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qdelaunay\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #vertices, #Delaunay regions,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2), tot area, 0\n\
    +    Fv   - count plus vertices for each Delaunay region\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d and 3-d)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc     - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - transparent outer ridges to view 3-d Delaunay\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Delaunay regions by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Delaunay regions (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Delaunay regions whose area is at least n\n\
    +    PG   - print neighbors of good regions (needs 'QGn' or 'QVn')\n\
    +    PMn  - keep n Delaunay regions with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qdelaunay- compute the Delaunay triangulation.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qdelaun.htm):\n\
    +    Qu   - furthest-site Delaunay triangulation\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each Delaunay region\n\
    +    Fx   - extreme points (vertices of the convex hull)\n\
    +    o    - OFF format (shows the points lifted to a paraboloid)\n\
    +    G    - Geomview output (2-d and 3-d points lifted to a paraboloid)\n\
    +    m    - Mathematica output (2-d inputs lifted to a paraboloid)\n\
    +    QVn  - print Delaunay regions that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox c P0 D2 | qdelaunay s o          rbox c P0 D2 | qdelaunay i\n\
    +    rbox c P0 D2 | qdelaunay Fv           rbox c P0 D2 | qdelaunay s Qu Fv\n\
    +    rbox c G1 d D2 | qdelaunay s i        rbox c G1 d D2 | qdelaunay Qt\n\
    +    rbox M3,4 z 100 D2 | qdelaunay s      rbox M3,4 z 100 D2 | qdelaunay s Qt\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + incidences     mathematica    OFF_format     points_lifted  summary\n\
    + facet_dump\n\
    +\n\
    + Farea          FArea_total    Fcoincident    Fd_cdd_in      FD_cdd_out\n\
    + FF_dump_xridge FIDs           Fmerges        Fneighbors     FNeigh_vertex\n\
    + FOptions       FPoint_near    FQdelaun       Fsummary       FSize\n\
    + Fvertices      Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QGood_point    QJoggle        Qsearch_1st    Qtriangulate   QupperDelaunay\n\
    + QVertex_good   Qzinfinite\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_option(qh, "delaunay  Qbbound-last", NULL, NULL);
    +    qh->DELAUNAY= True;     /* 'd'   */
    +    qh->SCALElast= True;    /* 'Qbb' */
    +    qh->KEEPcoplanar= True; /* 'Qc', to keep coplanars in 'p' */
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qdelaunay/qdelaunay.pro b/xs/src/qhull/src/qdelaunay/qdelaunay.pro
    new file mode 100644
    index 0000000000..138ac0589d
    --- /dev/null
    +++ b/xs/src/qhull/src/qdelaunay/qdelaunay.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qdelaunay.pro -- Qt project file for qvoronoi.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qdelaunay
    +
    +SOURCES += qdelaun.c
    diff --git a/xs/src/qhull/src/qhalf/qhalf.c b/xs/src/qhull/src/qhalf/qhalf.c
    new file mode 100644
    index 0000000000..4a5889ed78
    --- /dev/null
    +++ b/xs/src/qhull/src/qhalf/qhalf.c
    @@ -0,0 +1,316 @@
    +/*
      ---------------------------------
    +
    +   qhalf.c
    +     compute the intersection of halfspaces about a point
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qhalf.htm */
    +char hidden_options[]=" d n v Qbb QbB Qf Qg Qm Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qhalf- compute the intersection of halfspaces about a point\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    optional interior point: dimension, 1, coordinates\n\
    +    first lines: dimension+1 and number of halfspaces\n\
    +    other lines: halfspace coefficients followed by offset\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar halfspaces\n\
    +    Qi   - keep other redundant halfspaces\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    Qs   - search all halfspaces for the initial simplex\n\
    +    QGn  - print intersection if visible to halfspace n, -n for not\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when halfspace n added to intersection\n\
    +    TMn  - turn on tracing at merge n\n\
    +    TWn  - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding halfspace n, -n for before (see TCn)\n\
    +    TCn  - stop qhull after building cone for halfspace n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar halfspace\n\
    +    Wn   - min facet width for outside halfspace (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    i    - non-redundant halfspaces incident to each intersection\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    o    - OFF format (dual convex hull: dimension, points, and facets)\n\
    +    p    - vertex coordinates of dual convex hull (coplanars if 'Qc' or 'Qi')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus redundant halfspaces for each intersection\n\
    +         -   Qc (default) for coplanar and Qi for other redundant\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each intersection\n\
    +    Fm   - merge count for each intersection (511 max)\n\
    +    FM   - Maple output (dual convex hull)\n\
    +    Fn   - count plus neighboring intersections for each intersection\n\
    +    FN   - count plus intersections for each non-redundant halfspace\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates\n\
    +    FP   - nearest halfspace and distance for each redundant halfspace\n\
    +    FQ   - command used for qhalf\n\
    +    Fs   - summary: #int (8), dim, #halfspaces, #non-redundant, #intersections\n\
    +                      for output: #non-redundant, #intersections, #coplanar\n\
    +                                  halfspaces, #non-simplicial intersections\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    Fv   - count plus non-redundant halfspaces for each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d and 4-d; dual convex hull)\n\
    +    Ga   - all points (i.e., transformed halfspaces) as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices (i.e., non-redundant halfspaces) as spheres\n\
    +    Gi   - inner planes (i.e., halfspace intersections) only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets (i.e., intersections) by area\n\
    +    Pdk:n- drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n- drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhalf- halfspace intersection about a point.  Qhull %s\n\
    +    input (stdin): [dim, 1, interior point], dim+1, n, coefficients+offset\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qhalf.htm):\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    Fp   - intersection coordinates\n\
    +    Fv   - non-redundant halfspaces incident to each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +    o    - OFF file format (dual convex hull)\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox d | qconvex FQ n | qhalf s H0,0,0 Fp\n\
    +    rbox c | qconvex FQ FV n | qhalf s i\n\
    +    rbox c | qconvex FQ FV n | qhalf s o\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper_case options take an argument.\n\
    +\n\
    + incidences     Geomview       mathematica    OFF_format     point_dual\n\
    + summary        facet_dump\n\
    +\n\
    + Fc_redundant   Fd_cdd_in      FF_dump_xridge FIDs           Fmerges\n\
    + Fneighbors     FN_intersect   FOptions       Fp_coordinates FP_nearest\n\
    + FQhalf         Fsummary       Fv_halfspace   FMaple         Fx_non_redundant\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + Qbk:0Bk:0_drop Qcoplanar      QG_half_good   Qi_redundant   QJoggle\n\
    + Qsearch_1st    Qtriangulate   QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +        qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit = False;
    +    qh_option("Halfspace", NULL, NULL);
    +    qh HALFspace= True;    /* 'H'   */
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    if (qh SCALEinput) {
    +      fprintf(qh ferr, "\
    +qhull error: options 'Qbk:n' and 'QBk:n' are not used with qhalf.\n\
    +             Use 'Qbk:0Bk:0 to drop dimension k.\n");
    +      qh_errexit(qh_ERRinput, NULL, NULL);
    +    }
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("Qxact_merge", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhalf/qhalf.pro b/xs/src/qhull/src/qhalf/qhalf.pro
    new file mode 100644
    index 0000000000..ebad387893
    --- /dev/null
    +++ b/xs/src/qhull/src/qhalf/qhalf.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qhalf.pro -- Qt project file for qconvex.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qhalf
    +
    +SOURCES += qhalf.c
    diff --git a/xs/src/qhull/src/qhalf/qhalf_r.c b/xs/src/qhull/src/qhalf/qhalf_r.c
    new file mode 100644
    index 0000000000..c49d777f95
    --- /dev/null
    +++ b/xs/src/qhull/src/qhalf/qhalf_r.c
    @@ -0,0 +1,318 @@
    +/*
      ---------------------------------
    +
    +   qhalf.c
    +     compute the intersection of halfspaces about a point
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qhalf.htm */
    +char hidden_options[]=" d n v Qbb QbB Qf Qg Qm Qr QR Qv Qx Qz TR E V Fa FA FC FD FS Ft FV Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qhalf- compute the intersection of halfspaces about a point\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    optional interior point: dimension, 1, coordinates\n\
    +    first lines: dimension+1 and number of halfspaces\n\
    +    other lines: halfspace coefficients followed by offset\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar halfspaces\n\
    +    Qi   - keep other redundant halfspaces\n\
    +\n\
    +Qhull control options:\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    Qs   - search all halfspaces for the initial simplex\n\
    +    QGn  - print intersection if visible to halfspace n, -n for not\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when halfspace n added to intersection\n\
    +    TMn  - turn on tracing at merge n\n\
    +    TWn  - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding halfspace n, -n for before (see TCn)\n\
    +    TCn  - stop qhull after building cone for halfspace n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Un   - max distance below plane for a new, coplanar halfspace\n\
    +    Wn   - min facet width for outside halfspace (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    i    - non-redundant halfspaces incident to each intersection\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    o    - OFF format (dual convex hull: dimension, points, and facets)\n\
    +    p    - vertex coordinates of dual convex hull (coplanars if 'Qc' or 'Qi')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus redundant halfspaces for each intersection\n\
    +         -   Qc (default) for coplanar and Qi for other redundant\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    FI   - ID of each intersection\n\
    +    Fm   - merge count for each intersection (511 max)\n\
    +    FM   - Maple output (dual convex hull)\n\
    +    Fn   - count plus neighboring intersections for each intersection\n\
    +    FN   - count plus intersections for each non-redundant halfspace\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates\n\
    +    FP   - nearest halfspace and distance for each redundant halfspace\n\
    +    FQ   - command used for qhalf\n\
    +    Fs   - summary: #int (8), dim, #halfspaces, #non-redundant, #intersections\n\
    +                      for output: #non-redundant, #intersections, #coplanar\n\
    +                                  halfspaces, #non-simplicial intersections\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    Fv   - count plus non-redundant halfspaces for each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview output (2-d, 3-d and 4-d; dual convex hull)\n\
    +    Ga   - all points (i.e., transformed halfspaces) as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices (i.e., non-redundant halfspaces) as spheres\n\
    +    Gi   - inner planes (i.e., halfspace intersections) only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets (i.e., intersections) by area\n\
    +    Pdk:n- drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n- drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhalf- halfspace intersection about a point.  Qhull %s\n\
    +    input (stdin): [dim, 1, interior point], dim+1, n, coefficients+offset\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qhalf.htm):\n\
    +    Hn,n - specify coordinates of interior point\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and redundancy\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    Fp   - intersection coordinates\n\
    +    Fv   - non-redundant halfspaces incident to each intersection\n\
    +    Fx   - non-redundant halfspaces\n\
    +    o    - OFF file format (dual convex hull)\n\
    +    G    - Geomview output (dual convex hull)\n\
    +    m    - Mathematica output (dual convex hull)\n\
    +    QVn  - print intersections for halfspace n, -n if not\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox d | qconvex FQ n | qhalf s H0,0,0 Fp\n\
    +    rbox c | qconvex FQ FV n | qhalf s i\n\
    +    rbox c | qconvex FQ FV n | qhalf s o\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper_case options take an argument.\n\
    +\n\
    + incidences     Geomview       mathematica    OFF_format     point_dual\n\
    + summary        facet_dump\n\
    +\n\
    + Fc_redundant   Fd_cdd_in      FF_dump_xridge FIDs           Fmerges\n\
    + Fneighbors     FN_intersect   FOptions       Fp_coordinates FP_nearest\n\
    + FQhalf         Fsummary       Fv_halfspace   FMaple         Fx_non_redundant\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + Qbk:0Bk:0_drop Qcoplanar      QG_half_good   Qi_redundant   QJoggle\n\
    + Qsearch_1st    Qtriangulate   QVertex_good\n\
    +\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +        qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_option(qh, "Halfspace", NULL, NULL);
    +    qh->HALFspace= True;    /* 'H'   */
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    if (qh->SCALEinput) {
    +      fprintf(qh->ferr, "\
    +qhull error: options 'Qbk:n' and 'QBk:n' are not used with qhalf.\n\
    +             Use 'Qbk:0Bk:0 to drop dimension k.\n");
    +      qh_errexit(qh, qh_ERRinput, NULL, NULL);
    +    }
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "Qxact_merge", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhull-all.pro b/xs/src/qhull/src/qhull-all.pro
    new file mode 100644
    index 0000000000..1d3a0ac6f3
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-all.pro
    @@ -0,0 +1,94 @@
    +# -------------------------------------------------
    +# qhull-all.pro -- Qt project to build executables and static libraries
    +#
    +# To build with Qt on mingw
    +#   Download Qt SDK, install Perl
    +#   /c/qt/2010.05/qt> ./configure -static -platform win32-g++ -fast -no-qt3support
    +#
    +# To build DevStudio sln and proj files (Qhull ships with cmake derived files)
    +# qmake is in Qt's bin directory
    +# mkdir -p build && cd build && qmake -tp vc -r ../src/qhull-all.pro
    +# Additional Library Directories -- C:\qt\Qt5.2.0\5.2.0\msvc2012_64\lib
    +# libqhullcpp and libqhullstatic refered to $(QTDIR) but apparently didn't retrieve (should be %QTDIR%?)
    +# libqhull_r also needs ..\..\lib
    +# Need to change build/x64/Debug/*.lib to lib/ (or copy libs by hand, each time)
    +# Additional Build Dependencies
    +# See README.txt -- Need to add Build Dependencies, disable rtti, rename targets to qhull.dll, qhull6_p.dll and qhull6_pd.dll
    +# -------------------------------------------------
    +
    +TEMPLATE = subdirs
    +CONFIG += ordered
    +
    +SUBDIRS += libqhull_r      #shared library with reentrant code
    +SUBDIRS += libqhullstatic  #static library
    +SUBDIRS += libqhullstatic_r #static library with reentrant code
    +SUBDIRS += libqhullcpp     #static library for C++ interface with libqhullstatic_r
    +
    +SUBDIRS += qhull           #qhull program linked to libqhullstatic_r
    +SUBDIRS += rbox         
    +SUBDIRS += qconvex         #qhull programs linked to libqhullstatic
    +SUBDIRS += qdelaunay
    +SUBDIRS += qhalf
    +SUBDIRS += qvoronoi
    +
    +SUBDIRS += user_eg         #user programs linked to libqhull_r
    +SUBDIRS += user_eg2  
    +SUBDIRS += user_eg3        #user program with libqhullcpp and libqhullstatic_r
    +
    +SUBDIRS += qhulltest       #C++ test program with Qt, libqhullcpp, and libqhullstatic_r
    +SUBDIRS += testqset        #test program for qset.c with mem.c
    +SUBDIRS += testqset_r      #test program for qset_r.c with mem_r.c
    +                           #See eg/q_test for qhull tests
    +
    +OTHER_FILES += Changes.txt
    +OTHER_FILES += CMakeLists.txt
    +OTHER_FILES += Make-config.sh
    +OTHER_FILES += ../Announce.txt
    +OTHER_FILES += ../CMakeLists.txt
    +OTHER_FILES += ../COPYING.txt
    +OTHER_FILES += ../File_id.diz
    +OTHER_FILES += ../index.htm
    +OTHER_FILES += ../Makefile
    +OTHER_FILES += ../README.txt
    +OTHER_FILES += ../REGISTER.txt
    +OTHER_FILES += ../eg/q_eg
    +OTHER_FILES += ../eg/q_egtest
    +OTHER_FILES += ../eg/q_test
    +OTHER_FILES += ../html/index.htm
    +OTHER_FILES += ../html/qconvex.htm
    +OTHER_FILES += ../html/qdelau_f.htm
    +OTHER_FILES += ../html/qdelaun.htm
    +OTHER_FILES += ../html/qhalf.htm
    +OTHER_FILES += ../html/qh-code.htm
    +OTHER_FILES += ../html/qh-eg.htm
    +OTHER_FILES += ../html/qh-faq.htm
    +OTHER_FILES += ../html/qh-get.htm
    +OTHER_FILES += ../html/qh-impre.htm
    +OTHER_FILES += ../html/qh-optc.htm
    +OTHER_FILES += ../html/qh-optf.htm
    +OTHER_FILES += ../html/qh-optg.htm
    +OTHER_FILES += ../html/qh-opto.htm
    +OTHER_FILES += ../html/qh-optp.htm
    +OTHER_FILES += ../html/qh-optq.htm
    +OTHER_FILES += ../html/qh-optt.htm
    +OTHER_FILES += ../html/qh-quick.htm
    +OTHER_FILES += ../html/qhull.htm
    +OTHER_FILES += ../html/qhull.man
    +OTHER_FILES += ../html/qhull.txt
    +OTHER_FILES += ../html/qhull-cpp.xml
    +OTHER_FILES += ../html/qvoron_f.htm
    +OTHER_FILES += ../html/qvoronoi.htm
    +OTHER_FILES += ../html/rbox.htm
    +OTHER_FILES += ../html/rbox.man
    +OTHER_FILES += ../html/rbox.txt
    +OTHER_FILES += ../src/libqhull/Makefile
    +OTHER_FILES += ../src/libqhull_r/Makefile
    +OTHER_FILES += ../src/libqhull_r/qhull_r-exports.def
    +OTHER_FILES += ../src/qconvex/qconvex_r.c
    +OTHER_FILES += ../src/qdelaunay/qdelaun_r.c
    +OTHER_FILES += ../src/qhalf/qhalf_r.c
    +OTHER_FILES += ../src/qhull/rbox_r.c
    +OTHER_FILES += ../src/qvoronoi/qvoronoi_r.c
    +OTHER_FILES += ../src/qhull/unix.c
    +OTHER_FILES += ../src/user_eg/user_eg.c
    +OTHER_FILES += ../src/user_eg2/user_eg2.c
    diff --git a/xs/src/qhull/src/qhull-app-c.pri b/xs/src/qhull/src/qhull-app-c.pri
    new file mode 100644
    index 0000000000..05e5a00f28
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-c.pri
    @@ -0,0 +1,24 @@
    +# -------------------------------------------------
    +# qhull-app-c.pri -- Qt include project for C qhull applications linked to libqhull
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhullstatic_d
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhullstatic
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +INCLUDEPATH += ..
    +CONFIG += qhull_warn_conversion
    +
    diff --git a/xs/src/qhull/src/qhull-app-c_r.pri b/xs/src/qhull/src/qhull-app-c_r.pri
    new file mode 100644
    index 0000000000..9c2ef5600b
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-c_r.pri
    @@ -0,0 +1,26 @@
    +# -------------------------------------------------
    +# qhull-app-c_r.pri -- Qt include project for C qhull applications linked to qhullstatic_r
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhullstatic_rd
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhullstatic_r
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +INCLUDEPATH += ..
    +CONFIG += qhull_warn_conversion
    +
    diff --git a/xs/src/qhull/src/qhull-app-cpp.pri b/xs/src/qhull/src/qhull-app-cpp.pri
    new file mode 100644
    index 0000000000..a6f17d8ec4
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-cpp.pri
    @@ -0,0 +1,23 @@
    +# -------------------------------------------------
    +# qhull-app-cpp.pri -- Qt include project for qhull as C++ classes
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= rtti
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhullcpp_d
    +   LIBS += -lqhullstatic_rd  # Must be last, otherwise qh_fprintf,etc. are loaded from here instead of qhullcpp-d.lib
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhullcpp
    +   LIBS += -lqhullstatic_r  # Must be last, otherwise qh_fprintf,etc. are loaded from here instead of qhullcpp.lib
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +INCLUDEPATH += ../../src # "libqhull_r/qhull_a.h"
    diff --git a/xs/src/qhull/src/qhull-app-shared.pri b/xs/src/qhull/src/qhull-app-shared.pri
    new file mode 100644
    index 0000000000..1f4026a6aa
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-shared.pri
    @@ -0,0 +1,27 @@
    +# -------------------------------------------------
    +# qhull-app-shared.pri -- Deprecated Qt include project for C qhull applications linked with libqhull (shared library)
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhull_d
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhull
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEFINES += qh_dllimport # libqhull/user.h
    +
    +INCLUDEPATH += ../libqhull
    +CONFIG += qhull_warn_conversion
    +
    +
    diff --git a/xs/src/qhull/src/qhull-app-shared_r.pri b/xs/src/qhull/src/qhull-app-shared_r.pri
    new file mode 100644
    index 0000000000..e55c1a65f8
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-app-shared_r.pri
    @@ -0,0 +1,29 @@
    +# -------------------------------------------------
    +# qhull-app-shared_r.pri -- Qt include project for C qhull applications linked with libqhull_r (shared library)
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(qhull-warn.pri)
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +
    +LIBS += -L../../lib
    +build_pass:CONFIG(debug, debug|release){
    +   LIBS += -lqhull_rd
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   LIBS += -lqhull_r
    +   OBJECTS_DIR = Release
    +}
    +win32-msvc* : QMAKE_LFLAGS += /INCREMENTAL:NO
    +
    +win32-msvc* : DEFINES += qh_dllimport # libqhull_r/user.h
    +
    +INCLUDEPATH += ..
    +CONFIG += qhull_warn_conversion
    +
    +
    diff --git a/xs/src/qhull/src/qhull-libqhull-src.pri b/xs/src/qhull/src/qhull-libqhull-src.pri
    new file mode 100644
    index 0000000000..e7aff3f781
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-libqhull-src.pri
    @@ -0,0 +1,39 @@
    +# -------------------------------------------------
    +# qhull-libqhull-src.pri -- Qt include project for libqhull sources and headers
    +#   libqhull.pro, libqhullp.pro, and libqhulldll.pro are the same for SOURCES and HEADERS
    +# -------------------------------------------------
    +
    +# Order object files by frequency of execution.  Small files at end.
    +# Current directory is caller
    +
    +# libqhull/libqhull.pro and ../qhull-libqhull-src.pri have the same SOURCES and HEADERS
    +SOURCES += ../libqhull/global.c
    +SOURCES += ../libqhull/stat.c
    +SOURCES += ../libqhull/geom2.c
    +SOURCES += ../libqhull/poly2.c
    +SOURCES += ../libqhull/merge.c
    +SOURCES += ../libqhull/libqhull.c
    +SOURCES += ../libqhull/geom.c
    +SOURCES += ../libqhull/poly.c
    +SOURCES += ../libqhull/qset.c
    +SOURCES += ../libqhull/mem.c
    +SOURCES += ../libqhull/random.c
    +SOURCES += ../libqhull/usermem.c
    +SOURCES += ../libqhull/userprintf.c
    +SOURCES += ../libqhull/io.c
    +SOURCES += ../libqhull/user.c
    +SOURCES += ../libqhull/rboxlib.c
    +SOURCES += ../libqhull/userprintf_rbox.c
    +
    +# [2014] qmake locates the headers in the shadow build directory not the src directory
    +HEADERS += ../libqhull/geom.h
    +HEADERS += ../libqhull/io.h
    +HEADERS += ../libqhull/libqhull.h
    +HEADERS += ../libqhull/mem.h
    +HEADERS += ../libqhull/merge.h
    +HEADERS += ../libqhull/poly.h
    +HEADERS += ../libqhull/random.h
    +HEADERS += ../libqhull/qhull_a.h
    +HEADERS += ../libqhull/qset.h
    +HEADERS += ../libqhull/stat.h
    +HEADERS += ../libqhull/user.h
    diff --git a/xs/src/qhull/src/qhull-libqhull-src_r.pri b/xs/src/qhull/src/qhull-libqhull-src_r.pri
    new file mode 100644
    index 0000000000..3b53291b1b
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-libqhull-src_r.pri
    @@ -0,0 +1,39 @@
    +# -------------------------------------------------
    +# qhull-libqhull-src_r.pri -- Qt include project for libqhull_r sources and headers
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +# Order object files by frequency of execution.  Small files at end.
    +# Current directory is caller
    +
    +# libqhull_r/libqhull_r.pro and ../qhull-libqhull-src_r.pri have the same SOURCES and HEADERS
    +SOURCES += ../libqhull_r/global_r.c
    +SOURCES += ../libqhull_r/stat_r.c
    +SOURCES += ../libqhull_r/geom2_r.c
    +SOURCES += ../libqhull_r/poly2_r.c
    +SOURCES += ../libqhull_r/merge_r.c
    +SOURCES += ../libqhull_r/libqhull_r.c
    +SOURCES += ../libqhull_r/geom_r.c
    +SOURCES += ../libqhull_r/poly_r.c
    +SOURCES += ../libqhull_r/qset_r.c
    +SOURCES += ../libqhull_r/mem_r.c
    +SOURCES += ../libqhull_r/random_r.c
    +SOURCES += ../libqhull_r/usermem_r.c
    +SOURCES += ../libqhull_r/userprintf_r.c
    +SOURCES += ../libqhull_r/io_r.c
    +SOURCES += ../libqhull_r/user_r.c
    +SOURCES += ../libqhull_r/rboxlib_r.c
    +SOURCES += ../libqhull_r/userprintf_rbox_r.c
    +
    +HEADERS += ../libqhull_r/geom_r.h
    +HEADERS += ../libqhull_r/io_r.h
    +HEADERS += ../libqhull_r/libqhull_r.h
    +HEADERS += ../libqhull_r/mem_r.h
    +HEADERS += ../libqhull_r/merge_r.h
    +HEADERS += ../libqhull_r/poly_r.h
    +HEADERS += ../libqhull_r/random_r.h
    +HEADERS += ../libqhull_r/qhull_ra.h
    +HEADERS += ../libqhull_r/qset_r.h
    +HEADERS += ../libqhull_r/stat_r.h
    +HEADERS += ../libqhull_r/user_r.h
    diff --git a/xs/src/qhull/src/qhull-warn.pri b/xs/src/qhull/src/qhull-warn.pri
    new file mode 100644
    index 0000000000..7d0e7fa2f4
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull-warn.pri
    @@ -0,0 +1,57 @@
    +# -------------------------------------------------
    +# qhull-warn.pri -- Qt project warnings for warn_on
    +#   CONFIG += qhull_warn_all        # Qhull compiles with all warnings except for qhull_warn_shadow and qhull_warn_conversion
    +#   CONFIG += qhull_warn_conversion # Warn in Qt and Qhull about conversion errors
    +#   CONFIG += qhull_warn_error      # Turn warnings into errors
    +#   CONFIG += qhull_warn_shadow     # Warn in Qt about shadowing of functions and fields
    +# -------------------------------------------------
    +
    +# [apr'11] VERSION works erratically for msvc builds
    +# VERSION = 7.2.0
    +qhull_SOVERSION = 7
    +
    +# Uncomment to report warnings as errors
    +#CONFIG += qhull_warn_error
    +
    +*g++{
    +    qhull_warn_error{
    +        QMAKE_CFLAGS_WARN_ON += -Werror
    +        QMAKE_CXXFLAGS_WARN_ON += -Werror
    +    }
    +
    +    QMAKE_CFLAGS_WARN_ON += -Wcast-qual -Wextra -Wshadow -Wwrite-strings
    +
    +    QMAKE_CXXFLAGS_WARN_ON += -Wcast-qual -Wextra -Wwrite-strings
    +    QMAKE_CXXFLAGS_WARN_ON += -Wno-sign-conversion
    +
    +    qhull_warn_shadow{
    +        QMAKE_CXXFLAGS_WARN_ON += -Wshadow     # Shadowing occurs in Qt, e.g., nested foreach
    +    }
    +
    +    qhull_warn_conversion{
    +        QMAKE_CFLAGS_WARN_ON += -Wno-sign-conversion   # libqhullstatic has many size_t vs. int warnings
    +        QMAKE_CFLAGS_WARN_ON += -Wconversion           # libqhullstatic has no workaround for bit-field conversions
    +        QMAKE_CXXFLAGS_WARN_ON += -Wconversion         # Qt has conversion errors for qbitarray and qpalette
    +    }
    +
    +    qhull_warn_all{
    +        QMAKE_CFLAGS_WARN_ON += -Waddress -Warray-bounds -Wchar-subscripts -Wclobbered -Wcomment -Wempty-body
    +        QMAKE_CFLAGS_WARN_ON += -Wformat -Wignored-qualifiers -Wimplicit-function-declaration -Wimplicit-int
    +        QMAKE_CFLAGS_WARN_ON += -Wmain -Wmissing-braces -Wmissing-field-initializers -Wmissing-parameter-type
    +        QMAKE_CFLAGS_WARN_ON += -Wnonnull -Wold-style-declaration -Woverride-init -Wparentheses
    +        QMAKE_CFLAGS_WARN_ON += -Wpointer-sign -Wreturn-type -Wsequence-point -Wsign-compare
    +        QMAKE_CFLAGS_WARN_ON += -Wsign-compare -Wstrict-aliasing -Wstrict-overflow=1 -Wswitch
    +        QMAKE_CFLAGS_WARN_ON += -Wtrigraphs -Wtype-limits -Wuninitialized -Wuninitialized
    +        QMAKE_CFLAGS_WARN_ON += -Wunknown-pragmas -Wunused-function -Wunused-label -Wunused-parameter
    +        QMAKE_CFLAGS_WARN_ON += -Wunused-value -Wunused-variable -Wvolatile-register-var
    +
    +        QMAKE_CXXFLAGS_WARN_ON += -Waddress -Warray-bounds -Wc++0x-compat -Wchar-subscripts
    +        QMAKE_CXXFLAGS_WARN_ON += -Wclobbered -Wcomment -Wempty-body -Wenum-compare
    +        QMAKE_CXXFLAGS_WARN_ON += -Wformat -Wignored-qualifiers -Wmain -Wmissing-braces
    +        QMAKE_CXXFLAGS_WARN_ON += -Wmissing-field-initializers -Wparentheses -Wreorder -Wreturn-type
    +        QMAKE_CXXFLAGS_WARN_ON += -Wsequence-point -Wsign-compare -Wsign-compare -Wstrict-aliasing
    +        QMAKE_CXXFLAGS_WARN_ON += -Wstrict-overflow=1 -Wswitch -Wtrigraphs -Wtype-limits
    +        QMAKE_CXXFLAGS_WARN_ON += -Wuninitialized -Wunknown-pragmas -Wunused-function -Wunused-label
    +        QMAKE_CXXFLAGS_WARN_ON += -Wunused-parameter -Wunused-value -Wunused-variable -Wvolatile-register-var
    +    }
    +}
    diff --git a/xs/src/qhull/src/qhull/qhull.pro b/xs/src/qhull/src/qhull/qhull.pro
    new file mode 100644
    index 0000000000..8393728567
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull/qhull.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qhull.pro -- Qt project file for qhull.exe with libqhullstatic_r
    +# -------------------------------------------------
    +
    +include(../qhull-app-c_r.pri)
    +
    +TARGET = qhull
    +
    +SOURCES += unix_r.c
    diff --git a/xs/src/qhull/src/qhull/unix.c b/xs/src/qhull/src/qhull/unix.c
    new file mode 100644
    index 0000000000..892a819c31
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull/unix.c
    @@ -0,0 +1,372 @@
    +/*
      ---------------------------------
    +
    +   unix.c
    +     command line interface to qhull
    +         includes SIOUX interface for Macintoshes
    +
    +   see qh-qhull.htm
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/qhull/unix.c#4 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +*/
    +
    +#include "libqhull/libqhull.h"
    +#include "libqhull/qset.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  see:
    +    concise prompt below
    +*/
    +char qh_prompta[]= "\n\
    +qhull- compute convex hulls and related structures.\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +    halfspaces:  use dim plus one and put offset after coefficients.\n\
    +                 May be preceded by a single interior point ('H').\n\
    +\n\
    +options:\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram (dual of the Delaunay triangulation)\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    Hn,n,... - halfspace intersection about point [n,n,0,...]\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbb  - scale last coordinate to [0,m] for Delaunay triangulations\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qf   - partition point to furthest outside facet\n\
    +    Qg   - only build good facets (needs 'QGn', 'QVn', or 'PdD')\n\
    +    Qm   - only process points that would increase max_outside\n\
    +    Qr   - process random outside points instead of furthest ones\n\
    +    Qs   - search all points for the initial simplex\n\
    +    Qu   - for 'd' or 'v', compute upper hull without point at-infinity\n\
    +              returns furthest-site Delaunay triangulation\n\
    +    Qv   - test vertex neighbors for convexity\n\
    +    Qx   - exact pre-merges (skips coplanar and angle-coplanar facets)\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +    Q0   - turn off default premerge with 'C-0'/'Qx'\n\
    +    Q1     - sort merges by type instead of angle\n\
    +    Q2   - merge all non-convex at once instead of independent sets\n\
    +    Q3   - do not merge redundant vertices\n\
    +    Q4   - avoid old->new merges\n\
    +    Q5   - do not correct outer planes at end of qhull\n\
    +    Q6   - do not pre-merge concave or coplanar facets\n\
    +    Q7   - depth-first processing instead of breadth-first\n\
    +    Q8   - do not process near-inside points\n\
    +    Q9   - process furthest of furthest points\n\
    +    Q10  - no special processing for narrow distributions\n\
    +    Q11  - copy normals and recompute centrums for tricoplanar facets\n\
    +    Q12  - no error on wide merge due to duplicate ridge\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Topts- Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Ta   - annotate output with message codes\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TRn  - rerun qhull n times.  Use with 'QJn'\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    En   - max roundoff error for distance computation\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Vn   - min distance above plane for a visible facet (default 3C-n or En)\n\
    +    Un   - max distance below plane for a new, coplanar point (default Vn)\n\
    +    Wn   - min facet width for outside point (before roundoff, default 2Vn)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    o    - OFF format (dim, points and facets; Voronoi regions)\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates or Voronoi vertices (coplanar points if 'Qc')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum or Voronoi center for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +           for 'v', separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID of each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    FM   - Maple output (2-d and 3-d)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +           for 'v', separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates (halfspace only)\n\
    +    FP   - nearest vertex and distance for each coplanar point\n\
    +    FQ   - command used for qhull\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      output: #vertices, #facets, #coplanars, #nonsimplicial\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +           for 'v', Voronoi diagram as Voronoi vertices for pairs of sites\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d, 3-d, and 4-d; 2-d Voronoi)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - for 3-d 'd', transparent outer ridges\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhull- compute convex hulls and related structures.  Qhull %s\n\
    +    input (stdin): dimension, n, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +    halfspace: use dim+1 and put offsets after coefficients\n\
    +\n\
    +options (qh-quick.htm):\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram as the dual of the Delaunay triangulation\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    H1,1 - Halfspace intersection about [1,1,0,...] via polar duality\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of each option\n\
    +    -V   - version\n\
    +\n\
    +Output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (if 'Qc', includes coplanar points)\n\
    +           if 'v', Voronoi vertices\n\
    +    Fp   - halfspace intersections\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - compute total area and volume\n\
    +    o    - OFF format (if 'v', outputs Voronoi regions)\n\
    +    G    - Geomview output (2-d, 3-d and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox D4 | qhull Tv                        rbox 1000 s | qhull Tv s FA\n\
    +    rbox 10 D2 | qhull d QJ s i TO result     rbox 10 D2 | qhull v Qbb Qt p\n\
    +    rbox 10 D2 | qhull d Qu QJ m              rbox 10 D2 | qhull v Qu QJ o\n\
    +    rbox c d D2 | qhull Qc s f Fx | more      rbox c | qhull FV n | qhull H Fp\n\
    +    rbox d D12 | qhull QR0 FA                 rbox c D7 | qhull FA TF1000\n\
    +    rbox y 1000 W0 | qhull                    rbox c | qhull n\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + delaunay       voronoi        Geomview       Halfspace      facet_dump\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary\n\
    +\n\
    + Farea          FArea-total    Fcoplanars     FCentrums      Fd-cdd-in\n\
    + FD-cdd-out     FF-dump-xridge Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh-vertex  Fouter         FOptions       Fpoint-intersect\n\
    + FPoint_near    FQhull         Fsummary       FSize          Ftriangles\n\
    + Fvertices      Fvoronoi       FVertex-ave    Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea-keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge-keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  Qbk:0Bk:0_drop QbB-scale-box  Qbb-scale-last Qcoplanar\n\
    + Qfurthest      Qgood_only     QGood_point    Qinterior      Qmax_out\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QupperDelaunay QVertex_good   Qvneighbors    Qxact_merge    Qzinfinite\n\
    +\n\
    + Q0_no_premerge Q1_no_angle    Q2_no_independ Q3_no_redundant Q4_no_old\n\
    + Q5_no_check_out Q6_no_concave Q7_depth_first Q8_no_near_in  Q9_pick_furthest\n\
    + Q10_no_narrow  Q11_trinormals Q12_no_wide_dup\n\
    +\n\
    + T4_trace       Tannotate      Tcheck_often   Tstatistics    Tverify\n\
    + Tz_stdout      TFacet_log     TInput_file    TPoint_trace   TMerge_trace\n\
    + TOutput_file   TRerun         TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Error_round    Random_dist    Visible_min\n\
    + Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull( qh_ALL);
    +#else
    +  qh_freeqhull( !qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhull/unix_r.c b/xs/src/qhull/src/qhull/unix_r.c
    new file mode 100644
    index 0000000000..3f999f7fa9
    --- /dev/null
    +++ b/xs/src/qhull/src/qhull/unix_r.c
    @@ -0,0 +1,374 @@
    +/*
      ---------------------------------
    +
    +   unix.c
    +     command line interface to qhull
    +         includes SIOUX interface for Macintoshes
    +
    +   see qh-qhull.htm
    +
    +   Copyright (c) 1993-2015 The Geometry Center.
    +   $Id: //main/2015/qhull/src/qhull/unix_r.c#6 $$Change: 2066 $
    +   $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  see:
    +    concise prompt below
    +*/
    +char qh_prompta[]= "\n\
    +qhull- compute convex hulls and related structures.\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +    halfspaces:  use dim plus one and put offset after coefficients.\n\
    +                 May be preceded by a single interior point ('H').\n\
    +\n\
    +options:\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram (dual of the Delaunay triangulation)\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    Hn,n,... - halfspace intersection about point [n,n,0,...]\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Qc   - keep coplanar points with nearest facet\n\
    +    Qi   - keep interior points with nearest facet\n\
    +\n\
    +Qhull control options:\n\
    +    Qbk:n   - scale coord k so that low bound is n\n\
    +      QBk:n - scale coord k so that upper bound is n (QBk is %2.2g)\n\
    +    QbB  - scale input to unit cube centered at the origin\n\
    +    Qbb  - scale last coordinate to [0,m] for Delaunay triangulations\n\
    +    Qbk:0Bk:0 - remove k-th coordinate from input\n\
    +    QJn  - randomly joggle input in range [-n,n]\n\
    +    QRn  - random rotation (n=seed, n=0 time, n=-1 time/no rotate)\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qf   - partition point to furthest outside facet\n\
    +    Qg   - only build good facets (needs 'QGn', 'QVn', or 'PdD')\n\
    +    Qm   - only process points that would increase max_outside\n\
    +    Qr   - process random outside points instead of furthest ones\n\
    +    Qs   - search all points for the initial simplex\n\
    +    Qu   - for 'd' or 'v', compute upper hull without point at-infinity\n\
    +              returns furthest-site Delaunay triangulation\n\
    +    Qv   - test vertex neighbors for convexity\n\
    +    Qx   - exact pre-merges (skips coplanar and angle-coplanar facets)\n\
    +    Qz   - add point-at-infinity to Delaunay triangulation\n\
    +    QGn  - good facet if visible from point n, -n for not visible\n\
    +    QVn  - good facet if it includes point n, -n if not\n\
    +    Q0   - turn off default premerge with 'C-0'/'Qx'\n\
    +    Q1     - sort merges by type instead of angle\n\
    +    Q2   - merge all non-convex at once instead of independent sets\n\
    +    Q3   - do not merge redundant vertices\n\
    +    Q4   - avoid old->new merges\n\
    +    Q5   - do not correct outer planes at end of qhull\n\
    +    Q6   - do not pre-merge concave or coplanar facets\n\
    +    Q7   - depth-first processing instead of breadth-first\n\
    +    Q8   - do not process near-inside points\n\
    +    Q9   - process furthest of furthest points\n\
    +    Q10  - no special processing for narrow distributions\n\
    +    Q11  - copy normals and recompute centrums for tricoplanar facets\n\
    +    Q12  - no error on wide merge due to duplicate ridge\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Topts- Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Ta   - annotate output with message codes\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - print statistics\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TRn  - rerun qhull n times.  Use with 'QJn'\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    En   - max roundoff error for distance computation\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Vn   - min distance above plane for a visible facet (default 3C-n or En)\n\
    +    Un   - max distance below plane for a new, coplanar point (default Vn)\n\
    +    Wn   - min facet width for outside point (before roundoff, default 2Vn)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    f    - facet dump\n\
    +    G    - Geomview output (see below)\n\
    +    i    - vertices incident to each facet\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    o    - OFF format (dim, points and facets; Voronoi regions)\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates or Voronoi vertices (coplanar points if 'Qc')\n\
    +    s    - summary (stderr)\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fa   - area for each facet\n\
    +    FA   - compute total area and volume for option 's'\n\
    +    Fc   - count plus coplanar points for each facet\n\
    +           use 'Qc' (default) for coplanar and 'Qi' for interior\n\
    +    FC   - centrum or Voronoi center for each facet\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for numeric output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - inner plane for each facet\n\
    +           for 'v', separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID of each facet\n\
    +    Fm   - merge count for each facet (511 max)\n\
    +    FM   - Maple output (2-d and 3-d)\n\
    +    Fn   - count plus neighboring facets for each facet\n\
    +    FN   - count plus neighboring facets for each point\n\
    +    Fo   - outer plane (or max_outside) for each facet\n\
    +           for 'v', separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    Fp   - dim, count, and intersection coordinates (halfspace only)\n\
    +    FP   - nearest vertex and distance for each coplanar point\n\
    +    FQ   - command used for qhull\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                      output: #vertices, #facets, #coplanars, #nonsimplicial\n\
    +                    #real (2), max outer plane, min vertex\n\
    +    FS   - sizes:   #int (0)\n\
    +                    #real (2) tot area, tot volume\n\
    +    Ft   - triangulation with centrums for non-simplicial facets (OFF format)\n\
    +    Fv   - count plus vertices for each facet\n\
    +           for 'v', Voronoi diagram as Voronoi vertices for pairs of sites\n\
    +    FV   - average of vertices (a feasible point for 'H')\n\
    +    Fx   - extreme points (in order for 2-d)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d, 3-d, and 4-d; 2-d Voronoi)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +    Gt   - for 3-d 'd', transparent outer ridges\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest facets by area\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good facets (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep facets whose area is at least n\n\
    +    PG   - print neighbors of good facets\n\
    +    PMn  - keep n facets with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qhull- compute convex hulls and related structures.  Qhull %s\n\
    +    input (stdin): dimension, n, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +    halfspace: use dim+1 and put offsets after coefficients\n\
    +\n\
    +options (qh-quick.htm):\n\
    +    d    - Delaunay triangulation by lifting points to a paraboloid\n\
    +    d Qu - furthest-site Delaunay triangulation (upper convex hull)\n\
    +    v    - Voronoi diagram as the dual of the Delaunay triangulation\n\
    +    v Qu - furthest-site Voronoi diagram\n\
    +    H1,1 - Halfspace intersection about [1,1,0,...] via polar duality\n\
    +    Qt   - triangulated output\n\
    +    QJ   - joggled input instead of merged facets\n\
    +    Tv   - verify result: structure, convexity, and point inclusion\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of each option\n\
    +    -V   - version\n\
    +\n\
    +Output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    i    - vertices incident to each facet\n\
    +    n    - normals with offsets\n\
    +    p    - vertex coordinates (if 'Qc', includes coplanar points)\n\
    +           if 'v', Voronoi vertices\n\
    +    Fp   - halfspace intersections\n\
    +    Fx   - extreme points (convex hull vertices)\n\
    +    FA   - compute total area and volume\n\
    +    o    - OFF format (if 'v', outputs Voronoi regions)\n\
    +    G    - Geomview output (2-d, 3-d and 4-d)\n\
    +    m    - Mathematica output (2-d and 3-d)\n\
    +    QVn  - print facets that include point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +    rbox D4 | qhull Tv                        rbox 1000 s | qhull Tv s FA\n\
    +    rbox 10 D2 | qhull d QJ s i TO result     rbox 10 D2 | qhull v Qbb Qt p\n\
    +    rbox 10 D2 | qhull d Qu QJ m              rbox 10 D2 | qhull v Qu QJ o\n\
    +    rbox c d D2 | qhull Qc s f Fx | more      rbox c | qhull FV n | qhull H Fp\n\
    +    rbox d D12 | qhull QR0 FA                 rbox c D7 | qhull FA TF1000\n\
    +    rbox y 1000 W0 | qhull                    rbox c | qhull n\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + delaunay       voronoi        Geomview       Halfspace      facet_dump\n\
    + incidences     mathematica    normals        OFF_format     points\n\
    + summary\n\
    +\n\
    + Farea          FArea-total    Fcoplanars     FCentrums      Fd-cdd-in\n\
    + FD-cdd-out     FF-dump-xridge Finner         FIDs           Fmerges\n\
    + Fneighbors     FNeigh-vertex  Fouter         FOptions       Fpoint-intersect\n\
    + FPoint_near    FQhull         Fsummary       FSize          Ftriangles\n\
    + Fvertices      Fvoronoi       FVertex-ave    Fxtremes       FMaple\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    + Gtransparent\n\
    +\n\
    + PArea-keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge-keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QbBound 0:0.5  Qbk:0Bk:0_drop QbB-scale-box  Qbb-scale-last Qcoplanar\n\
    + Qfurthest      Qgood_only     QGood_point    Qinterior      Qmax_out\n\
    + QJoggle        Qrandom        QRotate        Qsearch_1st    Qtriangulate\n\
    + QupperDelaunay QVertex_good   Qvneighbors    Qxact_merge    Qzinfinite\n\
    +\n\
    + Q0_no_premerge Q1_no_angle    Q2_no_independ Q3_no_redundant Q4_no_old\n\
    + Q5_no_check_out Q6_no_concave Q7_depth_first Q8_no_near_in  Q9_pick_furthest\n\
    + Q10_no_narrow  Q11_trinormals Q12_no_wide_dup\n\
    +\n\
    + T4_trace       Tannotate      Tcheck_often   Tstatistics    Tverify\n\
    + Tz_stdout      TFacet_log     TInput_file    TPoint_trace   TMerge_trace\n\
    + TOutput_file   TRerun         TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Error_round    Random_dist    Visible_min\n\
    + Ucoplanar_max  Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version, qh_DEFAULTbox,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qhulltest/Coordinates_test.cpp b/xs/src/qhull/src/qhulltest/Coordinates_test.cpp
    new file mode 100644
    index 0000000000..3e8658a5bd
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/Coordinates_test.cpp
    @@ -0,0 +1,539 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/Coordinates_test.cpp#2 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class Coordinates_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void t_construct();
    +    void t_convert();
    +    void t_element();
    +    void t_readonly();
    +    void t_operator();
    +    void t_const_iterator();
    +    void t_iterator();
    +    void t_coord_iterator();
    +    void t_mutable_coord_iterator();
    +    void t_readwrite();
    +    void t_search();
    +    void t_io();
    +};//Coordinates_test
    +
    +void
    +add_Coordinates_test()
    +{
    +    new Coordinates_test();  // RoadTest::s_testcases
    +}
    +
    +void Coordinates_test::
    +t_construct()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.size(), 0U);
    +    QVERIFY(c.isEmpty());
    +    c << 1.0;
    +    QCOMPARE(c.count(), 1);
    +    Coordinates c2(c);
    +    c2 << 2.0;
    +    QCOMPARE(c2.count(), 2);
    +    Coordinates c3;
    +    c3 = c2;
    +    QCOMPARE(c3.count(), 2);
    +    QCOMPARE(c3[0]+c3[1], 3.0);
    +    QVERIFY(c2==c3);
    +    std::vector vc;
    +    vc.push_back(3.0);
    +    vc.push_back(4.0);
    +    Coordinates c4(vc);
    +    QCOMPARE(c4[0]+c4[1], 7.0);
    +    Coordinates c5(c3);
    +    QVERIFY(c5==c3);
    +    c5= vc;
    +    QVERIFY(c5!=c3);
    +    QVERIFY(c5==c4);
    +}//t_construct
    +
    +void Coordinates_test::
    +t_convert()
    +{
    +    Coordinates c;
    +    c << 1.0 << 3.0;
    +    QCOMPARE(c.data()[1], 3.0);
    +    coordT *c2= c.data();
    +    const coordT *c3= c.data();
    +    QCOMPARE(c2, c3);
    +    std::vector vc= c.toStdVector();
    +    QCOMPARE((size_t)vc.size(), c.size());
    +    for(int k= (int)vc.size(); k--; ){
    +        QCOMPARE(vc[k], c[k]);
    +    }
    +    QList qc= c.toQList();
    +    QCOMPARE(qc.count(), c.count());
    +    for(int k= qc.count(); k--; ){
    +        QCOMPARE(qc[k], c[k]);
    +    }
    +    Coordinates c4;
    +    c4= std::vector(2, 0.0);
    +    QCOMPARE(c4.back(), 0.0);
    +    Coordinates c5(std::vector(2, 0.0));
    +    QCOMPARE(c4.size(), c5.size());
    +    QVERIFY(c4==c5);
    +}//t_convert
    +
    +void Coordinates_test::
    +t_element()
    +{
    +    Coordinates c;
    +    c << 1.0 << -2.0;
    +    c.at(1)= -3;
    +    QCOMPARE(c.at(1), -3.0);
    +    QCOMPARE(c.back(), -3.0);
    +    QCOMPARE(c.front(), 1.0);
    +    c[1]= -2.0;
    +    QCOMPARE(c[1],-2.0);
    +    QCOMPARE(c.first(), 1.0);
    +    c.first()= 2.0;
    +    QCOMPARE(c.first(), 2.0);
    +    QCOMPARE(c.last(), -2.0);
    +    c.last()= 0.0;
    +    QCOMPARE(c.first()+c.last(), 2.0);
    +    coordT *c4= &c.first();
    +    const coordT *c5= &c.first();
    +    QCOMPARE(c4, c5);
    +    coordT *c6= &c.last();
    +    const coordT *c7= &c.last();
    +    QCOMPARE(c6, c7);
    +    Coordinates c2= c.mid(1);
    +    QCOMPARE(c2.count(), 1);
    +    c << 3.0;
    +    Coordinates c3= c.mid(1,1);
    +    QCOMPARE(c2, c3);
    +    QCOMPARE(c3.value(-1, -1.0), -1.0);
    +    QCOMPARE(c3.value(3, 4.0), 4.0);
    +    QCOMPARE(c.value(2, 4.0), 3.0);
    +}//t_element
    +
    +void Coordinates_test::
    +t_readonly()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.size(), 0u);
    +    QCOMPARE(c.count(), 0);
    +    QVERIFY(c.isEmpty());
    +    c << 1.0 << -2.0;
    +    QCOMPARE(c.size(), 2u);
    +    QCOMPARE(c.count(), 2);
    +    QVERIFY(!c.isEmpty());
    +}//t_readonly
    +
    +void Coordinates_test::
    +t_operator()
    +{
    +    Coordinates c;
    +    Coordinates c2(c);
    +    QVERIFY(c==c2);
    +    QVERIFY(!(c!=c2));
    +    c << 1.0;
    +    QVERIFY(!(c==c2));
    +    QVERIFY(c!=c2);
    +    c2 << 1.0;
    +    QVERIFY(c==c2);
    +    QVERIFY(!(c!=c2));
    +    c[0]= 0.0;
    +    QVERIFY(c!=c2);
    +    Coordinates c3= c+c2;
    +    QCOMPARE(c3.count(), 2);
    +    QCOMPARE(c3[0], 0.0);
    +    QCOMPARE(c3[1], 1.0);
    +    c3 += c3;
    +    QCOMPARE(c3.count(), 4);
    +    QCOMPARE(c3[2], 0.0);
    +    QCOMPARE(c3[3], 1.0);
    +    c3 += c2;
    +    QCOMPARE(c3[4], 1.0);
    +    c3 += 5.0;
    +    QCOMPARE(c3.count(), 6);
    +    QCOMPARE(c3[5], 5.0);
    +    // << checked above
    +}//t_operator
    +
    +void Coordinates_test::
    +t_const_iterator()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.begin(), c.end());
    +    // begin and end checked elsewhere
    +    c << 1.0 << 3.0;
    +    Coordinates::const_iterator i= c.begin();
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(i[1], 3.0);
    +    // i[1]= -3.0; // compiler error
    +    // operator-> is not applicable to double
    +    QCOMPARE(*i++, 1.0);
    +    QCOMPARE(*i, 3.0);
    +    QCOMPARE(*i--, 3.0);
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(*(i+1), 3.0);
    +    QCOMPARE(*++i, 3.0);
    +    QCOMPARE(*(i-1), 1.0);
    +    QCOMPARE(*--i, 1.0);
    +    QVERIFY(i==c.begin());
    +    QVERIFY(i==c.constBegin());
    +    QVERIFY(i!=c.end());
    +    QVERIFY(i!=c.constEnd());
    +    QVERIFY(i=c.begin());
    +    QVERIFY(i+1<=c.end());
    +    QVERIFY(i+1>c.begin());
    +    Coordinates::iterator i2= c.begin();
    +    Coordinates::const_iterator i3(i2);
    +    QCOMPARE(*i3, 1.0);
    +    QCOMPARE(i3[1], 3.0);
    +}//t_const_iterator
    +
    +void Coordinates_test::
    +t_iterator()
    +{
    +    Coordinates c;
    +    QCOMPARE(c.begin(), c.end());
    +    // begin and end checked elsewhere
    +    c << 1.0 << 3.0;
    +    Coordinates::iterator i= c.begin();
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(i[1], 3.0);
    +    *i= -1.0;
    +    QCOMPARE(*i, -1.0);
    +    i[1]= -3.0;
    +    QCOMPARE(i[1], -3.0);
    +    *i= 1.0;
    +    // operator-> is not applicable to double
    +    QCOMPARE(*i++, 1.0);
    +    QCOMPARE(*i, -3.0);
    +    *i= 3.0;
    +    QCOMPARE(*i--, 3.0);
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(*(i+1), 3.0);
    +    QCOMPARE(*++i, 3.0);
    +    QCOMPARE(*(i-1), 1.0);
    +    QCOMPARE(*--i, 1.0);
    +    QVERIFY(i==c.begin());
    +    QVERIFY(i==c.constBegin());
    +    QVERIFY(i!=c.end());
    +    QVERIFY(i!=c.constEnd());
    +    QVERIFY(i=c.begin());
    +    QVERIFY(i+1<=c.end());
    +    QVERIFY(i+1>c.begin());
    +}//t_iterator
    +
    +void Coordinates_test::
    +t_coord_iterator()
    +{
    +    Coordinates c;
    +    c << 1.0 << 3.0;
    +    CoordinatesIterator i(c);
    +    CoordinatesIterator i2= c;
    +    QVERIFY(i.findNext(1.0));
    +    QVERIFY(!i.findNext(2.0));
    +    QVERIFY(!i.findNext(3.0));
    +    QVERIFY(i.findPrevious(3.0));
    +    QVERIFY(!i.findPrevious(2.0));
    +    QVERIFY(!i.findPrevious(1.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(3.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(1.0));
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    i2.toFront();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(i.hasPrevious());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    Coordinates c2;
    +    i2= c2;
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekPrevious(), 3.0);
    +    QCOMPARE(i.previous(), 3.0);
    +    QCOMPARE(i.previous(), 1.0);
    +    QVERIFY(!i.hasPrevious());
    +    QCOMPARE(i.peekNext(), 1.0);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), 1.0);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    QCOMPARE(i.next(), 3.0);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), 1.0);
    +}//t_coord_iterator
    +
    +void Coordinates_test::
    +t_mutable_coord_iterator()
    +{
    +    // Same tests as CoordinatesIterator
    +    Coordinates c;
    +    c << 1.0 << 3.0;
    +    MutableCoordinatesIterator i(c);
    +    MutableCoordinatesIterator i2= c;
    +    QVERIFY(i.findNext(1.0));
    +    QVERIFY(!i.findNext(2.0));
    +    QVERIFY(!i.findNext(3.0));
    +    QVERIFY(i.findPrevious(3.0));
    +    QVERIFY(!i.findPrevious(2.0));
    +    QVERIFY(!i.findPrevious(1.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(3.0));
    +    QVERIFY(i2.findNext(3.0));
    +    QVERIFY(i2.findPrevious(1.0));
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    i2.toFront();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(i.hasPrevious());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    Coordinates c2;
    +    i2= c2;
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekPrevious(), 3.0);
    +    QCOMPARE(i.peekPrevious(), 3.0);
    +    QCOMPARE(i.previous(), 3.0);
    +    QCOMPARE(i.previous(), 1.0);
    +    QVERIFY(!i.hasPrevious());
    +    QCOMPARE(i.peekNext(), 1.0);
    +    QCOMPARE(i.next(), 1.0);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    QCOMPARE(i.next(), 3.0);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), 1.0);
    +
    +    // Mutable tests
    +    i.toFront();
    +    i.peekNext()= -1.0;
    +    QCOMPARE(i.peekNext(), -1.0);
    +    QCOMPARE((i.next()= 1.0), 1.0);
    +    QCOMPARE(i.peekPrevious(), 1.0);
    +    i.remove();
    +    QCOMPARE(c.count(), 1);
    +    i.remove();
    +    QCOMPARE(c.count(), 1);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    i.insert(1.0);
    +    i.insert(2.0);
    +    QCOMPARE(c.count(), 3);
    +    QCOMPARE(i.peekNext(), 3.0);
    +    QCOMPARE(i.peekPrevious(), 2.0);
    +    i.peekPrevious()= -2.0;
    +    QCOMPARE(i.peekPrevious(), -2.0);
    +    QCOMPARE((i.previous()= 2.0), 2.0);
    +    QCOMPARE(i.peekNext(), 2.0);
    +    i.toBack();
    +    i.remove();
    +    QCOMPARE(c.count(), 3); // unchanged
    +    i.toFront();
    +    i.remove();
    +    QCOMPARE(c.count(), 3); // unchanged
    +    QCOMPARE(i.peekNext(), 1.0);
    +    i.remove();
    +    QCOMPARE(c.count(), 3); // unchanged
    +    i.insert(0.0);
    +    QCOMPARE(c.count(), 4);
    +    QCOMPARE(i.value(), 0.0);
    +    QCOMPARE(i.peekPrevious(), 0.0);
    +    i.setValue(-10.0);
    +    QCOMPARE(c.count(), 4); // unchanged
    +    QCOMPARE(i.peekNext(), 1.0);
    +    QCOMPARE(i.peekPrevious(), -10.0);
    +    i.findNext(1.0);
    +    i.setValue(-1.0);
    +    QCOMPARE(i.peekPrevious(), -1.0);
    +    i.setValue(1.0);
    +    QCOMPARE(i.peekPrevious(), 1.0);
    +    QCOMPARE(i.value(), 1.0);
    +    i.findPrevious(1.0);
    +    i.setValue(-1.0);
    +    QCOMPARE(i.peekNext(), -1.0);
    +    i.toBack();
    +    QCOMPARE(i.previous(), 3.0);
    +    i.setValue(-3.0);
    +    QCOMPARE(i.peekNext(), -3.0);
    +    double d= i.value();
    +    QCOMPARE(d, -3.0);
    +    QCOMPARE(i.previous(), 2.0);
    +}//t_mutable_coord_iterator
    +
    +void Coordinates_test::
    +t_readwrite()
    +{
    +    Coordinates c;
    +    c.clear();
    +    QCOMPARE(c.count(), 0);
    +    c << 1.0 << 3.0;
    +    c.clear();
    +    QCOMPARE(c.count(), 0);
    +    coordT c2[4]= { 0.0, 1.0, 2.0, 3.0};
    +    c.append(4, c2);
    +    QCOMPARE(c.count(), 4);
    +    QCOMPARE(c[0], 0.0);
    +    QCOMPARE(c[1], 1.0);
    +    QCOMPARE(c[3], 3.0);
    +    c.clear();
    +    c << 1.0 << 3.0;
    +    c.erase(c.begin(), c.end());
    +    QCOMPARE(c.count(), 0);
    +    c << 1.0 << 0.0;
    +    Coordinates::iterator i= c.erase(c.begin());
    +    QCOMPARE(*i, 0.0);
    +    i= c.insert(c.end(), 1.0);
    +    QCOMPARE(*i, 1.0);
    +    QCOMPARE(c.count(), 2);
    +    c.pop_back();
    +    QCOMPARE(c.count(), 1);   // 0
    +    QCOMPARE(c[0], 0.0);
    +    c.push_back(2.0);
    +    QCOMPARE(c.count(), 2);
    +    c.append(3.0);
    +    QCOMPARE(c.count(), 3);   // 0, 2, 3
    +    QCOMPARE(c[2], 3.0);
    +    c.insert(0, 4.0);
    +    QCOMPARE(c[0], 4.0);
    +    QCOMPARE(c[3], 3.0);
    +    c.insert(c.count(), 5.0);
    +    QCOMPARE(c.count(), 5);   // 4, 0, 2, 3, 5
    +    QCOMPARE(c[4], 5.0);
    +    c.move(4, 0);
    +    QCOMPARE(c.count(), 5);   // 5, 4, 0, 2, 3
    +    QCOMPARE(c[0], 5.0);
    +    c.pop_front();
    +    QCOMPARE(c.count(), 4);
    +    QCOMPARE(c[0], 4.0);
    +    c.prepend(6.0);
    +    QCOMPARE(c.count(), 5);   // 6, 4, 0, 2, 3
    +    QCOMPARE(c[0], 6.0);
    +    c.push_front(7.0);
    +    QCOMPARE(c.count(), 6);
    +    QCOMPARE(c[0], 7.0);
    +    c.removeAt(1);
    +    QCOMPARE(c.count(), 5);   // 7, 4, 0, 2, 3
    +    QCOMPARE(c[1], 4.0);
    +    c.removeFirst();
    +    QCOMPARE(c.count(), 4);   // 4, 0, 2, 3
    +    QCOMPARE(c[0], 4.0);
    +    c.removeLast();
    +    QCOMPARE(c.count(), 3);
    +    QCOMPARE(c.last(), 2.0);
    +    c.replace(2, 8.0);
    +    QCOMPARE(c.count(), 3);   // 4, 0, 8
    +    QCOMPARE(c[2], 8.0);
    +    c.swap(0, 2);
    +    QCOMPARE(c[2], 4.0);
    +    double d= c.takeAt(2);
    +    QCOMPARE(c.count(), 2);   // 8, 0
    +    QCOMPARE(d, 4.0);
    +    double d2= c.takeFirst();
    +    QCOMPARE(c.count(), 1);   // 0
    +    QCOMPARE(d2, 8.0);
    +    double d3= c.takeLast();
    +    QVERIFY(c.isEmpty()); \
    +    QCOMPARE(d3, 0.0);
    +}//t_readwrite
    +
    +void Coordinates_test::
    +t_search()
    +{
    +    Coordinates c;
    +    c << 1.0 << 3.0 << 1.0;
    +    QVERIFY(c.contains(1.0));
    +    QVERIFY(c.contains(3.0));
    +    QVERIFY(!c.contains(0.0));
    +    QCOMPARE(c.count(1.0), 2);
    +    QCOMPARE(c.count(3.0), 1);
    +    QCOMPARE(c.count(0.0), 0);
    +    QCOMPARE(c.indexOf(1.0), 0);
    +    QCOMPARE(c.indexOf(3.0), 1);
    +    QCOMPARE(c.indexOf(1.0, -1), 2);
    +    QCOMPARE(c.indexOf(3.0, -1), -1);
    +    QCOMPARE(c.indexOf(3.0, -2), 1);
    +    QCOMPARE(c.indexOf(1.0, -3), 0);
    +    QCOMPARE(c.indexOf(1.0, -4), 0);
    +    QCOMPARE(c.indexOf(1.0, 1), 2);
    +    QCOMPARE(c.indexOf(3.0, 2), -1);
    +    QCOMPARE(c.indexOf(1.0, 2), 2);
    +    QCOMPARE(c.indexOf(1.0, 3), -1);
    +    QCOMPARE(c.indexOf(1.0, 4), -1);
    +    QCOMPARE(c.lastIndexOf(1.0), 2);
    +    QCOMPARE(c.lastIndexOf(3.0), 1);
    +    QCOMPARE(c.lastIndexOf(1.0, -1), 2);
    +    QCOMPARE(c.lastIndexOf(3.0, -1), 1);
    +    QCOMPARE(c.lastIndexOf(3.0, -2), 1);
    +    QCOMPARE(c.lastIndexOf(1.0, -3), 0);
    +    QCOMPARE(c.lastIndexOf(1.0, -4), -1);
    +    QCOMPARE(c.lastIndexOf(1.0, 1), 0);
    +    QCOMPARE(c.lastIndexOf(3.0, 2), 1);
    +    QCOMPARE(c.lastIndexOf(1.0, 2), 2);
    +    QCOMPARE(c.lastIndexOf(1.0, 3), 2);
    +    QCOMPARE(c.lastIndexOf(1.0, 4), 2);
    +    c.removeAll(3.0);
    +    QCOMPARE(c.count(), 2);
    +    c.removeAll(4.0);
    +    QCOMPARE(c.count(), 2);
    +    c.removeAll(1.0);
    +    QCOMPARE(c.count(), 0);
    +    c.removeAll(4.0);
    +    QCOMPARE(c.count(), 0);
    +}//t_search
    +
    +void Coordinates_test::
    +t_io()
    +{
    +    Coordinates c;
    +    c << 1.0 << 2.0 << 3.0;
    +    ostringstream os;
    +    os << "Coordinates 1-2-3\n" << c;
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count("2"), 2);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/Coordinates_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp b/xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp
    new file mode 100644
    index 0000000000..09285954df
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/PointCoordinates_test.cpp
    @@ -0,0 +1,478 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/PointCoordinates_test.cpp#2 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/PointCoordinates.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +using std::stringstream;
    +
    +namespace orgQhull {
    +
    +class PointCoordinates_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void t_construct_q();
    +    void t_construct_qh();
    +    void t_convert();
    +    void t_getset();
    +    void t_element();
    +    void t_foreach();
    +    void t_search();
    +    void t_modify();
    +    void t_append_points();
    +    void t_coord_iterator();
    +    void t_io();
    +};//PointCoordinates_test
    +
    +void
    +add_PointCoordinates_test()
    +{
    +    new PointCoordinates_test();  // RoadTest::s_testcases
    +}
    +
    +void PointCoordinates_test::
    +t_construct_q()
    +{
    +    Qhull q;
    +    PointCoordinates pc(q);
    +    QCOMPARE(pc.size(), 0U);
    +    QCOMPARE(pc.coordinateCount(), 0);
    +    QCOMPARE(pc.dimension(), 0);
    +    QCOMPARE(pc.coordinates(), (coordT *)0);
    +    QVERIFY(pc.isEmpty());
    +    pc.checkValid();
    +    PointCoordinates pc7(q, 2, "test explicit dimension");
    +    QCOMPARE(pc7.dimension(), 2);
    +    QCOMPARE(pc7.count(), 0);
    +    QVERIFY(pc7.isEmpty());
    +    QCOMPARE(pc7.comment(), std::string("test explicit dimension"));
    +    pc7.checkValid();
    +    PointCoordinates pc2(q, "Test pc2");
    +    QCOMPARE(pc2.count(), 0);
    +    QVERIFY(pc2.isEmpty());
    +    QCOMPARE(pc2.comment(), std::string("Test pc2"));
    +    pc2.checkValid();
    +    PointCoordinates pc3(q, 3, "Test 3-d pc3");
    +    QCOMPARE(pc3.dimension(), 3);
    +    QVERIFY(pc3.isEmpty());
    +    pc3.checkValid();
    +    coordT c[]= { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 };
    +    PointCoordinates pc4(q, 2, "Test 2-d pc4", 6, c);
    +    QCOMPARE(pc4.dimension(), 2);
    +    QCOMPARE(pc4.count(), 3);
    +    QCOMPARE(pc4.size(), 3u);
    +    QVERIFY(!pc4.isEmpty());
    +    pc4.checkValid();
    +    QhullPoint p= pc4[2];
    +    QCOMPARE(p[1], 5.0);
    +    // QhullPoint refers to PointCoordinates
    +    p[1] += 1.0;
    +    QCOMPARE(pc4[2][1], 6.0);
    +    PointCoordinates pc5(q, 4, "Test 4-d pc5 with insufficient coordinates", 6, c);
    +    QCOMPARE(pc5.dimension(), 4);
    +    QCOMPARE(pc5.count(), 1);
    +    QCOMPARE(pc5.extraCoordinatesCount(), 2);
    +    QCOMPARE(pc5.extraCoordinates()[1], 5.0);
    +    QVERIFY(!pc5.isEmpty());;
    +    std::vector vc;
    +    vc.push_back(3.0);
    +    vc.push_back(4.0);
    +    vc.push_back(5.0);
    +    vc.push_back(6.0);
    +    vc.push_back(7.0);
    +    vc.push_back(9.0);
    +    pc5.append(2, &vc[3]); // Copy of vc[]
    +    pc5.checkValid();
    +    QhullPoint p5(q, 4, &vc[1]);
    +    QCOMPARE(pc5[1], p5);
    +    PointCoordinates pc6(pc5); // Makes copy of point_coordinates
    +    QCOMPARE(pc6[1], p5);
    +    QVERIFY(pc6==pc5);
    +    QhullPoint p6= pc5[1];  // Refers to pc5.coordinates
    +    pc5[1][0] += 1.0;
    +    QCOMPARE(pc5[1], p6);
    +    QVERIFY(pc5[1]!=p5);
    +    QVERIFY(pc6!=pc5);
    +    pc6= pc5;
    +    QVERIFY(pc6==pc5);
    +    PointCoordinates pc8(q);
    +    pc6= pc8;
    +    QVERIFY(pc6!=pc5);
    +    QVERIFY(pc6.isEmpty());
    +}//t_construct_q
    +
    +void PointCoordinates_test::
    +t_construct_qh()
    +{
    +    QhullQh qh;
    +    PointCoordinates pc(&qh);
    +    QCOMPARE(pc.size(), 0U);
    +    QCOMPARE(pc.coordinateCount(), 0);
    +    QCOMPARE(pc.dimension(), 0);
    +    QCOMPARE(pc.coordinates(), (coordT *)0);
    +    QVERIFY(pc.isEmpty());
    +    pc.checkValid();
    +    PointCoordinates pc7(&qh, 2, "test explicit dimension");
    +    QCOMPARE(pc7.dimension(), 2);
    +    QCOMPARE(pc7.count(), 0);
    +    QVERIFY(pc7.isEmpty());
    +    QCOMPARE(pc7.comment(), std::string("test explicit dimension"));
    +    pc7.checkValid();
    +    PointCoordinates pc2(&qh, "Test pc2");
    +    QCOMPARE(pc2.count(), 0);
    +    QVERIFY(pc2.isEmpty());
    +    QCOMPARE(pc2.comment(), std::string("Test pc2"));
    +    pc2.checkValid();
    +    PointCoordinates pc3(&qh, 3, "Test 3-d pc3");
    +    QCOMPARE(pc3.dimension(), 3);
    +    QVERIFY(pc3.isEmpty());
    +    pc3.checkValid();
    +    coordT c[]= { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 };
    +    PointCoordinates pc4(&qh, 2, "Test 2-d pc4", 6, c);
    +    QCOMPARE(pc4.dimension(), 2);
    +    QCOMPARE(pc4.count(), 3);
    +    QCOMPARE(pc4.size(), 3u);
    +    QVERIFY(!pc4.isEmpty());
    +    pc4.checkValid();
    +    QhullPoint p= pc4[2];
    +    QCOMPARE(p[1], 5.0);
    +    // QhullPoint refers to PointCoordinates
    +    p[1] += 1.0;
    +    QCOMPARE(pc4[2][1], 6.0);
    +    PointCoordinates pc5(&qh, 4, "Test 4-d pc5 with insufficient coordinates", 6, c);
    +    QCOMPARE(pc5.dimension(), 4);
    +    QCOMPARE(pc5.count(), 1);
    +    QCOMPARE(pc5.extraCoordinatesCount(), 2);
    +    QCOMPARE(pc5.extraCoordinates()[1], 5.0);
    +    QVERIFY(!pc5.isEmpty());;
    +    std::vector vc;
    +    vc.push_back(3.0);
    +    vc.push_back(4.0);
    +    vc.push_back(5.0);
    +    vc.push_back(6.0);
    +    vc.push_back(7.0);
    +    vc.push_back(9.0);
    +    pc5.append(2, &vc[3]); // Copy of vc[]
    +    pc5.checkValid();
    +    QhullPoint p5(&qh, 4, &vc[1]);
    +    QCOMPARE(pc5[1], p5);
    +    PointCoordinates pc6(pc5); // Makes copy of point_coordinates
    +    QCOMPARE(pc6[1], p5);
    +    QVERIFY(pc6==pc5);
    +    QhullPoint p6= pc5[1];  // Refers to pc5.coordinates
    +    pc5[1][0] += 1.0;
    +    QCOMPARE(pc5[1], p6);
    +    QVERIFY(pc5[1]!=p5);
    +    QVERIFY(pc6!=pc5);
    +    pc6= pc5;
    +    QVERIFY(pc6==pc5);
    +    PointCoordinates pc8(&qh);
    +    pc6= pc8;
    +    QVERIFY(pc6!=pc5);
    +    QVERIFY(pc6.isEmpty());
    +}//t_construct_qh
    +
    +void PointCoordinates_test::
    +t_convert()
    +{
    +    Qhull q;
    +    //defineAs tested above
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates ps(q, 3, "two 3-d points", 6, c);
    +    QCOMPARE(ps.dimension(), 3);
    +    QCOMPARE(ps.size(), 2u);
    +    const coordT *c2= ps.constData();
    +    QVERIFY(c!=c2);
    +    QCOMPARE(c[0], c2[0]);
    +    const coordT *c3= ps.data();
    +    QCOMPARE(c3, c2);
    +    coordT *c4= ps.data();
    +    QCOMPARE(c4, c2);
    +    std::vector vs= ps.toStdVector();
    +    QCOMPARE(vs.size(), 6u);
    +    QCOMPARE(vs[5], 5.0);
    +    QList qs= ps.toQList();
    +    QCOMPARE(qs.size(), 6);
    +    QCOMPARE(qs[5], 5.0);
    +}//t_convert
    +
    +void PointCoordinates_test::
    +t_getset()
    +{
    +    // See t_construct() for test of coordinates, coordinateCount, dimension, empty, isEmpty, ==, !=
    +    // See t_construct() for test of checkValid, comment, setDimension
    +    Qhull q;
    +    PointCoordinates pc(q, "Coordinates c");
    +    pc.setComment("New comment");
    +    QCOMPARE(pc.comment(), std::string("New comment"));
    +    pc.checkValid();
    +    pc.makeValid();  // A no-op
    +    pc.checkValid();
    +    Coordinates cs= pc.getCoordinates();
    +    QVERIFY(cs.isEmpty());
    +    PointCoordinates pc2(pc);
    +    pc.setDimension(3);
    +    QVERIFY(pc2!=pc);
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    pc.append(6, c);
    +    pc.checkValid();
    +    pc.makeValid();  // A no-op
    +    QhullPoint p= pc[0];
    +    QCOMPARE(p[2], 2.0);
    +    try{
    +        pc.setDimension(2);
    +        QFAIL("setDimension(2) did not fail for 3-d.");
    +    }catch (const std::exception &e) {
    +        const char *s= e.what();
    +        cout << "INFO   : Caught " << s;
    +    }
    +}//t_getset
    +
    +void PointCoordinates_test::
    +t_element()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    QhullPoint p= pc.at(0);
    +    QCOMPARE(p, pc[0]);
    +    QCOMPARE(p, pc.first());
    +    QCOMPARE(p, pc.value(0));
    +    p= pc.back();
    +    QCOMPARE(p, pc[2]);
    +    QCOMPARE(p, pc.last());
    +    QCOMPARE(p, pc.value(2));
    +    QhullPoints ps= pc.mid(1, 2);
    +    QCOMPARE(ps[1], p);
    +}//t_element
    +
    +void PointCoordinates_test::
    +t_foreach()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    QhullPoints::Iterator i= pc.begin();
    +    QhullPoint p= pc[0];
    +    QCOMPARE(*i, p);
    +    QCOMPARE((*i)[0], 0.0);
    +    QhullPoint p3= pc[2];
    +    i= pc.end();
    +    QCOMPARE(i[-1], p3);
    +    const PointCoordinates pc2(q, 2, "2-d points", 6, c);
    +    QhullPoints::ConstIterator i2= pc.begin();
    +    const QhullPoint p0= pc2[0];
    +    QCOMPARE(*i2, p0);
    +    QCOMPARE((*i2)[0], 0.0);
    +    QhullPoints::ConstIterator i3= i2;
    +    QCOMPARE(i3, i2);
    +    QCOMPARE((*i3)[0], 0.0);
    +    i3= pc.constEnd();
    +    --i3;
    +    QhullPoint p2= pc2[2];
    +    QCOMPARE(*i3, p2);
    +    i= pc.end();
    +    QVERIFY(i-1==i3);
    +    i2= pc2.end();
    +    QVERIFY(i2-1!=i3);
    +    QCOMPARE(*(i2-1), *i3);
    +    foreach(QhullPoint p3, pc){ //Qt only
    +        QVERIFY(p3[0]>=0.0);
    +        QVERIFY(p3[0]<=5.0);
    +    }
    +    Coordinates::ConstIterator i4= pc.beginCoordinates();
    +    QCOMPARE(*i4, 0.0);
    +    Coordinates::Iterator i5= pc.beginCoordinates();
    +    QCOMPARE(*i5, 0.0);
    +    i4= pc.beginCoordinates(1);
    +    QCOMPARE(*i4, 2.0);
    +    i5= pc.beginCoordinates(1);
    +    QCOMPARE(*i5, 2.0);
    +    i4= pc.endCoordinates();
    +    QCOMPARE(*--i4, 5.0);
    +    i5= pc.endCoordinates();
    +    QCOMPARE(*--i5, 5.0);
    +}//t_foreach
    +
    +void PointCoordinates_test::
    +t_search()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    QhullPoint p0= pc[0];
    +    QhullPoint p2= pc[2];
    +    QVERIFY(pc.contains(p0));
    +    QVERIFY(pc.contains(p2));
    +    QCOMPARE(pc.count(p0), 1);
    +    QCOMPARE(pc.indexOf(p2), 2);
    +    QCOMPARE(pc.lastIndexOf(p0), 0);
    +}//t_search
    +
    +void PointCoordinates_test::
    +t_modify()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc(q, 2, "2-d points", 6, c);
    +    coordT c3[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    PointCoordinates pc5(q, 2, "test explicit dimension");
    +    pc5.append(6, c3); // 0-5
    +    QVERIFY(pc5==pc);
    +    PointCoordinates pc2(q, 2, "2-d");
    +    coordT c2[]= {6.0, 7.0, 8.0, 9.0, 10.0, 11.0};
    +    pc2.append(6, c2);
    +    QCOMPARE(pc2.count(), 3);
    +    pc2.append(14.0);
    +    QCOMPARE(pc2.count(), 3);
    +    QCOMPARE(pc2.extraCoordinatesCount(), 1);
    +    pc2.append(15.0); // 6-11, 14,15
    +    QCOMPARE(pc2.count(), 4);
    +    QCOMPARE(pc2.extraCoordinatesCount(), 0);
    +    QhullPoint p(pc[0]);
    +    pc2.append(p); // 6-11, 14,15, 0,1
    +    QCOMPARE(pc2.count(), 5);
    +    QCOMPARE(pc2.extraCoordinatesCount(), 0);
    +    QCOMPARE(pc2.lastIndexOf(p), 4);
    +    pc.append(pc2); // Invalidates p
    +    QCOMPARE(pc.count(), 8); // 0-11, 14,15, 0,1
    +    QCOMPARE(pc.extraCoordinatesCount(), 0);
    +    QCOMPARE(pc.lastIndexOf(pc[0]), 7);
    +    pc.appendComment(" operators");
    +    QCOMPARE(pc.comment(), std::string("2-d points operators"));
    +    pc.checkValid();
    +    // see t_append_points for appendPoints
    +    PointCoordinates pc3= pc+pc2;
    +    pc3.checkValid();
    +    QCOMPARE(pc3.count(), 13);
    +    QCOMPARE(pc3[6][0], 14.0);
    +    QCOMPARE(pc3[8][0], 6.0);
    +    pc3 += pc;
    +    QCOMPARE(pc3.count(), 21);
    +    QCOMPARE(pc3[14][0], 2.0);
    +    pc3 += 12.0;
    +    pc3 += 14.0;
    +    QCOMPARE(pc3.count(), 22);
    +    QCOMPARE(pc3.last()[0], 12.0);
    +    // QhullPoint p3= pc3.first(); // += throws error because append may move the data
    +    QhullPoint p3= pc2.first();
    +    pc3 += p3;
    +    QCOMPARE(pc3.count(), 23);
    +    QCOMPARE(pc3.last()[0], 6.0);
    +    pc3 << pc;
    +    QCOMPARE(pc3.count(), 31);
    +    QCOMPARE(pc3.last()[0], 0.0);
    +    pc3 << 12.0 << 14.0;
    +    QCOMPARE(pc3.count(), 32);
    +    QCOMPARE(pc3.last()[0], 12.0);
    +    PointCoordinates pc4(pc3);
    +    pc4.reserveCoordinates(100);
    +    QVERIFY(pc3==pc4);
    +}//t_modify
    +
    +void PointCoordinates_test::
    +t_append_points()
    +{
    +    Qhull q;
    +    PointCoordinates pc(q, 2, "stringstream");
    +    stringstream s("2 3 1 2 3 4 5 6");
    +    pc.appendPoints(s);
    +    QCOMPARE(pc.count(), 3);
    +}//t_append_points
    +
    +void PointCoordinates_test::
    +t_coord_iterator()
    +{
    +    Qhull q;
    +    PointCoordinates c(q, 2, "2-d");
    +    c << 0.0 << 1.0 << 2.0 << 3.0 << 4.0 << 5.0;
    +    PointCoordinatesIterator i(c);
    +    QhullPoint p0(c[0]);
    +    QhullPoint p1(c[1]);
    +    QhullPoint p2(c[2]);
    +    coordT c2[] = {-1.0, -2.0};
    +    QhullPoint p3(q, 2, c2);
    +    PointCoordinatesIterator i2= c;
    +    QVERIFY(i.findNext(p1));
    +    QVERIFY(!i.findNext(p1));
    +    QVERIFY(!i.findNext(p2));
    +    QVERIFY(!i.findNext(p3));
    +    QVERIFY(i.findPrevious(p2));
    +    QVERIFY(!i.findPrevious(p2));
    +    QVERIFY(!i.findPrevious(p0));
    +    QVERIFY(!i.findPrevious(p3));
    +    QVERIFY(i2.findNext(p2));
    +    QVERIFY(i2.findPrevious(p0));
    +    QVERIFY(i2.findNext(p1));
    +    QVERIFY(i2.findPrevious(p0));
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    i2.toFront();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(i.hasPrevious());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    PointCoordinates c3(q);
    +    PointCoordinatesIterator i3= c3;
    +    QVERIFY(!i3.hasNext());
    +    QVERIFY(!i3.hasPrevious());
    +    i3.toBack();
    +    QVERIFY(!i3.hasNext());
    +    QVERIFY(!i3.hasPrevious());
    +    QCOMPARE(i.peekPrevious(), p2);
    +    QCOMPARE(i.previous(), p2);
    +    QCOMPARE(i.previous(), p1);
    +    QCOMPARE(i.previous(), p0);
    +    QVERIFY(!i.hasPrevious());
    +    QCOMPARE(i.peekNext(), p0);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), p0);
    +    QCOMPARE(i.peekNext(), p1);
    +    QCOMPARE(i.next(), p1);
    +    QCOMPARE(i.next(), p2);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p0);
    +}//t_coord_iterator
    +
    +void PointCoordinates_test::
    +t_io()
    +{
    +    Qhull q;
    +    PointCoordinates c(q);
    +    ostringstream os;
    +    os << "PointCoordinates 0-d\n" << c;
    +    c.setDimension(2);
    +    c << 1.0 << 2.0 << 3.0 << 1.0 << 2.0 << 3.0;
    +    os << "PointCoordinates 1,2 3,1 2,3\n" << c;
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count("0"), 3);
    +    QCOMPARE(s.count("2"), 5);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/PointCoordinates_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp b/xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp
    new file mode 100644
    index 0000000000..5a09d01da9
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullFacetList_test.cpp
    @@ -0,0 +1,196 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullFacetList_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullFacetList_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct_qh();
    +    void t_construct_q();
    +    void t_convert();
    +    void t_readonly();
    +    void t_foreach();
    +    void t_io();
    +};//QhullFacetList_test
    +
    +void
    +add_QhullFacetList_test()
    +{
    +    new QhullFacetList_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullFacetList_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullFacetList_test::
    +t_construct_qh()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacetList fs2= q.facetList();
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),6);
    +    QhullFacetList fs3(q.endFacet(), q.endFacet());
    +    QVERIFY(fs3.isEmpty());
    +    QhullFacetList fs4(q.endFacet().previous(), q.endFacet());
    +    QCOMPARE(fs4.count(), 1);
    +    QhullFacetList fs5(q.beginFacet(), q.endFacet());
    +    QCOMPARE(fs2.count(), fs5.count());
    +    QVERIFY(fs2==fs5);
    +    QhullFacetList fs6= fs2; // copy constructor
    +    QVERIFY(fs6==fs2);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 6u);
    +}//t_construct_qh
    +
    +void QhullFacetList_test::
    +t_construct_q()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacetList fs2= q.facetList();
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),6);
    +    QhullFacetList fs3(q.endFacet(), q.endFacet());
    +    QVERIFY(fs3.isEmpty());
    +    QhullFacetList fs4(q.endFacet().previous(), q.endFacet());
    +    QCOMPARE(fs4.count(), 1);
    +    QhullFacetList fs5(q.beginFacet(), q.endFacet());
    +    QCOMPARE(fs2.count(), fs5.count());
    +    QVERIFY(fs2==fs5);
    +    QhullFacetList fs6= fs2; // copy constructor
    +    QVERIFY(fs6==fs2);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 6u);
    +}//t_construct_q
    +
    +void QhullFacetList_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0 QV2");  // rotated unit cube
    +    QhullFacetList fs2= q.facetList();
    +    QVERIFY(!fs2.isSelectAll());
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),3);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 3u);
    +    QList fv2= fs2.toQList();
    +    QCOMPARE(fv2.size(), 3);
    +    std::vector fv5= fs2.vertices_toStdVector();
    +    QCOMPARE(fv5.size(), 7u);
    +    QList fv6= fs2.vertices_toQList();
    +    QCOMPARE(fv6.size(), 7);
    +    fs2.selectAll();
    +    QVERIFY(fs2.isSelectAll());
    +    std::vector fv3= fs2.toStdVector();
    +    QCOMPARE(fv3.size(), 6u);
    +    QList fv4= fs2.toQList();
    +    QCOMPARE(fv4.size(), 6);
    +    std::vector fv7= fs2.vertices_toStdVector();
    +    QCOMPARE(fv7.size(), 8u);
    +    QList fv8= fs2.vertices_toQList();
    +    QCOMPARE(fv8.size(), 8);
    +}//t_convert
    +
    +//! Spot check properties and read-only.  See QhullLinkedList_test
    +void QhullFacetList_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QV0");  // good facets are adjacent to point 0
    +    QhullFacetList fs= q.facetList();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 3);
    +    QCOMPARE(fs.first(), q.firstFacet());
    +    fs.selectAll();
    +    QVERIFY(fs.isSelectAll());
    +    QCOMPARE(fs.count(), 6);
    +    fs.selectGood();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 3);
    +    fs.selectAll();
    +    QVERIFY(fs.isSelectAll());
    +    QCOMPARE(fs.count(), 6);
    +    QhullFacet f= fs.first();
    +    QhullFacet f2= fs.last();
    +    fs.selectAll();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(fs.contains(f2));
    +    QVERIFY(f.isGood());
    +    QVERIFY(!f2.isGood());
    +    fs.selectGood();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(!fs.contains(f2));
    +}//t_readonly
    +
    +void QhullFacetList_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    // Spot check predicates and accessors.  See QhullLinkedList_test
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacetList fs= q.facetList();
    +    QVERIFY(fs.contains(q.firstFacet()));
    +    QhullFacet f= q.firstFacet().next();
    +    QVERIFY(fs.contains(f));
    +    QCOMPARE(fs.first(), *fs.begin());
    +    QCOMPARE(*(fs.end()-1), fs.last());
    +    QCOMPARE(fs.first(), q.firstFacet());
    +    QCOMPARE(*fs.begin(), q.beginFacet());
    +    QCOMPARE(*fs.end(), q.endFacet());
    +}//t_foreach
    +
    +void QhullFacetList_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0 QV0");   // good facets are adjacent to point 0
    +        QhullFacetList fs= q.facetList();
    +        ostringstream os;
    +        os << fs.print("Show all of FacetList\n");
    +        os << "\nFacets only\n" << fs;
    +        os << "\nVertices only\n" << fs.printVertices();
    +        cout << os.str();
    +        QString facets= QString::fromStdString(os.str());
    +        QCOMPARE(facets.count("(v"), 2*7+12*3*2);
    +        QCOMPARE(facets.count(QRegExp("f\\d")), 2*3*7 + 13*3*2);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullFacetList_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp b/xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp
    new file mode 100644
    index 0000000000..a7fe123a28
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullFacetSet_test.cpp
    @@ -0,0 +1,153 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullFacetSet_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullFacetSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_readonly();
    +    void t_foreach();
    +    void t_io();
    +};//QhullFacetSet_test
    +
    +void
    +add_QhullFacetSet_test()
    +{
    +    new QhullFacetSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullFacetSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullFacetSet_test::
    +t_construct()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacet f= q.firstFacet();
    +    QhullFacetSet fs2= f.neighborFacets();
    +    QVERIFY(!fs2.isEmpty());
    +    QCOMPARE(fs2.count(),4);
    +    QhullFacetSet fs4= fs2; // copy constructor
    +    QVERIFY(fs4==fs2);
    +    QhullFacetSet fs3(q, q.qh()->facet_mergeset);
    +    QVERIFY(fs3.isEmpty());
    +}//t_construct
    +
    +void QhullFacetSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q2(rcube,"QR0 QV2");  // rotated unit cube
    +    QhullFacet f2= q2.firstFacet();
    +    QhullFacetSet fs2= f2.neighborFacets();
    +    QVERIFY(!fs2.isSelectAll());
    +    QCOMPARE(fs2.count(),2);
    +    std::vector fv= fs2.toStdVector();
    +    QCOMPARE(fv.size(), 2u);
    +    QList fv2= fs2.toQList();
    +    QCOMPARE(fv2.size(), 2);
    +    fs2.selectAll();
    +    QVERIFY(fs2.isSelectAll());
    +    std::vector fv3= fs2.toStdVector();
    +    QCOMPARE(fv3.size(), 4u);
    +    QList fv4= fs2.toQList();
    +    QCOMPARE(fv4.size(), 4);
    +}//t_convert
    +
    +//! Spot check properties and read-only.  See QhullSet_test
    +void QhullFacetSet_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QV0");  // good facets are adjacent to point 0
    +    QhullFacetSet fs= q.firstFacet().neighborFacets();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 2);
    +    fs.selectAll();
    +    QVERIFY(fs.isSelectAll());
    +    QCOMPARE(fs.count(), 4);
    +    fs.selectGood();
    +    QVERIFY(!fs.isSelectAll());
    +    QCOMPARE(fs.count(), 2);
    +    QhullFacet f= fs.first();
    +    QhullFacet f2= fs.last();
    +    fs.selectAll();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(fs.contains(f2));
    +    QVERIFY(f.isGood());
    +    QVERIFY(!f2.isGood());
    +    fs.selectGood();
    +    QVERIFY(fs.contains(f));
    +    QVERIFY(!fs.contains(f2));
    +}//t_readonly
    +
    +void QhullFacetSet_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    // Spot check predicates and accessors.  See QhullLinkedList_test
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacetSet fs= q.firstFacet().neighborFacets();
    +    QVERIFY(!fs.contains(q.firstFacet()));
    +    QVERIFY(fs.contains(fs.first()));
    +    QhullFacet f= q.firstFacet().next();
    +    if(!fs.contains(f)){  // check if 'f' is the facet opposite firstFacet()
    +        f= f.next();
    +    }
    +    QVERIFY(fs.contains(f));
    +    QCOMPARE(fs.first(), *fs.begin());
    +    QCOMPARE(*(fs.end()-1), fs.last());
    +}//t_foreach
    +
    +void QhullFacetSet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0 QV0");   // good facets are adjacent to point 0
    +        QhullFacetSet fs= q.firstFacet().neighborFacets();
    +        ostringstream os;
    +        os << fs.print("Neighbors of first facet with point 0");
    +        os << fs.printIdentifiers("\nFacet identifiers: ");
    +        cout << os.str();
    +        QString facets= QString::fromStdString(os.str());
    +        QCOMPARE(facets.count(QRegExp(" f[0-9]")), 2+13*2);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullFacetSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullFacet_test.cpp b/xs/src/qhull/src/qhulltest/QhullFacet_test.cpp
    new file mode 100644
    index 0000000000..271f63753c
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullFacet_test.cpp
    @@ -0,0 +1,283 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullFacet_test.cpp#4 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullPointSet.h"
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullFacet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct_qh();
    +    void t_constructConvert();
    +    void t_getSet();
    +    void t_value();
    +    void t_foreach();
    +    void t_io();
    +};//QhullFacet_test
    +
    +void
    +add_QhullFacet_test()
    +{
    +    new QhullFacet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullFacet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullFacet_test::
    +t_construct_qh()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    QhullQh qh;
    +    QhullFacet f(&qh);
    +    QVERIFY(!f.isValid());
    +    QCOMPARE(f.dimension(),0);
    +}//t_construct_qh
    +
    +void QhullFacet_test::
    +t_constructConvert()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    Qhull q2;
    +    QhullFacet f(q2);
    +    QVERIFY(!f.isValid());
    +    QCOMPARE(f.dimension(),0);
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacet f2(q.beginFacet());
    +    QCOMPARE(f2.dimension(),3);
    +    f= f2; // copy assignment
    +    QVERIFY(f.isValid());
    +    QCOMPARE(f.dimension(),3);
    +    QhullFacet f5= f2;
    +    QVERIFY(f5==f2);
    +    QVERIFY(f5==f);
    +    QhullFacet f3(q, f2.getFacetT());
    +    QCOMPARE(f,f3);
    +    QhullFacet f4(q, f2.getBaseT());
    +    QCOMPARE(f,f4);
    +}//t_constructConvert
    +
    +void QhullFacet_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        cout << " rbox c | qhull Qt QR0 QR" << q.rotateRandom() << "   distanceEpsilon " << q.distanceEpsilon() << endl;
    +        QCOMPARE(q.facetCount(), 12);
    +        QCOMPARE(q.vertexCount(), 8);
    +        QhullFacetListIterator i(q.facetList());
    +        while(i.hasNext()){
    +            const QhullFacet f= i.next();
    +            cout << f.id() << endl;
    +            QCOMPARE(f.dimension(),3);
    +            QVERIFY(f.id()>0 && f.id()<=39);
    +            QVERIFY(f.isValid());
    +            if(i.hasNext()){
    +                QCOMPARE(f.next(), i.peekNext());
    +                QVERIFY(f.next()!=f);
    +            }
    +            QVERIFY(i.hasPrevious());
    +            QCOMPARE(f, i.peekPrevious());
    +        }
    +
    +        // test tricoplanarOwner
    +        QhullFacet facet = q.beginFacet();
    +        QhullFacet tricoplanarOwner = facet.tricoplanarOwner();
    +        int tricoplanarCount= 0;
    +        i.toFront();
    +        while(i.hasNext()){
    +            const QhullFacet f= i.next();
    +            if(f.tricoplanarOwner()==tricoplanarOwner){
    +                tricoplanarCount++;
    +            }
    +        }
    +        QCOMPARE(tricoplanarCount, 2);
    +        int tricoplanarCount2= 0;
    +        foreach (QhullFacet f, q.facetList()){  // Qt only
    +            QhullHyperplane h= f.hyperplane();
    +            cout << "Hyperplane: " << h;
    +            QCOMPARE(h.count(), 3);
    +            QCOMPARE(h.offset(), -0.5);
    +            double n= h.norm();
    +            QCOMPARE(n, 1.0);
    +            QhullHyperplane hi= f.innerplane();
    +            QCOMPARE(hi.count(), 3);
    +            double innerOffset= hi.offset()+0.5;
    +            cout << "InnerPlane: " << hi << "   innerOffset+0.5 " << innerOffset << endl;
    +            QVERIFY(innerOffset >= 0.0-(2*q.distanceEpsilon())); // A guessed epsilon.  It needs to account for roundoff due to rotation of the vertices
    +            QhullHyperplane ho= f.outerplane();
    +            QCOMPARE(ho.count(), 3);
    +            double outerOffset= ho.offset()+0.5;
    +            cout << "OuterPlane: " << ho << "   outerOffset+0.5 " << outerOffset << endl;
    +            QVERIFY(outerOffset <= 0.0+(2*q.distanceEpsilon())); // A guessed epsilon.  It needs to account for roundoff due to rotation of the vertices
    +            QVERIFY(outerOffset-innerOffset < 1e-7);
    +            for(int k= 0; k<3; k++){
    +                QVERIFY(ho[k]==hi[k]);
    +                QVERIFY(ho[k]==h[k]);
    +            }
    +            QhullPoint center= f.getCenter();
    +            cout << "Center: " << center;
    +            double d= f.distance(center);
    +            QVERIFY(d < innerOffset-outerOffset);
    +            QhullPoint center2= f.getCenter(qh_PRINTcentrums);
    +            QCOMPARE(center, center2);
    +            if(f.tricoplanarOwner()==tricoplanarOwner){
    +                tricoplanarCount2++;
    +            }
    +            cout << endl;
    +        }
    +        QCOMPARE(tricoplanarCount2, 2);
    +        Qhull q2(rcube,"d Qz Qt QR0");  // 3-d triangulation of Delaunay triangulation (the cube)
    +        cout << " rbox c | qhull d Qz Qt QR0 QR" << q2.rotateRandom() << "   distanceEpsilon " << q2.distanceEpsilon() << endl;
    +        QhullFacet f2= q2.firstFacet();
    +        QhullPoint center3= f2.getCenter(qh_PRINTtriangles);
    +        QCOMPARE(center3.dimension(), 3);
    +        QhullPoint center4= f2.getCenter();
    +        QCOMPARE(center4.dimension(), 4);
    +        for(int k= 0; k<3; k++){
    +            QVERIFY(center4[k]==center3[k]);
    +        }
    +        Qhull q3(rcube,"v Qz QR0");  // Voronoi diagram of a cube (one vertex)
    +        cout << " rbox c | qhull v Qz QR0 QR" << q3.rotateRandom() << "   distanceEpsilon " << q3.distanceEpsilon() << endl;
    +
    +        q3.setFactorEpsilon(400); // Voronoi vertices are not necessarily within distance episilon
    +        QhullPoint origin= q3.inputOrigin();
    +        int voronoiCount= 0;
    +        foreach(QhullFacet f, q3.facetList()){ //Qt only
    +            if(f.isGood()){
    +                ++voronoiCount;
    +                QhullPoint p= f.voronoiVertex();
    +                cout << p.print("Voronoi vertex: ")
    +                    << " Is it within " << q3.factorEpsilon() << " * distanceEpsilon (" << q3.distanceEpsilon() << ") of the origin?" << endl;
    +                QCOMPARE(p, origin);
    +            }
    +        }
    +        QCOMPARE(voronoiCount, 1);
    +    }
    +}//t_getSet
    +
    +void QhullFacet_test::
    +t_value()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        coordT c[]= {0.0, 0.0, 0.0};
    +        foreach (QhullFacet f, q.facetList()){  // Qt only
    +            double d= f.distance(q.origin());
    +            QCOMPARE(d, -0.5);
    +            double d0= f.distance(c);
    +            QCOMPARE(d0, -0.5);
    +            double facetArea= f.facetArea();
    +            QCOMPARE(facetArea, 1.0);
    +            #if qh_MAXoutside
    +                double maxoutside= f.getFacetT()->maxoutside;
    +                QVERIFY(maxoutside<1e-7);
    +            #endif
    +        }
    +    }
    +}//t_value
    +
    +void QhullFacet_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c W0 300");  // cube plus 300 points on its surface
    +    {
    +        Qhull q(rcube, "QR0 Qc"); // keep coplanars, thick facet, and rotate the cube
    +        int coplanarCount= 0;
    +        foreach(const QhullFacet f, q.facetList()){
    +            QhullPointSet coplanars= f.coplanarPoints();
    +            coplanarCount += coplanars.count();
    +            QhullFacetSet neighbors= f.neighborFacets();
    +            QCOMPARE(neighbors.count(), 4);
    +            QhullPointSet outsides= f.outsidePoints();
    +            QCOMPARE(outsides.count(), 0);
    +            QhullRidgeSet ridges= f.ridges();
    +            QCOMPARE(ridges.count(), 4);
    +            QhullVertexSet vertices= f.vertices();
    +            QCOMPARE(vertices.count(), 4);
    +            int ridgeCount= 0;
    +            QhullRidge r= ridges.first();
    +            for(int r0= r.id(); ridgeCount==0 || r.id()!=r0; r= r.nextRidge3d(f)){
    +                ++ridgeCount;
    +                if(!r.hasNextRidge3d(f)){
    +                    QFAIL("Unexpected simplicial facet.  They only have ridges to non-simplicial neighbors.");
    +                }
    +            }
    +            QCOMPARE(ridgeCount, 4);
    +        }
    +        QCOMPARE(coplanarCount, 300);
    +    }
    +}//t_foreach
    +
    +void QhullFacet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullFacet f= q.beginFacet();
    +        cout << f;
    +        ostringstream os;
    +        os << f.print("\nWith a message\n");
    +        os << "\nPrint header for the same facet\n";
    +        os << f.printHeader();
    +        os << "\nPrint each component\n";
    +        os << f.printFlags("    - flags:");
    +        os << f.printCenter(qh_PRINTfacets, "    - center: ");
    +        os << f.printRidges();
    +        cout << os.str();
    +        ostringstream os2;
    +        os2 << f;
    +        QString facetString2= QString::fromStdString(os2.str());
    +        facetString2.replace(QRegExp("\\s\\s+"), " ");
    +        ostringstream os3;
    +        q.qh()->setOutputStream(&os3);
    +        q.outputQhull("f");
    +        QString facetsString= QString::fromStdString(os3.str());
    +        QString facetString3= facetsString.mid(facetsString.indexOf("- f1\n"));
    +        facetString3= facetString3.left(facetString3.indexOf("\n- f")+1);
    +        facetString3.replace(QRegExp("\\s\\s+"), " ");
    +        QCOMPARE(facetString2, facetString3);
    +    }
    +}//t_io
    +
    +// toQhullFacet is static_cast only
    +
    +}//orgQhull
    +
    +#include "moc/QhullFacet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp b/xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp
    new file mode 100644
    index 0000000000..d016989a97
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullHyperplane_test.cpp
    @@ -0,0 +1,429 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullHyperplane_test.cpp#4 $$Change: 2064 $
    +** $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullHyperplane.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullHyperplane_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_construct_qh();
    +    void t_convert();
    +    void t_readonly();
    +    void t_define();
    +    void t_value();
    +    void t_operator();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_qhullHyperplane_iterator();
    +    void t_io();
    +};//QhullHyperplane_test
    +
    +void
    +add_QhullHyperplane_test()
    +{
    +    new QhullHyperplane_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullHyperplane_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullHyperplane_test::
    +t_construct()
    +{
    +    QhullHyperplane h4;
    +    QVERIFY(!h4.isValid());
    +    QCOMPARE(h4.dimension(), 0);
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullHyperplane h(q);
    +    QVERIFY(!h.isValid());
    +    QCOMPARE(h.dimension(), 0);
    +    QCOMPARE(h.coordinates(),static_cast(0));
    +    QhullFacet f= q.firstFacet();
    +    QhullHyperplane h2(f.hyperplane());
    +    QVERIFY(h2.isValid());
    +    QCOMPARE(h2.dimension(), 3);
    +    h= h2;
    +    QCOMPARE(h, h2);
    +    QhullHyperplane h3(q, h2.dimension(), h2.coordinates(), h2.offset());
    +    QCOMPARE(h2, h3);
    +    QhullHyperplane h5= h2; // copy constructor
    +    QVERIFY(h5==h2);
    +}//t_construct
    +
    +void QhullHyperplane_test::
    +t_construct_qh()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacet f= q.firstFacet();
    +    QhullHyperplane h2(f.hyperplane());
    +    QVERIFY(h2.isValid());
    +    QCOMPARE(h2.dimension(), 3);
    +    // h= h2;  // copy assignment disabled, ambiguous
    +    QhullHyperplane h3(q.qh(), h2.dimension(), h2.coordinates(), h2.offset());
    +    QCOMPARE(h2, h3);
    +    QhullHyperplane h5= h2; // copy constructor
    +    QVERIFY(h5==h2);
    +}//t_construct_qh
    +
    +void QhullHyperplane_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullHyperplane h= q.firstFacet().hyperplane();
    +    std::vector fs= h.toStdVector();
    +    QCOMPARE(fs.size(), 4u);
    +    double offset= fs.back();
    +    fs.pop_back();
    +    QCOMPARE(offset, -0.5);
    +
    +    double squareNorm= inner_product(fs.begin(), fs.end(), fs.begin(), 0.0);
    +    QCOMPARE(squareNorm, 1.0);
    +    QList qs= h.toQList();
    +    QCOMPARE(qs.size(), 4);
    +    double offset2= qs.takeLast();
    +    QCOMPARE(offset2, -0.5);
    +    double squareNorm2= std::inner_product(qs.begin(), qs.end(), qs.begin(), 0.0);
    +    QCOMPARE(squareNorm2, 1.0);
    +}//t_convert
    +
    +void QhullHyperplane_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullFacetList fs= q.facetList();
    +        QhullFacetListIterator i(fs);
    +        while(i.hasNext()){
    +            QhullFacet f= i.next();
    +            QhullHyperplane h= f.hyperplane();
    +            int id= f.id();
    +            cout << "h" << id << endl;
    +            QVERIFY(h.isValid());
    +            QCOMPARE(h.dimension(),3);
    +            const coordT *c= h.coordinates();
    +            coordT *c2= h.coordinates();
    +            QCOMPARE(c, c2);
    +            const coordT *c3= h.begin();
    +            QCOMPARE(c, c3);
    +            QCOMPARE(h.offset(), -0.5);
    +            int j= h.end()-h.begin();
    +            QCOMPARE(j, 3);
    +            double squareNorm= std::inner_product(h.begin(), h.end(), h.begin(), 0.0);
    +            QCOMPARE(squareNorm, 1.0);
    +        }
    +        QhullHyperplane h2= fs.first().hyperplane();
    +        QhullHyperplane h3= fs.last().hyperplane();
    +        QVERIFY(h2!=h3);
    +        QVERIFY(h3.coordinates()!=h2.coordinates());
    +    }
    +}//t_readonly
    +
    +void QhullHyperplane_test::
    +t_define()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullFacetList fs= q.facetList();
    +        QhullHyperplane h= fs.first().hyperplane();
    +        QhullHyperplane h2= h;
    +        QVERIFY(h==h2);
    +        QhullHyperplane h3= fs.last().hyperplane();
    +        QVERIFY(h2!=h3);
    +
    +        QhullHyperplane h4= h3;
    +        h4.defineAs(h2);
    +        QVERIFY(h2==h4);
    +        QhullHyperplane p5= h3;
    +        p5.defineAs(h2.dimension(), h2.coordinates(), h2.offset());
    +        QVERIFY(h2==p5);
    +        QhullHyperplane h6= h3;
    +        h6.setCoordinates(h2.coordinates());
    +        QCOMPARE(h2.coordinates(), h6.coordinates());
    +        h6.setOffset(h2.offset());
    +        QCOMPARE(h2.offset(), h6.offset());
    +        QVERIFY(h2==h6);
    +        h6.setDimension(2);
    +        QCOMPARE(h6.dimension(), 2);
    +        QVERIFY(h2!=h6);
    +    }
    +}//t_define
    +
    +void QhullHyperplane_test::
    +t_value()
    +{
    +    RboxPoints rcube("c G1");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullFacet f= q.firstFacet();
    +    QhullFacet f2= f.neighborFacets().at(0);
    +    const QhullHyperplane h= f.hyperplane();
    +    const QhullHyperplane h2= f2.hyperplane();   // At right angles
    +    double dist= h.distance(q.origin());
    +    QCOMPARE(dist, -1.0);
    +    double norm= h.norm();
    +    QCOMPARE(norm, 1.0);
    +    double angle= h.hyperplaneAngle(h2);
    +    cout << "angle " << angle << endl;
    +    QCOMPARE(angle+1.0, 1.0); // qFuzzyCompare does not work for 0.0
    +    QVERIFY(h==h);
    +    QVERIFY(h!=h2);
    +}//t_value
    +
    +void QhullHyperplane_test::
    +t_operator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    const QhullHyperplane h= q.firstFacet().hyperplane();
    +    //operator== and operator!= tested elsewhere
    +    const coordT *c= h.coordinates();
    +    for(int k=h.dimension(); k--; ){
    +        QCOMPARE(c[k], h[k]);
    +    }
    +    //h[0]= 10.0; // compiler error, const
    +    QhullHyperplane h2= q.firstFacet().hyperplane();
    +    h2[0]= 10.0;  // Overwrites Hyperplane coordinate!
    +    QCOMPARE(h2[0], 10.0);
    +}//t_operator
    +
    +void QhullHyperplane_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullHyperplane h= q.firstFacet().hyperplane();
    +        QCOMPARE(h.count(), 3);
    +        QCOMPARE(h.size(), 3u);
    +        QhullHyperplane::Iterator i= h.begin();
    +        QhullHyperplane::iterator i2= h.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= h.begin();
    +        QVERIFY(i==i2);
    +        i2= h.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, h[0]);
    +        QCOMPARE(d2, h[2]);
    +        QhullHyperplane::Iterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), h[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        QhullHyperplane::ConstIterator i4= h.begin();
    +        QVERIFY(i==i4); // iterator COMP const_iterator
    +        QVERIFY(i<=i4);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4==i); // const_iterator COMP iterator
    +        QVERIFY(i4<=i);
    +        QVERIFY(i4>=i);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4<=i);
    +        QVERIFY(i2!=i4);
    +        QVERIFY(i2>i4);
    +        QVERIFY(i2>=i4);
    +        QVERIFY(i4!=i2);
    +        QVERIFY(i4i);
    +        QVERIFY(i4>=i);
    +
    +        i= h.begin();
    +        i2= h.begin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, h[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, h.begin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2 += 3, h.end());
    +        QCOMPARE(i2 -= 3, h.begin());
    +        QCOMPARE(i2+0, h.begin());
    +        QCOMPARE(i2+3, h.end());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, h.begin());
    +        QCOMPARE(i2-i, 3);
    +
    +        //h.begin end tested above
    +
    +        // QhullHyperplane is const-only
    +    }
    +}//t_iterator
    +
    +void QhullHyperplane_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullHyperplane h= q.firstFacet().hyperplane();
    +        QhullHyperplane::ConstIterator i= h.begin();
    +        QhullHyperplane::const_iterator i2= h.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= h.begin();
    +        QVERIFY(i==i2);
    +        i2= h.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, h[0]);
    +        QCOMPARE(d2, h[2]);
    +        QhullHyperplane::ConstIterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), h[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        // See t_iterator for const_iterator COMP iterator
    +
    +        i= h.begin();
    +        i2= h.constBegin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, h[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, h.constBegin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2+=3, h.constEnd());
    +        QCOMPARE(i2-=3, h.constBegin());
    +        QCOMPARE(i2+0, h.constBegin());
    +        QCOMPARE(i2+3, h.constEnd());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, h.constBegin());
    +        QCOMPARE(i2-i, 3);
    +
    +        // QhullHyperplane is const-only
    +    }
    +}//t_const_iterator
    +
    +void QhullHyperplane_test::
    +t_qhullHyperplane_iterator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullHyperplane h = q.firstFacet().hyperplane();
    +    QhullHyperplaneIterator i2(h);
    +    QCOMPARE(h.dimension(), 3);
    +    QhullHyperplaneIterator i= h;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    // i at front, i2 at end/back, 3 coordinates
    +    QCOMPARE(i.peekNext(), h[0]);
    +    QCOMPARE(i2.peekPrevious(), h[2]);
    +    QCOMPARE(i2.previous(), h[2]);
    +    QCOMPARE(i2.previous(), h[1]);
    +    QCOMPARE(i2.previous(), h[0]);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), h[0]);
    +    // i.peekNext()= 1.0; // compiler error, i is const
    +    QCOMPARE(i.next(), h[0]);
    +    QCOMPARE(i.peekNext(), h[1]);
    +    QCOMPARE(i.next(), h[1]);
    +    QCOMPARE(i.next(), h[2]);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), h[0]);
    +}//t_qhullHyperplane_iterator
    +
    +void QhullHyperplane_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullHyperplane h= q.firstFacet().hyperplane();
    +        ostringstream os;
    +        os << "Hyperplane:\n";
    +        os << h;
    +        os << h.print("message");
    +        os << h.print(" and a message ", " offset ");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count("1"), 3);
    +        // QCOMPARE(s.count(QRegExp("f\\d")), 3*7 + 13*3*2);
    +    }
    +}//t_io
    +
    +
    +}//orgQhull
    +
    +#include "moc/QhullHyperplane_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp b/xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp
    new file mode 100644
    index 0000000000..e0cde4050f
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullLinkedList_test.cpp
    @@ -0,0 +1,330 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullLinkedList_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h"
    +
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +namespace orgQhull {
    +
    +class QhullLinkedList_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_element();
    +    void t_search();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_QhullLinkedList_iterator();
    +    void t_io();
    +};//QhullLinkedList_test
    +
    +void
    +add_QhullLinkedList_test()
    +{
    +    new QhullLinkedList_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullLinkedList_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullLinkedList_test::
    +t_construct()
    +{
    +    // QhullLinkedList vs; //private (compiler error).  No memory allocation
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 12);
    +        QhullVertexList vs = QhullVertexList(q.beginVertex(), q.endVertex());
    +        QCOMPARE(vs.count(), 8);
    +        QCOMPARE(vs.size(), 8u);
    +        QVERIFY(!vs.isEmpty());
    +        QhullVertexList vs2 = q.vertexList();
    +        QCOMPARE(vs2.count(), 8);
    +        QCOMPARE(vs2.size(),8u);
    +        QVERIFY(!vs2.isEmpty());
    +        QVERIFY(vs==vs2);
    +        // vs= vs2; // disabled.  Would not copy the vertices
    +        QhullVertexList vs3= vs2; // copy constructor
    +        QVERIFY(vs3==vs2);
    +    }
    +}//t_construct
    +
    +void QhullLinkedList_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 12);
    +        QhullVertexList vs = q.vertexList();
    +        QCOMPARE(vs.size(), 8u);
    +        QVERIFY(!vs.isEmpty());
    +        std::vector vs2= vs.toStdVector();
    +        QCOMPARE(vs2.size(), vs.size());
    +        QhullVertexList::Iterator i= vs.begin();
    +        for(int k= 0; k<(int)vs2.size(); k++){
    +            QCOMPARE(vs2[k], *i++);
    +        }
    +        QList vs3= vs.toQList();
    +        QCOMPARE(vs3.count(), vs.count());
    +        i= vs.begin();
    +        for(int k= 0; k
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullPointSet.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +
    +namespace orgQhull {
    +
    +class QhullPointSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_element();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_search();
    +    void t_pointset_iterator();
    +    void t_io();
    +};//QhullPointSet_test
    +
    +void
    +add_QhullPointSet_test()
    +{
    +    new QhullPointSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullPointSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullPointSet_test::
    +t_construct()
    +{
    +    // Default constructor is disallowed (i.e., private)
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    int coplanarCount= 0;
    +    foreach(QhullFacet f, q.facetList()){
    +        QhullPointSet ps(q, f.getFacetT()->outsideset);
    +        QVERIFY(ps.isEmpty());
    +        QCOMPARE(ps.count(), 0);
    +        QCOMPARE(ps.size(), 0u);
    +        QhullPointSet ps2(q.qh(), f.getFacetT()->coplanarset);
    +        QVERIFY(!ps2.isEmpty());
    +        coplanarCount += ps2.count();
    +        QCOMPARE(ps2.count(), (int)ps2.size());
    +        QhullPointSet ps3(ps2);
    +        QVERIFY(!ps3.isEmpty());
    +        QCOMPARE(ps3.count(), ps2.count());
    +        QVERIFY(ps3==ps2);
    +        QVERIFY(ps3!=ps);
    +        QhullPointSet ps4= ps3;
    +        QVERIFY(ps4==ps2);
    +    }
    +    QCOMPARE(coplanarCount, 1000);
    +}//t_construct
    +
    +void QhullPointSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=1);   // Sometimes no coplanar points
    +    std::vector vs= ps.toStdVector();
    +    QCOMPARE(vs.size(), ps.size());
    +    QhullPoint p= ps[0];
    +    QhullPoint p2= vs[0];
    +    QCOMPARE(p, p2);
    +    QList qs= ps.toQList();
    +    QCOMPARE(qs.size(), static_cast(ps.size()));
    +    QhullPoint p3= qs[0];
    +    QCOMPARE(p3, p);
    +}//t_convert
    +
    +// readonly tested in t_construct
    +//   empty, isEmpty, ==, !=, size
    +
    +void QhullPointSet_test::
    +t_element()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPoint p= ps[0];
    +    QCOMPARE(p, ps[0]);
    +    QhullPoint p2= ps[ps.count()-1];
    +    QCOMPARE(ps.at(1), ps[1]);
    +    QCOMPARE(ps.second(), ps[1]);
    +    QCOMPARE(ps.first(), p);
    +    QCOMPARE(ps.front(), ps.first());
    +    QCOMPARE(ps.last(), p2);
    +    QCOMPARE(ps.back(), ps.last());
    +    QhullPoint p8(q);
    +    QCOMPARE(ps.value(2), ps[2]);
    +    QCOMPARE(ps.value(-1), p8);
    +    QCOMPARE(ps.value(ps.count()), p8);
    +    QCOMPARE(ps.value(ps.count(), p), p);
    +    QVERIFY(ps.value(1, p)!=p);
    +    QhullPointSet ps8= f.coplanarPoints();
    +    QhullPointSet::Iterator i= ps8.begin();
    +    foreach(QhullPoint p9, ps){  // Qt only
    +        QCOMPARE(p9.dimension(), 3);
    +        QCOMPARE(p9, *i++);
    +    }
    +}//t_element
    +
    +void QhullPointSet_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPointSet::Iterator i= ps.begin();
    +    QhullPointSet::iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p= *i;
    +    QCOMPARE(p.dimension(), q.dimension());
    +    QCOMPARE(p, ps[0]);
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p2.dimension(), q.dimension());
    +    QCOMPARE(p2, ps.last());
    +    QhullPointSet::Iterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +    QhullPointSet::Iterator i3= i+1;
    +    QVERIFY(i!=i3);
    +    QCOMPARE(i[1], *i3);
    +    (i3= i)++;
    +    QCOMPARE((*i3)[0], ps[1][0]);
    +    QCOMPARE((*i3).dimension(), 3);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i3);
    +    QVERIFY(ii);
    +    QVERIFY(i3>=i);
    +
    +    QhullPointSet::ConstIterator i4= ps.begin();
    +    QVERIFY(i==i4); // iterator COMP const_iterator
    +    QVERIFY(i<=i4);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4==i); // const_iterator COMP iterator
    +    QVERIFY(i4<=i);
    +    QVERIFY(i4>=i);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4<=i);
    +    QVERIFY(i2!=i4);
    +    QVERIFY(i2>i4);
    +    QVERIFY(i2>=i4);
    +    QVERIFY(i4!=i2);
    +    QVERIFY(i4i);
    +    QVERIFY(i4>=i);
    +    i4= ps.constBegin();
    +    QVERIFY(i==i4); // iterator COMP const_iterator
    +    QCOMPARE(i4+ps.count(), ps.constEnd());
    +
    +    i= ps.begin();
    +    i2= ps.begin();
    +    QCOMPARE(i, i2++);
    +    QCOMPARE(*i2, ps[1]);
    +    QCOMPARE(++i, i2);
    +    QCOMPARE(i, i2--);
    +    QCOMPARE(i2, ps.begin());
    +    QCOMPARE(--i, i2);
    +    QCOMPARE(i2+=ps.count(), ps.end());
    +    QCOMPARE(i2-=ps.count(), ps.begin());
    +    QCOMPARE(i2+0, ps.begin());
    +    QCOMPARE(i2+ps.count(), ps.end());
    +    i2 += ps.count();
    +    i= i2-0;
    +    QCOMPARE(i, i2);
    +    i= i2-ps.count();
    +    QCOMPARE(i, ps.begin());
    +    QCOMPARE(i2-i, ps.count());
    +
    +    //ps.begin end tested above
    +
    +    // QhullPointSet is const-only
    +}//t_iterator
    +
    +void QhullPointSet_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPointSet::ConstIterator i= ps.begin();
    +    QhullPointSet::const_iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +
    +    // See t_iterator for const_iterator COMP iterator
    +
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p= *i; // QhullPoint is the base class for QhullPointSet::iterator
    +    QCOMPARE(p.dimension(), q.dimension());
    +    QCOMPARE(p, ps[0]);
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p2.dimension(), q.dimension());
    +    QCOMPARE(p2, ps.last());
    +    QhullPointSet::ConstIterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +
    +
    +    QhullPointSet::ConstIterator i3= i+1;
    +    QVERIFY(i!=i3);
    +    QCOMPARE(i[1], *i3);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i3);
    +    QVERIFY(ii);
    +    QVERIFY(i3>=i);
    +
    +    // QhullPointSet is const-only
    +}//t_const_iterator
    +
    +
    +void QhullPointSet_test::
    +t_search()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPoint p= ps.first();
    +    QhullPoint p2= ps.last();
    +    QVERIFY(ps.contains(p));
    +    QVERIFY(ps.contains(p2));
    +    QVERIFY(p!=p2);
    +    QhullPoint p3= ps[2];
    +    QVERIFY(ps.contains(p3));
    +    QVERIFY(p!=p3);
    +    QCOMPARE(ps.indexOf(p), 0);
    +    QCOMPARE(ps.indexOf(p2), ps.count()-1);
    +    QCOMPARE(ps.indexOf(p3), 2);
    +    QhullPoint p4(q);
    +    QCOMPARE(ps.indexOf(p4), -1);
    +    QCOMPARE(ps.lastIndexOf(p), 0);
    +    QCOMPARE(ps.lastIndexOf(p2), ps.count()-1);
    +    QCOMPARE(ps.lastIndexOf(p3), 2);
    +    QCOMPARE(ps.lastIndexOf(p4), -1);
    +}//t_search
    +
    +void QhullPointSet_test::
    +t_pointset_iterator()
    +{
    +    RboxPoints rcube("c W0 1000");
    +    Qhull q(rcube,"Qc");  // cube with 1000 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps2= f.outsidePoints();
    +    QVERIFY(ps2.count()==0); // No outside points after constructing the convex hull
    +    QhullPointSetIterator i2= ps2;
    +    QCOMPARE(i2.countRemaining(), 0);
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    QhullPointSetIterator i(ps);
    +    i2= ps;
    +    QCOMPARE(i2.countRemaining(), ps.count());
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    QCOMPARE(i2.countRemaining(), 0);
    +    i.toFront();
    +    QCOMPARE(i.countRemaining(), ps.count());
    +    QCOMPARE(i2.countRemaining(), 0);
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullPoint p= ps[0];
    +    QhullPoint p2(ps[0]);
    +    QCOMPARE(p, p2);
    +    QVERIFY(p==p2);
    +    QhullPoint p3(ps.last());
    + // p2[0]= 0.0;
    +    QVERIFY(p==p2);
    +    QCOMPARE(i2.peekPrevious(), p3);
    +    QCOMPARE(i2.previous(), p3);
    +    QCOMPARE(i2.previous(), ps[ps.count()-2]);
    +    QVERIFY(i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), p);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), p);
    +    QCOMPARE(i.countRemaining(), ps.count()-1);
    +    QhullPoint p4= i.peekNext();
    +    QVERIFY(p4!=p3);
    +    QCOMPARE(i.next(), p4);
    +    QVERIFY(i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p);
    +}//t_pointset_iterator
    +
    +void QhullPointSet_test::
    +t_io()
    +{
    +    ostringstream os;
    +    RboxPoints rcube("c W0 120");
    +    Qhull q(rcube,"Qc");  // cube with 100 coplanar points
    +    QhullFacet f= q.firstFacet();
    +    QhullPointSet ps= f.coplanarPoints();
    +    QVERIFY(ps.count()>=3);  // Sometimes no coplanar points
    +    os << "QhullPointSet from coplanarPoints\n" << ps << endl;
    +    os << ps.print("\nWith message\n");
    +    os << ps.printIdentifiers("\nCoplanar points: ");
    +    os << "\nAs a point set:\n";
    +    os << ps;
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count(" 0.5\n"), 3*ps.count());
    +    QCOMPARE(s.count("p"), ps.count()+4);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullPointSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullPoint_test.cpp b/xs/src/qhull/src/qhulltest/QhullPoint_test.cpp
    new file mode 100644
    index 0000000000..1528086a15
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullPoint_test.cpp
    @@ -0,0 +1,437 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullPoint_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullPoint.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullPoint_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_readonly();
    +    void t_define();
    +    void t_operator();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_qhullpoint_iterator();
    +    void t_method();
    +    void t_io();
    +};//QhullPoint_test
    +
    +void
    +add_QhullPoint_test()
    +{
    +    new QhullPoint_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each test
    +void QhullPoint_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullPoint_test::
    +t_construct()
    +{
    +    QhullPoint p12;
    +    QVERIFY(!p12.isValid());
    +    QCOMPARE(p12.coordinates(), (coordT *)0);
    +    QCOMPARE(p12.dimension(), 0);
    +    QCOMPARE(p12.qh(), (QhullQh *)0);
    +    QCOMPARE(p12.id(), -3);
    +    QCOMPARE(p12.begin(), p12.end());
    +    QCOMPARE(p12.constBegin(), p12.constEnd());
    +
    +    RboxPoints rcube("c");
    +    Qhull q(rcube, "Qt QR0");  // triangulation of rotated unit cube
    +    QhullPoint p(q);
    +    QVERIFY(!p.isValid());
    +    QCOMPARE(p.dimension(),3);
    +    QCOMPARE(p.coordinates(),static_cast(0));
    +    QhullPoint p7(q.qh());
    +    QCOMPARE(p, p7);
    +
    +    // copy constructor and copy assignment
    +    QhullVertex v2(q.beginVertex());
    +    QhullPoint p2(v2.point());
    +    QVERIFY(p2.isValid());
    +    QCOMPARE(p2.dimension(),3);
    +    QVERIFY(p2!=p12);
    +    p= p2;
    +    QCOMPARE(p, p2);
    +
    +    QhullPoint p3(q, p2.dimension(), p2.coordinates());
    +    QCOMPARE(p3, p2);
    +    QhullPoint p8(q, p2.coordinates()); // Qhull defines dimension
    +    QCOMPARE(p8, p2);
    +    QhullPoint p9(q.qh(), p2.dimension(), p2.coordinates());
    +    QCOMPARE(p9, p2);
    +    QhullPoint p10(q.qh(), p2.coordinates()); // Qhull defines dimension
    +    QCOMPARE(p10, p2);
    +
    +    Coordinates c;
    +    c << 0.0 << 0.0 << 0.0;
    +    QhullPoint p6(q, c);
    +    QCOMPARE(p6, q.origin());
    +    QhullPoint p11(q.qh(), c);
    +    QCOMPARE(p11, q.origin());
    +
    +    QhullPoint p5= p2; // copy constructor
    +    QVERIFY(p5==p2);
    +}//t_construct
    +
    +void QhullPoint_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullVertex v= q.firstVertex();
    +    QhullPoint p= v.point();
    +    std::vector vs= p.toStdVector();
    +    QCOMPARE(vs.size(), 3u);
    +    for(int k=3; k--; ){
    +        QCOMPARE(vs[k], p[k]);
    +    }
    +    QList qs= p.toQList();
    +    QCOMPARE(qs.size(), 3);
    +    for(int k=3; k--; ){
    +        QCOMPARE(qs[k], p[k]);
    +    }
    +}//t_convert
    +
    +void QhullPoint_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullVertexList vs= q.vertexList();
    +        cout << "Point ids in 'rbox c'\n";
    +        QhullVertexListIterator i(vs);
    +        while(i.hasNext()){
    +            QhullPoint p= i.next().point();
    +            int id= p.id();
    +            cout << "p" << id << endl;
    +            QVERIFY(p.isValid());
    +            QCOMPARE(p.dimension(),3);
    +            QCOMPARE(id, p.id());
    +            QVERIFY(p.id()>=0 && p.id()<9);
    +            const coordT *c= p.coordinates();
    +            coordT *c2= p.coordinates();
    +            QCOMPARE(c, c2);
    +            QCOMPARE(p.dimension(), 3);
    +            QCOMPARE(q.qh(), p.qh());
    +        }
    +        QhullPoint p2= vs.first().point();
    +        QhullPoint p3= vs.last().point();
    +        QVERIFY(p2!=p3);
    +        QVERIFY(p3.coordinates()!=p2.coordinates());
    +    }
    +}//t_readonly
    +
    +void QhullPoint_test::
    +t_define()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QhullVertexList vs= q.vertexList();
    +        QhullPoint p= vs.first().point();
    +        QhullPoint p2= p;
    +        QVERIFY(p==p2);
    +        QhullPoint p3= vs.last().point();
    +        QVERIFY(p2!=p3);
    +        int idx= (p3.coordinates()-p2.coordinates())/p2.dimension();
    +        QVERIFY(idx>-8 && idx<8);
    +        p2.advancePoint(idx);
    +        QVERIFY(p2==p3);
    +        p2.advancePoint(-idx);
    +        QVERIFY(p2==p);
    +        p2.advancePoint(0);
    +        QVERIFY(p2==p);
    +
    +        QhullPoint p4= p3;
    +        QVERIFY(p4==p3);
    +        p4.defineAs(p2);
    +        QVERIFY(p2==p4);
    +        QhullPoint p5= p3;
    +        p5.defineAs(p2.dimension(), p2.coordinates());
    +        QVERIFY(p2==p5);
    +        QhullPoint p6= p3;
    +        p6.setCoordinates(p2.coordinates());
    +        QCOMPARE(p2.coordinates(), p6.coordinates());
    +        QVERIFY(p2==p6);
    +        p6.setDimension(2);
    +        QCOMPARE(p6.dimension(), 2);
    +        QVERIFY(p2!=p6);
    +    }
    +}//t_define
    +
    +void QhullPoint_test::
    +t_operator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    const QhullPoint p= q.firstVertex().point();
    +    //operator== and operator!= tested elsewhere
    +    const coordT *c= p.coordinates();
    +    for(int k=p.dimension(); k--; ){
    +        QCOMPARE(c[k], p[k]);
    +    }
    +    //p[0]= 10.0; // compiler error, const
    +    QhullPoint p2= q.firstVertex().point();
    +    p2[0]= 10.0;  // Overwrites point coordinate
    +    QCOMPARE(p2[0], 10.0);
    +}//t_operator
    +
    +void QhullPoint_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullPoint p2(q);
    +        QCOMPARE(p2.begin(), p2.end());
    +
    +        QhullPoint p= q.firstVertex().point();
    +        QhullPoint::Iterator i= p.begin();
    +        QhullPoint::iterator i2= p.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= p.begin();
    +        QVERIFY(i==i2);
    +        i2= p.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, p[0]);
    +        QCOMPARE(d2, p[2]);
    +        QhullPoint::Iterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), p[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        QhullPoint::ConstIterator i4= p.begin();
    +        QVERIFY(i==i4); // iterator COMP const_iterator
    +        QVERIFY(i<=i4);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4==i); // const_iterator COMP iterator
    +        QVERIFY(i4<=i);
    +        QVERIFY(i4>=i);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4<=i);
    +        QVERIFY(i2!=i4);
    +        QVERIFY(i2>i4);
    +        QVERIFY(i2>=i4);
    +        QVERIFY(i4!=i2);
    +        QVERIFY(i4i);
    +        QVERIFY(i4>=i);
    +
    +        i= p.begin();
    +        i2= p.begin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, p[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, p.begin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2 += 3, p.end());
    +        QCOMPARE(i2 -= 3, p.begin());
    +        QCOMPARE(i2+0, p.begin());
    +        QCOMPARE(i2+3, p.end());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, p.begin());
    +        QCOMPARE(i2-i, 3);
    +
    +        //p.begin end tested above
    +
    +        // QhullPoint is const-only
    +    }
    +}//t_iterator
    +
    +void QhullPoint_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullPoint p= q.firstVertex().point();
    +        QhullPoint::ConstIterator i= p.begin();
    +        QhullPoint::const_iterator i2= p.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= p.begin();
    +        QVERIFY(i==i2);
    +        i2= p.end();
    +        QVERIFY(i!=i2);
    +        double d3= *i;
    +        i2--;
    +        double d2= *i2;
    +        QCOMPARE(d3, p[0]);
    +        QCOMPARE(d2, p[2]);
    +        QhullPoint::ConstIterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3), p[1]);
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        // See t_iterator for const_iterator COMP iterator
    +
    +        i= p.begin();
    +        i2= p.constBegin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, p[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, p.constBegin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2+=3, p.constEnd());
    +        QCOMPARE(i2-=3, p.constBegin());
    +        QCOMPARE(i2+0, p.constBegin());
    +        QCOMPARE(i2+3, p.constEnd());
    +        i2 += 3;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-3;
    +        QCOMPARE(i, p.constBegin());
    +        QCOMPARE(i2-i, 3);
    +
    +        // QhullPoint is const-only
    +    }
    +}//t_const_iterator
    +
    +void QhullPoint_test::
    +t_qhullpoint_iterator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +
    +    QhullPoint p2(q);
    +    QhullPointIterator i= p2;
    +    QCOMPARE(p2.dimension(), 3);
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullPoint p = q.firstVertex().point();
    +    QhullPointIterator i2(p);
    +    QCOMPARE(p.dimension(), 3);
    +    i= p;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    // i at front, i2 at end/back, 3 coordinates
    +    QCOMPARE(i.peekNext(), p[0]);
    +    QCOMPARE(i2.peekPrevious(), p[2]);
    +    QCOMPARE(i2.previous(), p[2]);
    +    QCOMPARE(i2.previous(), p[1]);
    +    QCOMPARE(i2.previous(), p[0]);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), p[0]);
    +    // i.peekNext()= 1.0; // compiler error, i is const
    +    QCOMPARE(i.next(), p[0]);
    +    QCOMPARE(i.peekNext(), p[1]);
    +    QCOMPARE(i.next(), p[1]);
    +    QCOMPARE(i.next(), p[2]);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p[0]);
    +}//t_qhullpoint_iterator
    +
    +void QhullPoint_test::
    +t_method()
    +{
    +    // advancePoint tested above
    +    RboxPoints rcube("c");
    +    Qhull q(rcube, "");
    +    QhullPoint p = q.firstVertex().point();
    +    double dist= p.distance(q.origin());
    +    QCOMPARE(dist, sqrt(double(2.0+1.0))/2); // half diagonal of unit cube
    +}//t_qhullpoint_iterator
    +
    +void QhullPoint_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullPoint p= q.beginVertex().point();
    +        ostringstream os;
    +        os << "Point:\n";
    +        os << p;
    +        os << "Point w/ print:\n";
    +        os << p.print(" message ");
    +        os << p.printWithIdentifier(" Point with id and a message ");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count("p"), 2);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullPoint_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullPoints_test.cpp b/xs/src/qhull/src/qhulltest/QhullPoints_test.cpp
    new file mode 100644
    index 0000000000..c2d8347e28
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullPoints_test.cpp
    @@ -0,0 +1,561 @@
    +/****************************************************************************
    +**
    +** Copyright (p) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullPoints_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled header
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullPoints.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +
    +namespace orgQhull {
    +
    +class QhullPoints_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct_q();
    +    void t_construct_qh();
    +    void t_convert();
    +    void t_getset();
    +    void t_element();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_search();
    +    void t_points_iterator();
    +    void t_io();
    +};//QhullPoints_test
    +
    +void
    +add_QhullPoints_test()
    +{
    +    new QhullPoints_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullPoints_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullPoints_test::
    +t_construct_q()
    +{
    +    Qhull q;
    +    QhullPoints ps(q);
    +    QCOMPARE(ps.dimension(), 0);
    +    QVERIFY(ps.isEmpty());
    +    QCOMPARE(ps.count(), 0);
    +    QCOMPARE(ps.size(), 0u);
    +    QCOMPARE(ps.coordinateCount(), 0);
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps2(q);
    +    ps2.defineAs(2, 6, c);
    +    QCOMPARE(ps2.dimension(), 2);
    +    QVERIFY(!ps2.isEmpty());
    +    QCOMPARE(ps2.count(), 3);
    +    QCOMPARE(ps2.size(), 3u);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QhullPoints ps3(q, 2, 6, c);
    +    QCOMPARE(ps3.dimension(), 2);
    +    QVERIFY(!ps3.isEmpty());
    +    QCOMPARE(ps3.coordinates(), ps2.coordinates());
    +    QVERIFY(ps3==ps2);
    +    QVERIFY(ps3!=ps);
    +    QhullPoints ps4= ps3;
    +    QVERIFY(ps4==ps3);
    +    // ps4= ps3; //compiler error
    +    QhullPoints ps5(ps4);
    +    QVERIFY(ps5==ps4);
    +    QVERIFY(!(ps5!=ps4));
    +    coordT c2[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps6(q, 2, 6, c2);
    +    QVERIFY(ps6==ps2);
    +
    +    RboxPoints rbox("c D2");
    +    Qhull q2(rbox, "");
    +    QhullPoints ps8(q2);
    +    QCOMPARE(ps8.dimension(), 2);
    +    QCOMPARE(ps8.count(), 0);
    +    QCOMPARE(ps8.size(), 0u);
    +    QCOMPARE(ps8.coordinateCount(), 0);
    +    coordT c3[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps9(q2, 6, c3);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c3);
    +    QCOMPARE(ps9, ps2);  // DISTround
    +    c3[1]= 1.0+1e-17;
    +    QCOMPARE(ps9, ps2);  // DISTround
    +    c3[1]= 1.0+1e-15;
    +    QVERIFY(ps9!=ps2);  // DISTround
    +
    +    ps9.defineAs(6, c2);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c2);
    +}//t_construct_q
    +
    +void QhullPoints_test::
    +t_construct_qh()
    +{
    +    Qhull q;
    +    QhullQh *qh= q.qh();
    +    QhullPoints ps(qh);
    +    QCOMPARE(ps.dimension(), 0);
    +    QVERIFY(ps.isEmpty());
    +    QCOMPARE(ps.count(), 0);
    +    QCOMPARE(ps.size(), 0u);
    +    QCOMPARE(ps.coordinateCount(), 0);
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps2(qh);
    +    ps2.defineAs(2, 6, c);
    +    QCOMPARE(ps2.dimension(), 2);
    +    QVERIFY(!ps2.isEmpty());
    +    QCOMPARE(ps2.count(), 3);
    +    QCOMPARE(ps2.size(), 3u);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QhullPoints ps3(qh, 2, 6, c);
    +    QCOMPARE(ps3.dimension(), 2);
    +    QVERIFY(!ps3.isEmpty());
    +    QCOMPARE(ps3.coordinates(), ps2.coordinates());
    +    QVERIFY(ps3==ps2);
    +    QVERIFY(ps3!=ps);
    +    QhullPoints ps4= ps3;
    +    QVERIFY(ps4==ps3);
    +    // ps4= ps3; //compiler error
    +    QhullPoints ps5(ps4);
    +    QVERIFY(ps5==ps4);
    +    QVERIFY(!(ps5!=ps4));
    +    coordT c2[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps6(qh, 2, 6, c2);
    +    QVERIFY(ps6==ps2);
    +
    +    RboxPoints rbox("c D2");
    +    Qhull q2(rbox, "");
    +    QhullPoints ps8(q2.qh());
    +    QCOMPARE(ps8.dimension(), 2);
    +    QCOMPARE(ps8.count(), 0);
    +    QCOMPARE(ps8.size(), 0u);
    +    QCOMPARE(ps8.coordinateCount(), 0);
    +    coordT c3[]= {10.0, 11.0, 12.0, 13.0, 14.0, 15.0};
    +    QhullPoints ps9(q2.qh(), 6, c3);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c3);
    +    ps9.defineAs(6, c2);
    +    QCOMPARE(ps9.dimension(), 2);
    +    QCOMPARE(ps9.coordinateCount(), 6);
    +    QCOMPARE(ps9.count(), 3);
    +    QCOMPARE(ps9.coordinates(), c2);
    +}//t_construct_qh
    +
    +void QhullPoints_test::
    +t_convert()
    +{
    +    Qhull q;
    +    //defineAs tested above
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 3, 6, c);
    +    QCOMPARE(ps.dimension(), 3);
    +    QCOMPARE(ps.size(), 2u);
    +    const coordT *c2= ps.constData();
    +    QCOMPARE(c, c2);
    +    const coordT *c3= ps.data();
    +    QCOMPARE(c, c3);
    +    coordT *c4= ps.data();
    +    QCOMPARE(c, c4);
    +    std::vector vs= ps.toStdVector();
    +    QCOMPARE(vs.size(), 2u);
    +    QhullPoint p= vs[1];
    +    QCOMPARE(p[2], 5.0);
    +    QList qs= ps.toQList();
    +    QCOMPARE(qs.size(), 2);
    +    QhullPoint p2= qs[1];
    +    QCOMPARE(p2[2], 5.0);
    +}//t_convert
    +
    +void QhullPoints_test::
    +t_getset()
    +{
    +    Qhull q;
    +    //See t_construct for coordinates, count, defineAs, dimension, isempty, ==, !=, size
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 3, 6, c);
    +    QhullPoints ps2(q, 3, 6, c);
    +    QCOMPARE(ps2.dimension(), 3);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QCOMPARE(ps2.count(), 2);
    +    QCOMPARE(ps2.coordinateCount(), 6);
    +    coordT c2[]= {-1.0, -2.0, -3.0, -4.0, -5.0, -6.0};
    +    ps2.defineAs(6, c2);
    +    QCOMPARE(ps2.coordinates(), c2);
    +    QCOMPARE(ps2.count(), 2);
    +    QCOMPARE(ps2.size(), 2u);
    +    QCOMPARE(ps2.dimension(), 3);
    +    QVERIFY(!ps2.isEmpty());
    +    QVERIFY(ps!=ps2);
    +    // ps2= ps; // assignment not available, compiler error
    +    ps2.defineAs(ps);
    +    QVERIFY(ps==ps2);
    +    ps2.setDimension(2);
    +    QCOMPARE(ps2.dimension(), 2);
    +    QCOMPARE(ps2.coordinates(), c);
    +    QVERIFY(!ps2.isEmpty());
    +    QCOMPARE(ps2.count(), 3);
    +    QCOMPARE(ps2.size(), 3u);
    +    QVERIFY(ps!=ps2);
    +    QhullPoints ps3(ps2);
    +    ps3.setDimension(3);
    +    ps3.defineAs(5, c2);
    +    QCOMPARE(ps3.count(), 1);
    +    QCOMPARE(ps3.extraCoordinatesCount(), 2);
    +    QCOMPARE(ps3.extraCoordinates()[0], -4.0);
    +    QVERIFY(ps3.includesCoordinates(ps3.data()));
    +    QVERIFY(ps3.includesCoordinates(ps3.data()+ps3.count()-1));
    +    QVERIFY(!ps3.includesCoordinates(ps3.data()-1));
    +    QVERIFY(!ps3.includesCoordinates(ps3.data()+ps3.coordinateCount()));
    +}//t_getset
    +
    +
    +void QhullPoints_test::
    +t_element()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 2, 6, c);
    +    QCOMPARE(ps.count(), 3);
    +    QhullPoint p(q, 2, c);
    +    QCOMPARE(ps[0], p);
    +    QCOMPARE(ps.at(1), ps[1]);
    +    QCOMPARE(ps.first(), p);
    +    QCOMPARE(ps.front(), ps.first());
    +    QCOMPARE(ps.last(), ps.at(2));
    +    QCOMPARE(ps.back(), ps.last());
    +    QhullPoints ps2= ps.mid(2);
    +    QCOMPARE(ps2.count(), 1);
    +    QhullPoints ps3= ps.mid(3);
    +    QVERIFY(ps3.isEmpty());
    +    QhullPoints ps4= ps.mid(10);
    +    QVERIFY(ps4.isEmpty());
    +    QhullPoints ps5= ps.mid(-1);
    +    QVERIFY(ps5.isEmpty());
    +    QhullPoints ps6= ps.mid(1, 1);
    +    QCOMPARE(ps6.count(), 1);
    +    QCOMPARE(ps6[0], ps[1]);
    +    QhullPoints ps7= ps.mid(1, 10);
    +    QCOMPARE(ps7.count(), 2);
    +    QCOMPARE(ps7[1], ps[2]);
    +    QhullPoint p8(q);
    +    QCOMPARE(ps.value(2), ps[2]);
    +    QCOMPARE(ps.value(-1), p8);
    +    QCOMPARE(ps.value(3), p8);
    +    QCOMPARE(ps.value(3, p), p);
    +    QVERIFY(ps.value(1, p)!=p);
    +    foreach(QhullPoint p9, ps){  // Qt only
    +        QCOMPARE(p9.dimension(), 2);
    +        QVERIFY(p9[0]==0.0 || p9[0]==2.0 || p9[0]==4.0);
    +    }
    +}//t_element
    +
    +void QhullPoints_test::
    +t_iterator()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0};
    +    QhullPoints ps(q, 1, 3, c);
    +    QCOMPARE(ps.dimension(), 1);
    +    QhullPoints::Iterator i(ps);
    +    QhullPoints::iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p(i); // QhullPoint is the base class for QhullPoints::iterator
    +    QCOMPARE(p.dimension(), ps.dimension());
    +    QCOMPARE(p.coordinates(), ps.coordinates());
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p[0], 0.0);
    +    QCOMPARE(p2[0], 2.0);
    +    QhullPoints::Iterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +    coordT c3[]= {0.0, -1.0, -2.0};
    +    QhullPoints::Iterator i3(q, 1, c3);
    +    QVERIFY(i!=i3);
    +    QCOMPARE(*i, *i3);
    +
    +    (i3= i)++;
    +    QCOMPARE((*i3)[0], 1.0);
    +    QCOMPARE(i3->dimension(), 1);
    +    QCOMPARE(i3[0][0], 1.0);
    +    QCOMPARE(i3[0], ps[1]);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i2);
    +    QVERIFY(ii);
    +    QVERIFY(i2>=i);
    +
    +    QhullPoints::ConstIterator i4(q, 1, c);
    +    QVERIFY(i==i4); // iterator COMP const_iterator
    +    QVERIFY(i<=i4);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4==i); // const_iterator COMP iterator
    +    QVERIFY(i4<=i);
    +    QVERIFY(i4>=i);
    +    QVERIFY(i>=i4);
    +    QVERIFY(i4<=i);
    +    QVERIFY(i2!=i4);
    +    QVERIFY(i2>i4);
    +    QVERIFY(i2>=i4);
    +    QVERIFY(i4!=i2);
    +    QVERIFY(i4i);
    +    QVERIFY(i4>=i);
    +
    +    i= ps.begin();
    +    i2= ps.begin();
    +    QCOMPARE(i, i2++);
    +    QCOMPARE(*i2, ps[1]);
    +    QCOMPARE(++i, i2);
    +    QCOMPARE(i, i2--);
    +    QCOMPARE(i2, ps.begin());
    +    QCOMPARE(--i, i2);
    +    QCOMPARE(i2+=3, ps.end());
    +    QCOMPARE(i2-=3, ps.begin());
    +    QCOMPARE(i2+0, ps.begin());
    +    QCOMPARE(i2+3, ps.end());
    +    i2 += 3;
    +    i= i2-0;
    +    QCOMPARE(i, i2);
    +    i= i2-3;
    +    QCOMPARE(i, ps.begin());
    +    QCOMPARE(i2-i, 3);
    +
    +    //ps.begin end tested above
    +
    +    // QhullPoints is const-only
    +}//t_iterator
    +
    +void QhullPoints_test::
    +t_const_iterator()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0};
    +    const QhullPoints ps(q, 1, 3, c);
    +    QhullPoints::ConstIterator i(ps);
    +    QhullPoints::const_iterator i2= ps.begin();
    +    QVERIFY(i==i2);
    +    QVERIFY(i>=i2);
    +    QVERIFY(i<=i2);
    +    i= ps.begin();
    +    QVERIFY(i==i2);
    +    i2= ps.end();
    +    QVERIFY(i!=i2);
    +    QhullPoint p(i);
    +    QCOMPARE(p.dimension(), ps.dimension());
    +    QCOMPARE(p.coordinates(), ps.coordinates());
    +    i2--;
    +    QhullPoint p2= *i2;
    +    QCOMPARE(p[0], 0.0);
    +    QCOMPARE(p2[0], 2.0);
    +    QhullPoints::ConstIterator i5(i2);
    +    QCOMPARE(*i2, *i5);
    +    coordT c3[]= {0.0, -1.0, -2.0};
    +    QhullPoints::ConstIterator i3(q, 1, c3);
    +    QVERIFY(i!=i3);
    +    QCOMPARE(*i, *i3);
    +
    +    (i3= i)++;
    +    QCOMPARE((*i3)[0], 1.0);
    +    QCOMPARE(i3->dimension(), 1);
    +    QCOMPARE(i3[0][0], 1.0);
    +    QCOMPARE(i3[0][0], 1.0);
    +    QCOMPARE(i3[0], ps[1]);
    +
    +    QVERIFY(i==i);
    +    QVERIFY(i!=i2);
    +    QVERIFY(ii);
    +    QVERIFY(i2>=i);
    +
    +    // See t_iterator for const_iterator COMP iterator
    +
    +    i= ps.begin();
    +    i2= ps.constBegin();
    +    QCOMPARE(i, i2++);
    +    QCOMPARE(*i2, ps[1]);
    +    QCOMPARE(++i, i2);
    +    QCOMPARE(i, i2--);
    +    QCOMPARE(i2, ps.constBegin());
    +    QCOMPARE(--i, i2);
    +    QCOMPARE(i2+=3, ps.constEnd());
    +    QCOMPARE(i2-=3, ps.constBegin());
    +    QCOMPARE(i2+0, ps.constBegin());
    +    QCOMPARE(i2+3, ps.constEnd());
    +    i2 += 3;
    +    i= i2-0;
    +    QCOMPARE(i, i2);
    +    i= i2-3;
    +    QCOMPARE(i, ps.constBegin());
    +    QCOMPARE(i2-i, 3);
    +
    +    // QhullPoints is const-only
    +}//t_const_iterator
    +
    +
    +void QhullPoints_test::
    +t_search()
    +{
    +    Qhull q;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 0, 1};
    +    QhullPoints ps(q, 2, 8, c); //2-d array of 4 points
    +    QhullPoint p= ps.first();
    +    QhullPoint p2= ps.last();
    +    QVERIFY(ps.contains(p));
    +    QVERIFY(ps.contains(p2));
    +    QVERIFY(p==p2);
    +    QhullPoint p5= ps[2];
    +    QVERIFY(p!=p5);
    +    QVERIFY(ps.contains(p5));
    +    coordT c2[]= {0.0, 1.0, 2.0, 3.0};
    +    QhullPoint p3(q, 2, c2); //2-d point
    +    QVERIFY(ps.contains(p3));
    +    QhullPoint p4(q, 3, c2); //3-d point
    +    QVERIFY(!ps.contains(p4));
    +    p4.defineAs(2, c); //2-d point
    +    QVERIFY(ps.contains(p4));
    +    p4.defineAs(2, c+1); //2-d point
    +    QVERIFY(!ps.contains(p4));
    +    QhullPoint p6(q, 2, c2+2); //2-d point
    +    QCOMPARE(ps.count(p), 2);
    +    QCOMPARE(ps.count(p2), 2);
    +    QCOMPARE(ps.count(p3), 2);
    +    QCOMPARE(ps.count(p4), 0);
    +    QCOMPARE(ps.count(p6), 1);
    +    QCOMPARE(ps.indexOf(&ps[0][0]), 0);
    +    //QCOMPARE(ps.indexOf(ps.end()), -1); //ps.end() is a QhullPoint which may match
    +    QCOMPARE(ps.indexOf(0), -1);
    +    QCOMPARE(ps.indexOf(&ps[3][0]), 3);
    +    QCOMPARE(ps.indexOf(&ps[3][1], QhullError::NOthrow), 3);
    +    QCOMPARE(ps.indexOf(ps.data()+ps.coordinateCount(), QhullError::NOthrow), -1);
    +    QCOMPARE(ps.indexOf(p), 0);
    +    QCOMPARE(ps.indexOf(p2), 0);
    +    QCOMPARE(ps.indexOf(p3), 0);
    +    QCOMPARE(ps.indexOf(p4), -1);
    +    QCOMPARE(ps.indexOf(p5), 2);
    +    QCOMPARE(ps.indexOf(p6), 1);
    +    QCOMPARE(ps.lastIndexOf(p), 3);
    +    QCOMPARE(ps.lastIndexOf(p4), -1);
    +    QCOMPARE(ps.lastIndexOf(p6), 1);
    +    QhullPoints ps3(q);
    +    QCOMPARE(ps3.indexOf(ps3.data()), -1);
    +    QCOMPARE(ps3.indexOf(ps3.data()+1, QhullError::NOthrow), -1);
    +    QCOMPARE(ps3.indexOf(p), -1);
    +    QCOMPARE(ps3.lastIndexOf(p), -1);
    +    QhullPoints ps4(q, 2, 0, c);
    +    QCOMPARE(ps4.indexOf(p), -1);
    +    QCOMPARE(ps4.lastIndexOf(p), -1);
    +}//t_search
    +
    +void QhullPoints_test::
    +t_points_iterator()
    +{
    +    Qhull q;
    +    coordT c2[]= {0.0};
    +    QhullPoints ps2(q, 0, 0, c2); // 0-dimensional
    +    QhullPointsIterator i2= ps2;
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    i2.toBack();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps(q, 3, 6, c); // 3-dimensional
    +    QhullPointsIterator i(ps);
    +    i2= ps;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullPoint p= ps[0];
    +    QhullPoint p2(ps[0]);
    +    QCOMPARE(p, p2);
    +    QVERIFY(p==p2);
    +    QhullPoint p3(ps[1]);
    + // p2[0]= 0.0;
    +    QVERIFY(p==p2);
    +    QCOMPARE(i2.peekPrevious(), p3);
    +    QCOMPARE(i2.previous(), p3);
    +    QCOMPARE(i2.previous(), p);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), p);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), p);
    +    QCOMPARE(i.peekNext(), p3);
    +    QCOMPARE(i.next(), p3);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), p);
    +}//t_points_iterator
    +
    +void QhullPoints_test::
    +t_io()
    +{
    +    Qhull q;
    +    QhullPoints ps(q);
    +    ostringstream os;
    +    os << "Empty QhullPoints\n" << ps << endl;
    +    coordT c[]= {0.0, 1.0, 2.0, 3.0, 4.0, 5.0};
    +    QhullPoints ps2(q, 3, 6, c); // 3-dimensional explicit
    +    os << "QhullPoints from c[]\n" << ps2 << endl;
    +
    +    RboxPoints rcube("c");
    +    Qhull q2(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullPoints ps3= q2.points();
    +    os << "QhullPoints\n" << ps3;
    +    os << ps3.print("message\n");
    +    os << ps3.printWithIdentifier("w/ identifiers\n");
    +    cout << os.str();
    +    QString s= QString::fromStdString(os.str());
    +    QCOMPARE(s.count("p"), 8+1);
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullPoints_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullRidge_test.cpp b/xs/src/qhull/src/qhulltest/QhullRidge_test.cpp
    new file mode 100644
    index 0000000000..420a7f06d3
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullRidge_test.cpp
    @@ -0,0 +1,159 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullRidge_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullRidge_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_getSet();
    +    void t_foreach();
    +    void t_io();
    +};//QhullRidge_test
    +
    +void
    +add_QhullRidge_test()
    +{
    +    new QhullRidge_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullRidge_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullRidge_test::
    +t_construct()
    +{
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // triangulation of rotated unit cube
    +    QhullRidge r(q);
    +    QVERIFY(!r.isValid());
    +    QCOMPARE(r.dimension(),2);
    +    QhullFacet f(q.firstFacet());
    +    QhullRidgeSet rs(f.ridges());
    +    QVERIFY(!rs.isEmpty()); // Simplicial facets do not have ridges()
    +    QhullRidge r2(rs.first());
    +    QCOMPARE(r2.dimension(), 2); // One dimension lower than the facet
    +    r= r2;
    +    QVERIFY(r.isValid());
    +    QCOMPARE(r.dimension(), 2);
    +    QhullRidge r3(q, r2.getRidgeT());
    +    QCOMPARE(r,r3);
    +    QhullRidge r4(q, r2.getBaseT());
    +    QCOMPARE(r,r4);
    +    QhullRidge r5= r2; // copy constructor
    +    QVERIFY(r5==r2);
    +    QVERIFY(r5==r);
    +}//t_construct
    +
    +void QhullRidge_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 6);
    +        QCOMPARE(q.vertexCount(), 8);
    +        QhullFacet f(q.firstFacet());
    +        QhullRidgeSet rs= f.ridges();
    +        QhullRidgeSetIterator i(rs);
    +        while(i.hasNext()){
    +            const QhullRidge r= i.next();
    +            cout << r.id() << endl;
    +            QVERIFY(r.bottomFacet()!=r.topFacet());
    +            QCOMPARE(r.dimension(), 2); // Ridge one-dimension less than facet
    +            QVERIFY(r.id()>=0 && r.id()<9*27);
    +            QVERIFY(r.isValid());
    +            QVERIFY(r==r);
    +            QVERIFY(r==i.peekPrevious());
    +            QCOMPARE(r.otherFacet(r.bottomFacet()),r.topFacet());
    +            QCOMPARE(r.otherFacet(r.topFacet()),r.bottomFacet());
    +        }
    +    }
    +}//t_getSet
    +
    +void QhullRidge_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");  // cube
    +    {
    +        Qhull q(rcube, "QR0"); // rotated cube
    +        QhullFacet f(q.firstFacet());
    +        foreach (const QhullRidge &r, f.ridges()){  // Qt only
    +            QhullVertexSet vs= r.vertices();
    +            QCOMPARE(vs.count(), 2);
    +            foreach (const QhullVertex &v, vs){  // Qt only
    +                QVERIFY(f.vertices().contains(v));
    +            }
    +        }
    +        QhullRidgeSet rs= f.ridges();
    +        QhullRidge r= rs.first();
    +        QhullRidge r2= r;
    +        QList vs;
    +        int count= 0;
    +        while(!count || r2!=r){
    +            ++count;
    +            QhullVertex v(q);
    +            QVERIFY2(r2.hasNextRidge3d(f),"A cube should only have non-simplicial facets.");
    +            QhullRidge r3= r2.nextRidge3d(f, &v);
    +            QVERIFY(!vs.contains(v));
    +            vs << v;
    +            r2= r2.nextRidge3d(f);
    +            QCOMPARE(r3, r2);
    +        }
    +        QCOMPARE(vs.count(), rs.count());
    +        QCOMPARE(count, rs.count());
    +    }
    +}//t_foreach
    +
    +void QhullRidge_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullFacet f(q.firstFacet());
    +        QhullRidgeSet rs= f.ridges();
    +        QhullRidge r= rs.first();
    +        ostringstream os;
    +        os << "Ridges\n" << rs << "Ridge\n" << r;
    +        os << r.print("\nRidge with message");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count(" r"), 6);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullRidge_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullSet_test.cpp b/xs/src/qhull/src/qhulltest/QhullSet_test.cpp
    new file mode 100644
    index 0000000000..87fcf4acf2
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullSet_test.cpp
    @@ -0,0 +1,434 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2009-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullSet_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullRidge.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +class QhullSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_qhullsetbase();
    +    void t_convert();
    +    void t_element();
    +    void t_search();
    +    void t_iterator();
    +    void t_const_iterator();
    +    void t_qhullset_iterator();
    +    void t_io();
    +};//QhullSet_test
    +
    +void
    +add_QhullSet_test()
    +{
    +    new QhullSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +// Test QhullFacetSet and QhullSet.
    +// Use QhullRidgeSet to test methods overloaded by QhullFacetSet
    +
    +void QhullSet_test::
    +t_qhullsetbase()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // triangulation of rotated unit cube
    +        // Fake an empty set.  Default constructor not defined.  No memory allocation.
    +        QhullFacet f4 = q.beginFacet();
    +        QhullFacetSet fs = f4.neighborFacets();
    +        fs.defineAs(q.qh()->other_points); // Force an empty set
    +        QVERIFY(fs.isEmpty());
    +        QCOMPARE(fs.count(), 0);
    +        QCOMPARE(fs.size(), 0u);
    +        QCOMPARE(fs.begin(), fs.end()); // beginPointer(), endPointer()
    +        QVERIFY(QhullSetBase::isEmpty(fs.getSetT()));
    +
    +        QhullRidgeSet rs = f4.ridges();
    +        QVERIFY(!rs.isEmpty());
    +        QCOMPARE(rs.count(), 4);
    +        QCOMPARE(rs.size(), 4u);
    +        QVERIFY(rs.begin()!=rs.end());
    +        QVERIFY(!QhullSetBase::isEmpty(rs.getSetT()));
    +        QhullRidgeSet rs2= rs; // copy constructor
    +        // rs= rs2; // disabled.  Would not copy ridges
    +        QCOMPARE(rs2, rs);
    +
    +        QCOMPARE(q.facetCount(), 6);
    +        QhullFacet f = q.beginFacet();
    +        QhullFacetSet fs2 = f.neighborFacets();
    +        QCOMPARE(fs2.count(), 4);
    +        QCOMPARE(fs2.size(), 4u);
    +        QVERIFY(!fs2.isEmpty());
    +        QVERIFY(!QhullSetBase::isEmpty(fs2.getSetT()));
    +        QVERIFY(fs!=fs2);
    +        setT *s= fs2.getSetT();
    +        fs.defineAs(s);
    +        QVERIFY(fs==fs2);
    +        QCOMPARE(fs[1], fs2[1]); // elementPointer
    +        QhullFacetSet fs3(fs2);
    +        QVERIFY(fs3==fs);
    +        // fs= fs2; // disabled.  Would not copy facets
    +        QhullFacetSet fs4= fs2; // copy constructor
    +        QVERIFY(fs4==fs2);
    +    }
    +}//t_qhullsetbase
    +
    +// constructors tested by t_qhullsetbase
    +
    +void QhullSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullFacet f= q.firstFacet();
    +        f= f.next();
    +        QhullRidgeSet rs= f.ridges();
    +        QCOMPARE(rs.count(),4);
    +        std::vector rv= rs.toStdVector();
    +        QCOMPARE(rv.size(), 4u);
    +        QList rv2= rs.toQList();
    +        QCOMPARE(rv2.size(), 4);
    +        std::vector::iterator i= rv.begin();
    +        foreach(QhullRidge r, rv2){  // Qt only
    +            QhullRidge r2= *i++;
    +            QCOMPARE(r, r2);
    +        }
    +
    +        Qhull q2(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q2.facetCount(), 12);
    +        QhullFacet f2 = q2.beginFacet();
    +        QhullFacetSet fs = f2.neighborFacets();
    +        QCOMPARE(fs.size(), 3U);
    +        std::vector vs= fs.toStdVector();
    +        QCOMPARE(vs.size(), fs.size());
    +        for(int k= fs.count(); k--; ){
    +            QCOMPARE(vs[k], fs[k]);
    +        }
    +        QList qv= fs.toQList();
    +        QCOMPARE(qv.count(), fs.count());
    +        for(int k= fs.count(); k--; ){
    +            QCOMPARE(qv[k], fs[k]);
    +        }
    +    }
    +}//t_convert
    +
    +//ReadOnly (count, isEmpty) tested by t_convert
    +//  operator== tested by t_search
    +
    +void QhullSet_test::
    +t_element()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacet f = q.beginFacet();
    +    QhullFacetSet fs = f.neighborFacets();
    +
    +    QCOMPARE(fs.at(1), fs[1]);
    +    QCOMPARE(fs.first(), fs[0]);
    +    QCOMPARE(fs.front(), fs.first());
    +    QCOMPARE(fs.last(), fs.at(3));
    +    QCOMPARE(fs.back(), fs.last());
    +    facetT **d = fs.data();
    +    facetT * const *d2= fs.data();
    +    facetT * const *d3= fs.constData();
    +    QVERIFY(d==d2);
    +    QVERIFY(d2==d3);
    +    QCOMPARE(QhullFacet(q, *d), fs.first());
    +    QhullFacetSet::iterator i(q.qh(), d+4);
    +    QCOMPARE(i, fs.end());
    +    QCOMPARE(d[4], static_cast(0));
    +    QhullFacet f4(q, d[4]);
    +    QVERIFY(!f4.isValid());
    +    QCOMPARE(fs.second(), fs[1]);
    +    const QhullFacet f2= fs.second();
    +    QVERIFY(f2==fs[1]);
    +    const QhullFacet f3= fs[1];
    +    QCOMPARE(f2, f3);
    +
    +    QCOMPARE(fs.value(2), fs[2]);
    +    QCOMPARE(fs.value(-1), QhullFacet());
    +    QCOMPARE(fs.value(10), QhullFacet());
    +    QCOMPARE(fs.value(2, f), fs[2]);
    +    QCOMPARE(fs.value(4, f), f);
    +    // mid() not available (read-only)
    +}//t_element
    +
    +void QhullSet_test::
    +t_search()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    QhullFacet f = q.beginFacet();
    +    QhullFacetSet fs = f.neighborFacets();
    +    QhullFacet f2= *fs.begin();
    +    QhullFacet f3= fs.last();
    +    QVERIFY(fs.contains(f2));
    +    QVERIFY(fs.contains(f3));
    +    QVERIFY(!fs.contains(f));
    +
    +    QhullFacetSet fs2= f2.neighborFacets();
    +    QVERIFY(fs==fs);
    +    QVERIFY(fs!=fs2);
    +    QCOMPARE(fs.count(f2), 1);
    +    QCOMPARE(fs.count(f3), 1);
    +    QCOMPARE(fs.count(f), 0);
    +    QCOMPARE(fs.indexOf(f2), 0);
    +    QCOMPARE(fs.indexOf(f3), 3);
    +    QCOMPARE(fs.indexOf(f), -1);
    +    QCOMPARE(fs.lastIndexOf(f2), 0);
    +    QCOMPARE(fs.lastIndexOf(f3), 3);
    +    QCOMPARE(fs.lastIndexOf(f), -1);
    +}//t_search
    +
    +void QhullSet_test::
    +t_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullFacet f = q.beginFacet();
    +        QhullFacetSet fs = f.neighborFacets();
    +        QhullFacetSet::Iterator i= fs.begin();
    +        QhullFacetSet::iterator i2= fs.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= fs.begin();
    +        QVERIFY(i==i2);
    +        i2= fs.end();
    +        QVERIFY(i!=i2);
    +        QhullFacet f3(*i);
    +        i2--;
    +        QhullFacet f2= *i2;
    +        QCOMPARE(f3.id(), fs[0].id());
    +        QCOMPARE(f2.id(), fs[3].id());
    +        QhullFacetSet::Iterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3).id(), fs[1].id());
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        QhullFacetSet::ConstIterator i4= fs.begin();
    +        QVERIFY(i==i4); // iterator COMP const_iterator
    +        QVERIFY(i<=i4);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4==i); // const_iterator COMP iterator
    +        QVERIFY(i4<=i);
    +        QVERIFY(i4>=i);
    +        QVERIFY(i>=i4);
    +        QVERIFY(i4<=i);
    +        QVERIFY(i2!=i4);
    +        QVERIFY(i2>i4);
    +        QVERIFY(i2>=i4);
    +        QVERIFY(i4!=i2);
    +        QVERIFY(i4i);
    +        QVERIFY(i4>=i);
    +
    +        i= fs.begin();
    +        i2= fs.begin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, fs[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, fs.begin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2 += 4, fs.end());
    +        QCOMPARE(i2 -= 4, fs.begin());
    +        QCOMPARE(i2+0, fs.begin());
    +        QCOMPARE(i2+4, fs.end());
    +        i2 += 4;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-4;
    +        QCOMPARE(i, fs.begin());
    +        QCOMPARE(i2-i, 4);
    +
    +        //fs.begin end tested above
    +
    +        // QhullFacetSet is const-only
    +    }
    +}//t_iterator
    +
    +void QhullSet_test::
    +t_const_iterator()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0");  // rotated unit cube
    +        QhullFacet f = q.beginFacet();
    +        QhullFacetSet fs = f.neighborFacets();
    +        QhullFacetSet::ConstIterator i= fs.begin();
    +        QhullFacetSet::const_iterator i2= fs.begin();
    +        QVERIFY(i==i2);
    +        QVERIFY(i>=i2);
    +        QVERIFY(i<=i2);
    +        i= fs.begin();
    +        QVERIFY(i==i2);
    +        i2= fs.end();
    +        QVERIFY(i!=i2);
    +        QhullFacet f3(*i);
    +        i2--;
    +        QhullFacet f2= *i2;
    +        QCOMPARE(f3.id(), fs[0].id());
    +        QCOMPARE(f2.id(), fs[3].id());
    +        QhullFacetSet::ConstIterator i3(i2);
    +        QCOMPARE(*i2, *i3);
    +
    +        (i3= i)++;
    +        QCOMPARE((*i3).id(), fs[1].id());
    +        QVERIFY(i==i);
    +        QVERIFY(i!=i2);
    +        QVERIFY(ii);
    +        QVERIFY(i2>=i);
    +
    +        // See t_iterator for const_iterator COMP iterator
    +
    +        i= fs.begin();
    +        i2= fs.constBegin();
    +        QCOMPARE(i, i2++);
    +        QCOMPARE(*i2, fs[1]);
    +        QCOMPARE(++i, i2);
    +        QCOMPARE(i, i2--);
    +        QCOMPARE(i2, fs.constBegin());
    +        QCOMPARE(--i, i2);
    +        QCOMPARE(i2+=4, fs.constEnd());
    +        QCOMPARE(i2-=4, fs.constBegin());
    +        QCOMPARE(i2+0, fs.constBegin());
    +        QCOMPARE(i2+4, fs.constEnd());
    +        i2 += 4;
    +        i= i2-0;
    +        QCOMPARE(i, i2);
    +        i= i2-4;
    +        QCOMPARE(i, fs.constBegin());
    +        QCOMPARE(i2-i, 4);
    +
    +        // QhullFacetSet is const-only
    +    }
    +}//t_const_iterator
    +
    +void QhullSet_test::
    +t_qhullset_iterator()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    // Fake an empty set.  Default constructor not defined.  No memory allocation.
    +    QhullFacet f = q.beginFacet();
    +    QhullFacetSet fs = f.neighborFacets();
    +    fs.defineAs(q.qh()->other_points);
    +    QhullFacetSetIterator i(fs);
    +    QCOMPARE(fs.count(), 0);
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i.toBack();
    +    QVERIFY(!i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    QhullFacet f2 = q.beginFacet();
    +    QhullFacetSet fs2 = f2.neighborFacets();
    +    QhullFacetSetIterator i2(fs2);
    +    QCOMPARE(fs2.count(), 4);
    +    i= fs2;
    +    QVERIFY(i2.hasNext());
    +    QVERIFY(!i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +    i2.toBack();
    +    i.toFront();
    +    QVERIFY(!i2.hasNext());
    +    QVERIFY(i2.hasPrevious());
    +    QVERIFY(i.hasNext());
    +    QVERIFY(!i.hasPrevious());
    +
    +    // i at front, i2 at end/back, 4 neighbors
    +    QhullFacetSet fs3 = f2.neighborFacets(); // same as fs2
    +    QhullFacet f3(fs2[0]);
    +    QhullFacet f4= fs3[0];
    +    QCOMPARE(f3, f4);
    +    QVERIFY(f3==f4);
    +    QhullFacet f5(fs3[1]);
    +    QVERIFY(f4!=f5);
    +    QhullFacet f6(fs3[2]);
    +    QhullFacet f7(fs3[3]);
    +    QCOMPARE(i2.peekPrevious(), f7);
    +    QCOMPARE(i2.previous(), f7);
    +    QCOMPARE(i2.previous(), f6);
    +    QCOMPARE(i2.previous(), f5);
    +    QCOMPARE(i2.previous(), f4);
    +    QVERIFY(!i2.hasPrevious());
    +    QCOMPARE(i.peekNext(), f4);
    +    // i.peekNext()= 1.0; // compiler error
    +    QCOMPARE(i.next(), f4);
    +    QCOMPARE(i.peekNext(), f5);
    +    QCOMPARE(i.next(), f5);
    +    QCOMPARE(i.next(), f6);
    +    QCOMPARE(i.next(), f7);
    +    QVERIFY(!i.hasNext());
    +    i.toFront();
    +    QCOMPARE(i.next(), f4);
    +}//t_qhullset_iterator
    +
    +void QhullSet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    // Fake an empty set.  Default constructor not defined.  No memory allocation.
    +    QhullFacet f= q.beginFacet();
    +    QhullFacetSet fs= f.neighborFacets();
    +    fs.defineAs(q.qh()->other_points);
    +    cout << "INFO:     empty set" << fs << std::endl;
    +    QhullFacet f2= q.beginFacet();
    +    QhullFacetSet fs2= f2.neighborFacets();
    +    cout << "INFO:   Neighboring facets\n";
    +    cout << fs2 << std::endl;
    +
    +    QhullRidgeSet rs= f.ridges();
    +    cout << "INFO:   Ridges for a facet\n";
    +    cout << rs << std::endl;
    +}//t_io
    +
    +}//namespace orgQhull
    +
    +#include "moc/QhullSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp b/xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp
    new file mode 100644
    index 0000000000..41caacd290
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullVertexSet_test.cpp
    @@ -0,0 +1,152 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullVertexSet_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/RboxPoints.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullVertexSet_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_convert();
    +    void t_readonly();
    +    void t_foreach();
    +    void t_io();
    +};//QhullVertexSet_test
    +
    +void
    +add_QhullVertexSet_test()
    +{
    +    new QhullVertexSet_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullVertexSet_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullVertexSet_test::
    +t_construct()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +    QhullFacet f= q.firstFacet();
    +    QhullVertexSet vs= f.vertices();
    +    QVERIFY(!vs.isEmpty());
    +    QCOMPARE(vs.count(),4);
    +    QhullVertexSet vs4= vs; // copy constructor
    +    QVERIFY(vs4==vs);
    +    QhullVertexSet vs3(q, q.qh()->del_vertices);
    +    QVERIFY(vs3.isEmpty());
    +}//t_construct
    +
    +void QhullVertexSet_test::
    +t_convert()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QR0 QV2");  // rotated unit cube with "good" facets adjacent to point 0
    +    cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +    QhullFacet f= q.firstFacet();
    +    QhullVertexSet vs2= f.vertices();
    +    QCOMPARE(vs2.count(),4);
    +    std::vector fv= vs2.toStdVector();
    +    QCOMPARE(fv.size(), 4u);
    +    QList fv2= vs2.toQList();
    +    QCOMPARE(fv2.size(), 4);
    +    std::vector fv3= vs2.toStdVector();
    +    QCOMPARE(fv3.size(), 4u);
    +    QList fv4= vs2.toQList();
    +    QCOMPARE(fv4.size(), 4);
    +}//t_convert
    +
    +//! Spot check properties and read-only.  See QhullSet_test
    +void QhullVertexSet_test::
    +t_readonly()
    +{
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"QV0");  // good facets are adjacent to point 0
    +    QhullVertexSet vs= q.firstFacet().vertices();
    +    QCOMPARE(vs.count(), 4);
    +    QCOMPARE(vs.count(), 4);
    +    QhullVertex v= vs.first();
    +    QhullVertex v2= vs.last();
    +    QVERIFY(vs.contains(v));
    +    QVERIFY(vs.contains(v2));
    +}//t_readonly
    +
    +void QhullVertexSet_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    // Spot check predicates and accessors.  See QhullLinkedList_test
    +    Qhull q(rcube,"QR0");  // rotated unit cube
    +    cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +    QhullVertexSet vs= q.firstFacet().vertices();
    +    QVERIFY(vs.contains(vs.first()));
    +    QVERIFY(vs.contains(vs.last()));
    +    QCOMPARE(vs.first(), *vs.begin());
    +    QCOMPARE(*(vs.end()-1), vs.last());
    +}//t_foreach
    +
    +void QhullVertexSet_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"QR0 QV0");   // good facets are adjacent to point 0
    +        cout << "INFO   : Cube rotated by QR" << q.rotateRandom() << std::endl;
    +        QhullVertexSet vs= q.firstFacet().vertices();
    +        ostringstream os;
    +        os << vs.print("Vertices of first facet with point 0");
    +        os << vs.printIdentifiers("\nVertex identifiers: ");
    +        cout<< os.str();
    +        QString vertices= QString::fromStdString(os.str());
    +        QCOMPARE(vertices.count(QRegExp(" v[0-9]")), 4);
    +    }
    +}//t_io
    +
    +#ifdef QHULL_USES_QT
    +QList QhullVertexSet::
    +toQList() const
    +{
    +    QhullSetIterator i(*this);
    +    QList vs;
    +    while(i.hasNext()){
    +        QhullVertex v= i.next();
    +        vs.append(v);
    +    }
    +    return vs;
    +}//toQList
    +#endif //QHULL_USES_QT
    +
    +}//orgQhull
    +
    +#include "moc/QhullVertexSet_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/QhullVertex_test.cpp b/xs/src/qhull/src/qhulltest/QhullVertex_test.cpp
    new file mode 100644
    index 0000000000..fb6ec9640a
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/QhullVertex_test.cpp
    @@ -0,0 +1,184 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/QhullVertex_test.cpp#3 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Coordinates.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetSet.h"
    +#include "libqhullcpp/QhullVertexSet.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::ostream;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +class QhullVertex_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_constructConvert();
    +    void t_getSet();
    +    void t_foreach();
    +    void t_io();
    +};//QhullVertex_test
    +
    +void
    +add_QhullVertex_test()
    +{
    +    new QhullVertex_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void QhullVertex_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void QhullVertex_test::
    +t_constructConvert()
    +{
    +    QhullVertex v6;
    +    QVERIFY(!v6.isValid());
    +    QCOMPARE(v6.dimension(),0);
    +    // Qhull.runQhull() constructs QhullFacets as facetT
    +    RboxPoints rcube("c");
    +    Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +    QhullVertex v(q);
    +    QVERIFY(!v.isValid());
    +    QCOMPARE(v.dimension(),3);
    +    QhullVertex v2(q.beginVertex());
    +    QCOMPARE(v2.dimension(),3);
    +    v= v2;  // copy assignment
    +    QVERIFY(v.isValid());
    +    QCOMPARE(v.dimension(),3);
    +    QhullVertex v5= v2; // copy constructor
    +    QVERIFY(v5==v2);
    +    QVERIFY(v5==v);
    +    QhullVertex v3(q, v2.getVertexT());
    +    QCOMPARE(v,v3);
    +    QhullVertex v4(q, v2.getBaseT());
    +    QCOMPARE(v,v4);
    +}//t_constructConvert
    +
    +void QhullVertex_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube,"Qt QR0");  // triangulation of rotated unit cube
    +        QCOMPARE(q.facetCount(), 12);
    +        QCOMPARE(q.vertexCount(), 8);
    +
    +        // Also spot-test QhullVertexList.  See QhullLinkedList_test.cpp
    +        QhullVertexList vs= q.vertexList();
    +        QhullVertexListIterator i(vs);
    +        while(i.hasNext()){
    +            const QhullVertex v= i.next();
    +            cout << v.id() << endl;
    +            QCOMPARE(v.dimension(),3);
    +            QVERIFY(v.id()>=0 && v.id()<9);
    +            QVERIFY(v.isValid());
    +            if(i.hasNext()){
    +                QCOMPARE(v.next(), i.peekNext());
    +                QVERIFY(v.next()!=v);
    +                QVERIFY(v.next().previous()==v);
    +            }
    +            QVERIFY(i.hasPrevious());
    +            QCOMPARE(v, i.peekPrevious());
    +        }
    +
    +        // test point()
    +        foreach (QhullVertex v, q.vertexList()){  // Qt only
    +            QhullPoint p= v.point();
    +            int j= p.id();
    +            cout << "Point " << j << ":\n" << p << endl;
    +            QVERIFY(j>=0 && j<8);
    +        }
    +    }
    +}//t_getSet
    +
    +void QhullVertex_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c W0 300");  // 300 points on surface of cube
    +    {
    +        Qhull q(rcube, "QR0 Qc"); // keep coplanars, thick facet, and rotate the cube
    +        foreach (QhullVertex v, q.vertexList()){  // Qt only
    +            QhullFacetSet fs= v.neighborFacets();
    +            QCOMPARE(fs.count(), 3);
    +            foreach (QhullFacet f, fs){  // Qt only
    +                QVERIFY(f.vertices().contains(v));
    +            }
    +        }
    +    }
    +}//t_foreach
    +
    +void QhullVertex_test::
    +t_io()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q(rcube, "");
    +        QhullVertex v= q.beginVertex();
    +        ostringstream os;
    +        os << "Vertex and vertices:\n";
    +        os << v;
    +        QhullVertexSet vs= q.firstFacet().vertices();
    +        os << vs;
    +        os << "\nVertex and vertices with message:\n";
    +        os << v.print("Vertex");
    +        os << vs.print("\nVertices:");
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count("(v"), 10);
    +        QCOMPARE(s.count(": f"), 2);
    +    }
    +    RboxPoints r10("10 D3");  // Without QhullVertex::facetNeighbors
    +    {
    +        Qhull q(r10, "");
    +        QhullVertex v= q.beginVertex();
    +        ostringstream os;
    +        os << "\nTry again with simplicial facets.  No neighboring facets listed for vertices.\n";
    +        os << "Vertex and vertices:\n";
    +        os << v;
    +        q.defineVertexNeighborFacets();
    +        os << "This time with neighborFacets() defined for all vertices:\n";
    +        os << v;
    +        cout << os.str();
    +        QString s= QString::fromStdString(os.str());
    +        QCOMPARE(s.count(": f"), 1);
    +
    +        Qhull q2(r10, "v"); // Voronoi diagram
    +        QhullVertex v2= q2.beginVertex();
    +        ostringstream os2;
    +        os2 << "\nTry again with Voronoi diagram of simplicial facets.  Neighboring facets automatically defined for vertices.\n";
    +        os2 << "Vertex and vertices:\n";
    +        os2 << v2;
    +        cout << os2.str();
    +        QString s2= QString::fromStdString(os2.str());
    +        QCOMPARE(s2.count(": f"), 1);
    +    }
    +}//t_io
    +
    +}//orgQhull
    +
    +#include "moc/QhullVertex_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/Qhull_test.cpp b/xs/src/qhull/src/qhulltest/Qhull_test.cpp
    new file mode 100644
    index 0000000000..cc3918a050
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/Qhull_test.cpp
    @@ -0,0 +1,360 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/Qhull_test.cpp#4 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "qhulltest/RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/Qhull.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::string;
    +
    +namespace orgQhull {
    +
    +//! Test C++ interface to Qhull
    +//! See eg/q_test for tests of Qhull commands
    +class Qhull_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void cleanup();
    +    void t_construct();
    +    void t_attribute();
    +    void t_message();
    +    void t_getSet();
    +    void t_getQh();
    +    void t_getValue();
    +    void t_foreach();
    +    void t_modify();
    +};//Qhull_test
    +
    +void
    +add_Qhull_test()
    +{
    +    new Qhull_test();  // RoadTest::s_testcases
    +}
    +
    +//Executed after each testcase
    +void Qhull_test::
    +cleanup()
    +{
    +    RoadTest::cleanup();
    +}
    +
    +void Qhull_test::
    +t_construct()
    +{
    +    {
    +        Qhull q;
    +        QCOMPARE(q.dimension(),0);
    +        QVERIFY(q.qh()!=0);
    +        QCOMPARE(QString(q.qhullCommand()),QString(""));
    +        QCOMPARE(QString(q.rboxCommand()),QString(""));
    +        try{
    +            QCOMPARE(q.area(),0.0);
    +            QFAIL("area() did not fail.");
    +        }catch (const std::exception &e) {
    +            cout << "INFO   : Caught " << e.what();
    +        }
    +    }
    +    {
    +        RboxPoints rbox("10000");
    +        Qhull q(rbox, "QR0"); // Random points in a randomly rotated cube.
    +        QCOMPARE(q.dimension(),3);
    +        QVERIFY(q.volume() < 1.0);
    +        QVERIFY(q.volume() > 0.99);
    +    }
    +    {
    +        double points[] = {
    +            0, 0,
    +            1, 0,
    +            1, 1
    +        };
    +        Qhull q("triangle", 2, 3, points, "");
    +        QCOMPARE(q.dimension(),2);
    +        QCOMPARE(q.facetCount(),3);
    +        QCOMPARE(q.vertexCount(),3);
    +        QCOMPARE(q.dimension(),2);
    +        QCOMPARE(q.area(), 2.0+sqrt(2.0)); // length of boundary
    +        QCOMPARE(q.volume(), 0.5);        // the 2-d area
    +    }
    +}//t_construct
    +
    +void Qhull_test::
    +t_attribute()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        double normals[] = {
    +            0,  -1, -0.5,
    +           -1,   0, -0.5,
    +            1,   0, -0.5,
    +            0,   1, -0.5
    +        };
    +        Qhull q;
    +        Coordinates feasible;
    +        feasible << 0.0 << 0.0;
    +        q.setFeasiblePoint(feasible);
    +        Coordinates c(std::vector(2, 0.0));
    +        QVERIFY(q.feasiblePoint()==c);
    +        q.setOutputStream(&cout);
    +        q.runQhull("normals of square", 3, 4, normals, "H"); // halfspace intersect
    +        QVERIFY(q.feasiblePoint()==c); // from qh.feasible_point after runQhull()
    +        QCOMPARE(q.facetList().count(), 4); // Vertices of square
    +        cout << "Expecting summary of halfspace intersection\n";
    +        q.outputQhull();
    +        q.qh()->disableOutputStream();  // Same as q.disableOutputStream()
    +        cout << "Expecting no output from qh_fprintf() in Qhull.cpp\n";
    +        q.outputQhull();
    +    }
    +}//t_attribute
    +
    +//! No QhullMessage for errors outside of qhull
    +void Qhull_test::
    +t_message()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        QCOMPARE(q.qhullMessage(), string(""));
    +        QCOMPARE(q.qhullStatus(), qh_ERRnone);
    +        QVERIFY(!q.hasQhullMessage());
    +        try{
    +            q.runQhull(rcube, "Fd");
    +            QFAIL("runQhull Fd did not fail.");
    +        }catch (const std::exception &e) {
    +            const char *s= e.what();
    +            cout << "INFO   : Caught " << s;
    +            QCOMPARE(QString::fromStdString(s).left(6), QString("QH6029"));
    +            // FIXUP QH11025 -- review decision to clearQhullMessage at QhullError()            // Cleared when copied to QhullError
    +            QVERIFY(!q.hasQhullMessage());
    +            // QCOMPARE(q.qhullMessage(), QString::fromStdString(s).remove(0, 7));
    +            // QCOMPARE(q.qhullStatus(), 6029);
    +            q.clearQhullMessage();
    +            QVERIFY(!q.hasQhullMessage());
    +        }
    +        q.appendQhullMessage("Append 1");
    +        QVERIFY(q.hasQhullMessage());
    +        QCOMPARE(QString::fromStdString(q.qhullMessage()), QString("Append 1"));
    +        q.appendQhullMessage("\nAppend 2\n");
    +        QCOMPARE(QString::fromStdString(q.qhullMessage()), QString("Append 1\nAppend 2\n"));
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +        QCOMPARE(QString::fromStdString(q.qhullMessage()), QString(""));
    +    }
    +    {
    +        cout << "INFO   : Error stream without output stream\n";
    +        Qhull q;
    +        q.setErrorStream(&cout);
    +        q.setOutputStream(0);
    +        try{
    +            q.runQhull(rcube, "Fd");
    +            QFAIL("runQhull Fd did not fail.");
    +        }catch (const QhullError &e) {
    +            cout << "INFO   : Caught " << e;
    +            QCOMPARE(e.errorCode(), 6029);
    +        }
    +        //FIXUP QH11025 Qhullmessage cleared when QhullError thrown.  Switched to e
    +        //QVERIFY(q.hasQhullMessage());
    +        //QCOMPARE(QString::fromStdString(q.qhullMessage()).left(6), QString("QH6029"));
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +    }
    +    {
    +        cout << "INFO   : Error output sent to output stream without error stream\n";
    +        Qhull q;
    +        q.setErrorStream(0);
    +        q.setOutputStream(&cout);
    +        try{
    +            q.runQhull(rcube, "Tz H0");
    +            QFAIL("runQhull TZ did not fail.");
    +        }catch (const std::exception &e) {
    +            const char *s= e.what();
    +            cout << "INFO   : Caught " << s;
    +            QCOMPARE(QString::fromLatin1(s).left(6), QString("QH6023"));
    +        }
    +        //FIXUP QH11025 Qhullmessage cleared when QhullError thrown.  Switched to e
    +        //QVERIFY(q.hasQhullMessage());
    +        //QCOMPARE(QString::fromStdString(q.qhullMessage()).left(17), QString("qhull: no message"));
    +        //QCOMPARE(q.qhullStatus(), 6023);
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +    }
    +    {
    +        cout << "INFO   : No error stream or output stream\n";
    +        Qhull q;
    +        q.setErrorStream(0);
    +        q.setOutputStream(0);
    +        try{
    +            q.runQhull(rcube, "Fd");
    +            QFAIL("outputQhull did not fail.");
    +        }catch (const std::exception &e) {
    +            const char *s= e.what();
    +            cout << "INFO   : Caught " << s;
    +            QCOMPARE(QString::fromLatin1(s).left(6), QString("QH6029"));
    +        }
    +        //FIXUP QH11025 Qhullmessage cleared when QhullError thrown.  Switched to e
    +        //QVERIFY(q.hasQhullMessage());
    +        //QCOMPARE(QString::fromStdString(q.qhullMessage()).left(9), QString("qhull err"));
    +        //QCOMPARE(q.qhullStatus(), 6029);
    +        q.clearQhullMessage();
    +        QVERIFY(!q.hasQhullMessage());
    +    }
    +}//t_message
    +
    +void Qhull_test::
    +t_getSet()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        QVERIFY(!q.initialized());
    +        q.runQhull(rcube, "s");
    +        QVERIFY(q.initialized());
    +        QCOMPARE(q.dimension(), 3);
    +        QhullPoint p= q.origin();
    +        QCOMPARE(p.dimension(), 3);
    +        QCOMPARE(p[0]+p[1]+p[2], 0.0);
    +        q.setErrorStream(&cout);
    +        q.outputQhull();
    +    }
    +    {
    +        Qhull q;
    +        q.runQhull(rcube, "");
    +        q.setOutputStream(&cout);
    +        q.outputQhull();
    +    }
    +}//t_getSet
    +
    +void Qhull_test::
    +t_getQh()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        q.runQhull(rcube, "s");
    +        QCOMPARE(QString(q.qhullCommand()), QString("qhull s"));
    +        QCOMPARE(QString(q.rboxCommand()), QString("rbox \"c\""));
    +        QCOMPARE(q.facetCount(), 6);
    +        QCOMPARE(q.vertexCount(), 8);
    +        // Sample fields from Qhull's qhT [libqhull.h]
    +        QCOMPARE(q.qh()->ALLpoints, 0u);
    +        QCOMPARE(q.qh()->GOODpoint, 0);
    +        QCOMPARE(q.qh()->IStracing, 0);
    +        QCOMPARE(q.qh()->MAXcoplanar+1.0, 1.0); // fuzzy compare
    +        QCOMPARE(q.qh()->MERGING, 1u);
    +        QCOMPARE(q.qh()->input_dim, 3);
    +        QCOMPARE(QString(q.qh()->qhull_options).left(8), QString("  run-id"));
    +        QCOMPARE(q.qh()->num_facets, 6);
    +        QCOMPARE(q.qh()->hasTriangulation, 0u);
    +        QCOMPARE(q.qh()->max_outside - q.qh()->min_vertex + 1.0, 1.0); // fuzzy compare
    +        QCOMPARE(*q.qh()->gm_matrix+1.0, 1.0); // fuzzy compare
    +    }
    +}//t_getQh
    +
    +void Qhull_test::
    +t_getValue()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        q.runQhull(rcube, "");
    +        QCOMPARE(q.area(), 6.0);
    +        QCOMPARE(q.volume(), 1.0);
    +    }
    +}//t_getValue
    +
    +void Qhull_test::
    +t_foreach()
    +{
    +    RboxPoints rcube("c");
    +    {
    +        Qhull q;
    +        QCOMPARE(q.beginFacet(),q.endFacet());
    +        QCOMPARE(q.beginVertex(),q.endVertex());
    +        q.runQhull(rcube, "");
    +        QCOMPARE(q.facetList().count(), 6);
    +
    +        // defineVertexNeighborFacets() tested in QhullVertex_test::t_io()
    +
    +        QhullFacetList facets(q.beginFacet(), q.endFacet());
    +        QCOMPARE(facets.count(), 6);
    +        QCOMPARE(q.firstFacet(), q.beginFacet());
    +        QhullVertexList vertices(q.beginVertex(), q.endVertex());
    +        QCOMPARE(vertices.count(), 8);
    +        QCOMPARE(q.firstVertex(), q.beginVertex());
    +        QhullPoints ps= q.points();
    +        QCOMPARE(ps.count(), 8);
    +        QhullPointSet ps2= q.otherPoints();
    +        QCOMPARE(ps2.count(), 0);
    +        // ps2= q.otherPoints(); //disabled, would not copy the points
    +        QCOMPARE(q.facetCount(), 6);
    +        QCOMPARE(q.vertexCount(), 8);
    +        coordT *c= q.pointCoordinateBegin(); // of q.points()
    +        QVERIFY(*c==0.5 || *c==-0.5);
    +        coordT *c3= q.pointCoordinateEnd();
    +        QVERIFY(c3[-1]==0.5 || c3[-1]==-0.5);
    +        QCOMPARE(c3-c, 8*3);
    +        QCOMPARE(q.vertexList().count(), 8);
    +    }
    +}//t_foreach
    +
    +void Qhull_test::
    +t_modify()
    +{
    +    //addPoint() tested in t_foreach
    +    RboxPoints diamond("d");
    +    Qhull q(diamond, "o");
    +    q.setOutputStream(&cout);
    +    cout << "Expecting vertexList and facetList of a 3-d diamond.\n";
    +    q.outputQhull();
    +    cout << "Expecting normals of a 3-d diamond.\n";
    +    q.outputQhull("n");
    +    // runQhull tested in t_attribute(), t_message(), etc.
    +}//t_modify
    +
    +}//orgQhull
    +
    +// Redefine Qhull's usermem_r.c in order to report erroneous calls to qh_exit
    +void qh_exit(int exitcode) {
    +    cout << "FAIL!  : Qhull called qh_exit().  Qhull's error handling not available.\n.. See the corresponding Qhull:qhull_message or setErrorStream().\n";
    +    exit(exitcode);
    +}
    +void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) {
    +    va_list args;
    +
    +    va_start(args, fmt);
    +    if(msgcode)
    +        fprintf(stderr, "QH%.4d ", msgcode);
    +    vfprintf(stderr, fmt, args);
    +    va_end(args);
    +} /* fprintf_stderr */
    +void qh_free(void *mem) {
    +    free(mem);
    +}
    +void *qh_malloc(size_t size) {
    +    return malloc(size);
    +}
    +
    +#if 0
    +template<> char * QTest::
    +toString(const std::string &s)
    +{
    +    QByteArray ba = s.c_str();
    +    return qstrdup(ba.data());
    +}
    +#endif
    +
    +#include "moc/Qhull_test.moc"
    diff --git a/xs/src/qhull/src/qhulltest/RboxPoints_test.cpp b/xs/src/qhull/src/qhulltest/RboxPoints_test.cpp
    new file mode 100644
    index 0000000000..4f4ea984f8
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/RboxPoints_test.cpp
    @@ -0,0 +1,215 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2006-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/RboxPoints_test.cpp#2 $$Change: 2062 $
    +** $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullError.h"
    +
    +using std::cout;
    +using std::endl;
    +using std::ostringstream;
    +using std::string;
    +using std::stringstream;
    +
    +namespace orgQhull {
    +
    +//! Test C++ interface to Rbox
    +//! See eg/q_test for tests of rbox commands
    +class RboxPoints_test : public RoadTest
    +{
    +    Q_OBJECT
    +
    +#//!\name Test slots
    +private slots:
    +    void t_construct();
    +    void t_error();
    +    void t_test();
    +    void t_getSet();
    +    void t_foreach();
    +    void t_change();
    +    void t_ostream();
    +};
    +
    +void
    +add_RboxPoints_test()
    +{
    +    new RboxPoints_test();  // RoadTest::s_testcases
    +}
    +
    +void RboxPoints_test::
    +t_construct()
    +{
    +    RboxPoints rp;
    +    QCOMPARE(rp.dimension(), 0);
    +    QCOMPARE(rp.count(), 0);
    +    QVERIFY(QString::fromStdString(rp.comment()) != QString(""));
    +    QVERIFY(rp.isEmpty());
    +    QVERIFY(!rp.hasRboxMessage());
    +    QCOMPARE(rp.rboxStatus(), qh_ERRnone);
    +    QCOMPARE(QString::fromStdString(rp.rboxMessage()), QString("rbox warning: no points generated\n"));
    +
    +    RboxPoints rp2("c"); // 3-d cube
    +    QCOMPARE(rp2.dimension(), 3);
    +    QCOMPARE(rp2.count(), 8);
    +    QCOMPARE(QString::fromStdString(rp2.comment()), QString("rbox \"c\""));
    +    QVERIFY(!rp2.isEmpty());
    +    QVERIFY(!rp2.hasRboxMessage());
    +    QCOMPARE(rp2.rboxStatus(), qh_ERRnone);
    +    QCOMPARE(QString::fromStdString(rp2.rboxMessage()), QString("rbox: OK\n"));
    +}//t_construct
    +
    +void RboxPoints_test::
    +t_error()
    +{
    +    RboxPoints rp;
    +    try{
    +        rp.appendPoints("D0 c");
    +        QFAIL("'D0 c' did not fail.");
    +    }catch (const std::exception &e) {
    +        const char *s= e.what();
    +        cout << "INFO   : Caught " << s;
    +        QCOMPARE(QString(s).left(6), QString("QH6189"));
    +        QVERIFY(rp.hasRboxMessage());
    +        QCOMPARE(QString::fromStdString(rp.rboxMessage()).left(8), QString("rbox err"));
    +        QCOMPARE(rp.rboxStatus(), 6189);
    +        rp.clearRboxMessage();
    +        QVERIFY(!rp.hasRboxMessage());
    +    }
    +    try{
    +        RboxPoints rp2;
    +        rp2.setDimension(-1);
    +        QFAIL("setDimension(-1) did not fail.");
    +    }catch (const RoadError &e) {
    +        const char *s= e.what();
    +        cout << "INFO   : Caught " << s;
    +        QCOMPARE(QString(s).left(7), QString("QH10062"));
    +        QCOMPARE(e.errorCode(), 10062);
    +        QCOMPARE(QString::fromStdString(e.what()), QString(s));
    +        RoadLogEvent logEvent= e.roadLogEvent();
    +        QCOMPARE(logEvent.int1(), -1);
    +    }
    +}//t_error
    +
    +void RboxPoints_test::
    +t_test()
    +{
    +    // isEmpty -- t_construct
    +}//t_test
    +
    +void RboxPoints_test::
    +t_getSet()
    +{
    +    // comment -- t_construct
    +    // count -- t_construct
    +    // dimension -- t_construct
    +
    +    RboxPoints rp;
    +    QCOMPARE(rp.dimension(), 0);
    +    rp.setDimension(2);
    +    QCOMPARE(rp.dimension(), 2);
    +    try{
    +        rp.setDimension(102);
    +        QFAIL("setDimension(102) did not fail.");
    +    }catch (const std::exception &e) {
    +        cout << "INFO   : Caught " << e.what();
    +    }
    +    QCOMPARE(rp.newCount(), 0);
    +    rp.appendPoints("D2 P1 P2");
    +    QCOMPARE(rp.count(), 2);
    +    QCOMPARE(rp.newCount(), 2); // From previous appendPoints();
    +    PointCoordinates pc(rp.qh(), 2, "Test qh() and <<");
    +    pc << 1.0 << 0.0 << 2.0 << 0.0;
    +    QCOMPARE(pc.dimension(), 2);
    +    QCOMPARE(pc.count(), 2);
    +    QVERIFY(rp==pc);
    +    rp.setNewCount(10);  // Normally only used by appendPoints for rbox processing
    +    QCOMPARE(rp.newCount(), 10);
    +    rp.reservePoints();
    +    QVERIFY(rp==pc);
    +}//t_getSet
    +
    +void RboxPoints_test::
    +t_foreach()
    +{
    +    RboxPoints rp("c");
    +    Coordinates::ConstIterator cci= rp.beginCoordinates();
    +    orgQhull::Coordinates::Iterator ci= rp.beginCoordinates();
    +    QCOMPARE(*cci, -0.5);
    +    QCOMPARE(*ci, *cci);
    +    int i=1;
    +    while(++cci
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +#//!\name class variable
    +
    +QList RoadTest::
    +s_testcases;
    +
    +int RoadTest::
    +s_test_count= 0;
    +
    +int RoadTest::
    +s_test_fail= 0;
    +
    +QStringList RoadTest::
    +s_failed_tests;
    +
    +#//!\name Slot
    +
    +//! Executed after each test
    +void RoadTest::
    +cleanup()
    +{
    +    s_test_count++;
    +    if(QTest::currentTestFailed()){
    +        recordFailedTest();
    +    }
    +}//cleanup
    +
    +#//!\name Helper
    +
    +void RoadTest::
    +recordFailedTest()
    +{
    +    s_test_fail++;
    +    QString className= metaObject()->className();
    +    s_failed_tests << className + "::" + QTest::currentTestFunction();
    +}
    +
    +#//!\name class function
    +
    +void RoadTest::
    +deleteTests()
    +{
    +    foreach(RoadTest *testcase, s_testcases){
    +        delete testcase;
    +    }
    +    s_failed_tests.clear();
    +}
    +
    +int RoadTest::
    +runTests(QStringList arguments)
    +{
    +    int result= 0; // assume success
    +
    +    foreach(RoadTest *testcase, s_testcases){
    +        try{
    +            result += QTest::qExec(testcase, arguments);
    +        }catch(const std::exception &e){
    +            cout << "FAIL!  : Threw error ";
    +            cout << e.what() << endl;
    +    s_test_count++;
    +            testcase->recordFailedTest();
    +            // Qt 4.5.2 OK.  In Qt 4.3.3, qtestcase did not clear currentTestObject
    +        }
    +    }
    +    if(s_test_fail){
    +        cout << "Failed " << s_test_fail << " of " << s_test_count << " tests.\n";
    +        cout << s_failed_tests.join("\n").toLocal8Bit().constData() << std::endl;
    +    }else{
    +        cout << "Passed " << s_test_count << " tests.\n";
    +    }
    +    return result;
    +}//runTests
    +
    +}//orgQhull
    +
    +#include "moc/moc_RoadTest.cpp"
    diff --git a/xs/src/qhull/src/qhulltest/RoadTest.h b/xs/src/qhull/src/qhulltest/RoadTest.h
    new file mode 100644
    index 0000000000..adfe0bf8c1
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/RoadTest.h
    @@ -0,0 +1,102 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/RoadTest.h#2 $$Change: 2062 $
    +** $Date: 2016/01/17 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +#ifndef ROADTEST_H
    +#define ROADTEST_H
    +
    +//pre-compiled with RoadTest.h
    +#include     // Qt C++ Framework
    +#include 
    +
    +#define QHULL_USES_QT 1
    +
    +namespace orgQhull {
    +
    +#//!\name Defined here
    +
    +    //! RoadTest -- Generic test for Qt's QTest
    +    class RoadTest;
    +    //! TESTadd_(t) -- Add a RoadTest
    +
    +/** Test Name objects using Qt's QTestLib
    +
    +Template:
    +
    +class Name_test : public RoadTest
    +{
    +    Q_OBJECT
    +#//!\name Test slot
    +private slots:
    +    void t_name();
    +    //Executed before any test
    +    void initTestCase();
    +    void init();          // Each test
    +    //Executed after each test
    +    void cleanup(); //RoadTest::cleanup();
    +    // Executed after last test
    +    void cleanupTestCase();
    +};
    +
    +void
    +add_Name_test()
    +{
    +    new Name_test();  // RoadTest::s_testcases
    +}
    +
    +Send additional output to cout
    +*/
    +
    +class RoadTest : public QObject
    +{
    +    Q_OBJECT
    +
    +#//!\name Class globals
    +protected:
    +    static QList
    +                        s_testcases; ///! List of testcases to execute.  Initialized via add_...()
    +    static int          s_test_count; ///! Total number of tests executed
    +    static int          s_test_fail; ///! Number of failed tests
    +    static QStringList  s_failed_tests; ///! List of failed tests
    +
    +#//!\name Test slots
    +public slots:
    +    void cleanup();
    +
    +public:
    +#//!\name Constructors, etc.
    +                        RoadTest()  { s_testcases.append(this); }
    +    virtual             ~RoadTest() {} // Derived from QObject
    +
    +#//!\name Helper
    +    void                recordFailedTest();
    +
    +
    +#//!\name Class functions
    +    static void         deleteTests();
    +    static int          runTests(QStringList arguments);
    +
    +};//RoadTest
    +
    +#define TESTadd_(t) extern void t(); t();
    +
    +
    +}//orgQhull
    +
    +namespace QTest{
    +
    +template<>
    +inline char *
    +toString(const std::string &s)
    +{
    +    return qstrdup(s.c_str());
    +}
    +
    +}//namespace QTest
    +
    +#endif //ROADTEST_H
    +
    diff --git a/xs/src/qhull/src/qhulltest/qhulltest.cpp b/xs/src/qhull/src/qhulltest/qhulltest.cpp
    new file mode 100644
    index 0000000000..5bfe16e9cf
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/qhulltest.cpp
    @@ -0,0 +1,94 @@
    +/****************************************************************************
    +**
    +** Copyright (c) 2008-2015 C.B. Barber. All rights reserved.
    +** $Id: //main/2015/qhull/src/qhulltest/qhulltest.cpp#5 $$Change: 2079 $
    +** $DateTime: 2016/02/07 17:43:34 $$Author: bbarber $
    +**
    +****************************************************************************/
    +
    +//pre-compiled headers
    +#include "libqhull_r/user_r.h"
    +
    +#include 
    +#include "RoadTest.h" // QT_VERSION
    +
    +#include "libqhullcpp/RoadError.h"
    +#include "libqhull_r/qhull_ra.h"
    +
    +#include 
    +#include 
    +#include 
    +
    +using std::cout;
    +using std::endl;
    +
    +namespace orgQhull {
    +
    +void addQhullTests(QStringList &args)
    +{
    +    TESTadd_(add_Qhull_test);
    +
    +    if(args.contains("--all")){
    +        args.removeAll("--all");
    +        // up-to-date
    +        TESTadd_(add_Coordinates_test);
    +        TESTadd_(add_PointCoordinates_test);
    +        TESTadd_(add_QhullFacet_test);
    +        TESTadd_(add_QhullFacetList_test);
    +        TESTadd_(add_QhullFacetSet_test);
    +        TESTadd_(add_QhullHyperplane_test);
    +        TESTadd_(add_QhullLinkedList_test);
    +        TESTadd_(add_QhullPoint_test);
    +        TESTadd_(add_QhullPoints_test);
    +        TESTadd_(add_QhullPointSet_test);
    +        TESTadd_(add_QhullRidge_test);
    +        TESTadd_(add_QhullSet_test);
    +        TESTadd_(add_QhullVertex_test);
    +        TESTadd_(add_QhullVertexSet_test);
    +        TESTadd_(add_RboxPoints_test);
    +        // qhullStat
    +        TESTadd_(add_Qhull_test);
    +    }//--all
    +}//addQhullTests
    +
    +int main(int argc, char *argv[])
    +{
    +
    +    QCoreApplication app(argc, argv);
    +    QStringList args= app.arguments();
    +    bool isAll= args.contains("--all");
    +
    +    QHULL_LIB_CHECK /* Check for compatible library */
    +
    +    addQhullTests(args);
    +    int status=1010;
    +    try{
    +        status= RoadTest::runTests(args);
    +    }catch(const std::exception &e){
    +        cout << "FAIL!  : runTests() did not catch error\n";
    +        cout << e.what() << endl;
    +        if(!RoadError::emptyGlobalLog()){
    +            cout << RoadError::stringGlobalLog() << endl;
    +            RoadError::clearGlobalLog();
    +        }
    +    }
    +    if(!RoadError::emptyGlobalLog()){
    +        cout << RoadError::stringGlobalLog() << endl;
    +        RoadError::clearGlobalLog();
    +    }
    +    if(isAll){
    +        cout << "Finished test of libqhullcpp.  Test libqhull_r with eg/q_test after building libqhull_r/Makefile" << endl;
    +    }else{
    +        cout << "Finished test of one class.  Test all classes with 'qhulltest --all'" << endl;
    +    }
    +    RoadTest::deleteTests();
    +    return status;
    +}
    +
    +}//orgQhull
    +
    +int main(int argc, char *argv[])
    +{
    +    return orgQhull::main(argc, argv); // Needs RoadTest:: for TESTadd_() linkage
    +}
    +
    diff --git a/xs/src/qhull/src/qhulltest/qhulltest.pro b/xs/src/qhull/src/qhulltest/qhulltest.pro
    new file mode 100644
    index 0000000000..0da34d3755
    --- /dev/null
    +++ b/xs/src/qhull/src/qhulltest/qhulltest.pro
    @@ -0,0 +1,36 @@
    +# -------------------------------------------------
    +# qhulltest.pro -- Qt project for qhulltest.exe (QTestLib)
    +# cd $qh/build/qhulltest && qmake -tp vc -r ../../src/qhulltest/qhulltest.pro
    +# -------------------------------------------------
    +
    +include(../qhull-app-cpp.pri)
    +
    +TARGET = qhulltest
    +QT += testlib
    +MOC_DIR = moc
    +INCLUDEPATH += ..  # for MOC_DIR
    +
    +PRECOMPILED_HEADER = RoadTest.h
    +
    +HEADERS += RoadTest.h
    +
    +SOURCES += ../libqhullcpp/qt-qhull.cpp
    +SOURCES += Coordinates_test.cpp
    +SOURCES += PointCoordinates_test.cpp
    +SOURCES += Qhull_test.cpp
    +SOURCES += QhullFacet_test.cpp
    +SOURCES += QhullFacetList_test.cpp
    +SOURCES += QhullFacetSet_test.cpp
    +SOURCES += QhullHyperplane_test.cpp
    +SOURCES += QhullLinkedList_test.cpp
    +SOURCES += QhullPoint_test.cpp
    +SOURCES += QhullPoints_test.cpp
    +SOURCES += QhullPointSet_test.cpp
    +SOURCES += QhullRidge_test.cpp
    +SOURCES += QhullSet_test.cpp
    +SOURCES += qhulltest.cpp
    +SOURCES += QhullVertex_test.cpp
    +SOURCES += QhullVertexSet_test.cpp
    +SOURCES += RboxPoints_test.cpp
    +SOURCES += RoadTest.cpp
    +
    diff --git a/xs/src/qhull/src/qvoronoi/qvoronoi.c b/xs/src/qhull/src/qvoronoi/qvoronoi.c
    new file mode 100644
    index 0000000000..b93d237114
    --- /dev/null
    +++ b/xs/src/qhull/src/qvoronoi/qvoronoi.c
    @@ -0,0 +1,303 @@
    +/*
      ---------------------------------
    +
    +   qvoronoi.c
    +     compute Voronoi diagrams and furthest-point Voronoi
    +     diagrams using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull/libqhull.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qvoron_f.htm and qvoronoi.htm
    +   QJ and Qt are deprecated, but allowed for backwards compatibility
    +*/
    +char hidden_options[]=" d n m v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V Fa FA FC Fp FS Ft FV Pv Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qvoronoi- compute the Voronoi diagram\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +\n\
    +Qhull control options:\n\
    +    Qz   - add point-at-infinity to Voronoi diagram\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - Voronoi vertices if visible from point n, -n if not\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for non-coincident point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    s    - summary to stderr\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    i    - Delaunay regions (use 'Pp' to avoid warning)\n\
    +    f    - facet dump\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus coincident points (by Voronoi vertex)\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID for each Voronoi vertex\n\
    +    Fm   - merge count for each Voronoi vertex (511 max)\n\
    +    Fn   - count plus neighboring Voronoi vertices for each Voronoi vertex\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fo   - separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qvoronoi\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #Voronoi regions, #Voronoi vertices,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane and min vertex\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d only)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Voronoi vertices by 'area'\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Voronoi vertices (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Voronoi vertices whose 'area' is at least n\n\
    +    PG   - print neighbors of good Voronoi vertices\n\
    +    PMn  - keep n Voronoi vertices with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qvoronoi- compute the Voronoi diagram.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qvoronoi.htm):\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF file format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fi   - separating hyperplanes for bounded regions, 'Fo' for unbounded\n\
    +    G    - Geomview output (2-d only)\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +rbox c P0 D2 | qvoronoi s o         rbox c P0 D2 | qvoronoi Fi\n\
    +rbox c P0 D2 | qvoronoi Fo          rbox c P0 D2 | qvoronoi Fv\n\
    +rbox c P0 D2 | qvoronoi s Qu Fv     rbox c P0 D2 | qvoronoi Qu Fo\n\
    +rbox c G1 d D2 | qvoronoi s p       rbox c P0 D2 | qvoronoi s Fv QV0\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + OFF_format     p_vertices     i_delaunay     summary        facet_dump\n\
    +\n\
    + Fcoincident    Fd_cdd_in      FD_cdd_out     FF-dump-xridge Fi_bounded\n\
    + Fxtremes       Fmerges        Fneighbors     FNeigh_region  FOptions\n\
    + Fo_unbounded   FPoint_near    FQvoronoi      Fsummary       Fvoronoi\n\
    + FIDs\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QG_vertex_good Qsearch_1st    Qupper_voronoi QV_point_good  Qzinfinite\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(stdin, stdout, stderr, argc, argv);  /* sets qh qhull_command */
    +  exitcode= setjmp(qh errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh NOerrexit= False;
    +    qh_option("voronoi  _bbound-last  _coplanar-keep", NULL, NULL);
    +    qh DELAUNAY= True;     /* 'v'   */
    +    qh VORONOI= True;
    +    qh SCALElast= True;    /* 'Qbb' */
    +    qh_checkflags(qh qhull_command, hidden_options);
    +    qh_initflags(qh qhull_command);
    +    points= qh_readpoints(&numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option("_merge-exact", NULL, NULL);
    +      qh MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(points, numpoints, dim, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();
    +    if (qh VERIFYoutput && !qh FORCEoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    exitcode= qh_ERRnone;
    +  }
    +  qh NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh_ALL);
    +#else
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/qvoronoi/qvoronoi.pro b/xs/src/qhull/src/qvoronoi/qvoronoi.pro
    new file mode 100644
    index 0000000000..4646c84472
    --- /dev/null
    +++ b/xs/src/qhull/src/qvoronoi/qvoronoi.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# qvoronoi.pro -- Qt project file for qvoronoi.exe
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = qvoronoi
    +
    +SOURCES += qvoronoi.c
    diff --git a/xs/src/qhull/src/qvoronoi/qvoronoi_r.c b/xs/src/qhull/src/qvoronoi/qvoronoi_r.c
    new file mode 100644
    index 0000000000..6323c8b496
    --- /dev/null
    +++ b/xs/src/qhull/src/qvoronoi/qvoronoi_r.c
    @@ -0,0 +1,305 @@
    +/*
      ---------------------------------
    +
    +   qvoronoi.c
    +     compute Voronoi diagrams and furthest-point Voronoi
    +     diagrams using qhull
    +
    +   see unix.c for full interface
    +
    +   Copyright (c) 1993-2015, The Geometry Center
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#if __cplusplus
    +extern "C" {
    +  int isatty(int);
    +}
    +
    +#elif _MSC_VER
    +#include 
    +#define isatty _isatty
    +/* int _isatty(int); */
    +
    +#else
    +int isatty(int);  /* returns 1 if stdin is a tty
    +                   if "Undefined symbol" this can be deleted along with call in main() */
    +#endif
    +
    +/*---------------------------------
    +
    +  qh_prompt
    +    long prompt for qhull
    +
    +  notes:
    +    restricted version of libqhull.c
    +
    +  see:
    +    concise prompt below
    +*/
    +
    +/* duplicated in qvoron_f.htm and qvoronoi.htm
    +   QJ and Qt are deprecated, but allowed for backwards compatibility
    +*/
    +char hidden_options[]=" d n m v H U Qb QB Qc Qf Qg Qi Qm Qr QR Qv Qx TR E V Fa FA FC Fp FS Ft FV Pv Gt Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8 Q9 ";
    +
    +char qh_prompta[]= "\n\
    +qvoronoi- compute the Voronoi diagram\n\
    +    http://www.qhull.org  %s\n\
    +\n\
    +input (stdin):\n\
    +    first lines: dimension and number of points (or vice-versa).\n\
    +    other lines: point coordinates, best if one point per line\n\
    +    comments:    start with a non-numeric character\n\
    +\n\
    +options:\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +\n\
    +Qhull control options:\n\
    +    Qz   - add point-at-infinity to Voronoi diagram\n\
    +%s%s%s%s";  /* split up qh_prompt for Visual C++ */
    +char qh_promptb[]= "\
    +    Qs   - search all points for the initial simplex\n\
    +    QGn  - Voronoi vertices if visible from point n, -n if not\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +\n\
    +";
    +char qh_promptc[]= "\
    +Trace options:\n\
    +    T4   - trace at level n, 4=all, 5=mem/gauss, -1= events\n\
    +    Tc   - check frequently during execution\n\
    +    Ts   - statistics\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    Tz   - send all output to stdout\n\
    +    TFn  - report summary when n or more facets created\n\
    +    TI file - input data from file, no spaces or single quotes\n\
    +    TO file - output results to file, may be enclosed in single quotes\n\
    +    TPn  - turn on tracing when point n added to hull\n\
    +     TMn - turn on tracing at merge n\n\
    +     TWn - trace merge facets when width > n\n\
    +    TVn  - stop qhull after adding point n, -n for before (see TCn)\n\
    +     TCn - stop qhull after building cone for point n (see TVn)\n\
    +\n\
    +Precision options:\n\
    +    Cn   - radius of centrum (roundoff added).  Merge facets if non-convex\n\
    +     An  - cosine of maximum angle.  Merge facets if cosine > n or non-convex\n\
    +           C-0 roundoff, A-0.99/C-0.01 pre-merge, A0.99/C0.01 post-merge\n\
    +    Rn   - randomly perturb computations by a factor of [1-n,1+n]\n\
    +    Wn   - min facet width for non-coincident point (before roundoff)\n\
    +\n\
    +Output formats (may be combined; if none, produces a summary to stdout):\n\
    +    s    - summary to stderr\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    i    - Delaunay regions (use 'Pp' to avoid warning)\n\
    +    f    - facet dump\n\
    +\n\
    +";
    +char qh_promptd[]= "\
    +More formats:\n\
    +    Fc   - count plus coincident points (by Voronoi vertex)\n\
    +    Fd   - use cdd format for input (homogeneous with offset first)\n\
    +    FD   - use cdd format for output (offset first)\n\
    +    FF   - facet dump without ridges\n\
    +    Fi   - separating hyperplanes for bounded Voronoi regions\n\
    +    FI   - ID for each Voronoi vertex\n\
    +    Fm   - merge count for each Voronoi vertex (511 max)\n\
    +    Fn   - count plus neighboring Voronoi vertices for each Voronoi vertex\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fo   - separating hyperplanes for unbounded Voronoi regions\n\
    +    FO   - options and precision constants\n\
    +    FP   - nearest point and distance for each coincident point\n\
    +    FQ   - command used for qvoronoi\n\
    +    Fs   - summary: #int (8), dimension, #points, tot vertices, tot facets,\n\
    +                    for output: #Voronoi regions, #Voronoi vertices,\n\
    +                                #coincident points, #non-simplicial regions\n\
    +                    #real (2), max outer plane and min vertex\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fx   - extreme points of Delaunay triangulation (on convex hull)\n\
    +\n\
    +";
    +char qh_prompte[]= "\
    +Geomview options (2-d only)\n\
    +    Ga   - all points as dots\n\
    +     Gp  -  coplanar points and vertices as radii\n\
    +     Gv  -  vertices as spheres\n\
    +    Gi   - inner planes only\n\
    +     Gn  -  no planes\n\
    +     Go  -  outer planes only\n\
    +    Gc   - centrums\n\
    +    Gh   - hyperplane intersections\n\
    +    Gr   - ridges\n\
    +    GDn  - drop dimension n in 3-d and 4-d output\n\
    +\n\
    +Print options:\n\
    +    PAn  - keep n largest Voronoi vertices by 'area'\n\
    +    Pdk:n - drop facet if normal[k] <= n (default 0.0)\n\
    +    PDk:n - drop facet if normal[k] >= n\n\
    +    Pg   - print good Voronoi vertices (needs 'QGn' or 'QVn')\n\
    +    PFn  - keep Voronoi vertices whose 'area' is at least n\n\
    +    PG   - print neighbors of good Voronoi vertices\n\
    +    PMn  - keep n Voronoi vertices with most merges\n\
    +    Po   - force output.  If error, output neighborhood of facet\n\
    +    Pp   - do not report precision problems\n\
    +\n\
    +    .    - list of all options\n\
    +    -    - one line descriptions of all options\n\
    +    -V   - version\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt2
    +    synopsis for qhull
    +*/
    +char qh_prompt2[]= "\n\
    +qvoronoi- compute the Voronoi diagram.  Qhull %s\n\
    +    input (stdin): dimension, number of points, point coordinates\n\
    +    comments start with a non-numeric character\n\
    +\n\
    +options (qvoronoi.htm):\n\
    +    Qu   - compute furthest-site Voronoi diagram\n\
    +    Tv   - verify result: structure, convexity, and in-circle test\n\
    +    .    - concise list of all options\n\
    +    -    - one-line description of all options\n\
    +    -V   - version\n\
    +\n\
    +output options (subset):\n\
    +    s    - summary of results (default)\n\
    +    p    - Voronoi vertices\n\
    +    o    - OFF file format (dim, Voronoi vertices, and Voronoi regions)\n\
    +    FN   - count and Voronoi vertices for each Voronoi region\n\
    +    Fv   - Voronoi diagram as Voronoi vertices between adjacent input sites\n\
    +    Fi   - separating hyperplanes for bounded regions, 'Fo' for unbounded\n\
    +    G    - Geomview output (2-d only)\n\
    +    QVn  - Voronoi vertices for input point n, -n if not\n\
    +    TO file- output results to file, may be enclosed in single quotes\n\
    +\n\
    +examples:\n\
    +rbox c P0 D2 | qvoronoi s o         rbox c P0 D2 | qvoronoi Fi\n\
    +rbox c P0 D2 | qvoronoi Fo          rbox c P0 D2 | qvoronoi Fv\n\
    +rbox c P0 D2 | qvoronoi s Qu Fv     rbox c P0 D2 | qvoronoi Qu Fo\n\
    +rbox c G1 d D2 | qvoronoi s p       rbox c P0 D2 | qvoronoi s Fv QV0\n\
    +\n\
    +";
    +/* for opts, don't assign 'e' or 'E' to a flag (already used for exponent) */
    +
    +/*---------------------------------
    +
    +  qh_prompt3
    +    concise prompt for qhull
    +*/
    +char qh_prompt3[]= "\n\
    +Qhull %s.\n\
    +Except for 'F.' and 'PG', upper-case options take an argument.\n\
    +\n\
    + OFF_format     p_vertices     i_delaunay     summary        facet_dump\n\
    +\n\
    + Fcoincident    Fd_cdd_in      FD_cdd_out     FF-dump-xridge Fi_bounded\n\
    + Fxtremes       Fmerges        Fneighbors     FNeigh_region  FOptions\n\
    + Fo_unbounded   FPoint_near    FQvoronoi      Fsummary       Fvoronoi\n\
    + FIDs\n\
    +\n\
    + Gvertices      Gpoints        Gall_points    Gno_planes     Ginner\n\
    + Gcentrums      Ghyperplanes   Gridges        Gouter         GDrop_dim\n\
    +\n\
    + PArea_keep     Pdrop d0:0D0   Pgood          PFacet_area_keep\n\
    + PGood_neighbors PMerge_keep   Poutput_forced Pprecision_not\n\
    +\n\
    + QG_vertex_good Qsearch_1st    Qupper_voronoi QV_point_good  Qzinfinite\n\
    + T4_trace       Tcheck_often   Tstatistics    Tverify        Tz_stdout\n\
    + TFacet_log     TInput_file    TPoint_trace   TMerge_trace   TOutput_file\n\
    + TWide_trace    TVertex_stop   TCone_stop\n\
    +\n\
    + Angle_max      Centrum_size   Random_dist    Wide_outside\n\
    +";
    +
    +/*---------------------------------
    +
    +  main( argc, argv )
    +    processes the command line, calls qhull() to do the work, and exits
    +
    +  design:
    +    initializes data structures
    +    reads points
    +    finishes initialization
    +    computes convex hull and other structures
    +    checks the result
    +    writes the output
    +    frees memory
    +*/
    +int main(int argc, char *argv[]) {
    +  int curlong, totlong; /* used !qh_NOmem */
    +  int exitcode, numpoints, dim;
    +  coordT *points;
    +  boolT ismalloc;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK /* Check for compatible library */
    +
    +  if ((argc == 1) && isatty( 0 /*stdin*/)) {
    +    fprintf(stdout, qh_prompt2, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompta, qh_version,
    +                qh_promptb, qh_promptc, qh_promptd, qh_prompte);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '.' && !*(argv[1]+1)) {
    +    fprintf(stdout, qh_prompt3, qh_version);
    +    exit(qh_ERRnone);
    +  }
    +  if (argc > 1 && *argv[1] == '-' && *(argv[1]+1)=='V') {
    +      fprintf(stdout, "%s\n", qh_version2);
    +      exit(qh_ERRnone);
    +  }
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /* sets qh->qhull_command */
    +  exitcode= setjmp(qh->errexit); /* simple statement for CRAY J916 */
    +  if (!exitcode) {
    +    qh->NOerrexit = False;
    +    qh_option(qh, "voronoi  _bbound-last  _coplanar-keep", NULL, NULL);
    +    qh->DELAUNAY= True;     /* 'v'   */
    +    qh->VORONOI= True;
    +    qh->SCALElast= True;    /* 'Qbb' */
    +    qh_checkflags(qh, qh->qhull_command, hidden_options);
    +    qh_initflags(qh, qh->qhull_command);
    +    points= qh_readpoints(qh, &numpoints, &dim, &ismalloc);
    +    if (dim >= 5) {
    +      qh_option(qh, "_merge-exact", NULL, NULL);
    +      qh->MERGEexact= True; /* 'Qx' always */
    +    }
    +    qh_init_B(qh, points, numpoints, dim, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);
    +    if (qh->VERIFYoutput && !qh->FORCEoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    exitcode= qh_ERRnone;
    +  }
    +  qh->NOerrexit= True;  /* no more setjmp */
    +#ifdef qh_NOmem
    +  qh_freeqhull(qh, qh_ALL);
    +#else
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +    qh_fprintf_stderr(6263, "qhull internal warning (main): did not free %d bytes of long memory(%d pieces)\n",
    +       totlong, curlong);
    +#endif
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/rbox/rbox.c b/xs/src/qhull/src/rbox/rbox.c
    new file mode 100644
    index 0000000000..d7c51b1aaf
    --- /dev/null
    +++ b/xs/src/qhull/src/rbox/rbox.c
    @@ -0,0 +1,88 @@
    +/*
      ---------------------------------
    +
    +   rbox.c
    +     rbox program for generating input points for qhull.
    +
    +   notes:
    +     50 points generated for 'rbox D4'
    +
    +*/
    +
    +#include "libqhull/libqhull.h"
    +#include "libqhull/random.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#endif
    +
    +char prompt[]= "\n\
    +-rbox- generate various point distributions.  Default is random in cube.\n\
    +\n\
    +args (any order, space separated):                    Version: 2016/01/18\n\
    +  3000    number of random points in cube, lens, spiral, sphere or grid\n\
    +  D3      dimension 3-d\n\
    +  c       add a unit cube to the output ('c G2.0' sets size)\n\
    +  d       add a unit diamond to the output ('d G2.0' sets size)\n\
    +  l       generate a regular 3-d spiral\n\
    +  r       generate a regular polygon, ('r s Z1 G0.1' makes a cone)\n\
    +  s       generate cospherical points\n\
    +  x       generate random points in simplex, may use 'r' or 'Wn'\n\
    +  y       same as 'x', plus simplex\n\
    +  Cn,r,m  add n nearly coincident points within radius r of m points\n\
    +  Pn,m,r  add point [n,m,r] first, pads with 0, maybe repeated\n\
    +\n\
    +  Ln      lens distribution of radius n.  Also 's', 'r', 'G', 'W'.\n\
    +  Mn,m,r  lattice(Mesh) rotated by [n,-m,0], [m,n,0], [0,0,r], ...\n\
    +          '27 M1,0,1' is {0,1,2} x {0,1,2} x {0,1,2}.  Try 'M3,4 z'.\n\
    +  W0.1    random distribution within 0.1 of the cube's or sphere's surface\n\
    +  Z0.5 s  random points in a 0.5 disk projected to a sphere\n\
    +  Z0.5 s G0.6 same as Z0.5 within a 0.6 gap\n\
    +\n\
    +  Bn      bounding box coordinates, default %2.2g\n\
    +  h       output as homogeneous coordinates for cdd\n\
    +  n       remove command line from the first line of output\n\
    +  On      offset coordinates by n\n\
    +  t       use time as the random number seed(default is command line)\n\
    +  tn      use n as the random number seed\n\
    +  z       print integer coordinates, default 'Bn' is %2.2g\n\
    +";
    +
    +/*--------------------------------------------
    +-rbox-  main procedure of rbox application
    +*/
    +int main(int argc, char **argv) {
    +  char *command;
    +  int command_size;
    +  int return_status;
    +
    +  QHULL_LIB_CHECK_RBOX
    +
    +  if (argc == 1) {
    +    printf(prompt, qh_DEFAULTbox, qh_DEFAULTzbox);
    +    return 1;
    +  }
    +  if (argc == 2 && strcmp(argv[1], "D4")==0)
    +    qh_fprintf_stderr(0, "\nStarting the rbox smoketest for qhull.  An immediate failure indicates\nthat non-reentrant rbox was linked to reentrant routines.  An immediate\nfailure of qhull may indicate that qhull was linked to the wrong\nqhull library.  Also try 'rbox D4 | qhull T1'\n");
    +
    +  command_size= qh_argv_to_command_size(argc, argv);
    +  if ((command= (char *)qh_malloc((size_t)command_size))) {
    +    if (!qh_argv_to_command(argc, argv, command, command_size)) {
    +      qh_fprintf_stderr(6264, "rbox internal error: allocated insufficient memory (%d) for arguments\n", command_size);
    +      return_status= qh_ERRinput;
    +    }else{
    +      return_status= qh_rboxpoints(stdout, stderr, command);
    +    }
    +    qh_free(command);
    +  }else {
    +    qh_fprintf_stderr(6265, "rbox error: insufficient memory for %d bytes\n", command_size);
    +    return_status= qh_ERRmem;
    +  }
    +  return return_status;
    +}/*main*/
    +
    diff --git a/xs/src/qhull/src/rbox/rbox.pro b/xs/src/qhull/src/rbox/rbox.pro
    new file mode 100644
    index 0000000000..6c21bdb6df
    --- /dev/null
    +++ b/xs/src/qhull/src/rbox/rbox.pro
    @@ -0,0 +1,9 @@
    +# -------------------------------------------------
    +# rbox.pro -- Qt project for rbox.exe with libqhullstatic
    +# -------------------------------------------------
    +
    +include(../qhull-app-c.pri)
    +
    +TARGET = rbox
    +
    +SOURCES += rbox.c
    diff --git a/xs/src/qhull/src/rbox/rbox_r.c b/xs/src/qhull/src/rbox/rbox_r.c
    new file mode 100644
    index 0000000000..6ec74d914b
    --- /dev/null
    +++ b/xs/src/qhull/src/rbox/rbox_r.c
    @@ -0,0 +1,78 @@
    +
    +/*
      ---------------------------------
    +
    +   rbox.c
    +     rbox program for generating input points for qhull.
    +
    +   notes:
    +     50 points generated for 'rbox D4'
    +
    +*/
    +
    +#include "libqhull_r/libqhull_r.h"
    +#include "libqhull/random_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#ifdef _MSC_VER  /* Microsoft Visual C++ -- warning level 4 */
    +#pragma warning( disable : 4706)  /* assignment within conditional function */
    +#endif
    +
    +char prompt[]= "\n\
    +-rbox- generate various point distributions.  Default is random in cube.\n\
    +\n\
    +args (any order, space separated):                    Version: 2016/01/18 r\n\
    +  3000    number of random points in cube, lens, spiral, sphere or grid\n\
    +  D3      dimension 3-d\n\
    +  c       add a unit cube to the output ('c G2.0' sets size)\n\
    +  d       add a unit diamond to the output ('d G2.0' sets size)\n\
    +  l       generate a regular 3-d spiral\n\
    +  r       generate a regular polygon, ('r s Z1 G0.1' makes a cone)\n\
    +  s       generate cospherical points\n\
    +  x       generate random points in simplex, may use 'r' or 'Wn'\n\
    +  y       same as 'x', plus simplex\n\
    +  Cn,r,m  add n nearly coincident points within radius r of m points\n\
    +  Pn,m,r  add point [n,m,r] first, pads with 0, maybe repeated\n\
    +\n\
    +  Ln      lens distribution of radius n.  Also 's', 'r', 'G', 'W'.\n\
    +  Mn,m,r  lattice(Mesh) rotated by [n,-m,0], [m,n,0], [0,0,r], ...\n\
    +          '27 M1,0,1' is {0,1,2} x {0,1,2} x {0,1,2}.  Try 'M3,4 z'.\n\
    +  W0.1    random distribution within 0.1 of the cube's or sphere's surface\n\
    +  Z0.5 s  random points in a 0.5 disk projected to a sphere\n\
    +  Z0.5 s G0.6 same as Z0.5 within a 0.6 gap\n\
    +\n\
    +  Bn      bounding box coordinates, default %2.2g\n\
    +  h       output as homogeneous coordinates for cdd\n\
    +  n       remove command line from the first line of output\n\
    +  On      offset coordinates by n\n\
    +  t       use time as the random number seed(default is command line)\n\
    +  tn      use n as the random number seed\n\
    +  z       print integer coordinates, default 'Bn' is %2.2g\n\
    +";
    +
    +/*--------------------------------------------
    +-rbox-  main procedure of rbox application
    +*/
    +int main(int argc, char **argv) {
    +  int return_status;
    +  qhT qh_qh;
    +  qhT *qh= &qh_qh;
    +
    +  QHULL_LIB_CHECK_RBOX
    +
    +  if (argc == 1) {
    +    printf(prompt, qh_DEFAULTbox, qh_DEFAULTzbox);
    +    return 1;
    +  }
    +  if (argc == 2 && strcmp(argv[1], "D4")==0)
    +    qh_fprintf_stderr(0, "\nStarting the rbox smoketest for qhull.  An immediate failure indicates\nthat reentrant rbox was linked to non-reentrant routines.  An immediate\nfailure of qhull may indicate that qhull was linked to the wrong\nqhull library.  Also try 'rbox D4 | qhull T1'\n");
    +
    +  qh_init_A(qh, stdin, stdout, stderr, argc, argv);  /*no qh_errexit, sets qh->qhull_command */
    +  return_status= qh_rboxpoints(qh, qh->qhull_command); /* Traps its own errors, qh_errexit_rbox() */
    +  return return_status;
    +}/*main*/
    +
    diff --git a/xs/src/qhull/src/testqset/testqset.c b/xs/src/qhull/src/testqset/testqset.c
    new file mode 100644
    index 0000000000..61057eef9c
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset/testqset.c
    @@ -0,0 +1,891 @@
    +/*
      ---------------------------------
    +
    +   testset.c -- test qset.c and its use of mem.c
    +
    +   The test sets are pointers to int.  Normally a set is a pointer to a type (e.g., facetT, ridgeT, etc.).
    +   For consistency in notation, an "int" is typedef'd to i2T
    +
    +Functions and macros from qset.h.  Counts occurrences in this test.  Does not correspond to thoroughness.
    +    qh_setaddsorted -- 4 tests
    +    qh_setaddnth -- 1 test
    +    qh_setappend -- 7 tests
    +    qh_setappend_set -- 1 test
    +    qh_setappend2ndlast -- 1 test
    +    qh_setcheck -- lots of tests
    +    qh_setcompact -- 7 tests
    +    qh_setcopy -- 3 tests
    +    qh_setdel -- 1 tests
    +    qh_setdellast -- 1 tests
    +    qh_setdelnth -- 2 tests
    +    qh_setdelnthsorted -- 2 tests
    +    qh_setdelsorted -- 1 test
    +    qh_setduplicate -- not testable here
    +    qh_setequal -- 4 tests
    +    qh_setequal_except -- 2 tests
    +    qh_setequal_skip -- 2 tests
    +    qh_setfree -- 11+ tests
    +    qh_setfree2 -- not testable here
    +    qh_setfreelong -- 2 tests
    +    qh_setin -- 3 tests
    +    qh_setindex -- 4 tests
    +    qh_setlarger -- 1 test
    +    qh_setlast -- 2 tests
    +    qh_setnew -- 6 tests
    +    qh_setnew_delnthsorted
    +    qh_setprint -- tested elsewhere
    +    qh_setreplace -- 1 test
    +    qh_setsize -- 9+ tests
    +    qh_settemp -- 2 tests
    +    qh_settempfree -- 1 test
    +    qh_settempfree_all -- 1 test
    +    qh_settemppop -- 1 test
    +    qh_settemppush -- 1 test
    +    qh_settruncate -- 3 tests
    +    qh_setunique -- 3 tests
    +    qh_setzero -- 1 test
    +    FOREACHint_ -- 2 test
    +    FOREACHint4_
    +    FOREACHint_i_ -- 1 test
    +    FOREACHintreverse_
    +    FOREACHintreverse12_
    +    FOREACHsetelement_ -- 1 test
    +    FOREACHsetelement_i_ -- 1 test
    +    FOREACHsetelementreverse_ -- 1 test
    +    FOREACHsetelementreverse12_ -- 1 test
    +    SETelem_ -- 3 tests
    +    SETelemaddr_ -- 2 tests
    +    SETelemt_ -- not tested (generic)
    +    SETempty_ -- 1 test
    +    SETfirst_ -- 4 tests
    +    SETfirstt_ -- 2 tests
    +    SETindex_ -- 2 tests
    +    SETref_ -- 2 tests
    +    SETreturnsize_ -- 2 tests
    +    SETsecond_ -- 1 test
    +    SETsecondt_ -- 2 tests
    +    SETtruncate_ -- 2 tests
    +
    +    Copyright (c) 2012-2015 C.B. Barber. All rights reserved.
    +    $Id: //main/2015/qhull/src/testqset/testqset.c#4 $$Change: 2062 $
    +    $DateTime: 2016/01/17 13:13:18 $$Author: bbarber $
    +*/
    +
    +#include "libqhull/user.h"  /* QHULL_CRTDBG */
    +#include "libqhull/qset.h"
    +#include "libqhull/mem.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +typedef int i2T;
    +#define MAXerrorCount 100 /* quit after n errors */
    +
    +#define FOREACHint_( ints ) FOREACHsetelement_( i2T, ints, i2)
    +#define FOREACHint4_( ints ) FOREACHsetelement_( i2T, ints, i4)
    +#define FOREACHint_i_( ints ) FOREACHsetelement_i_( i2T, ints, i2)
    +#define FOREACHintreverse_( ints ) FOREACHsetelementreverse_( i2T, ints, i2)
    +#define FOREACHintreverse12_( ints ) FOREACHsetelementreverse12_( i2T, ints, i2)
    +
    +enum {
    +    MAXint= 0x7fffffff,
    +};
    +
    +char prompt[]= "testqset N [M] [T5] -- Test qset.c and mem.c\n\
    +  \n\
    +  If this test fails then qhull will not work.\n\
    +  \n\
    +  Test qsets of 0..N integers with a check every M iterations (default ~log10)\n\
    +  Additional checking and logging if M is 1\n\
    +  \n\
    +  T5 turns on memory logging (qset does not log)\n\
    +  \n\
    +  For example:\n\
    +    testqset 10000\n\
    +";
    +
    +int error_count= 0;  /* Global error_count.  checkSetContents() keeps its own error count.  It exits on too many errors */
    +
    +/* Macros normally defined in geom.h */
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/* Macros normally defined in user.h */
    +
    +#define realT double
    +#define qh_MEMalign ((int)(fmax_(sizeof(realT), sizeof(void *))))
    +#define qh_MEMbufsize 0x10000       /* allocate 64K memory buffers */
    +#define qh_MEMinitbuf 0x20000      /* initially allocate 128K buffer */
    +
    +/* Macros normally defined in QhullSet.h */
    +
    +
    +/* Functions normally defined in user.h for usermem.c */
    +
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/* Normally defined in user.c */
    +
    +void    qh_errexit(int exitcode, void *f, void *r)
    +{
    +    (void)f; /* unused */
    +    (void)r; /* unused */
    +    qh_exit(exitcode);
    +}
    +
    +/* Normally defined in userprintf.c */
    +
    +void    qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... )
    +{
    +    static int needs_cr= 0;  /* True if qh_fprintf needs a CR */
    +
    +    size_t fmtlen= strlen(fmt);
    +    va_list args;
    +
    +    if (!fp) {
    +        /* Do not use qh_fprintf_stderr.  This is a standalone program */
    +        fprintf(stderr, "QH6232 qh_fprintf: fp not defined for '%s'", fmt);
    +        qh_errexit(6232, NULL, NULL);
    +    }
    +    if(fmtlen>0){
    +        if(fmt[fmtlen-1]=='\n'){
    +            if(needs_cr && fmtlen>1){
    +                fprintf(fp, "\n");
    +            }
    +            needs_cr= 0;
    +        }else{
    +            needs_cr= 1;
    +        }
    +    }
    +    if(msgcode>=6000 && msgcode<7000){
    +        fprintf(fp, "Error TQ%d ", msgcode);
    +    }
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +}
    +
    +/* Defined below in order of use */
    +int main(int argc, char **argv);
    +void readOptions(int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel);
    +void setupMemory(int tracelevel, int numInts, int **intarray);
    +
    +void testSetappendSettruncate(int numInts, int *intarray, int checkEvery);
    +void testSetdelSetadd(int numInts, int *intarray, int checkEvery);
    +void testSetappendSet(int numInts, int *intarray, int checkEvery);
    +void testSetcompactCopy(int numInts, int *intarray, int checkEvery);
    +void testSetequalInEtc(int numInts, int *intarray, int checkEvery);
    +void testSettemp(int numInts, int *intarray, int checkEvery);
    +void testSetlastEtc(int numInts, int *intarray, int checkEvery);
    +void testSetdelsortedEtc(int numInts, int *intarray, int checkEvery);
    +
    +int log_i(setT *set, const char *s, int i, int numInts, int checkEvery);
    +void checkSetContents(const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC);
    +
    +int main(int argc, char **argv) {
    +    int *intarray= NULL;
    +    int numInts;
    +    int checkEvery= MAXint;
    +    int curlong, totlong;
    +    int traceLevel= 4; /* 4 normally, no tracing since qset does not log.  5 for memory tracing */
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user.h */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    readOptions(argc, argv, prompt, &numInts, &checkEvery, &traceLevel);
    +    setupMemory(traceLevel, numInts, &intarray);
    +
    +    testSetappendSettruncate(numInts, intarray, checkEvery);
    +    testSetdelSetadd(numInts, intarray, checkEvery);
    +    testSetappendSet(numInts, intarray, checkEvery);
    +    testSetcompactCopy(numInts, intarray, checkEvery);
    +    testSetequalInEtc(numInts, intarray, checkEvery);
    +    testSettemp(numInts, intarray, checkEvery);
    +    testSetlastEtc(numInts, intarray, checkEvery);
    +    testSetdelsortedEtc(numInts, intarray, checkEvery);
    +    printf("\n\nNot testing qh_setduplicate and qh_setfree2.\n  These routines use heap-allocated set contents.  See qhull tests.\n");
    +
    +    qh_memstatistics(stdout);
    +    qh_memfreeshort(&curlong, &totlong);
    +    if (curlong || totlong){
    +        qh_fprintf(stderr, 8043, "qh_memfreeshort: did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +        error_count++;
    +    }
    +    if(error_count){
    +        qh_fprintf(stderr, 8012, "testqset: %d errors\n\n", error_count);
    +        exit(1);
    +    }else{
    +        printf("testqset: OK\n\n");
    +    }
    +    return 0;
    +}/*main*/
    +
    +void readOptions(int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel)
    +{
    +    long numIntsArg;
    +    long checkEveryArg;
    +    char *endp;
    +    int isTracing= 0;
    +
    +    if (argc < 2 || argc > 4) {
    +        printf("%s", promptstr);
    +        exit(0);
    +    }
    +    numIntsArg= strtol(argv[1], &endp, 10);
    +    if(numIntsArg<1){
    +        qh_fprintf(stderr, 6301, "First argument should be 1 or greater.  Got '%s'\n", argv[1]);
    +        exit(1);
    +    }
    +    if(numIntsArg>MAXint){
    +        qh_fprintf(stderr, 6302, "qset does not currently support 64-bit ints.  Maximum count is %d\n", MAXint);
    +        exit(1);
    +    }
    +    *numInts= (int)numIntsArg;
    +
    +    if(argc==3 && argv[2][0]=='T' && argv[2][1]=='5' ){
    +        isTracing= 1;
    +        *traceLevel= 5;
    +    }
    +    if(argc==4 || (argc==3 && !isTracing)){
    +        checkEveryArg= strtol(argv[2], &endp, 10);
    +        if(checkEveryArg<1){
    +            qh_fprintf(stderr, 6321, "checkEvery argument should be 1 or greater.  Got '%s'\n", argv[2]);
    +            exit(1);
    +        }
    +        if(checkEveryArg>MAXint){
    +            qh_fprintf(stderr, 6322, "qset does not currently support 64-bit ints.  Maximum checkEvery is %d\n", MAXint);
    +            exit(1);
    +        }
    +        if(argc==4){
    +            if(argv[3][0]=='T' && argv[3][1]=='5' ){
    +                isTracing= 1;
    +                *traceLevel= 5;
    +            }else{
    +                qh_fprintf(stderr, 6242, "Optional third argument must be 'T5'.  Got '%s'\n", argv[3]);
    +                exit(1);
    +            }
    +        }
    +        *checkEvery= (int)checkEveryArg;
    +    }
    +}/*readOptions*/
    +
    +void setupMemory(int tracelevel, int numInts, int **intarray)
    +{
    +    int i;
    +    if(numInts<0 || numInts*(int)sizeof(int)<0){
    +        qh_fprintf(stderr, 6303, "qset does not currently support 64-bit ints.  Integer overflow\n");
    +        exit(1);
    +    }
    +    *intarray= qh_malloc(numInts * sizeof(int));
    +    if(!*intarray){
    +        qh_fprintf(stderr, 6304, "Failed to allocate %d bytes of memory\n", numInts * sizeof(int));
    +        exit(1);
    +    }
    +    for(i= 0; i=2){
    +        isCheck= log_i(ints, "n", numInts/2, numInts, checkEvery);
    +        qh_settruncate(ints, numInts/2);
    +        checkSetContents("qh_settruncate by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(ints, "n", 0, numInts, checkEvery);
    +    qh_settruncate(ints, 0);
    +    checkSetContents("qh_settruncate", ints, 0, -1, -1, -1);
    +
    +    qh_fprintf(stderr, 8003, "\n\nTesting qh_setappend2ndlast 0,0..%d.  Test 0", numInts-1);
    +    qh_setfree(&ints);
    +    ints= qh_setnew(4);
    +    qh_setappend(&ints, intarray+0);
    +    for(i= 0; i=2){
    +        isCheck= log_i(ints, "n", numInts/2, numInts, checkEvery);
    +        SETtruncate_(ints, numInts/2);
    +        checkSetContents("SETtruncate_ by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(ints, "n", 0, numInts, checkEvery);
    +    SETtruncate_(ints, 0);
    +    checkSetContents("SETtruncate_", ints, 0, -1, -1, -1);
    +
    +    qh_setfree(&ints);
    +}/*testSetappendSettruncate*/
    +
    +void testSetdelSetadd(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints=qh_setnew(1);
    +    int i,j,isCheck;
    +
    +    qh_fprintf(stderr, 8003, "\n\nTesting qh_setdelnthsorted and qh_setaddnth 1..%d. Test", numInts-1);
    +    for(j=1; j3){
    +                qh_setdelsorted(ints, intarray+i/2);
    +                checkSetContents("qh_setdelsorted", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(&ints, intarray+i/2);
    +                checkSetContents("qh_setaddsorted i/2", ints, j, 0, 0, -1);
    +            }
    +            qh_setdellast(ints);
    +            checkSetContents("qh_setdellast", ints, (j ? j-1 : 0), 0, -1, -1);
    +            if(j>0){
    +                qh_setaddsorted(&ints, intarray+j-1);
    +                checkSetContents("qh_setaddsorted j-1", ints, j, 0, -1, -1);
    +            }
    +            if(j>4){
    +                qh_setdelnthsorted(ints, i/2);
    +                if (checkEvery==1)
    +                  checkSetContents("qh_setdelnthsorted", ints, j-1, 0, i/2+1, -1);
    +                /* test qh_setdelnth and move-to-front */
    +                qh_setdelsorted(ints, intarray+i/2+1);
    +                checkSetContents("qh_setdelsorted 2", ints, j-2, 0, i/2+2, -1);
    +                qh_setaddsorted(&ints, intarray+i/2+1);
    +                if (checkEvery==1)
    +                  checkSetContents("qh_setaddsorted i/2+1", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(&ints, intarray+i/2);
    +                checkSetContents("qh_setaddsorted i/2 again", ints, j, 0, -1, -1);
    +            }
    +            qh_setfree(&ints2);
    +            ints2= qh_setcopy(ints, 0);
    +            qh_setcompact(ints);
    +            qh_setcompact(ints2);
    +            checkSetContents("qh_setcompact", ints, j, 0, 0, -1);
    +            checkSetContents("qh_setcompact 2", ints2, j, 0, 0, -1);
    +            qh_setcompact(ints);
    +            checkSetContents("qh_setcompact 3", ints, j, 0, 0, -1);
    +            qh_setfree(&ints2);
    +        }
    +    }
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSetdelsortedEtc*/
    +
    +void testSetequalInEtc(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j,n;
    +
    +    qh_fprintf(stderr, 8019, "\n\nTesting qh_setequal*, qh_setin*, qh_setdel, qh_setdelnth, and qh_setlarger 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(qh_setequal(ints, ints2)){
    +                    qh_fprintf(stderr, 6324, "testSetequalInEtc: non-empty set equal to empty set\n", j);
    +                    error_count++;
    +                }
    +                qh_setfree(&ints3);
    +                ints3= qh_setcopy(ints, 0);
    +                checkSetContents("qh_setreplace", ints3, j, 0, -1, -1);
    +                qh_setreplace(ints3, intarray+j/2, intarray+j/2+1);
    +                if(j==1){
    +                    checkSetContents("qh_setreplace 2", ints3, j, j/2+1, -1, -1);
    +                }else if(j==2){
    +                    checkSetContents("qh_setreplace 3", ints3, j, 0, j/2+1, -1);
    +                }else{
    +                    checkSetContents("qh_setreplace 3", ints3, j, 0, j/2+1, j/2+1);
    +                }
    +                if(qh_setequal(ints, ints3)){
    +                    qh_fprintf(stderr, 6325, "testSetequalInEtc: modified set equal to original set at %d/2\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_except(ints, intarray+j/2, ints3, intarray+j/2+1)){
    +                    qh_fprintf(stderr, 6326, "qh_setequal_except: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(qh_setequal_except(ints, intarray+j/2, ints3, intarray)){
    +                    qh_fprintf(stderr, 6327, "qh_setequal_except: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_skip(ints, j/2, ints3, j/2)){
    +                    qh_fprintf(stderr, 6328, "qh_setequal_skip: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(j>2 && qh_setequal_skip(ints, j/2, ints3, 0)){
    +                    qh_fprintf(stderr, 6329, "qh_setequal_skip: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(intarray+j/2+1!=qh_setdel(ints3, intarray+j/2+1)){
    +                    qh_fprintf(stderr, 6330, "qh_setdel: failed to find added element\n", j);
    +                    error_count++;
    +                }
    +                checkSetContents("qh_setdel", ints3, j-1, 0, j-1, (j==1 ? -1 : j/2+1));  /* swaps last element with deleted element */
    +                if(j>3){
    +                    qh_setdelnth(ints3, j/2); /* Delete at the same location as the original replace, for only one out-of-order element */
    +                    checkSetContents("qh_setdelnth", ints3, j-2, 0, j-2, (j==2 ? -1 : j/2+1));
    +                }
    +                if(qh_setin(ints3, intarray+j/2)){
    +                    qh_fprintf(stderr, 6331, "qh_setin: found deleted element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+1)){
    +                    qh_fprintf(stderr, 6332, "qh_setin: did not find second element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+j-2)){
    +                    qh_fprintf(stderr, 6333, "qh_setin: did not find last element\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints2, intarray)){
    +                    qh_fprintf(stderr, 6334, "qh_setindex: found element in empty set\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints3, intarray+j/2)){
    +                    qh_fprintf(stderr, 6335, "qh_setindex: found deleted element in set\n");
    +                    error_count++;
    +                }
    +                if(0!=qh_setindex(ints, intarray)){
    +                    qh_fprintf(stderr, 6336, "qh_setindex: did not find first in set\n");
    +                    error_count++;
    +                }
    +                if(j-1!=qh_setindex(ints, intarray+j-1)){
    +                    qh_fprintf(stderr, 6337, "qh_setindex: did not find last in set\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_setfree(&ints2);
    +        }
    +    }
    +    qh_setfree(&ints3);
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSetequalInEtc*/
    +
    +
    +void testSetlastEtc(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    int i,j,prepend;
    +
    +    qh_fprintf(stderr, 8020, "\n\nTesting qh_setlast, qh_setnew_delnthsorted, qh_setunique, and qh_setzero 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(intarray+j-1!=qh_setlast(ints)){
    +                    qh_fprintf(stderr, 6338, "qh_setlast: wrong last element\n");
    +                    error_count++;
    +                }
    +                prepend= (j<100 ? j/4 : 0);
    +                ints2= qh_setnew_delnthsorted(ints, qh_setsize(ints), j/2, prepend);
    +                if(qh_setsize(ints2)!=j+prepend-1){
    +                    qh_fprintf(stderr, 6345, "qh_setnew_delnthsorted: Expecting %d elements, got %d\n", j+prepend-1, qh_setsize(ints2));
    +                    error_count++;
    +                }
    +                /* Define prepended elements.  Otherwise qh_setdelnthsorted may fail */
    +                for(i= 0; i2){
    +                    qh_setzero(ints2, j/2, j-1);  /* max size may be j-1 */
    +                    if(qh_setsize(ints2)!=j-1){
    +                        qh_fprintf(stderr, 6342, "qh_setzero: Expecting %d elements, got %d\n", j, qh_setsize(ints2));
    +                        error_count++;
    +                    }
    +                    qh_setcompact(ints2);
    +                    checkSetContents("qh_setzero", ints2, j/2, 0, -1, -1);
    +                }
    +            }
    +            qh_setfree(&ints2);
    +        }
    +    }
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSetlastEtc*/
    +
    +void testSettemp(int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j;
    +
    +    qh_fprintf(stderr, 8021, "\n\nTesting qh_settemp* 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                qh_settemppush(ints);
    +                ints3= qh_settemppop();
    +                if(ints!=ints3){
    +                    qh_fprintf(stderr, 6343, "qh_settemppop: didn't pop the push\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_settempfree(&ints2);
    +        }
    +    }
    +    qh_setfreelong(&ints);
    +    if(ints){
    +        qh_setfree(&ints); /* Was quick memory */
    +    }
    +}/*testSettemp*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +   Returns -1 if should check results
    +*/
    +int log_i(setT *set, const char *s, int i, int numInts, int checkEvery)
    +{
    +    int j= i;
    +    int scale= 1;
    +    int e= 0;
    +    int *i2, **i2p;
    +
    +    if(*s || checkEvery==1){
    +        if(i<10){
    +            qh_fprintf(stderr, 8004, " %s%d", s, i);
    +        }else{
    +            if(i==11 && checkEvery==1){
    +                qh_fprintf(stderr, 8005, "\nResults after 10: ");
    +                FOREACHint_(set){
    +                    qh_fprintf(stderr, 8006, " %d", *i2);
    +                }
    +                qh_fprintf(stderr, 8007, " Continue");
    +            }
    +            while((j= j/10)>=1){
    +                scale *= 10;
    +                e++;
    +            }
    +            if(i==numInts-1){
    +                qh_fprintf(stderr, 8008, " %s%d", s, i);
    +            }else if(i==scale){
    +                if(i<=1000){
    +                    qh_fprintf(stderr, 8010, " %s%d", s, i);
    +                }else{
    +                    qh_fprintf(stderr, 8009, " %s1e%d", s, e);
    +                }
    +            }
    +        }
    +    }
    +    if(i<1000 || i%checkEvery==0 || i== scale || i==numInts-1){
    +        return 1;
    +    }
    +    return 0;
    +}/*log_i*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +*/
    +void checkSetContents(const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC)
    +{
    +
    +    i2T *i2, **i2p;
    +    int i2_i, i2_n;
    +    int prev= -1; /* avoid warning */
    +    int i;
    +    int first= -3;
    +    int second= -3;
    +    int rangeCount=1;
    +    int actualSize= 0;
    +
    +    qh_setcheck(set, name, 0);
    +    if(set){
    +        SETreturnsize_(set, actualSize);  /* normally used only when speed is critical */
    +        if(*qh_setendpointer(set)!=NULL){
    +            qh_fprintf(stderr, 6344, "%s: qh_setendpointer(), 0x%x, is not NULL terminator of set 0x%x", name, qh_setendpointer(set), set);
    +            error_count++;
    +        }
    +    }
    +    if(actualSize!=qh_setsize(set)){
    +        qh_fprintf(stderr, 6305, "%s: SETreturnsize_() returned %d while qh_setsize() returns %d\n", name, actualSize, qh_setsize(set));
    +        error_count++;
    +    }else if(actualSize!=count){
    +        qh_fprintf(stderr, 6306, "%s: Expecting %d elements for set.  Got %d elements\n", name, count, actualSize);
    +        error_count++;
    +    }
    +    if(SETempty_(set)){
    +        if(count!=0){
    +            qh_fprintf(stderr, 6307, "%s: Got empty set instead of count %d, rangeA %d, rangeB %d, rangeC %d\n", name, count, rangeA, rangeB, rangeC);
    +            error_count++;
    +        }
    +    }else{
    +        /* Must be first, otherwise trips msvc 8 */
    +        i2T **p= SETaddr_(set, i2T);
    +        if(*p!=SETfirstt_(set, i2T)){
    +            qh_fprintf(stderr, 6309, "%s: SETaddr_(set, i2t) [%p] is not the same as SETfirst_(set) [%p]\n", name, SETaddr_(set, i2T), SETfirst_(set));
    +            error_count++;
    +        }
    +        first= *(int *)SETfirst_(set);
    +        if(SETfirst_(set)!=SETfirstt_(set, i2T)){
    +            qh_fprintf(stderr, 6308, "%s: SETfirst_(set) [%p] is not the same as SETfirstt_(set, i2T [%p]\n", name, SETfirst_(set), SETfirstt_(set, i2T));
    +            error_count++;
    +        }
    +        if(qh_setsize(set)>1){
    +            second= *(int *)SETsecond_(set);
    +            if(SETsecond_(set)!=SETsecondt_(set, i2T)){
    +                qh_fprintf(stderr, 6310, "%s: SETsecond_(set) [%p] is not the same as SETsecondt_(set, i2T) [%p]\n", name, SETsecond_(set), SETsecondt_(set, i2T));
    +                error_count++;
    +            }
    +        }
    +    }
    +    /* Test first run of ints in set*/
    +    i= 0;
    +    FOREACHint_(set){
    +        if(i2!=SETfirst_(set) && *i2!=prev+1){
    +            break;
    +        }
    +        prev= *i2;
    +        if(SETindex_(set, i2)!=i){
    +            qh_fprintf(stderr, 6311, "%s: Expecting SETIndex_(set, pointer-to-%d) to be %d.  Got %d\n", name, *i2, i, SETindex_(set, i2));
    +            error_count++;;
    +        }
    +        if(i2!=SETref_(i2)){
    +            qh_fprintf(stderr, 6312, "%s: SETref_(i2) [%p] does not point to i2 (the %d'th element)\n", name, SETref_(i2), i);
    +            error_count++;;
    +        }
    +        i++;
    +    }
    +    FOREACHint_i_(set){
    +        /* Must be first conditional, otherwise it trips up msvc 8 */
    +        i2T **p= SETelemaddr_(set, i2_i, i2T);
    +        if(i2!=*p){
    +            qh_fprintf(stderr, 6320, "%s: SETelemaddr_(set, %d, i2T) [%p] does not point to i2\n", name, i2_i, SETelemaddr_(set, i2_i, int));
    +            error_count++;;
    +        }
    +        if(i2_i==0){
    +            if(first!=*i2){
    +                qh_fprintf(stderr, 6314, "%s: First element is %d instead of SETfirst %d\n", name, *i2, first);
    +                error_count++;;
    +            }
    +            if(rangeA!=*i2){
    +                qh_fprintf(stderr, 6315, "%s: starts with %d instead of rangeA %d\n", name, *i2, rangeA);
    +                error_count++;;
    +            }
    +            prev= rangeA;
    +        }else{
    +            if(i2_i==1 && second!=*i2){
    +                qh_fprintf(stderr, 6316, "%s: Second element is %d instead of SETsecond %d\n", name, *i2, second);
    +                error_count++;;
    +            }
    +            if(prev+1==*i2){
    +                prev++;
    +            }else{
    +                if(*i2==rangeB){
    +                    prev= rangeB;
    +                    rangeB= -1;
    +                    rangeCount++;
    +                }else if(rangeB==-1 && *i2==rangeC){
    +                    prev= rangeC;
    +                    rangeC= -1;
    +                    rangeCount++;
    +                }else{
    +                    prev++;
    +                    qh_fprintf(stderr, 6317, "%s: Expecting %d'th element to be %d.  Got %d\n", name, i2_i, prev, *i2);
    +                    error_count++;
    +                }
    +            }
    +        }
    +        if(i2!=SETelem_(set, i2_i)){
    +            qh_fprintf(stderr, 6318, "%s: SETelem_(set, %d) [%p] is not i2 [%p] (the %d'th element)\n", name, i2_i, SETelem_(set, i2_i), i2, i2_i);
    +            error_count++;;
    +        }
    +        if(SETelemt_(set, i2_i, i2T)!=SETelem_(set, i2_i)){   /* Normally SETelemt_ is used for generic sets */
    +            qh_fprintf(stderr, 6319, "%s: SETelemt_(set, %d, i2T) [%p] is not SETelem_(set, %d) [%p] (the %d'th element)\n", name, i2_i, SETelemt_(set, i2_i, int), i2_i, SETelem_(set, i2_i), i2_i);
    +            error_count++;;
    +        }
    +    }
    +    if(error_count>=MAXerrorCount){
    +        qh_fprintf(stderr, 8011, "testqset: Stop testing after %d errors\n", error_count);
    +        exit(1);
    +    }
    +}/*checkSetContents*/
    +
    diff --git a/xs/src/qhull/src/testqset/testqset.pro b/xs/src/qhull/src/testqset/testqset.pro
    new file mode 100644
    index 0000000000..3f69048aac
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset/testqset.pro
    @@ -0,0 +1,30 @@
    +# -------------------------------------------------
    +# testqset.pro -- Qt project file for testqset.exe
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +TARGET = testqset
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +CONFIG += qhull_warn_conversion
    +
    +build_pass:CONFIG(debug, debug|release){
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   OBJECTS_DIR = Release
    +}
    +
    +INCLUDEPATH += ..
    +
    +SOURCES += testqset.c
    +SOURCES += ../libqhull/qset.c
    +SOURCES += ../libqhull/mem.c
    +SOURCES += ../libqhull/usermem.c
    +
    +HEADERS += ../libqhull/mem.h
    +HEADERS += ../libqhull/qset.h
    +
    diff --git a/xs/src/qhull/src/testqset_r/testqset_r.c b/xs/src/qhull/src/testqset_r/testqset_r.c
    new file mode 100644
    index 0000000000..9a6d496e40
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset_r/testqset_r.c
    @@ -0,0 +1,890 @@
    +/*
      ---------------------------------
    +
    +   testset.c -- test qset.c and its use of mem.c
    +
    +   The test sets are pointers to int.  Normally a set is a pointer to a type (e.g., facetT, ridgeT, etc.).
    +   For consistency in notation, an "int" is typedef'd to i2T
    +
    +Functions and macros from qset.h.  Counts occurrences in this test.  Does not correspond to thoroughness.
    +    qh_setaddsorted -- 4 tests
    +    qh_setaddnth -- 1 test
    +    qh_setappend -- 7 tests
    +    qh_setappend_set -- 1 test
    +    qh_setappend2ndlast -- 1 test
    +    qh_setcheck -- lots of tests
    +    qh_setcompact -- 7 tests
    +    qh_setcopy -- 3 tests
    +    qh_setdel -- 1 tests
    +    qh_setdellast -- 1 tests
    +    qh_setdelnth -- 2 tests
    +    qh_setdelnthsorted -- 2 tests
    +    qh_setdelsorted -- 1 test
    +    qh_setduplicate -- not testable here
    +    qh_setequal -- 4 tests
    +    qh_setequal_except -- 2 tests
    +    qh_setequal_skip -- 2 tests
    +    qh_setfree -- 11+ tests
    +    qh_setfree2 -- not testable here
    +    qh_setfreelong -- 2 tests
    +    qh_setin -- 3 tests
    +    qh_setindex -- 4 tests
    +    qh_setlarger -- 1 test
    +    qh_setlast -- 2 tests
    +    qh_setnew -- 6 tests
    +    qh_setnew_delnthsorted
    +    qh_setprint -- tested elsewhere
    +    qh_setreplace -- 1 test
    +    qh_setsize -- 9+ tests
    +    qh_settemp -- 2 tests
    +    qh_settempfree -- 1 test
    +    qh_settempfree_all -- 1 test
    +    qh_settemppop -- 1 test
    +    qh_settemppush -- 1 test
    +    qh_settruncate -- 3 tests
    +    qh_setunique -- 3 tests
    +    qh_setzero -- 1 test
    +    FOREACHint_ -- 2 test
    +    FOREACHint4_
    +    FOREACHint_i_ -- 1 test
    +    FOREACHintreverse_
    +    FOREACHintreverse12_
    +    FOREACHsetelement_ -- 1 test
    +    FOREACHsetelement_i_ -- 1 test
    +    FOREACHsetelementreverse_ -- 1 test
    +    FOREACHsetelementreverse12_ -- 1 test
    +    SETelem_ -- 3 tests
    +    SETelemaddr_ -- 2 tests
    +    SETelemt_ -- not tested (generic)
    +    SETempty_ -- 1 test
    +    SETfirst_ -- 4 tests
    +    SETfirstt_ -- 2 tests
    +    SETindex_ -- 2 tests
    +    SETref_ -- 2 tests
    +    SETreturnsize_ -- 2 tests
    +    SETsecond_ -- 1 test
    +    SETsecondt_ -- 2 tests
    +    SETtruncate_ -- 2 tests
    +
    +    Copyright (c) 2012-2015 C.B. Barber. All rights reserved.
    +    $Id: //main/2015/qhull/src/testqset_r/testqset_r.c#5 $$Change: 2064 $
    +    $DateTime: 2016/01/18 12:36:08 $$Author: bbarber $
    +*/
    +
    +#include "libqhull_r/user_r.h"  /* QHULL_CRTDBG */
    +#include "libqhull_r/qset_r.h"
    +#include "libqhull_r/mem_r.h"
    +#include "libqhull_r/libqhull_r.h"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +typedef int i2T;
    +#define MAXerrorCount 100 /* quit after n errors */
    +
    +#define FOREACHint_( ints ) FOREACHsetelement_( i2T, ints, i2)
    +#define FOREACHint4_( ints ) FOREACHsetelement_( i2T, ints, i4)
    +#define FOREACHint_i_( qh, ints ) FOREACHsetelement_i_( qh, i2T, ints, i2)
    +#define FOREACHintreverse_( qh, ints ) FOREACHsetelementreverse_( qh, i2T, ints, i2)
    +#define FOREACHintreverse12_( ints ) FOREACHsetelementreverse12_( i2T, ints, i2)
    +
    +enum {
    +    MAXint= 0x7fffffff,
    +};
    +
    +char prompt[]= "testqset_r N [M] [T5] -- Test reentrant qset_r.c and mem_r.c\n\
    +  \n\
    +  If this test fails then reentrant Qhull will not work.\n\
    +  \n\
    +  Test qsets of 0..N integers with a check every M iterations (default ~log10)\n\
    +  Additional checking and logging if M is 1\n\
    +  \n\
    +  T5 turns on memory logging (qset does not log)\n\
    +  \n\
    +  For example:\n\
    +    testqset_r 10000\n\
    +";
    +
    +int error_count= 0;  /* Global error_count.  checkSetContents(qh) keeps its own error count.  It exits on too many errors */
    +
    +/* Macros normally defined in geom.h */
    +#define fmax_( a,b )  ( ( a ) < ( b ) ? ( b ) : ( a ) )
    +
    +/* Macros normally defined in QhullSet.h */
    +
    +/* Functions normally defined in user_r.h for usermem_r.c */
    +
    +void    qh_exit(int exitcode);
    +void    qh_fprintf_stderr(int msgcode, const char *fmt, ... );
    +void    qh_free(void *mem);
    +void   *qh_malloc(size_t size);
    +
    +/* Normally defined in user_r.c */
    +
    +void    qh_errexit(qhT *qh, int exitcode, facetT *f, ridgeT *r)
    +{
    +    (void)f; /* unused */
    +    (void)r; /* unused */
    +    (void)qh; /* unused */
    +    qh_exit(exitcode);
    +}
    +
    +/* Normally defined in userprintf.c */
    +
    +void    qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... )
    +{
    +    static int needs_cr= 0;  /* True if qh_fprintf needs a CR. testqset_r is not itself reentrant */
    +
    +    size_t fmtlen= strlen(fmt);
    +    va_list args;
    +
    +    if (!fp) {
    +        /* Do not use qh_fprintf_stderr.  This is a standalone program */
    +        if(!qh)
    +            fprintf(stderr, "QH6241 qh_fprintf: fp and qh not defined for '%s'", fmt);
    +        else
    +            fprintf(stderr, "QH6232 qh_fprintf: fp is 0.  Was wrong qh_fprintf called for '%s'", fmt);
    +        qh_errexit(qh, 6232, NULL, NULL);
    +    }
    +    if(fmtlen>0){
    +        if(fmt[fmtlen-1]=='\n'){
    +            if(needs_cr && fmtlen>1){
    +                fprintf(fp, "\n");
    +            }
    +            needs_cr= 0;
    +        }else{
    +            needs_cr= 1;
    +        }
    +    }
    +    if(msgcode>=6000 && msgcode<7000){
    +        fprintf(fp, "Error TQ%d ", msgcode);
    +    }
    +    va_start(args, fmt);
    +    vfprintf(fp, fmt, args);
    +    va_end(args);
    +}
    +
    +/* Defined below in order of use */
    +int main(int argc, char **argv);
    +void readOptions(qhT *qh, int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel);
    +void setupMemory(qhT *qh, int tracelevel, int numInts, int **intarray);
    +
    +void testSetappendSettruncate(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetdelSetadd(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetappendSet(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetcompactCopy(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetequalInEtc(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSettemp(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetlastEtc(qhT *qh, int numInts, int *intarray, int checkEvery);
    +void testSetdelsortedEtc(qhT *qh, int numInts, int *intarray, int checkEvery);
    +
    +int log_i(qhT *qh, setT *set, const char *s, int i, int numInts, int checkEvery);
    +void checkSetContents(qhT *qh, const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC);
    +
    +int main(int argc, char **argv) {
    +    int *intarray= NULL;
    +    int numInts;
    +    int checkEvery= MAXint;
    +    int curlong, totlong;
    +    int traceLevel= 4; /* 4 normally, no tracing since qset does not log.  Option 'T5' for memory tracing */
    +    qhT qh_qh;
    +    qhT *qh= &qh_qh;
    +
    +#if defined(_MSC_VER) && defined(_DEBUG) && defined(QHULL_CRTDBG)  /* user_r.h */
    +    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) );
    +    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG );
    +    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR );
    +#endif
    +
    +    readOptions(qh, argc, argv, prompt, &numInts, &checkEvery, &traceLevel);
    +    setupMemory(qh, traceLevel, numInts, &intarray);
    +
    +    testSetappendSettruncate(qh, numInts, intarray, checkEvery);
    +    testSetdelSetadd(qh, numInts, intarray, checkEvery);
    +    testSetappendSet(qh, numInts, intarray, checkEvery);
    +    testSetcompactCopy(qh, numInts, intarray, checkEvery);
    +    testSetequalInEtc(qh, numInts, intarray, checkEvery);
    +    testSettemp(qh, numInts, intarray, checkEvery);
    +    testSetlastEtc(qh, numInts, intarray, checkEvery);
    +    testSetdelsortedEtc(qh, numInts, intarray, checkEvery);
    +    printf("\n\nNot testing qh_setduplicate and qh_setfree2.\n  These routines use heap-allocated set contents.  See qhull tests.\n");
    +
    +    qh_memstatistics(qh, stdout);
    +    qh_memfreeshort(qh, &curlong, &totlong);
    +    if (curlong || totlong){
    +        qh_fprintf(qh, stderr, 8043, "qh_memfreeshort: did not free %d bytes of long memory(%d pieces)\n", totlong, curlong);
    +        error_count++;
    +    }
    +    if(error_count){
    +        qh_fprintf(qh, stderr, 8012, "testqset: %d errors\n\n", error_count);
    +        exit(1);
    +    }else{
    +        printf("testqset_r: OK\n\n");
    +    }
    +    return 0;
    +}/*main*/
    +
    +void readOptions(qhT *qh, int argc, char **argv, const char *promptstr, int *numInts, int *checkEvery, int *traceLevel)
    +{
    +    long numIntsArg;
    +    long checkEveryArg;
    +    char *endp;
    +    int isTracing= 0;
    +
    +    if (argc < 2 || argc > 4) {
    +        printf("%s", promptstr);
    +        exit(0);
    +    }
    +    numIntsArg= strtol(argv[1], &endp, 10);
    +    if(numIntsArg<1){
    +        qh_fprintf(qh, stderr, 6301, "First argument should be 1 or greater.  Got '%s'\n", argv[1]);
    +        exit(1);
    +    }
    +    if(numIntsArg>MAXint){
    +        qh_fprintf(qh, stderr, 6302, "qset does not currently support 64-bit ints.  Maximum count is %d\n", MAXint);
    +        exit(1);
    +    }
    +    *numInts= (int)numIntsArg;
    +
    +    if(argc==3 && argv[2][0]=='T' && argv[2][1]=='5' ){
    +        isTracing= 1;
    +        *traceLevel= 5;
    +    }
    +    if(argc==4 || (argc==3 && !isTracing)){
    +        checkEveryArg= strtol(argv[2], &endp, 10);
    +        if(checkEveryArg<1){
    +            qh_fprintf(qh, stderr, 6321, "checkEvery argument should be 1 or greater.  Got '%s'\n", argv[2]);
    +            exit(1);
    +        }
    +        if(checkEveryArg>MAXint){
    +            qh_fprintf(qh, stderr, 6322, "qset does not currently support 64-bit ints.  Maximum checkEvery is %d\n", MAXint);
    +            exit(1);
    +        }
    +        if(argc==4){
    +            if(argv[3][0]=='T' && argv[3][1]=='5' ){
    +                isTracing= 1;
    +                *traceLevel= 5;
    +            }else{
    +                qh_fprintf(qh, stderr, 6242, "Optional third argument must be 'T5'.  Got '%s'\n", argv[3]);
    +                exit(1);
    +            }
    +        }
    +        *checkEvery= (int)checkEveryArg;
    +    }
    +}/*readOptions*/
    +
    +void setupMemory(qhT *qh, int tracelevel, int numInts, int **intarray)
    +{
    +    int i;
    +    if(numInts<0 || numInts*(int)sizeof(int)<0){
    +        qh_fprintf(qh, stderr, 6303, "qset does not currently support 64-bit ints.  Integer overflow\n");
    +        exit(1);
    +    }
    +    *intarray= qh_malloc(numInts * sizeof(int));
    +    if(!*intarray){
    +        qh_fprintf(qh, stderr, 6304, "Failed to allocate %d bytes of memory\n", numInts * sizeof(int));
    +        exit(1);
    +    }
    +    for(i= 0; i=2){
    +        isCheck= log_i(qh, ints, "n", numInts/2, numInts, checkEvery);
    +        qh_settruncate(qh, ints, numInts/2);
    +        checkSetContents(qh, "qh_settruncate by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(qh, ints, "n", 0, numInts, checkEvery);
    +    qh_settruncate(qh, ints, 0);
    +    checkSetContents(qh, "qh_settruncate", ints, 0, -1, -1, -1);
    +
    +    qh_fprintf(qh, stderr, 8003, "\n\nTesting qh_setappend2ndlast 0,0..%d.  Test 0", numInts-1);
    +    qh_setfree(qh, &ints);
    +    ints= qh_setnew(qh, 4);
    +    qh_setappend(qh, &ints, intarray+0);
    +    for(i= 0; i=2){
    +        isCheck= log_i(qh, ints, "n", numInts/2, numInts, checkEvery);
    +        SETtruncate_(ints, numInts/2);
    +        checkSetContents(qh, "SETtruncate_ by half", ints, numInts/2, 0, -1, -1);
    +    }
    +    isCheck= log_i(qh, ints, "n", 0, numInts, checkEvery);
    +    SETtruncate_(ints, 0);
    +    checkSetContents(qh, "SETtruncate_", ints, 0, -1, -1, -1);
    +
    +    qh_setfree(qh, &ints);
    +}/*testSetappendSettruncate*/
    +
    +void testSetdelSetadd(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints=qh_setnew(qh, 1);
    +    int i,j,isCheck;
    +
    +    qh_fprintf(qh, stderr, 8003, "\n\nTesting qh_setdelnthsorted and qh_setaddnth 1..%d. Test", numInts-1);
    +    for(j=1; j3){
    +                qh_setdelsorted(ints, intarray+i/2);
    +                checkSetContents(qh, "qh_setdelsorted", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(qh, &ints, intarray+i/2);
    +                checkSetContents(qh, "qh_setaddsorted i/2", ints, j, 0, 0, -1);
    +            }
    +            qh_setdellast(ints);
    +            checkSetContents(qh, "qh_setdellast", ints, (j ? j-1 : 0), 0, -1, -1);
    +            if(j>0){
    +                qh_setaddsorted(qh, &ints, intarray+j-1);
    +                checkSetContents(qh, "qh_setaddsorted j-1", ints, j, 0, -1, -1);
    +            }
    +            if(j>4){
    +                qh_setdelnthsorted(qh, ints, i/2);
    +                if (checkEvery==1)
    +                  checkSetContents(qh, "qh_setdelnthsorted", ints, j-1, 0, i/2+1, -1);
    +                /* test qh_setdelnth and move-to-front */
    +                qh_setdelsorted(ints, intarray+i/2+1);
    +                checkSetContents(qh, "qh_setdelsorted 2", ints, j-2, 0, i/2+2, -1);
    +                qh_setaddsorted(qh, &ints, intarray+i/2+1);
    +                if (checkEvery==1)
    +                  checkSetContents(qh, "qh_setaddsorted i/2+1", ints, j-1, 0, i/2+1, -1);
    +                qh_setaddsorted(qh, &ints, intarray+i/2);
    +                checkSetContents(qh, "qh_setaddsorted i/2 again", ints, j, 0, -1, -1);
    +            }
    +            qh_setfree(qh, &ints2);
    +            ints2= qh_setcopy(qh, ints, 0);
    +            qh_setcompact(qh, ints);
    +            qh_setcompact(qh, ints2);
    +            checkSetContents(qh, "qh_setcompact", ints, j, 0, 0, -1);
    +            checkSetContents(qh, "qh_setcompact 2", ints2, j, 0, 0, -1);
    +            qh_setcompact(qh, ints);
    +            checkSetContents(qh, "qh_setcompact 3", ints, j, 0, 0, -1);
    +            qh_setfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSetdelsortedEtc*/
    +
    +void testSetequalInEtc(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j,n;
    +
    +    qh_fprintf(qh, stderr, 8019, "\n\nTesting qh_setequal*, qh_setin*, qh_setdel, qh_setdelnth, and qh_setlarger 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(qh_setequal(ints, ints2)){
    +                    qh_fprintf(qh, stderr, 6324, "testSetequalInEtc: non-empty set equal to empty set\n", j);
    +                    error_count++;
    +                }
    +                qh_setfree(qh, &ints3);
    +                ints3= qh_setcopy(qh, ints, 0);
    +                checkSetContents(qh, "qh_setreplace", ints3, j, 0, -1, -1);
    +                qh_setreplace(qh, ints3, intarray+j/2, intarray+j/2+1);
    +                if(j==1){
    +                    checkSetContents(qh, "qh_setreplace 2", ints3, j, j/2+1, -1, -1);
    +                }else if(j==2){
    +                    checkSetContents(qh, "qh_setreplace 3", ints3, j, 0, j/2+1, -1);
    +                }else{
    +                    checkSetContents(qh, "qh_setreplace 3", ints3, j, 0, j/2+1, j/2+1);
    +                }
    +                if(qh_setequal(ints, ints3)){
    +                    qh_fprintf(qh, stderr, 6325, "testSetequalInEtc: modified set equal to original set at %d/2\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_except(ints, intarray+j/2, ints3, intarray+j/2+1)){
    +                    qh_fprintf(qh, stderr, 6326, "qh_setequal_except: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(qh_setequal_except(ints, intarray+j/2, ints3, intarray)){
    +                    qh_fprintf(qh, stderr, 6327, "qh_setequal_except: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(!qh_setequal_skip(ints, j/2, ints3, j/2)){
    +                    qh_fprintf(qh, stderr, 6328, "qh_setequal_skip: modified set not equal to original set except modified\n", j);
    +                    error_count++;
    +                }
    +                if(j>2 && qh_setequal_skip(ints, j/2, ints3, 0)){
    +                    qh_fprintf(qh, stderr, 6329, "qh_setequal_skip: modified set equal to original set with wrong excepts\n", j);
    +                    error_count++;
    +                }
    +                if(intarray+j/2+1!=qh_setdel(ints3, intarray+j/2+1)){
    +                    qh_fprintf(qh, stderr, 6330, "qh_setdel: failed to find added element\n", j);
    +                    error_count++;
    +                }
    +                checkSetContents(qh, "qh_setdel", ints3, j-1, 0, j-1, (j==1 ? -1 : j/2+1));  /* swaps last element with deleted element */
    +                if(j>3){
    +                    qh_setdelnth(qh, ints3, j/2); /* Delete at the same location as the original replace, for only one out-of-order element */
    +                    checkSetContents(qh, "qh_setdelnth", ints3, j-2, 0, j-2, (j==2 ? -1 : j/2+1));
    +                }
    +                if(qh_setin(ints3, intarray+j/2)){
    +                    qh_fprintf(qh, stderr, 6331, "qh_setin: found deleted element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+1)){
    +                    qh_fprintf(qh, stderr, 6332, "qh_setin: did not find second element\n");
    +                    error_count++;
    +                }
    +                if(j>4 && !qh_setin(ints3, intarray+j-2)){
    +                    qh_fprintf(qh, stderr, 6333, "qh_setin: did not find last element\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints2, intarray)){
    +                    qh_fprintf(qh, stderr, 6334, "qh_setindex: found element in empty set\n");
    +                    error_count++;
    +                }
    +                if(-1!=qh_setindex(ints3, intarray+j/2)){
    +                    qh_fprintf(qh, stderr, 6335, "qh_setindex: found deleted element in set\n");
    +                    error_count++;
    +                }
    +                if(0!=qh_setindex(ints, intarray)){
    +                    qh_fprintf(qh, stderr, 6336, "qh_setindex: did not find first in set\n");
    +                    error_count++;
    +                }
    +                if(j-1!=qh_setindex(ints, intarray+j-1)){
    +                    qh_fprintf(qh, stderr, 6337, "qh_setindex: did not find last in set\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_setfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfree(qh, &ints3);
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSetequalInEtc*/
    +
    +
    +void testSetlastEtc(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    int i,j,prepend;
    +
    +    qh_fprintf(qh, stderr, 8020, "\n\nTesting qh_setlast, qh_setnew_delnthsorted, qh_setunique, and qh_setzero 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                if(intarray+j-1!=qh_setlast(ints)){
    +                    qh_fprintf(qh, stderr, 6338, "qh_setlast: wrong last element\n");
    +                    error_count++;
    +                }
    +                prepend= (j<100 ? j/4 : 0);
    +                ints2= qh_setnew_delnthsorted(qh, ints, qh_setsize(qh, ints), j/2, prepend);
    +                if(qh_setsize(qh, ints2)!=j+prepend-1){
    +                    qh_fprintf(qh, stderr, 6345, "qh_setnew_delnthsorted: Expecting %d elements, got %d\n", j+prepend-1, qh_setsize(qh, ints2));
    +                    error_count++;
    +                }
    +                /* Define prepended elements.  Otherwise qh_setdelnthsorted may fail */
    +                for(i= 0; i2){
    +                    qh_setzero(qh, ints2, j/2, j-1);  /* max size may be j-1 */
    +                    if(qh_setsize(qh, ints2)!=j-1){
    +                        qh_fprintf(qh, stderr, 6342, "qh_setzero: Expecting %d elements, got %d\n", j, qh_setsize(qh, ints2));
    +                        error_count++;
    +                    }
    +                    qh_setcompact(qh, ints2);
    +                    checkSetContents(qh, "qh_setzero", ints2, j/2, 0, -1, -1);
    +                }
    +            }
    +            qh_setfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSetlastEtc*/
    +
    +void testSettemp(qhT *qh, int numInts, int *intarray, int checkEvery)
    +{
    +    setT *ints= NULL;
    +    setT *ints2= NULL;
    +    setT *ints3= NULL;
    +    int i,j;
    +
    +    qh_fprintf(qh, stderr, 8021, "\n\nTesting qh_settemp* 0..%d. Test", numInts-1);
    +    for(j=0; j0){
    +                qh_settemppush(qh, ints);
    +                ints3= qh_settemppop(qh);
    +                if(ints!=ints3){
    +                    qh_fprintf(qh, stderr, 6343, "qh_settemppop: didn't pop the push\n");
    +                    error_count++;
    +                }
    +            }
    +            qh_settempfree(qh, &ints2);
    +        }
    +    }
    +    qh_setfreelong(qh, &ints);
    +    if(ints){
    +        qh_setfree(qh, &ints); /* Was quick memory */
    +    }
    +}/*testSettemp*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +   Returns -1 if should check results
    +*/
    +int log_i(qhT *qh, setT *set, const char *s, int i, int numInts, int checkEvery)
    +{
    +    int j= i;
    +    int scale= 1;
    +    int e= 0;
    +    int *i2, **i2p;
    +
    +    if(*s || checkEvery==1){
    +        if(i<10){
    +            qh_fprintf(qh, stderr, 8004, " %s%d", s, i);
    +        }else{
    +            if(i==11 && checkEvery==1){
    +                qh_fprintf(qh, stderr, 8005, "\nResults after 10: ");
    +                FOREACHint_(set){
    +                    qh_fprintf(qh, stderr, 8006, " %d", *i2);
    +                }
    +                qh_fprintf(qh, stderr, 8007, " Continue");
    +            }
    +            while((j= j/10)>=1){
    +                scale *= 10;
    +                e++;
    +            }
    +            if(i==numInts-1){
    +                qh_fprintf(qh, stderr, 8008, " %s%d", s, i);
    +            }else if(i==scale){
    +                if(i<=1000){
    +                    qh_fprintf(qh, stderr, 8010, " %s%d", s, i);
    +                }else{
    +                    qh_fprintf(qh, stderr, 8009, " %s1e%d", s, e);
    +                }
    +            }
    +        }
    +    }
    +    if(i<1000 || i%checkEvery==0 || i== scale || i==numInts-1){
    +        return 1;
    +    }
    +    return 0;
    +}/*log_i*/
    +
    +/* Check that a set contains count elements
    +   Ranges are consecutive (e.g., 1,2,3,...) starting with first, mid, and last
    +   Use -1 for missing ranges
    +*/
    +void checkSetContents(qhT *qh, const char *name, setT *set, int count, int rangeA, int rangeB, int rangeC)
    +{
    +
    +    i2T *i2, **i2p;
    +    int i2_i, i2_n;
    +    int prev= -1; /* avoid warning */
    +    int i;
    +    int first= -3;
    +    int second= -3;
    +    int rangeCount=1;
    +    int actualSize= 0;
    +
    +    qh_setcheck(qh, set, name, 0);
    +    if(set){
    +        SETreturnsize_(set, actualSize);  /* normally used only when speed is critical */
    +        if(*qh_setendpointer(set)!=NULL){
    +            qh_fprintf(qh, stderr, 6344, "%s: qh_setendpointer(set), 0x%x, is not NULL terminator of set 0x%x", name, qh_setendpointer(set), set);
    +            error_count++;
    +        }
    +    }
    +    if(actualSize!=qh_setsize(qh, set)){
    +        qh_fprintf(qh, stderr, 6305, "%s: SETreturnsize_(qh) returned %d while qh_setsize(qh) returns %d\n", name, actualSize, qh_setsize(qh, set));
    +        error_count++;
    +    }else if(actualSize!=count){
    +        qh_fprintf(qh, stderr, 6306, "%s: Expecting %d elements for set.  Got %d elements\n", name, count, actualSize);
    +        error_count++;
    +    }
    +    if(SETempty_(set)){
    +        if(count!=0){
    +            qh_fprintf(qh, stderr, 6307, "%s: Got empty set instead of count %d, rangeA %d, rangeB %d, rangeC %d\n", name, count, rangeA, rangeB, rangeC);
    +            error_count++;
    +        }
    +    }else{
    +        /* Must be first, otherwise trips msvc 8 */
    +        i2T **p= SETaddr_(set, i2T);
    +        if(*p!=SETfirstt_(set, i2T)){
    +            qh_fprintf(qh, stderr, 6309, "%s: SETaddr_(set, i2t) [%p] is not the same as SETfirst_(set) [%p]\n", name, SETaddr_(set, i2T), SETfirst_(set));
    +            error_count++;
    +        }
    +        first= *(int *)SETfirst_(set);
    +        if(SETfirst_(set)!=SETfirstt_(set, i2T)){
    +            qh_fprintf(qh, stderr, 6308, "%s: SETfirst_(set) [%p] is not the same as SETfirstt_(set, i2T [%p]\n", name, SETfirst_(set), SETfirstt_(set, i2T));
    +            error_count++;
    +        }
    +        if(qh_setsize(qh, set)>1){
    +            second= *(int *)SETsecond_(set);
    +            if(SETsecond_(set)!=SETsecondt_(set, i2T)){
    +                qh_fprintf(qh, stderr, 6310, "%s: SETsecond_(set) [%p] is not the same as SETsecondt_(set, i2T) [%p]\n", name, SETsecond_(set), SETsecondt_(set, i2T));
    +                error_count++;
    +            }
    +        }
    +    }
    +    /* Test first run of ints in set*/
    +    i= 0;
    +    FOREACHint_(set){
    +        if(i2!=SETfirst_(set) && *i2!=prev+1){
    +            break;
    +        }
    +        prev= *i2;
    +        if(SETindex_(set, i2)!=i){
    +            qh_fprintf(qh, stderr, 6311, "%s: Expecting SETindex_(set, pointer-to-%d) to be %d.  Got %d\n", name, *i2, i, SETindex_(set, i2));
    +            error_count++;;
    +        }
    +        if(i2!=SETref_(i2)){
    +            qh_fprintf(qh, stderr, 6312, "%s: SETref_(i2) [%p] does not point to i2 (the %d'th element)\n", name, SETref_(i2), i);
    +            error_count++;;
    +        }
    +        i++;
    +    }
    +    FOREACHint_i_(qh, set){
    +        /* Must be first conditional, otherwise it trips up msvc 8 */
    +        i2T **p= SETelemaddr_(set, i2_i, i2T);
    +        if(i2!=*p){
    +            qh_fprintf(qh, stderr, 6320, "%s: SETelemaddr_(set, %d, i2T) [%p] does not point to i2\n", name, i2_i, SETelemaddr_(set, i2_i, int));
    +            error_count++;;
    +        }
    +        if(i2_i==0){
    +            if(first!=*i2){
    +                qh_fprintf(qh, stderr, 6314, "%s: First element is %d instead of SETfirst %d\n", name, *i2, first);
    +                error_count++;;
    +            }
    +            if(rangeA!=*i2){
    +                qh_fprintf(qh, stderr, 6315, "%s: starts with %d instead of rangeA %d\n", name, *i2, rangeA);
    +                error_count++;;
    +            }
    +            prev= rangeA;
    +        }else{
    +            if(i2_i==1 && second!=*i2){
    +                qh_fprintf(qh, stderr, 6316, "%s: Second element is %d instead of SETsecond %d\n", name, *i2, second);
    +                error_count++;;
    +            }
    +            if(prev+1==*i2){
    +                prev++;
    +            }else{
    +                if(*i2==rangeB){
    +                    prev= rangeB;
    +                    rangeB= -1;
    +                    rangeCount++;
    +                }else if(rangeB==-1 && *i2==rangeC){
    +                    prev= rangeC;
    +                    rangeC= -1;
    +                    rangeCount++;
    +                }else{
    +                    prev++;
    +                    qh_fprintf(qh, stderr, 6317, "%s: Expecting %d'th element to be %d.  Got %d\n", name, i2_i, prev, *i2);
    +                    error_count++;
    +                }
    +            }
    +        }
    +        if(i2!=SETelem_(set, i2_i)){
    +            qh_fprintf(qh, stderr, 6318, "%s: SETelem_(set, %d) [%p] is not i2 [%p] (the %d'th element)\n", name, i2_i, SETelem_(set, i2_i), i2, i2_i);
    +            error_count++;;
    +        }
    +        if(SETelemt_(set, i2_i, i2T)!=SETelem_(set, i2_i)){   /* Normally SETelemt_ is used for generic sets */
    +            qh_fprintf(qh, stderr, 6319, "%s: SETelemt_(set, %d, i2T) [%p] is not SETelem_(set, %d) [%p] (the %d'th element)\n", name, i2_i, SETelemt_(set, i2_i, int), i2_i, SETelem_(set, i2_i), i2_i);
    +            error_count++;;
    +        }
    +    }
    +    if(error_count>=MAXerrorCount){
    +        qh_fprintf(qh, stderr, 8011, "testqset: Stop testing after %d errors\n", error_count);
    +        exit(1);
    +    }
    +}/*checkSetContents*/
    +
    diff --git a/xs/src/qhull/src/testqset_r/testqset_r.pro b/xs/src/qhull/src/testqset_r/testqset_r.pro
    new file mode 100644
    index 0000000000..951e0624e8
    --- /dev/null
    +++ b/xs/src/qhull/src/testqset_r/testqset_r.pro
    @@ -0,0 +1,30 @@
    +# -------------------------------------------------
    +# testqset_r.pro -- Qt project file for testqset_r.exe
    +# -------------------------------------------------
    +
    +include(../qhull-warn.pri)
    +
    +TARGET = testqset_r
    +
    +DESTDIR = ../../bin
    +TEMPLATE = app
    +CONFIG += console warn_on
    +CONFIG -= qt
    +CONFIG += qhull_warn_conversion
    +
    +build_pass:CONFIG(debug, debug|release){
    +   OBJECTS_DIR = Debug
    +}else:build_pass:CONFIG(release, debug|release){
    +   OBJECTS_DIR = Release
    +}
    +
    +INCLUDEPATH += ..
    +
    +SOURCES += testqset_r.c
    +SOURCES += ../libqhull_r/qset_r.c
    +SOURCES += ../libqhull_r/mem_r.c
    +SOURCES += ../libqhull_r/usermem_r.c
    +
    +HEADERS += ../libqhull_r/mem_r.h
    +HEADERS += ../libqhull_r/qset_r.h
    +
    diff --git a/xs/src/qhull/src/user_eg/user_eg.c b/xs/src/qhull/src/user_eg/user_eg.c
    new file mode 100644
    index 0000000000..9c5fee51b3
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg/user_eg.c
    @@ -0,0 +1,330 @@
    +/*
      ---------------------------------
    +
    +  user_eg.c
    +  sample code for calling qhull() from an application
    +
    +  call with:
    +
    +     user_eg "cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg                             # return summaries
    +
    +     user_eg "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg "n Qt" "o" "Fp"             # triangulated cube
    +
    +     user_eg "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube
    +
    +     2a) compute the Delaunay triangulation of random points
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond
    +
    + notes:
    +
    +   For another example, see main() in unix.c and user_eg2.c.
    +   These examples, call qh_qhull() directly.  They allow
    +   tighter control on the code loaded with Qhull.
    +
    +   For a C++ example, see user_eg3/user_eg3_r.cpp
    +
    +   Summaries are sent to stderr if other output formats are used
    +
    +   compiled by 'make bin/user_eg'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +*/
    +
    +#define qh_QHimport
    +#include "libqhull/qhull_a.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(void);
    +void makecube(coordT *points, int numpoints, int dim);
    +void makeDelaunay(coordT *points, int numpoints, int dim, int seed);
    +void findDelaunay(int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +
    +/*-------------------------------------------------
    +-print_summary()
    +*/
    +void print_summary(void) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh num_vertices, qh num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jlocate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(dim+1, 1, point);
    +  facet= qh_findbestfacet(point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-dimensional diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; j&1'\n\n");
    +
    +#if qh_QHpointer  /* see user.h */
    +  if (qh_qh){
    +      printf("QH6233: Qhull link error.  The global variable qh_qh was not initialized\n\
    +to NULL by global.c.  Please compile user_eg.c with -Dqh_QHpointer_dllimport\n\
    +as well as -Dqh_QHpointer, or use libqhullstatic, or use a different tool chain.\n\n");
    +      return -1;
    +  }
    +#endif
    +
    +  /*
    +    Run 1: convex hull
    +  */
    +  printf( "\ncompute convex hull of cube after rotating input\n");
    +  sprintf(flags, "qhull s Tcv %s", argc >= 2 ? argv[1] : "");
    +  numpoints= SIZEcube;
    +  makecube(points, numpoints, DIM);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh facet_list' contains the convex hull */
    +    print_summary();
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(!qh_ALL);                   /* free long memory  */
    +  qh_memfreeshort(&curlong, &totlong);    /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #1): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 2: Delaunay triangulation
    +  */
    +
    +  printf( "\ncompute %d-d Delaunay triangulation\n", dim);
    +  sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +  numpoints= SIZEcube;
    +  makeDelaunay(points, numpoints, dim, (int)time(NULL));
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh facet_list' contains the convex hull */
    +    /* If you want a Voronoi diagram ('v') and do not request output (i.e., outfile=NULL),
    +       call qh_setvoronoi_all() after qh_new_qhull(). */
    +    print_summary();
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +    printf( "\nfind %d-d Delaunay triangle closest to [0.5, 0.5, ...]\n", dim);
    +    exitcode= setjmp(qh errexit);
    +    if (!exitcode) {
    +      /* Trap Qhull errors in findDelaunay().  Without the setjmp(), Qhull
    +         will exit() after reporting an error */
    +      qh NOerrexit= False;
    +      findDelaunay(DIM);
    +    }
    +    qh NOerrexit= True;
    +  }
    +#if qh_QHpointer  /* see user.h */
    +  {
    +    qhT *oldqhA, *oldqhB;
    +    coordT pointsB[DIM*TOTpoints]; /* array of coordinates for each point */
    +
    +    printf( "\nsave first triangulation and compute a new triangulation\n");
    +    oldqhA= qh_save_qhull();
    +    sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    numpoints= SIZEcube;
    +    makeDelaunay(pointsB, numpoints, dim, (int)time(NULL)+1);
    +    for (i=numpoints; i--; )
    +      rows[i]= pointsB+dim*i;
    +    qh_printmatrix(outfile, "input", rows, numpoints, dim);
    +    exitcode= qh_new_qhull(dim, numpoints, pointsB, ismalloc,
    +                      flags, outfile, errfile);
    +    if (!exitcode)
    +      print_summary();
    +    printf( "\nsave second triangulation and restore first one\n");
    +    oldqhB= qh_save_qhull();
    +    qh_restore_qhull(&oldqhA);
    +    print_summary();
    +    printf( "\nfree first triangulation and restore second one.\n");
    +    qh_freeqhull(qh_ALL);               /* free short and long memory used by first call */
    +                                         /* do not use qh_memfreeshort */
    +    qh_restore_qhull(&oldqhB);
    +    print_summary();
    +  }
    +#endif
    +  qh_freeqhull(!qh_ALL);                 /* free long memory */
    +  qh_memfreeshort(&curlong, &totlong);  /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #2): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 3: halfspace intersection about the origin
    +  */
    +  printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +  sprintf(flags, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "Fp");
    +  numpoints= SIZEcube;
    +  makehalf(points, numpoints, dim);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+(dim+1)*i;
    +  qh_printmatrix(outfile, "input as halfspace coefficients + offsets", rows, numpoints, dim+1);
    +  /* use qh_sethalfspace_all to transform the halfspaces yourself.
    +     If so, set 'qh feasible_point and do not use option 'Hn,...' [it would retransform the halfspaces]
    +  */
    +  exitcode= qh_new_qhull(dim+1, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode)
    +    print_summary();
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)  /* could also check previous runs */
    +    fprintf(stderr, "qhull internal warning (user_eg, #3): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/user_eg/user_eg.pro b/xs/src/qhull/src/user_eg/user_eg.pro
    new file mode 100644
    index 0000000000..9dda010099
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg/user_eg.pro
    @@ -0,0 +1,11 @@
    +# -------------------------------------------------
    +# user_eg.pro -- Qt project for Qhull demonstration using shared Qhull library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-app-shared_r.pri)
    +
    +TARGET = user_eg
    +
    +SOURCES += user_eg_r.c
    diff --git a/xs/src/qhull/src/user_eg/user_eg_r.c b/xs/src/qhull/src/user_eg/user_eg_r.c
    new file mode 100644
    index 0000000000..21b0ccf4e9
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg/user_eg_r.c
    @@ -0,0 +1,326 @@
    +/*
      ---------------------------------
    +
    +  user_eg_r.c
    +  sample code for calling qhull() from an application.  Uses reentrant libqhull_r
    +
    +  call with:
    +
    +     user_eg "cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg                             # return summaries
    +
    +     user_eg "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg "n Qt" "o" "Fp"             # triangulated cube
    +
    +     user_eg "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube
    +
    +     2a) compute the Delaunay triangulation of random points
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond
    +
    + notes:
    +
    +   For another example, see main() in unix_r.c and user_eg2_r.c.
    +   These examples, call qh_qhull() directly.  They allow
    +   tighter control on the code loaded with Qhull.
    +
    +   For a C++ example, see user_eg3/user_eg3_r.cpp
    +
    +   Summaries are sent to stderr if other output formats are used
    +
    +   compiled by 'make bin/user_eg'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +*/
    +
    +#define qh_QHimport
    +#include "libqhull_r/qhull_ra.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(qhT *qh);
    +void makecube(coordT *points, int numpoints, int dim);
    +void makeDelaunay(qhT *qh, coordT *points, int numpoints, int dim, int seed);
    +void findDelaunay(qhT *qh, int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +
    +/*-------------------------------------------------
    +-print_summary(qh)
    +*/
    +void print_summary(qhT *qh) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh->num_vertices, qh->num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh->hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jlocate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(qhT *qh, int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(qh, dim+1, 1, point);
    +  facet= qh_findbestfacet(qh, point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-dimensional diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; j&1'\n\n");
    +
    +  /*
    +    Run 1: convex hull
    +  */
    +  printf( "\ncompute convex hull of cube after rotating input\n");
    +  sprintf(flags, "qhull s Tcv %s", argc >= 2 ? argv[1] : "");
    +  numpoints= SIZEcube;
    +  makecube(points, numpoints, DIM);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(qh, outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh->facet_list' contains the convex hull */
    +    print_summary(qh);
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +  }
    +  qh_freeqhull(qh, !qh_ALL);                   /* free long memory  */
    +  qh_memfreeshort(qh, &curlong, &totlong);    /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #1): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 2: Delaunay triangulation, reusing the previous qh/qh_qh
    +  */
    +
    +  printf( "\ncompute %d-d Delaunay triangulation\n", dim);
    +  sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +  numpoints= SIZEcube;
    +  makeDelaunay(qh, points, numpoints, dim, (int)time(NULL));
    +  for (i=numpoints; i--; )
    +    rows[i]= points+dim*i;
    +  qh_printmatrix(qh, outfile, "input", rows, numpoints, dim);
    +  exitcode= qh_new_qhull(qh, dim, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode) {                  /* if no error */
    +    /* 'qh->facet_list' contains the convex hull */
    +    /* If you want a Voronoi diagram ('v') and do not request output (i.e., outfile=NULL),
    +       call qh_setvoronoi_all() after qh_new_qhull(). */
    +    print_summary(qh);
    +    FORALLfacets {
    +       /* ... your code ... */
    +    }
    +    printf( "\nfind %d-d Delaunay triangle closest to [0.5, 0.5, ...]\n", dim);
    +    exitcode= setjmp(qh->errexit);
    +    if (!exitcode) {
    +      /* Trap Qhull errors in findDelaunay().  Without the setjmp(), Qhull
    +         will exit() after reporting an error */
    +      qh->NOerrexit= False;
    +      findDelaunay(qh, DIM);
    +    }
    +    qh->NOerrexit= True;
    +  }
    +  {
    +    coordT pointsB[DIM*TOTpoints]; /* array of coordinates for each point */
    +
    +    qhT qh_qhB;    /* Create a new instance of Qhull (qhB) */
    +    qhT *qhB= &qh_qhB;
    +    qh_zero(qhB, errfile);
    +
    +    printf( "\nCompute a new triangulation as a separate instance of Qhull\n");
    +    sprintf(flags, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    numpoints= SIZEcube;
    +    makeDelaunay(qhB, pointsB, numpoints, dim, (int)time(NULL)+1);
    +    for (i=numpoints; i--; )
    +      rows[i]= pointsB+dim*i;
    +    qh_printmatrix(qhB, outfile, "input", rows, numpoints, dim);
    +    exitcode= qh_new_qhull(qhB, dim, numpoints, pointsB, ismalloc,
    +                      flags, outfile, errfile);
    +    if (!exitcode)
    +      print_summary(qhB);
    +    printf( "\nFree memory allocated by the new instance of Qhull, and redisplay the old results.\n");
    +    qh_freeqhull(qhB, !qh_ALL);                 /* free long memory */
    +    qh_memfreeshort(qhB, &curlong, &totlong);  /* free short memory and memory allocator */
    +    if (curlong || totlong)
    +        fprintf(errfile, "qhull internal warning (user_eg, #4): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +    printf( "\n\n");
    +    print_summary(qh);  /* The other instance is unchanged */
    +    /* Exiting the block frees qh_qhB */
    +  }
    +  qh_freeqhull(qh, !qh_ALL);                 /* free long memory */
    +  qh_memfreeshort(qh, &curlong, &totlong);  /* free short memory and memory allocator */
    +  if (curlong || totlong)
    +    fprintf(errfile, "qhull internal warning (user_eg, #2): did not free %d bytes of long memory (%d pieces)\n", totlong, curlong);
    +
    +  /*
    +    Run 3: halfspace intersection about the origin
    +  */
    +  printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +  sprintf(flags, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "Fp");
    +  numpoints= SIZEcube;
    +  makehalf(points, numpoints, dim);
    +  for (i=numpoints; i--; )
    +    rows[i]= points+(dim+1)*i;
    +  qh_printmatrix(qh, outfile, "input as halfspace coefficients + offsets", rows, numpoints, dim+1);
    +  /* use qh_sethalfspace_all to transform the halfspaces yourself.
    +     If so, set 'qh->feasible_point and do not use option 'Hn,...' [it would retransform the halfspaces]
    +  */
    +  exitcode= qh_new_qhull(qh, dim+1, numpoints, points, ismalloc,
    +                      flags, outfile, errfile);
    +  if (!exitcode)
    +    print_summary(qh);
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)  /* could also check previous runs */
    +    fprintf(stderr, "qhull internal warning (user_eg, #3): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    diff --git a/xs/src/qhull/src/user_eg2/user_eg2.c b/xs/src/qhull/src/user_eg2/user_eg2.c
    new file mode 100644
    index 0000000000..a455f025d1
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg2/user_eg2.c
    @@ -0,0 +1,746 @@
    +/*
      ---------------------------------
    +
    +  user_eg2.c
    +
    +  sample code for calling qhull() from an application.
    +
    +  See user_eg.c for a simpler method using qh_new_qhull().
    +  The method used here and in unix.c gives you additional
    +  control over Qhull.
    +
    +  See user_eg3/user_eg3_r.cpp for a C++ example
    +
    +  call with:
    +
    +     user_eg2 "triangulated cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg2                             # return summaries
    +
    +     user_eg2 "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg2 "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube, and incrementally add a diamond
    +
    +     2a) compute the Delaunay triangulation of random points, and add points.
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond, and add a cube
    +
    + notes:
    +
    +   summaries are sent to stderr if other output formats are used
    +
    +   derived from unix.c and compiled by 'make bin/user_eg2'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +
    +   If you want to control all output to stdio and input to stdin,
    +   set the #if below to "1" and delete all lines that contain "io.c".
    +   This prevents the loading of io.o.  Qhull will
    +   still write to 'qh ferr' (stderr) for error reporting and tracing.
    +
    +   Defining #if 1, also prevents user.o from being loaded.
    +*/
    +
    +#include "libqhull/qhull_a.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(void);
    +void makecube(coordT *points, int numpoints, int dim);
    +void adddiamond(coordT *points, int numpoints, int numnew, int dim);
    +void makeDelaunay(coordT *points, int numpoints, int dim);
    +void addDelaunay(coordT *points, int numpoints, int numnew, int dim);
    +void findDelaunay(int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +void addhalf(coordT *points, int numpoints, int numnew, int dim, coordT *feasible);
    +
    +/*-------------------------------------------------
    +-print_summary()
    +*/
    +void print_summary(void) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh num_vertices, qh num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jlocate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim-1; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(dim, 1, point);
    +  facet= qh_findbestfacet(point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim-1; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-d diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; j= 2 ? argv[1] : "");
    +    qh_initflags(options);
    +    printf( "\ncompute triangulated convex hull of cube after rotating input\n");
    +    makecube(array[0], SIZEcube, DIM);
    +    qh_init_B(array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull();
    +    qh_check_output();
    +    qh_triangulate();  /* requires option 'Q11' if want to add points */
    +    print_summary();
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nadd points in a diamond\n");
    +    adddiamond(array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output();
    +    print_summary();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +  }
    +  qh NOerrexit= True;
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    fprintf(stderr, "qhull warning (user_eg, run 1): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +
    +  /*
    +    Run 2: Delaunay triangulation
    +  */
    +  qh_init_A(stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM];
    +
    +    strcat(qh rbox_command, "user_eg Delaunay");
    +    sprintf(options, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    qh_initflags(options);
    +    printf( "\ncompute %d-d Delaunay triangulation\n", DIM-1);
    +    makeDelaunay(array[0], SIZEcube, DIM);
    +    /* Instead of makeDelaunay with qh_setdelaunay, you may
    +       produce a 2-d array of points, set DIM to 2, and set
    +       qh PROJECTdelaunay to True.  qh_init_B will call
    +       qh_projectinput to project the points to the paraboloid
    +       and add a point "at-infinity".
    +    */
    +    qh_init_B(array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull();
    +    /* If you want Voronoi ('v') without qh_produce_output(), call
    +       qh_setvoronoi_all() after qh_qhull() */
    +    qh_check_output();
    +    print_summary();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nadd points to triangulation\n");
    +    addDelaunay(array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nfind Delaunay triangle closest to [0.5, 0.5, ...]\n");
    +    findDelaunay(DIM);
    +  }
    +  qh NOerrexit= True;
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    fprintf(stderr, "qhull warning (user_eg, run 2): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +
    +  /*
    +    Run 3: halfspace intersection
    +  */
    +  qh_init_A(stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM+1];  /* +1 for halfspace offset */
    +    pointT *points;
    +
    +    strcat(qh rbox_command, "user_eg halfspaces");
    +    sprintf(options, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "");
    +    qh_initflags(options);
    +    printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +    makehalf(array[0], SIZEcube, DIM);
    +    qh_setfeasible(DIM); /* from io.c, sets qh feasible_point from 'Hn,n' */
    +    /* you may malloc and set qh feasible_point directly.  It is only used for
    +       option 'Fp' */
    +    points= qh_sethalfspace_all( DIM+1, SIZEcube, array[0], qh feasible_point);
    +    qh_init_B(points, SIZEcube, DIM, True); /* qh_freeqhull frees points */
    +    qh_qhull();
    +    qh_check_output();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +    printf( "\nadd halfspaces for cube to intersection\n");
    +    addhalf(array[0], SIZEcube, SIZEdiamond, DIM, qh feasible_point);
    +    qh_check_output();
    +    qh_produce_output();  /* delete this line to help avoid io.c */
    +    if (qh VERIFYoutput && !qh STOPpoint && !qh STOPcone)
    +      qh_check_points();
    +  }
    +  qh NOerrexit= True;
    +  qh NOerrexit= True;
    +  qh_freeqhull(!qh_ALL);
    +  qh_memfreeshort(&curlong, &totlong);
    +  if (curlong || totlong)
    +    fprintf(stderr, "qhull warning (user_eg, run 3): did not free %d bytes of long memory (%d pieces)\n",
    +       totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    +#if 1    /* use 1 to prevent loading of io.o and user.o */
    +/*-------------------------------------------
    +-errexit- return exitcode to system after an error
    +  assumes exitcode non-zero
    +  prints useful information
    +  see qh_errexit2() in libqhull.c for 2 facets
    +*/
    +void qh_errexit(int exitcode, facetT *facet, ridgeT *ridge) {
    +  QHULL_UNUSED(facet);
    +  QHULL_UNUSED(ridge);
    +
    +  if (qh ERREXITcalled) {
    +    fprintf(qh ferr, "qhull error while processing previous error.  Exit program\n");
    +    exit(1);
    +  }
    +  qh ERREXITcalled= True;
    +  if (!qh QHULLfinished)
    +    qh hulltime= (unsigned)clock() - qh hulltime;
    +  fprintf(qh ferr, "\nWhile executing: %s | %s\n", qh rbox_command, qh qhull_command);
    +  fprintf(qh ferr, "Options selected:\n%s\n", qh qhull_options);
    +  if (qh furthest_id >= 0) {
    +    fprintf(qh ferr, "\nLast point added to hull was p%d", qh furthest_id);
    +    if (zzval_(Ztotmerge))
    +      fprintf(qh ferr, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh QHULLfinished)
    +      fprintf(qh ferr, "\nQhull has finished constructing the hull.");
    +    else if (qh POSTmerging)
    +      fprintf(qh ferr, "\nQhull has started post-merging");
    +    fprintf(qh ferr, "\n\n");
    +  }
    +  if (qh NOerrexit) {
    +    fprintf(qh ferr, "qhull error while ending program.  Exit program\n");
    +    exit(1);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  qh NOerrexit= True;
    +  longjmp(qh errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*-------------------------------------------
    +-errprint- prints out the information of the erroneous object
    +    any parameter may be NULL, also prints neighbors and geomview output
    +*/
    +void qh_errprint(const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +
    +  fprintf(qh ferr, "%s facets f%d f%d ridge r%d vertex v%d\n",
    +           string, getid_(atfacet), getid_(otherfacet), getid_(atridge),
    +           getid_(atvertex));
    +} /* errprint */
    +
    +
    +void qh_printfacetlist(facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  /* remove these calls to help avoid io.c */
    +  qh_printbegin(qh ferr, qh_PRINTfacets, facetlist, facets, printall);/*io.c*/
    +  FORALLfacet_(facetlist)                                              /*io.c*/
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  FOREACHfacet_(facets)                                                /*io.c*/
    +    qh_printafacet(qh ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  qh_printend(qh ferr, qh_PRINTfacets, facetlist, facets, printall);  /*io.c*/
    +
    +  FORALLfacet_(facetlist)
    +    fprintf( qh ferr, "facet f%d\n", facet->id);
    +} /* printfacetlist */
    +
    +/* qh_printhelp_degenerate( fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(FILE *fp) {
    +
    +  if (qh MERGEexact || qh PREmerge || qh JOGGLEmax < REALmax/2)
    +    qh_fprintf(fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh DELAUNAY && !qh SCALElast && qh MAXabs_coord > 1e4)
    +      qh_fprintf(fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh DELAUNAY && !qh ATinfinity)
    +      qh_fprintf(fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh DISTround);
    +    qh_fprintf(fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/* qh_printhelp_narrowhull( minangle )
    +     Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(FILE *fp, realT minangle) {
    +
    +    qh_fprintf(fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +A coplanar point may lead to a wide facet.  Options 'QbB' (scale to unit box)\n\
    +or 'Qbb' (scale last coordinate) may remove this warning.  Use 'Pp' to skip\n\
    +this warning.  See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/* qh_printhelp_singular
    +      prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh hull_dim);
    +  qh_printvertexlist(fp, "", qh facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh DISTround);
    +  qh_printpointid(fp, "center point", qh hull_dim, qh interior_point, -1);
    +  qh_fprintf(fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(fp, 9380, " p%d", qh_pointid(vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh interior_point, facet, &dist);
    +    qh_fprintf(fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh HALFspace)
    +      qh_fprintf(fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh hull_dim >= qh_INITIALmax)
    +      qh_fprintf(fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh num_points, coord= qh first_point+k; i--; coord += qh hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh DISTround);
    +#if REALfloat
    +    qh_fprintf(fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull.h).\n");
    +#endif
    +    qh_fprintf(fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +    if (qh DELAUNAY && !qh ATinfinity)
    +      qh_fprintf(fp, 9390, "\n\n\
    +This is a Delaunay triangulation and the input is co-circular or co-spherical:\n\
    +  - use 'Qz' to add a point \"at infinity\" (i.e., above the paraboloid)\n\
    +  - or use 'QJ' to joggle the input and avoid co-circular data\n");
    +  }
    +} /* printhelp_singular */
    +
    +
    +/*-----------------------------------------
    +-user_memsizes- allocate up to 10 additional, quick allocation sizes
    +*/
    +void qh_user_memsizes(void) {
    +
    +  /* qh_memsize(size); */
    +} /* user_memsizes */
    +
    +#endif
    diff --git a/xs/src/qhull/src/user_eg2/user_eg2.pro b/xs/src/qhull/src/user_eg2/user_eg2.pro
    new file mode 100644
    index 0000000000..c841bfe134
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg2/user_eg2.pro
    @@ -0,0 +1,11 @@
    +# -------------------------------------------------
    +# user_eg2.pro -- Qt project for Qhull demonstration using the static Qhull library
    +#
    +# It uses reentrant Qhull
    +# -------------------------------------------------
    +
    +include(../qhull-app-c_r.pri)
    +
    +TARGET = user_eg2
    +
    +SOURCES += user_eg2_r.c
    diff --git a/xs/src/qhull/src/user_eg2/user_eg2_r.c b/xs/src/qhull/src/user_eg2/user_eg2_r.c
    new file mode 100644
    index 0000000000..2f8b4e6c76
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg2/user_eg2_r.c
    @@ -0,0 +1,742 @@
    +/*
      ---------------------------------
    +
    +  user_eg2_r.c
    +
    +  sample code for calling qhull() from an application.
    +
    +  See user_eg_r.c for a simpler method using qh_new_qhull().
    +  The method used here and in unix_r.c gives you additional
    +  control over Qhull.
    +
    +  See user_eg3/user_eg3_r.cpp for a C++ example
    +
    +  call with:
    +
    +     user_eg2 "triangulated cube/diamond options" "delaunay options" "halfspace options"
    +
    +  for example:
    +
    +     user_eg2                             # return summaries
    +
    +     user_eg2 "n" "o" "Fp"                # return normals, OFF, points
    +
    +     user_eg2 "QR0 p" "QR0 v p" "QR0 Fp"  # rotate input and return points
    +                                         # 'v' returns Voronoi
    +                                         # transform is rotated for halfspaces
    +
    +   main() makes three runs of qhull.
    +
    +     1) compute the convex hull of a cube, and incrementally add a diamond
    +
    +     2a) compute the Delaunay triangulation of random points, and add points.
    +
    +     2b) find the Delaunay triangle closest to a point.
    +
    +     3) compute the halfspace intersection of a diamond, and add a cube
    +
    + notes:
    +
    +   summaries are sent to stderr if other output formats are used
    +
    +   derived from unix.c and compiled by 'make bin/user_eg2'
    +
    +   see libqhull.h for data structures, macros, and user-callable functions.
    +
    +   If you want to control all output to stdio and input to stdin,
    +   set the #if below to "1" and delete all lines that contain "io.c".
    +   This prevents the loading of io.o.  Qhull will
    +   still write to 'qh->ferr' (stderr) for error reporting and tracing.
    +
    +   Defining #if 1, also prevents user.o from being loaded.
    +*/
    +
    +#include "libqhull_r/qhull_ra.h"
    +
    +/*-------------------------------------------------
    +-internal function prototypes
    +*/
    +void print_summary(qhT *qh);
    +void makecube(coordT *points, int numpoints, int dim);
    +void adddiamond(qhT *qh, coordT *points, int numpoints, int numnew, int dim);
    +void makeDelaunay(qhT *qh, coordT *points, int numpoints, int dim);
    +void addDelaunay(qhT *qh, coordT *points, int numpoints, int numnew, int dim);
    +void findDelaunay(qhT *qh, int dim);
    +void makehalf(coordT *points, int numpoints, int dim);
    +void addhalf(qhT *qh, coordT *points, int numpoints, int numnew, int dim, coordT *feasible);
    +
    +/*-------------------------------------------------
    +-print_summary(qh)
    +*/
    +void print_summary(qhT *qh) {
    +  facetT *facet;
    +  int k;
    +
    +  printf("\n%d vertices and %d facets with normals:\n",
    +                 qh->num_vertices, qh->num_facets);
    +  FORALLfacets {
    +    for (k=0; k < qh->hull_dim; k++)
    +      printf("%6.2g ", facet->normal[k]);
    +    printf("\n");
    +  }
    +}
    +
    +/*--------------------------------------------------
    +-makecube- set points to vertices of cube
    +  points is numpoints X dim
    +*/
    +void makecube(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jfirst_point)  /* in case of 'QRn' */
    +      qh->num_points= numpoints+j+1;
    +    /* qh.num_points sets the size of the points array.  You may
    +       allocate the points elsewhere.  If so, qh_addpoint records
    +       the point's address in qh->other_points
    +    */
    +    for (k=dim; k--; ) {
    +      if (j/2 == k)
    +        point[k]= (j & 1) ? 2.0 : -2.0;
    +      else
    +        point[k]= 0.0;
    +    }
    +    facet= qh_findbestfacet(qh, point, !qh_ALL, &bestdist, &isoutside);
    +    if (isoutside) {
    +      if (!qh_addpoint(qh, point, facet, False))
    +        break;  /* user requested an early exit with 'TVn' or 'TCn' */
    +    }
    +    printf("%d vertices and %d facets\n",
    +                 qh->num_vertices, qh->num_facets);
    +    /* qh_produce_output(); */
    +  }
    +  if (qh->DOcheckmax)
    +    qh_check_maxout(qh);
    +  else if (qh->KEEPnearinside)
    +    qh_nearcoplanar(qh);
    +} /*.adddiamond.*/
    +
    +/*--------------------------------------------------
    +-makeDelaunay- set points for dim-1 Delaunay triangulation of random points
    +  points is numpoints X dim.  Each point is projected to a paraboloid.
    +*/
    +void makeDelaunay(qhT *qh, coordT *points, int numpoints, int dim) {
    +  int j,k, seed;
    +  coordT *point, realr;
    +
    +  seed= (int)time(NULL); /* time_t to int */
    +  printf("seed: %d\n", seed);
    +  qh_RANDOMseed_(qh, seed);
    +  for (j=0; jfirst_point)  /* in case of 'QRn' */
    +      qh->num_points= numpoints+j+1;
    +    /* qh.num_points sets the size of the points array.  You may
    +       allocate the point elsewhere.  If so, qh_addpoint records
    +       the point's address in qh->other_points
    +    */
    +    for (k= 0; k < dim-1; k++) {
    +      realr= qh_RANDOMint;
    +      point[k]= 2.0 * realr/(qh_RANDOMmax+1) - 1.0;
    +    }
    +    qh_setdelaunay(qh, dim, 1, point);
    +    facet= qh_findbestfacet(qh, point, !qh_ALL, &bestdist, &isoutside);
    +    if (isoutside) {
    +      if (!qh_addpoint(qh, point, facet, False))
    +        break;  /* user requested an early exit with 'TVn' or 'TCn' */
    +    }
    +    qh_printpoint(qh, stdout, "added point", point);
    +    printf("%d points, %d extra points, %d vertices, and %d facets in total\n",
    +                  qh->num_points, qh_setsize(qh, qh->other_points),
    +                  qh->num_vertices, qh->num_facets);
    +
    +    /* qh_produce_output(qh); */
    +  }
    +  if (qh->DOcheckmax)
    +    qh_check_maxout(qh);
    +  else if (qh->KEEPnearinside)
    +    qh_nearcoplanar(qh);
    +} /*.addDelaunay.*/
    +
    +/*--------------------------------------------------
    +-findDelaunay- find Delaunay triangle for [0.5,0.5,...]
    +  assumes dim < 100
    +notes:
    +  calls qh_setdelaunay() to project the point to a parabaloid
    +warning:
    +  This is not implemented for tricoplanar facets ('Qt'),
    +  See locate a facet with qh_findbestfacet()
    +*/
    +void findDelaunay(qhT *qh, int dim) {
    +  int k;
    +  coordT point[ 100];
    +  boolT isoutside;
    +  realT bestdist;
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +
    +  for (k= 0; k < dim-1; k++)
    +    point[k]= 0.5;
    +  qh_setdelaunay(qh, dim, 1, point);
    +  facet= qh_findbestfacet(qh, point, qh_ALL, &bestdist, &isoutside);
    +  if (facet->tricoplanar) {
    +    fprintf(stderr, "findDelaunay: not implemented for triangulated, non-simplicial Delaunay regions (tricoplanar facet, f%d).\n",
    +       facet->id);
    +    qh_errexit(qh, qh_ERRqhull, facet, NULL);
    +  }
    +  FOREACHvertex_(facet->vertices) {
    +    for (k=0; k < dim-1; k++)
    +      printf("%5.2f ", vertex->point[k]);
    +    printf("\n");
    +  }
    +} /*.findDelaunay.*/
    +
    +/*--------------------------------------------------
    +-makehalf- set points to halfspaces for a (dim)-d diamond
    +  points is numpoints X dim+1
    +
    +  each halfspace consists of dim coefficients followed by an offset
    +*/
    +void makehalf(coordT *points, int numpoints, int dim) {
    +  int j,k;
    +  coordT *point;
    +
    +  for (j=0; jnum_points, qh_setsize(qh, qh->other_points),
    +                  qh->num_vertices, qh->num_facets);
    +    /* qh_produce_output(qh); */
    +  }
    +  if (qh->DOcheckmax)
    +    qh_check_maxout(qh);
    +  else if (qh->KEEPnearinside)
    +    qh_nearcoplanar(qh);
    +} /*.addhalf.*/
    +
    +#define DIM 3     /* dimension of points, must be < 31 for SIZEcube */
    +#define SIZEcube (1<&1'\n\n");
    +
    +  ismalloc= False;      /* True if qh_freeqhull should 'free(array)' */
    +  /*
    +    Run 1: convex hull
    +  */
    +  qh_init_A(qh, stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh->errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM];
    +
    +    qh->NOerrexit= False;
    +    strcat(qh->rbox_command, "user_eg2 cube example");
    +    sprintf(options, "qhull s Tcv Q11 %s ", argc >= 2 ? argv[1] : "");
    +    qh_initflags(qh, options);
    +    printf( "\ncompute triangulated convex hull of cube after rotating input\n");
    +    makecube(array[0], SIZEcube, DIM);
    +    qh_init_B(qh, array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_triangulate(qh);  /* requires option 'Q11' if want to add points */
    +    print_summary(qh);
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nadd points in a diamond\n");
    +    adddiamond(qh, array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output(qh);
    +    print_summary(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +  }
    +  qh->NOerrexit= True;
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +      fprintf(stderr, "qhull warning (user_eg2, run 1): did not free %d bytes of long memory (%d pieces)\n",
    +          totlong, curlong);
    +  /*
    +    Run 2: Delaunay triangulation
    +  */
    +  qh_init_A(qh, stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh->errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM];
    +
    +    qh->NOerrexit= False;
    +    strcat(qh->rbox_command, "user_eg2 Delaunay example");
    +    sprintf(options, "qhull s d Tcv %s", argc >= 3 ? argv[2] : "");
    +    qh_initflags(qh, options);
    +    printf( "\ncompute %d-d Delaunay triangulation\n", DIM-1);
    +    makeDelaunay(qh, array[0], SIZEcube, DIM);
    +    /* Instead of makeDelaunay with qh_setdelaunay, you may
    +       produce a 2-d array of points, set DIM to 2, and set
    +       qh->PROJECTdelaunay to True.  qh_init_B will call
    +       qh_projectinput to project the points to the paraboloid
    +       and add a point "at-infinity".
    +    */
    +    qh_init_B(qh, array[0], SIZEcube, DIM, ismalloc);
    +    qh_qhull(qh);
    +    /* If you want Voronoi ('v') without qh_produce_output(), call
    +       qh_setvoronoi_all() after qh_qhull() */
    +    qh_check_output(qh);
    +    print_summary(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nadd points to triangulation\n");
    +    addDelaunay(qh, array[0], SIZEcube, SIZEdiamond, DIM);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nfind Delaunay triangle closest to [0.5, 0.5, ...]\n");
    +    findDelaunay(qh, DIM);
    +  }
    +  qh->NOerrexit= True;
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong) 
    +      fprintf(stderr, "qhull warning (user_eg2, run 2): did not free %d bytes of long memory (%d pieces)\n",
    +         totlong, curlong);
    +  /*
    +    Run 3: halfspace intersection
    +  */
    +  qh_init_A(qh, stdin, stdout, stderr, 0, NULL);
    +  exitcode= setjmp(qh->errexit);
    +  if (!exitcode) {
    +    coordT array[TOTpoints][DIM+1];  /* +1 for halfspace offset */
    +    pointT *points;
    +
    +    qh->NOerrexit= False;
    +    strcat(qh->rbox_command, "user_eg2 halfspace example");
    +    sprintf(options, "qhull H0 s Tcv %s", argc >= 4 ? argv[3] : "");
    +    qh_initflags(qh, options);
    +    printf( "\ncompute halfspace intersection about the origin for a diamond\n");
    +    makehalf(array[0], SIZEcube, DIM);
    +    qh_setfeasible(qh, DIM); /* from io.c, sets qh->feasible_point from 'Hn,n' */
    +    /* you may malloc and set qh->feasible_point directly.  It is only used for
    +       option 'Fp' */
    +    points= qh_sethalfspace_all(qh, DIM+1, SIZEcube, array[0], qh->feasible_point);
    +    qh_init_B(qh, points, SIZEcube, DIM, True); /* qh_freeqhull frees points */
    +    qh_qhull(qh);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +    printf( "\nadd halfspaces for cube to intersection\n");
    +    addhalf(qh, array[0], SIZEcube, SIZEdiamond, DIM, qh->feasible_point);
    +    qh_check_output(qh);
    +    qh_produce_output(qh);  /* delete this line to help avoid io.c */
    +    if (qh->VERIFYoutput && !qh->STOPpoint && !qh->STOPcone)
    +      qh_check_points(qh);
    +  }
    +  qh->NOerrexit= True;
    +  qh->NOerrexit= True;
    +  qh_freeqhull(qh, !qh_ALL);
    +  qh_memfreeshort(qh, &curlong, &totlong);
    +  if (curlong || totlong)
    +      fprintf(stderr, "qhull warning (user_eg2, run 3): did not free %d bytes of long memory (%d pieces)\n",
    +          totlong, curlong);
    +  return exitcode;
    +} /* main */
    +
    +#if 1    /* use 1 to prevent loading of io.o and user.o */
    +/*-------------------------------------------
    +-errexit- return exitcode to system after an error
    +  assumes exitcode non-zero
    +  prints useful information
    +  see qh_errexit2() in libqhull.c for 2 facets
    +*/
    +void qh_errexit(qhT *qh, int exitcode, facetT *facet, ridgeT *ridge) {
    +  QHULL_UNUSED(facet);
    +  QHULL_UNUSED(ridge);
    +
    +  if (qh->ERREXITcalled) {
    +    fprintf(qh->ferr, "qhull error while processing previous error.  Exit program\n");
    +    exit(1);
    +  }
    +  qh->ERREXITcalled= True;
    +  if (!qh->QHULLfinished)
    +    qh->hulltime= (unsigned)clock() - qh->hulltime;
    +  fprintf(qh->ferr, "\nWhile executing: %s | %s\n", qh->rbox_command, qh->qhull_command);
    +  fprintf(qh->ferr, "Options selected:\n%s\n", qh->qhull_options);
    +  if (qh->furthest_id >= 0) {
    +    fprintf(qh->ferr, "\nLast point added to hull was p%d", qh->furthest_id);
    +    if (zzval_(Ztotmerge))
    +      fprintf(qh->ferr, "  Last merge was #%d.", zzval_(Ztotmerge));
    +    if (qh->QHULLfinished)
    +      fprintf(qh->ferr, "\nQhull has finished constructing the hull.");
    +    else if (qh->POSTmerging)
    +      fprintf(qh->ferr, "\nQhull has started post-merging");
    +    fprintf(qh->ferr, "\n\n");
    +  }
    +  if (qh->NOerrexit) {
    +    fprintf(qh->ferr, "qhull error while ending program.  Exit program\n");
    +    exit(1);
    +  }
    +  if (!exitcode)
    +    exitcode= qh_ERRqhull;
    +  qh->NOerrexit= True;
    +  longjmp(qh->errexit, exitcode);
    +} /* errexit */
    +
    +
    +/*-------------------------------------------
    +-errprint- prints out the information of the erroneous object
    +    any parameter may be NULL, also prints neighbors and geomview output
    +*/
    +void qh_errprint(qhT *qh, const char *string, facetT *atfacet, facetT *otherfacet, ridgeT *atridge, vertexT *atvertex) {
    +
    +  fprintf(qh->ferr, "%s facets f%d f%d ridge r%d vertex v%d\n",
    +           string, getid_(atfacet), getid_(otherfacet), getid_(atridge),
    +           getid_(atvertex));
    +} /* errprint */
    +
    +
    +void qh_printfacetlist(qhT *qh, facetT *facetlist, setT *facets, boolT printall) {
    +  facetT *facet, **facetp;
    +
    +  /* remove these calls to help avoid io.c */
    +  qh_printbegin(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);/*io.c*/
    +  FORALLfacet_(facetlist)                                              /*io.c*/
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  FOREACHfacet_(facets)                                                /*io.c*/
    +    qh_printafacet(qh, qh->ferr, qh_PRINTfacets, facet, printall);          /*io.c*/
    +  qh_printend(qh, qh->ferr, qh_PRINTfacets, facetlist, facets, printall);  /*io.c*/
    +
    +  FORALLfacet_(facetlist)
    +    fprintf( qh->ferr, "facet f%d\n", facet->id);
    +} /* printfacetlist */
    +
    +/* qh_printhelp_degenerate( fp )
    +    prints descriptive message for precision error
    +
    +  notes:
    +    no message if qh_QUICKhelp
    +*/
    +void qh_printhelp_degenerate(qhT *qh, FILE *fp) {
    +
    +  if (qh->MERGEexact || qh->PREmerge || qh->JOGGLEmax < REALmax/2)
    +    qh_fprintf(qh, fp, 9368, "\n\
    +A Qhull error has occurred.  Qhull should have corrected the above\n\
    +precision error.  Please send the input and all of the output to\n\
    +qhull_bug@qhull.org\n");
    +  else if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9369, "\n\
    +Precision problems were detected during construction of the convex hull.\n\
    +This occurs because convex hull algorithms assume that calculations are\n\
    +exact, but floating-point arithmetic has roundoff errors.\n\
    +\n\
    +To correct for precision problems, do not use 'Q0'.  By default, Qhull\n\
    +selects 'C-0' or 'Qx' and merges non-convex facets.  With option 'QJ',\n\
    +Qhull joggles the input to prevent precision problems.  See \"Imprecision\n\
    +in Qhull\" (qh-impre.htm).\n\
    +\n\
    +If you use 'Q0', the output may include\n\
    +coplanar ridges, concave ridges, and flipped facets.  In 4-d and higher,\n\
    +Qhull may produce a ridge with four neighbors or two facets with the same \n\
    +vertices.  Qhull reports these events when they occur.  It stops when a\n\
    +concave ridge, flipped facet, or duplicate facet occurs.\n");
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9370, "\
    +\n\
    +Qhull is currently using single precision arithmetic.  The following\n\
    +will probably remove the precision problems:\n\
    +  - recompile qhull for realT precision(#define REALfloat 0 in user.h).\n");
    +#endif
    +    if (qh->DELAUNAY && !qh->SCALElast && qh->MAXabs_coord > 1e4)
    +      qh_fprintf(qh, fp, 9371, "\
    +\n\
    +When computing the Delaunay triangulation of coordinates > 1.0,\n\
    +  - use 'Qbb' to scale the last coordinate to [0,m] (max previous coordinate)\n");
    +    if (qh->DELAUNAY && !qh->ATinfinity)
    +      qh_fprintf(qh, fp, 9372, "\
    +When computing the Delaunay triangulation:\n\
    +  - use 'Qz' to add a point at-infinity.  This reduces precision problems.\n");
    +
    +    qh_fprintf(qh, fp, 9373, "\
    +\n\
    +If you need triangular output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft'.  It triangulates non-simplicial facets with added points.\n\
    +\n\
    +If you must use 'Q0',\n\
    +try one or more of the following options.  They can not guarantee an output.\n\
    +  - use 'QbB' to scale the input to a cube.\n\
    +  - use 'Po' to produce output and prevent partitioning for flipped facets\n\
    +  - use 'V0' to set min. distance to visible facet as 0 instead of roundoff\n\
    +  - use 'En' to specify a maximum roundoff error less than %2.2g.\n\
    +  - options 'Qf', 'Qbb', and 'QR0' may also help\n",
    +               qh->DISTround);
    +    qh_fprintf(qh, fp, 9374, "\
    +\n\
    +To guarantee simplicial output:\n\
    +  - use option 'Qt' to triangulate the output\n\
    +  - use option 'QJ' to joggle the input points and remove precision errors\n\
    +  - use option 'Ft' to triangulate the output by adding points\n\
    +  - use exact arithmetic (see \"Imprecision in Qhull\", qh-impre.htm)\n\
    +");
    +  }
    +} /* printhelp_degenerate */
    +
    +
    +/* qh_printhelp_narrowhull( minangle )
    +     Warn about a narrow hull
    +
    +  notes:
    +    Alternatively, reduce qh_WARNnarrow in user.h
    +
    +*/
    +void qh_printhelp_narrowhull(qhT *qh, FILE *fp, realT minangle) {
    +
    +    qh_fprintf(qh, fp, 9375, "qhull precision warning: \n\
    +The initial hull is narrow (cosine of min. angle is %.16f).\n\
    +A coplanar point may lead to a wide facet.  Options 'QbB' (scale to unit box)\n\
    +or 'Qbb' (scale last coordinate) may remove this warning.  Use 'Pp' to skip\n\
    +this warning.  See 'Limitations' in qh-impre.htm.\n",
    +          -minangle);   /* convert from angle between normals to angle between facets */
    +} /* printhelp_narrowhull */
    +
    +/* qh_printhelp_singular
    +      prints descriptive message for singular input
    +*/
    +void qh_printhelp_singular(qhT *qh, FILE *fp) {
    +  facetT *facet;
    +  vertexT *vertex, **vertexp;
    +  realT min, max, *coord, dist;
    +  int i,k;
    +
    +  qh_fprintf(qh, fp, 9376, "\n\
    +The input to qhull appears to be less than %d dimensional, or a\n\
    +computation has overflowed.\n\n\
    +Qhull could not construct a clearly convex simplex from points:\n",
    +           qh->hull_dim);
    +  qh_printvertexlist(qh, fp, "", qh->facet_list, NULL, qh_ALL);
    +  if (!qh_QUICKhelp)
    +    qh_fprintf(qh, fp, 9377, "\n\
    +The center point is coplanar with a facet, or a vertex is coplanar\n\
    +with a neighboring facet.  The maximum round off error for\n\
    +computing distances is %2.2g.  The center point, facets and distances\n\
    +to the center point are as follows:\n\n", qh->DISTround);
    +  qh_printpointid(qh, fp, "center point", qh->hull_dim, qh->interior_point, -1);
    +  qh_fprintf(qh, fp, 9378, "\n");
    +  FORALLfacets {
    +    qh_fprintf(qh, fp, 9379, "facet");
    +    FOREACHvertex_(facet->vertices)
    +      qh_fprintf(qh, fp, 9380, " p%d", qh_pointid(qh, vertex->point));
    +    zinc_(Zdistio);
    +    qh_distplane(qh, qh->interior_point, facet, &dist);
    +    qh_fprintf(qh, fp, 9381, " distance= %4.2g\n", dist);
    +  }
    +  if (!qh_QUICKhelp) {
    +    if (qh->HALFspace)
    +      qh_fprintf(qh, fp, 9382, "\n\
    +These points are the dual of the given halfspaces.  They indicate that\n\
    +the intersection is degenerate.\n");
    +    qh_fprintf(qh, fp, 9383,"\n\
    +These points either have a maximum or minimum x-coordinate, or\n\
    +they maximize the determinant for k coordinates.  Trial points\n\
    +are first selected from points that maximize a coordinate.\n");
    +    if (qh->hull_dim >= qh_INITIALmax)
    +      qh_fprintf(qh, fp, 9384, "\n\
    +Because of the high dimension, the min x-coordinate and max-coordinate\n\
    +points are used if the determinant is non-zero.  Option 'Qs' will\n\
    +do a better, though much slower, job.  Instead of 'Qs', you can change\n\
    +the points by randomly rotating the input with 'QR0'.\n");
    +  }
    +  qh_fprintf(qh, fp, 9385, "\nThe min and max coordinates for each dimension are:\n");
    +  for (k=0; k < qh->hull_dim; k++) {
    +    min= REALmax;
    +    max= -REALmin;
    +    for (i=qh->num_points, coord= qh->first_point+k; i--; coord += qh->hull_dim) {
    +      maximize_(max, *coord);
    +      minimize_(min, *coord);
    +    }
    +    qh_fprintf(qh, fp, 9386, "  %d:  %8.4g  %8.4g  difference= %4.4g\n", k, min, max, max-min);
    +  }
    +  if (!qh_QUICKhelp) {
    +    qh_fprintf(qh, fp, 9387, "\n\
    +If the input should be full dimensional, you have several options that\n\
    +may determine an initial simplex:\n\
    +  - use 'QJ'  to joggle the input and make it full dimensional\n\
    +  - use 'QbB' to scale the points to the unit cube\n\
    +  - use 'QR0' to randomly rotate the input for different maximum points\n\
    +  - use 'Qs'  to search all points for the initial simplex\n\
    +  - use 'En'  to specify a maximum roundoff error less than %2.2g.\n\
    +  - trace execution with 'T3' to see the determinant for each point.\n",
    +                     qh->DISTround);
    +#if REALfloat
    +    qh_fprintf(qh, fp, 9388, "\
    +  - recompile qhull for realT precision(#define REALfloat 0 in libqhull.h).\n");
    +#endif
    +    qh_fprintf(qh, fp, 9389, "\n\
    +If the input is lower dimensional:\n\
    +  - use 'QJ' to joggle the input and make it full dimensional\n\
    +  - use 'Qbk:0Bk:0' to delete coordinate k from the input.  You should\n\
    +    pick the coordinate with the least range.  The hull will have the\n\
    +    correct topology.\n\
    +  - determine the flat containing the points, rotate the points\n\
    +    into a coordinate plane, and delete the other coordinates.\n\
    +  - add one or more points to make the input full dimensional.\n\
    +");
    +    if (qh->DELAUNAY && !qh->ATinfinity)
    +      qh_fprintf(qh, fp, 9390, "\n\n\
    +This is a Delaunay triangulation and the input is co-circular or co-spherical:\n\
    +  - use 'Qz' to add a point \"at infinity\" (i.e., above the paraboloid)\n\
    +  - or use 'QJ' to joggle the input and avoid co-circular data\n");
    +  }
    +} /* printhelp_singular */
    +
    +
    +/*-----------------------------------------
    +-user_memsizes- allocate up to 10 additional, quick allocation sizes
    +*/
    +void qh_user_memsizes(qhT *qh) {
    +
    +  QHULL_UNUSED(qh);
    +  /* qh_memsize(qh, size); */
    +} /* user_memsizes */
    +
    +#endif
    diff --git a/xs/src/qhull/src/user_eg3/user_eg3.pro b/xs/src/qhull/src/user_eg3/user_eg3.pro
    new file mode 100644
    index 0000000000..35372fbf92
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg3/user_eg3.pro
    @@ -0,0 +1,12 @@
    +# -------------------------------------------------
    +# user_eg3.pro -- Qt project for cpp demonstration user_eg3.exe
    +#
    +# The C++ interface requires reentrant Qhull.
    +# -------------------------------------------------
    +
    +include(../qhull-app-cpp.pri)
    +
    +TARGET = user_eg3
    +CONFIG -= qt
    +
    +SOURCES += user_eg3_r.cpp
    diff --git a/xs/src/qhull/src/user_eg3/user_eg3_r.cpp b/xs/src/qhull/src/user_eg3/user_eg3_r.cpp
    new file mode 100644
    index 0000000000..5257872ab8
    --- /dev/null
    +++ b/xs/src/qhull/src/user_eg3/user_eg3_r.cpp
    @@ -0,0 +1,162 @@
    +#//! user_eg3_r.cpp -- Invoke rbox and qhull from C++
    +
    +#include "libqhullcpp/RboxPoints.h"
    +#include "libqhullcpp/QhullError.h"
    +#include "libqhullcpp/QhullQh.h"
    +#include "libqhullcpp/QhullFacet.h"
    +#include "libqhullcpp/QhullFacetList.h"
    +#include "libqhullcpp/QhullLinkedList.h"
    +#include "libqhullcpp/QhullVertex.h"
    +#include "libqhullcpp/Qhull.h"
    +
    +#include    /* for printf() of help message */
    +#include 
    +#include 
    +
    +using std::cerr;
    +using std::cin;
    +using std::cout;
    +using std::endl;
    +
    +using orgQhull::Qhull;
    +using orgQhull::QhullError;
    +using orgQhull::QhullFacet;
    +using orgQhull::QhullFacetList;
    +using orgQhull::QhullQh;
    +using orgQhull::RboxPoints;
    +using orgQhull::QhullVertex;
    +using orgQhull::QhullVertexSet;
    +
    +int main(int argc, char **argv);
    +int user_eg3(int argc, char **argv);
    +
    +char prompt[]= "\n\
    +user_eg3 -- demonstrate calling rbox and qhull from C++.\n\
    +\n\
    +user_eg3 is statically linked to reentrant qhull.  If user_eg3\n\
    +fails immediately, it is probably linked to the non-reentrant qhull.\n\
    +Try 'user_eg3 rbox qhull \"T1\"'\n\
    +\n\
    +  eg-100                       Run the example in qh-code.htm\n\
    +  rbox \"200 D4\" ...            Generate points from rbox\n\
    +  qhull \"d p\" ...              Run qhull and produce output\n\
    +  qhull-cout \"o\" ...           Run qhull and produce output to cout\n\
    +  qhull \"T1\" ...               Run qhull with level-1 trace to cerr\n\
    +  facets                       Print facets when done\n\
    +\n\
    +For example\n\
    +  user_eg3 rbox qhull\n\
    +  user_eg3 rbox qhull d\n\
    +  user_eg3 rbox \"10 D2\"  \"2 D2\" qhull  \"s p\" facets\n\
    +\n\
    +";
    +
    +
    +/*--------------------------------------------
    +-user_eg3-  main procedure of user_eg3 application
    +*/
    +int main(int argc, char **argv) {
    +
    +    QHULL_LIB_CHECK
    +
    +    if(argc==1){
    +        cout << prompt;
    +        return 1;
    +    }
    +    try{
    +        return user_eg3(argc, argv);
    +    }catch(QhullError &e){
    +        cerr << e.what() << std::endl;
    +        return e.errorCode();
    +    }
    +}//main
    +
    +int user_eg3(int argc, char **argv)
    +{
    +    if(strcmp(argv[1], "eg-100")==0){
    +        RboxPoints rbox("100");
    +        Qhull q(rbox, "");
    +        QhullFacetList facets= q.facetList();
    +        cout << facets;
    +        return 0;
    +    }
    +    bool printFacets= false;
    +    RboxPoints rbox;
    +    Qhull qhull;
    +    int readingRbox= 0;
    +    int readingQhull= 0;
    +    for(int i=1; i
    Date: Wed, 15 Aug 2018 11:18:20 +0200
    Subject: [PATCH 131/185] Improvement of extruder selection for the object/part
     according to the actually extruders count
    
    ---
     xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 35 ++++++++++++++++++---------
     xs/src/slic3r/GUI/GUI_ObjectParts.hpp |  3 +++
     xs/src/slic3r/GUI/Tab.cpp             |  2 +-
     3 files changed, 27 insertions(+), 13 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    index 8f74158e90..616f0dc57f 100644
    --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    @@ -8,7 +8,6 @@
     #include "../../libslic3r/Utils.hpp"
     
     #include 
    -#include 
     #include 
     #include 
     #include "Geometry.hpp"
    @@ -210,6 +209,18 @@ wxPoint get_mouse_position_in_control() {
                        pt.y - win->GetScreenPosition().y);
     }
     
    +wxDataViewColumn* object_ctrl_create_extruder_column(int extruders_count)
    +{
    +    wxArrayString choices;
    +    choices.Add("default");
    +    for (int i = 1; i <= extruders_count; ++i)
    +        choices.Add(wxString::Format("%d", i));
    +    wxDataViewChoiceRenderer *c =
    +        new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL);
    +    wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 3, 60, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
    +    return column;
    +}
    +
     void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz)
     {
     	m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize);
    @@ -238,17 +249,7 @@ void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz)
     		wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
     
     	// column 3 of the view control:
    -	wxArrayString choices;
    -	choices.Add("default");
    -	choices.Add("1");
    -	choices.Add("2");
    -	choices.Add("3");
    -	choices.Add("4");
    -	wxDataViewChoiceRenderer *c =
    -		new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL);
    -	wxDataViewColumn *column3 =
    -		new wxDataViewColumn(_(L("Extruder")), c, 3, 60, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
    -	m_objects_ctrl->AppendColumn(column3);
    +    m_objects_ctrl->AppendColumn(object_ctrl_create_extruder_column(4));
     
     	// column 4 of the view control:
     	m_objects_ctrl->AppendBitmapColumn(" ", 4, wxDATAVIEW_CELL_INERT, 25,
    @@ -1608,5 +1609,15 @@ void on_drop(wxDataViewEvent &event)
         g_prevent_list_events = false;
     }
     
    +void update_objects_list_extruder_column(const int extruders_count)
    +{
    +    // delete old 3rd column
    +    m_objects_ctrl->DeleteColumn(m_objects_ctrl->GetColumnAt(3));
    +    // insert new created 3rd column
    +    m_objects_ctrl->InsertColumn(3, object_ctrl_create_extruder_column(extruders_count));
    +    // set show/hide for this column 
    +    set_extruder_column_hidden(extruders_count <= 1);
    +}
    +
     } //namespace GUI
     } //namespace Slic3r 
    \ No newline at end of file
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp
    index f9fffb58b7..15be90bbfa 100644
    --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp
    @@ -118,6 +118,9 @@ void on_begin_drag(wxDataViewEvent &event);
     void on_drop_possible(wxDataViewEvent &event);
     void on_drop(wxDataViewEvent &event);
     
    +// update extruder column for objects_ctrl according to extruders count
    +void update_objects_list_extruder_column(const int extruders_count);
    +
     } //namespace GUI
     } //namespace Slic3r 
     #endif  //slic3r_GUI_ObjectParts_hpp_
    \ No newline at end of file
    diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
    index 8fbedb1219..c4736a2dd0 100644
    --- a/xs/src/slic3r/GUI/Tab.cpp
    +++ b/xs/src/slic3r/GUI/Tab.cpp
    @@ -1698,6 +1698,7 @@ void TabPrinter::extruders_count_changed(size_t extruders_count){
     	build_extruder_pages();
     	reload_config();
     	on_value_change("extruders_count", extruders_count);
    +    update_objects_list_extruder_column(extruders_count);
     }
     
     void TabPrinter::append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key)
    @@ -2010,7 +2011,6 @@ void Tab::load_current_preset()
     			const Preset* parent_preset = m_presets->get_selected_preset_parent();
     			static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 :
     				static_cast(parent_preset->config.option("nozzle_diameter"))->values.size();
    -			set_extruder_column_hidden(static_cast(this)->m_sys_extruders_count <= 1);
     		}
     		m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue;
     		init_options_list();
    
    From 76249e56258a3e5df086175be3811d4a738d1339 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Wed, 15 Aug 2018 12:47:46 +0200
    Subject: [PATCH 132/185] Fixed typo in LambdaObjectDialog. It was a reason of
     the wrong cube size updating for generic modifier "Cube".
    
    + some code cleaning
    ---
     xs/src/slic3r/GUI/GUI_ObjectParts.cpp    | 17 ++++++-----------
     xs/src/slic3r/GUI/LambdaObjectDialog.cpp |  6 +++---
     2 files changed, 9 insertions(+), 14 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    index 616f0dc57f..8c29976765 100644
    --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    @@ -690,14 +690,12 @@ void set_object_scale(int idx, int scale)
     
     void unselect_objects()
     {
    -    printf("UNSELECT OBJECTS\n");
    +    if (!m_objects_ctrl->GetSelection())
    +        return;
    +
         g_prevent_list_events = true;
    -    if (m_objects_ctrl->GetSelection()) {
    -        m_objects_ctrl->UnselectAll();
    -        part_selection_changed();
    -    }
    -    else
    -        printf("all items are UNSELECTED\n");
    +    m_objects_ctrl->UnselectAll();
    +    part_selection_changed();
         g_prevent_list_events = false;
     }
     
    @@ -1327,7 +1325,6 @@ void update_settings_value()
     		og->set_value("scale_x", 0);
     		og->set_value("scale_y", 0);
     		og->set_value("scale_z", 0);
    -        printf("return because of unselect\n");
             og->disable();
     		return;
     	}
    @@ -1478,7 +1475,6 @@ void set_extruder_column_hidden(bool hide)
     
     void update_extruder_in_config(const wxString& selection)
     {
    -    printf("BEGIN OF update_extruder_in_config\n");
         if (!m_config || selection.empty())
             return;
     
    @@ -1489,7 +1485,6 @@ void update_extruder_in_config(const wxString& selection)
             wxCommandEvent e(m_event_update_scene);
             get_main_frame()->ProcessWindowEvent(e);
         }
    -    printf("END OF update_extruder_in_config\n");
     }
     
     void update_scale_values()
    @@ -1612,7 +1607,7 @@ void on_drop(wxDataViewEvent &event)
     void update_objects_list_extruder_column(const int extruders_count)
     {
         // delete old 3rd column
    -    m_objects_ctrl->DeleteColumn(m_objects_ctrl->GetColumnAt(3));
    +    m_objects_ctrl->DeleteColumn(m_objects_ctrl->GetColumn(3));
         // insert new created 3rd column
         m_objects_ctrl->InsertColumn(3, object_ctrl_create_extruder_column(extruders_count));
         // set show/hide for this column 
    diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.cpp b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp
    index b5479fb129..7543821c0e 100644
    --- a/xs/src/slic3r/GUI/LambdaObjectDialog.cpp
    +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp
    @@ -30,9 +30,9 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent)
     
     	auto optgroup = init_modificator_options_page(_(L("Box")));
     		optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
    -			int opt_id =	opt_key == "L" ? 0 :
    -							opt_key == "W" ? 1 : 
    -							opt_key == "L" ? 2 : -1;
    +			int opt_id =	opt_key == "l" ? 0 :
    +							opt_key == "w" ? 1 : 
    +							opt_key == "h" ? 2 : -1;
     			if (opt_id < 0) return;
     			object_parameters.dim[opt_id] = boost::any_cast(value);
     		};
    
    From 4d98d321996de6e6d036403c319b6ef044144dcd Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Wed, 15 Aug 2018 12:50:06 +0200
    Subject: [PATCH 133/185] Use of bounding box of rotated 3D convex hull for out
     of print volume detection
    
    ---
     xs/src/libslic3r/Format/3mf.cpp   |   1 +
     xs/src/libslic3r/Format/AMF.cpp   |   1 +
     xs/src/libslic3r/Model.cpp        | 129 +++++++++--------------------
     xs/src/libslic3r/Model.hpp        |  36 +++++---
     xs/src/libslic3r/TriangleMesh.cpp | 133 ++++++++++++++++++++++++++++++
     xs/src/libslic3r/TriangleMesh.hpp |   6 +-
     xs/src/slic3r/GUI/3DScene.cpp     |  76 ++++++++++++-----
     xs/src/slic3r/GUI/3DScene.hpp     |  16 ++--
     xs/src/slic3r/GUI/GLCanvas3D.cpp  |   2 +-
     xs/src/slic3r/GUI/GLGizmo.cpp     |  14 ++--
     xs/src/slic3r/GUI/GLGizmo.hpp     |   2 +-
     xs/xsp/GUI_3DScene.xsp            |   1 -
     12 files changed, 276 insertions(+), 141 deletions(-)
    
    diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp
    index dd3500eba0..5de1d26c5f 100644
    --- a/xs/src/libslic3r/Format/3mf.cpp
    +++ b/xs/src/libslic3r/Format/3mf.cpp
    @@ -1491,6 +1491,7 @@ namespace Slic3r {
     
                 stl_get_size(&stl);
                 volume->mesh.repair();
    +            volume->calculate_convex_hull();
     
                 // apply volume's name and config data
                 for (const Metadata& metadata : volume_data.metadata)
    diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp
    index 600aa6cd97..886bbae971 100644
    --- a/xs/src/libslic3r/Format/AMF.cpp
    +++ b/xs/src/libslic3r/Format/AMF.cpp
    @@ -406,6 +406,7 @@ void AMFParserContext::endElement(const char * /* name */)
             }
             stl_get_size(&stl);
             m_volume->mesh.repair();
    +        m_volume->calculate_convex_hull();
             m_volume_facets.clear();
             m_volume = nullptr;
             break;
    diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
    index f9936537fa..23d4477487 100644
    --- a/xs/src/libslic3r/Model.cpp
    +++ b/xs/src/libslic3r/Model.cpp
    @@ -17,6 +17,11 @@
     #include "SVG.hpp"
     #include 
     
    +static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f,
    +                                     0.0f, 1.0f, 0.0f, 0.0f,
    +                                     0.0f, 0.0f, 1.0f, 0.0f,
    +                                     0.0f, 0.0f, 0.0f, 1.0f };
    +
     namespace Slic3r {
     
         unsigned int Model::s_auto_extruder_id = 1;
    @@ -235,14 +240,6 @@ BoundingBoxf3 Model::bounding_box() const
         return bb;
     }
     
    -BoundingBoxf3 Model::transformed_bounding_box() const
    -{
    -    BoundingBoxf3 bb;
    -    for (const ModelObject* obj : this->objects)
    -        bb.merge(obj->tight_bounding_box(false));
    -    return bb;
    -}
    -
     void Model::center_instances_around_point(const Pointf &point)
     {
     //    BoundingBoxf3 bb = this->bounding_box();
    @@ -623,54 +620,6 @@ const BoundingBoxf3& ModelObject::bounding_box() const
         return m_bounding_box;
     }
     
    -BoundingBoxf3 ModelObject::tight_bounding_box(bool include_modifiers) const
    -{
    -    BoundingBoxf3 bb;
    -
    -    for (const ModelVolume* vol : this->volumes)
    -    {
    -        if (include_modifiers || !vol->modifier)
    -        {
    -            for (const ModelInstance* inst : this->instances)
    -            {
    -                double c = cos(inst->rotation);
    -                double s = sin(inst->rotation);
    -
    -                for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f)
    -                {
    -                    const stl_facet& facet = vol->mesh.stl.facet_start[f];
    -
    -                    for (int i = 0; i < 3; ++i)
    -                    {
    -                        // original point
    -                        const stl_vertex& v = facet.vertex[i];
    -                        Pointf3 p((double)v.x, (double)v.y, (double)v.z);
    -
    -                        // scale
    -                        p.x *= inst->scaling_factor;
    -                        p.y *= inst->scaling_factor;
    -                        p.z *= inst->scaling_factor;
    -
    -                        // rotate Z
    -                        double x = p.x;
    -                        double y = p.y;
    -                        p.x = c * x - s * y;
    -                        p.y = s * x + c * y;
    -
    -                        // translate
    -                        p.x += inst->offset.x;
    -                        p.y += inst->offset.y;
    -
    -                        bb.merge(p);
    -                    }
    -                }
    -            }
    -        }
    -    }
    -
    -    return bb;
    -}
    -
     // A mesh containing all transformed instances of this object.
     TriangleMesh ModelObject::mesh() const
     {
    @@ -755,15 +704,22 @@ void ModelObject::center_around_origin()
     void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z)
     {
         for (ModelVolume *v : this->volumes)
    +    {
             v->mesh.translate(float(x), float(y), float(z));
    -    if (m_bounding_box_valid) 
    +        v->m_convex_hull.translate(float(x), float(y), float(z));
    +    }
    +
    +    if (m_bounding_box_valid)
             m_bounding_box.translate(x, y, z);
     }
     
     void ModelObject::scale(const Pointf3 &versor)
     {
         for (ModelVolume *v : this->volumes)
    +    {
             v->mesh.scale(versor);
    +        v->m_convex_hull.scale(versor);
    +    }
         // reset origin translation since it doesn't make sense anymore
         this->origin_translation = Pointf3(0,0,0);
         this->invalidate_bounding_box();
    @@ -774,6 +730,7 @@ void ModelObject::rotate(float angle, const Axis &axis)
         for (ModelVolume *v : this->volumes)
         {
             v->mesh.rotate(angle, axis);
    +        v->m_convex_hull.rotate(angle, axis);
         }
     
         center_around_origin();
    @@ -790,6 +747,7 @@ void ModelObject::transform(const float* matrix3x4)
         for (ModelVolume* v : volumes)
         {
             v->mesh.transform(matrix3x4);
    +        v->m_convex_hull.transform(matrix3x4);
         }
     
         origin_translation = Pointf3(0.0, 0.0, 0.0);
    @@ -799,8 +757,12 @@ void ModelObject::transform(const float* matrix3x4)
     void ModelObject::mirror(const Axis &axis)
     {
         for (ModelVolume *v : this->volumes)
    +    {
             v->mesh.mirror(axis);
    -    this->origin_translation = Pointf3(0,0,0);
    +        v->m_convex_hull.mirror(axis);
    +    }
    +
    +    this->origin_translation = Pointf3(0, 0, 0);
         this->invalidate_bounding_box();
     }
     
    @@ -904,45 +866,20 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
     
     void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
     {
    -    for (ModelVolume* vol : this->volumes)
    +    for (const ModelVolume* vol : this->volumes)
         {
             if (!vol->modifier)
             {
                 for (ModelInstance* inst : this->instances)
                 {
    -                BoundingBoxf3 bb;
    +                std::vector world_mat(UNIT_MATRIX, std::end(UNIT_MATRIX));
    +                Eigen::Transform m = Eigen::Transform::Identity();
    +                m.translate(Eigen::Vector3f((float)inst->offset.x, (float)inst->offset.y, 0.0f));
    +                m.rotate(Eigen::AngleAxisf(inst->rotation, Eigen::Vector3f::UnitZ()));
    +                m.scale(inst->scaling_factor);
    +                ::memcpy((void*)world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
     
    -                double c = cos(inst->rotation);
    -                double s = sin(inst->rotation);
    -
    -                for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f)
    -                {
    -                    const stl_facet& facet = vol->mesh.stl.facet_start[f];
    -
    -                    for (int i = 0; i < 3; ++i)
    -                    {
    -                        // original point
    -                        const stl_vertex& v = facet.vertex[i];
    -                        Pointf3 p((double)v.x, (double)v.y, (double)v.z);
    -
    -                        // scale
    -                        p.x *= inst->scaling_factor;
    -                        p.y *= inst->scaling_factor;
    -                        p.z *= inst->scaling_factor;
    -
    -                        // rotate Z
    -                        double x = p.x;
    -                        double y = p.y;
    -                        p.x = c * x - s * y;
    -                        p.y = s * x + c * y;
    -
    -                        // translate
    -                        p.x += inst->offset.x;
    -                        p.y += inst->offset.y;
    -
    -                        bb.merge(p);
    -                    }
    -                }
    +                BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(world_mat);
     
                     if (print_volume.contains(bb))
                         inst->print_volume_state = ModelInstance::PVS_Inside;
    @@ -1025,6 +962,16 @@ ModelMaterial* ModelVolume::assign_unique_material()
         return model->add_material(this->_material_id);
     }
     
    +void ModelVolume::calculate_convex_hull()
    +{
    +    m_convex_hull = mesh.convex_hull_3d();
    +}
    +
    +const TriangleMesh& ModelVolume::get_convex_hull() const
    +{
    +    return m_convex_hull;
    +}
    +
     // Split this volume, append the result to the object owning this volume.
     // Return the number of volumes created from this one.
     // This is useful to assign different materials to different volumes of an object.
    diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
    index 4c650f0de8..23af9fb1c4 100644
    --- a/xs/src/libslic3r/Model.hpp
    +++ b/xs/src/libslic3r/Model.hpp
    @@ -105,9 +105,6 @@ public:
         // This bounding box is being cached.
         const BoundingBoxf3& bounding_box() const;
         void invalidate_bounding_box() { m_bounding_box_valid = false; }
    -    // Returns a snug bounding box of the transformed instances.
    -    // This bounding box is not being cached.
    -    BoundingBoxf3 tight_bounding_box(bool include_modifiers) const;
     
         // A mesh containing all transformed instances of this object.
         TriangleMesh mesh() const;
    @@ -157,6 +154,10 @@ private:
     class ModelVolume
     {
         friend class ModelObject;
    +
    +    // The convex hull of this model's mesh.
    +    TriangleMesh m_convex_hull;
    +
     public:
         std::string name;
         // The triangular model.
    @@ -180,19 +181,32 @@ public:
     
         ModelMaterial* assign_unique_material();
         
    +    void calculate_convex_hull();
    +    const TriangleMesh& get_convex_hull() const;
    +
     private:
         // Parent object owning this ModelVolume.
         ModelObject* object;
         t_model_material_id _material_id;
         
    -    ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) {}
    -    ModelVolume(ModelObject *object, TriangleMesh &&mesh) : mesh(std::move(mesh)), modifier(false), object(object) {}
    -    ModelVolume(ModelObject *object, const ModelVolume &other) : 
    -        name(other.name), mesh(other.mesh), config(other.config), modifier(other.modifier), object(object)
    -        { this->material_id(other.material_id()); }
    -    ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : 
    +    ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object)
    +    {
    +        if (mesh.stl.stats.number_of_facets > 1)
    +            calculate_convex_hull();
    +    }
    +    ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {}
    +    ModelVolume(ModelObject *object, const ModelVolume &other) :
    +        name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object)
    +    {
    +        this->material_id(other.material_id());
    +    }
    +    ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
             name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object)
    -        { this->material_id(other.material_id()); }
    +    {
    +        this->material_id(other.material_id());
    +        if (mesh.stl.stats.number_of_facets > 1)
    +            calculate_convex_hull();
    +    }
     };
     
     // A single instance of a ModelObject.
    @@ -285,8 +299,6 @@ public:
         bool add_default_instances();
         // Returns approximate axis aligned bounding box of this model
         BoundingBoxf3 bounding_box() const;
    -    // Returns tight axis aligned bounding box of this model
    -    BoundingBoxf3 transformed_bounding_box() const;
         void center_instances_around_point(const Pointf &point);
         void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
         TriangleMesh mesh() const;
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index 45e4b6f5dc..fc72a45aad 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -1,6 +1,9 @@
     #include "TriangleMesh.hpp"
     #include "ClipperUtils.hpp"
     #include "Geometry.hpp"
    +#include "qhull/src/libqhullcpp/Qhull.h"
    +#include "qhull/src/libqhullcpp/QhullFacetList.h"
    +#include "qhull/src/libqhullcpp/QhullVertexSet.h"
     #include 
     #include 
     #include 
    @@ -15,6 +18,8 @@
     
     #include 
     
    +#include 
    +
     #if 0
         #define DEBUG
         #define _DEBUG
    @@ -597,6 +602,134 @@ TriangleMesh::bounding_box() const
         return bb;
     }
     
    +BoundingBoxf3 TriangleMesh::transformed_bounding_box(const std::vector& matrix) const
    +{
    +    bool has_shared = (stl.v_shared != nullptr);
    +    if (!has_shared)
    +        stl_generate_shared_vertices(&stl);
    +
    +    unsigned int vertices_count = (stl.stats.shared_vertices > 0) ? (unsigned int)stl.stats.shared_vertices : 3 * (unsigned int)stl.stats.number_of_facets;
    +
    +    if (vertices_count == 0)
    +        return BoundingBoxf3();
    +
    +    Eigen::MatrixXf src_vertices(3, vertices_count);
    +
    +    if (stl.stats.shared_vertices > 0)
    +    {
    +        stl_vertex* vertex_ptr = stl.v_shared;
    +        for (int i = 0; i < stl.stats.shared_vertices; ++i)
    +        {
    +            src_vertices(0, i) = vertex_ptr->x;
    +            src_vertices(1, i) = vertex_ptr->y;
    +            src_vertices(2, i) = vertex_ptr->z;
    +            vertex_ptr += 1;
    +        }
    +    }
    +    else
    +    {
    +        stl_facet* facet_ptr = stl.facet_start;
    +        unsigned int v_id = 0;
    +        while (facet_ptr < stl.facet_start + stl.stats.number_of_facets)
    +        {
    +            for (int i = 0; i < 3; ++i)
    +            {
    +                src_vertices(0, v_id) = facet_ptr->vertex[i].x;
    +                src_vertices(1, v_id) = facet_ptr->vertex[i].y;
    +                src_vertices(2, v_id) = facet_ptr->vertex[i].z;
    +            }
    +            facet_ptr += 1;
    +            ++v_id;
    +        }
    +    }
    +
    +    if (!has_shared && (stl.stats.shared_vertices > 0))
    +        stl_invalidate_shared_vertices(&stl);
    +
    +    Eigen::Transform m;
    +    ::memcpy((void*)m.data(), (const void*)matrix.data(), 16 * sizeof(float));
    +
    +    Eigen::MatrixXf dst_vertices(3, vertices_count);
    +    dst_vertices = m * src_vertices.colwise().homogeneous();
    +
    +    float min_x = dst_vertices(0, 0);
    +    float max_x = dst_vertices(0, 0);
    +    float min_y = dst_vertices(1, 0);
    +    float max_y = dst_vertices(1, 0);
    +    float min_z = dst_vertices(2, 0);
    +    float max_z = dst_vertices(2, 0);
    +
    +    for (int i = 1; i < vertices_count; ++i)
    +    {
    +        min_x = std::min(min_x, dst_vertices(0, i));
    +        max_x = std::max(max_x, dst_vertices(0, i));
    +        min_y = std::min(min_y, dst_vertices(1, i));
    +        max_y = std::max(max_y, dst_vertices(1, i));
    +        min_z = std::min(min_z, dst_vertices(2, i));
    +        max_z = std::max(max_z, dst_vertices(2, i));
    +    }
    +
    +    return BoundingBoxf3(Pointf3((coordf_t)min_x, (coordf_t)min_y, (coordf_t)min_z), Pointf3((coordf_t)max_x, (coordf_t)max_y, (coordf_t)max_z));
    +}
    +
    +TriangleMesh TriangleMesh::convex_hull_3d() const
    +{
    +    // Helper struct for qhull:
    +    struct PointForQHull{
    +        PointForQHull(float x_p, float y_p, float z_p) : x((realT)x_p), y((realT)y_p), z((realT)z_p) {}
    +        realT x, y, z;
    +    };
    +    std::vector src_vertices;
    +
    +    // We will now fill the vector with input points for computation:
    +    stl_facet* facet_ptr = stl.facet_start;
    +    while (facet_ptr < stl.facet_start + stl.stats.number_of_facets)
    +    {
    +        for (int i = 0; i < 3; ++i)
    +        {
    +            const stl_vertex& v = facet_ptr->vertex[i];
    +            src_vertices.emplace_back(v.x, v.y, v.z);
    +        }
    +
    +        facet_ptr += 1;
    +    }
    +
    +    // The qhull call:
    +    orgQhull::Qhull qhull;
    +    qhull.disableOutputStream(); // we want qhull to be quiet
    +    try
    +    {
    +        qhull.runQhull("", 3, (int)src_vertices.size(), (const realT*)(src_vertices.data()), "Qt");
    +    }
    +    catch (...)
    +    {
    +        std::cout << "Unable to create convex hull" << std::endl;
    +        return TriangleMesh();
    +    }
    +
    +    // Let's collect results:
    +    Pointf3s det_vertices;
    +    std::vector facets;
    +    auto facet_list = qhull.facetList().toStdVector();
    +    for (const orgQhull::QhullFacet& facet : facet_list)
    +    {   // iterate through facets
    +        orgQhull::QhullVertexSet vertices = facet.vertices();
    +        for (int i = 0; i < 3; ++i)
    +        {   // iterate through facet's vertices
    +
    +            orgQhull::QhullPoint p = vertices[i].point();
    +            const float* coords = p.coordinates();
    +            det_vertices.emplace_back(coords[0], coords[1], coords[2]);
    +        }
    +        unsigned int size = (unsigned int)det_vertices.size();
    +        facets.emplace_back(size - 3, size - 2, size - 1);
    +    }
    +
    +    TriangleMesh output_mesh(det_vertices, facets);
    +    output_mesh.repair();
    +    return output_mesh;
    +}
    +
     void
     TriangleMesh::require_shared_vertices()
     {
    diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
    index c700784a51..6ab52efe25 100644
    --- a/xs/src/libslic3r/TriangleMesh.hpp
    +++ b/xs/src/libslic3r/TriangleMesh.hpp
    @@ -55,6 +55,10 @@ public:
         ExPolygons horizontal_projection() const;
         Polygon convex_hull();
         BoundingBoxf3 bounding_box() const;
    +    // Returns the bbox of this TriangleMesh transformed by the given matrix
    +    BoundingBoxf3 transformed_bounding_box(const std::vector& matrix) const;
    +    // Returns the convex hull of this TriangleMesh
    +    TriangleMesh convex_hull_3d() const;
         void reset_repair_stats();
         bool needed_repair() const;
         size_t facets_count() const;
    @@ -66,7 +70,7 @@ public:
         // Count disconnected triangle patches.
         size_t number_of_patches() const;
     
    -    stl_file stl;
    +    mutable stl_file stl;
         bool repaired;
         
     private:
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 3f01eb20c1..0709271b8c 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -202,7 +202,8 @@ const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f };
     GLVolume::GLVolume(float r, float g, float b, float a)
         : m_angle_z(0.0f)
         , m_scale_factor(1.0f)
    -    , m_dirty(true)
    +    , m_transformed_bounding_box_dirty(true)
    +    , m_transformed_convex_hull_bounding_box_dirty(true)
         , composite_id(-1)
         , select_group_id(-1)
         , drag_group_id(-1)
    @@ -219,8 +220,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
         , tverts_range(0, size_t(-1))
         , qverts_range(0, size_t(-1))
     {
    -    m_world_mat = std::vector(UNIT_MATRIX, std::end(UNIT_MATRIX));
    -
         color[0] = r;
         color[1] = g;
         color[2] = b;
    @@ -264,45 +263,76 @@ const Pointf3& GLVolume::get_origin() const
     
     void GLVolume::set_origin(const Pointf3& origin)
     {
    -    m_origin = origin;
    -    m_dirty = true;
    +    if (m_origin != origin)
    +    {
    +        m_origin = origin;
    +        m_transformed_bounding_box_dirty = true;
    +        m_transformed_convex_hull_bounding_box_dirty = true;
    +    }
     }
     
     void GLVolume::set_angle_z(float angle_z)
     {
    -    m_angle_z = angle_z;
    -    m_dirty = true;
    +    if (m_angle_z != angle_z)
    +    {
    +        m_angle_z = angle_z;
    +        m_transformed_bounding_box_dirty = true;
    +        m_transformed_convex_hull_bounding_box_dirty = true;
    +    }
     }
     
     void GLVolume::set_scale_factor(float scale_factor)
     {
    -    m_scale_factor = scale_factor;
    -    m_dirty = true;
    +    if (m_scale_factor != scale_factor)
    +    {
    +        m_scale_factor = scale_factor;
    +        m_transformed_bounding_box_dirty = true;
    +        m_transformed_convex_hull_bounding_box_dirty = true;
    +    }
     }
     
    -const std::vector& GLVolume::world_matrix() const
    +void GLVolume::set_convex_hull(const TriangleMesh& convex_hull)
     {
    -    if (m_dirty)
    -    {
    -        Eigen::Transform m = Eigen::Transform::Identity();
    -        m.translate(Eigen::Vector3f((float)m_origin.x, (float)m_origin.y, (float)m_origin.z));
    -        m.rotate(Eigen::AngleAxisf(m_angle_z, Eigen::Vector3f::UnitZ()));
    -        m.scale(m_scale_factor);
    -        ::memcpy((void*)m_world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
    -        m_dirty = false;
    -    }
    +    m_convex_hull = convex_hull;
    +}
     
    -    return m_world_mat;
    +std::vector GLVolume::world_matrix() const
    +{
    +    std::vector world_mat(UNIT_MATRIX, std::end(UNIT_MATRIX));
    +    Eigen::Transform m = Eigen::Transform::Identity();
    +    m.translate(Eigen::Vector3f((float)m_origin.x, (float)m_origin.y, (float)m_origin.z));
    +    m.rotate(Eigen::AngleAxisf(m_angle_z, Eigen::Vector3f::UnitZ()));
    +    m.scale(m_scale_factor);
    +    ::memcpy((void*)world_mat.data(), (const void*)m.data(), 16 * sizeof(float));
    +    return world_mat;
     }
     
     BoundingBoxf3 GLVolume::transformed_bounding_box() const
     {
    -    if (m_dirty)
    +    if (m_transformed_bounding_box_dirty)
    +    {
             m_transformed_bounding_box = bounding_box.transformed(world_matrix());
    +        m_transformed_bounding_box_dirty = false;
    +    }
     
         return m_transformed_bounding_box;
     }
     
    +BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box() const
    +{
    +    if (m_transformed_convex_hull_bounding_box_dirty)
    +    {
    +        if (m_convex_hull.stl.stats.number_of_facets > 0)
    +            m_transformed_convex_hull_bounding_box = m_convex_hull.transformed_bounding_box(world_matrix());
    +        else
    +            m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix());
    +
    +        m_transformed_convex_hull_bounding_box_dirty = false;
    +    }
    +
    +    return m_transformed_convex_hull_bounding_box;
    +}
    +
     void GLVolume::set_range(double min_z, double max_z)
     {
         this->qverts_range.first  = 0;
    @@ -629,6 +659,7 @@ std::vector GLVolumeCollection::load_object(
     
                 if (!model_volume->modifier)
                 {
    +                v.set_convex_hull(model_volume->get_convex_hull());
                     v.layer_height_texture = layer_height_texture;
                     if (extruder_id != -1)
                         v.extruder_id = extruder_id;
    @@ -716,6 +747,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
         v.drag_group_id = obj_idx * 1000;
         v.is_wipe_tower = true;
         v.shader_outside_printer_detection_enabled = ! size_unknown;
    +    v.set_convex_hull(mesh.convex_hull_3d());
         return int(this->volumes.size() - 1);
     }
     
    @@ -803,7 +835,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
         {
             if ((volume != nullptr) && !volume->is_modifier)
             {
    -            const BoundingBoxf3& bb = volume->transformed_bounding_box();
    +            const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
                 bool contained = print_volume.contains(bb);
                 all_contained &= contained;
     
    diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
    index a552b32a7c..1265bc20de 100644
    --- a/xs/src/slic3r/GUI/3DScene.hpp
    +++ b/xs/src/slic3r/GUI/3DScene.hpp
    @@ -260,12 +260,16 @@ private:
         float                 m_angle_z;
         // Scale factor of the volume to be rendered.
         float                 m_scale_factor;
    -    // World matrix of the volume to be rendered.
    -    std::vector    m_world_mat;
         // Bounding box of this volume, in unscaled coordinates.
         mutable BoundingBoxf3 m_transformed_bounding_box;
    -    // Whether or not is needed to recalculate the world matrix.
    -    mutable bool          m_dirty;
    +    // Whether or not is needed to recalculate the transformed bounding box.
    +    mutable bool          m_transformed_bounding_box_dirty;
    +    // Convex hull of the original mesh, if any.
    +    TriangleMesh          m_convex_hull;
    +    // Bounding box of this volume, in unscaled coordinates.
    +    mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
    +    // Whether or not is needed to recalculate the transformed convex hull bounding box.
    +    mutable bool          m_transformed_convex_hull_bounding_box_dirty;
     
     public:
     
    @@ -323,13 +327,15 @@ public:
         void set_origin(const Pointf3& origin);
         void set_angle_z(float angle_z);
         void set_scale_factor(float scale_factor);
    +    void set_convex_hull(const TriangleMesh& convex_hull);
     
         int                 object_idx() const { return this->composite_id / 1000000; }
         int                 volume_idx() const { return (this->composite_id / 1000) % 1000; }
         int                 instance_idx() const { return this->composite_id % 1000; }
     
    -    const std::vector& world_matrix() const;
    +    std::vector world_matrix() const;
         BoundingBoxf3       transformed_bounding_box() const;
    +    BoundingBoxf3       transformed_convex_hull_bounding_box() const;
     
         bool                empty() const { return this->indexed_vertex_array.empty(); }
         bool                indexed() const { return this->indexed_vertex_array.indexed(); }
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index ab4095e6fe..92bde34ce7 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -3243,7 +3243,7 @@ BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
         {
             for (const GLVolume* volume : selected_volumes)
             {
    -            bb.merge(volume->transformed_bounding_box());
    +            bb.merge(volume->transformed_convex_hull_bounding_box());
             }
         }
     
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 47b01e8a28..7207022bd3 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -189,7 +189,7 @@ GLGizmoRotate::GLGizmoRotate()
         , m_angle_z(0.0f)
         , m_center(Pointf(0.0, 0.0))
         , m_radius(0.0f)
    -    , m_keep_radius(false)
    +    , m_keep_initial_values(false)
     {
     }
     
    @@ -229,7 +229,7 @@ bool GLGizmoRotate::on_init()
     
     void GLGizmoRotate::on_set_state()
     {
    -    m_keep_radius = (m_state == On) ? false : true;
    +    m_keep_initial_values = (m_state == On) ? false : true;
     }
     
     void GLGizmoRotate::on_update(const Pointf& mouse_pos)
    @@ -255,19 +255,19 @@ void GLGizmoRotate::on_update(const Pointf& mouse_pos)
     
     void GLGizmoRotate::on_refresh()
     {
    -    m_keep_radius = false;
    +    m_keep_initial_values = false;
     }
     
     void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
     {
         ::glDisable(GL_DEPTH_TEST);
     
    -    const Pointf3& size = box.size();
    -    m_center = box.center();
    -    if (!m_keep_radius)
    +    if (!m_keep_initial_values)
         {
    +        const Pointf3& size = box.size();
    +        m_center = box.center();
             m_radius = Offset + ::sqrt(sqr(0.5f * size.x) + sqr(0.5f * size.y));
    -        m_keep_radius = true;
    +        m_keep_initial_values = true;
         }
     
         ::glLineWidth(2.0f);
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 506b3972e7..839969090d 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -101,7 +101,7 @@ class GLGizmoRotate : public GLGizmoBase
     
         mutable Pointf m_center;
         mutable float m_radius;
    -    mutable bool m_keep_radius;
    +    mutable bool m_keep_initial_values;
     
     public:
         GLGizmoRotate();
    diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp
    index 0e0a03f5ed..1a0fc9b9f9 100644
    --- a/xs/xsp/GUI_3DScene.xsp
    +++ b/xs/xsp/GUI_3DScene.xsp
    @@ -65,7 +65,6 @@
                  %};
         Clone bounding_box() const
             %code%{ RETVAL = THIS->bounding_box; %};
    -    Clone transformed_bounding_box() const;
     
         bool                empty() const;
         bool                indexed() const;
    
    From 6b4d8ac934a5af5894bdae80c87d2bb4208c2b20 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Wed, 15 Aug 2018 13:19:58 +0200
    Subject: [PATCH 134/185] Removed plater's toolbar
    
    ---
     lib/Slic3r/GUI/Plater.pm | 292 +++++++++++++++++++--------------------
     1 file changed, 146 insertions(+), 146 deletions(-)
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index da7b8a251e..9f7094700d 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -316,50 +316,50 @@ sub new {
             }
         });
         
    -    # toolbar for object manipulation
    -    if (!&Wx::wxMSW) {
    -        Wx::ToolTip::Enable(1);
    -        $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL);
    -        $self->{htoolbar}->AddTool(TB_ADD, L("Add…"), Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddTool(TB_REMOVE, L("Delete"), Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddTool(TB_RESET, L("Delete All"), Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddTool(TB_ARRANGE, L("Arrange"), Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddSeparator;
    -        $self->{htoolbar}->AddTool(TB_MORE, L("More"), Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddTool(TB_FEWER, L("Fewer"), Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddSeparator;
    -        $self->{htoolbar}->AddTool(TB_45CCW, L("45° ccw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddTool(TB_45CW, L("45° cw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddTool(TB_SCALE, L("Scale…"), Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddTool(TB_SPLIT, L("Split"), Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddTool(TB_CUT, L("Cut…"), Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddSeparator;
    -        $self->{htoolbar}->AddTool(TB_SETTINGS, L("Settings…"), Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), '');
    -        $self->{htoolbar}->AddTool(TB_LAYER_EDITING, L('Layer Editing'), Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing');
    -    } else {
    -        my %tbar_buttons = (
    -            add             => L("Add…"),
    -            remove          => L("Delete"),
    -            reset           => L("Delete All"),
    -            arrange         => L("Arrange"),
    -            increase        => "",
    -            decrease        => "",
    -            rotate45ccw     => "",
    -            rotate45cw      => "",
    -            changescale     => L("Scale…"),
    -            split           => L("Split"),
    -            cut             => L("Cut…"),
    -            settings        => L("Settings…"),
    -            layer_editing   => L("Layer editing"),
    -        );
    -        $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
    -        for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) {
    -            $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
    -            $self->{btoolbar}->Add($self->{"btn_$_"});
    -        }
    -        $self->{"btn_layer_editing"} = Wx::ToggleButton->new($self, -1, $tbar_buttons{'layer_editing'}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
    -        $self->{btoolbar}->Add($self->{"btn_layer_editing"});
    -    }
    +#    # toolbar for object manipulation
    +#    if (!&Wx::wxMSW) {
    +#        Wx::ToolTip::Enable(1);
    +#        $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL);
    +#        $self->{htoolbar}->AddTool(TB_ADD, L("Add…"), Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddTool(TB_REMOVE, L("Delete"), Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddTool(TB_RESET, L("Delete All"), Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddTool(TB_ARRANGE, L("Arrange"), Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddSeparator;
    +#        $self->{htoolbar}->AddTool(TB_MORE, L("More"), Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddTool(TB_FEWER, L("Fewer"), Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddSeparator;
    +#        $self->{htoolbar}->AddTool(TB_45CCW, L("45° ccw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddTool(TB_45CW, L("45° cw"), Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddTool(TB_SCALE, L("Scale…"), Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddTool(TB_SPLIT, L("Split"), Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddTool(TB_CUT, L("Cut…"), Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddSeparator;
    +#        $self->{htoolbar}->AddTool(TB_SETTINGS, L("Settings…"), Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), '');
    +#        $self->{htoolbar}->AddTool(TB_LAYER_EDITING, L('Layer Editing'), Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing');
    +#    } else {
    +#        my %tbar_buttons = (
    +#            add             => L("Add…"),
    +#            remove          => L("Delete"),
    +#            reset           => L("Delete All"),
    +#            arrange         => L("Arrange"),
    +#            increase        => "",
    +#            decrease        => "",
    +#            rotate45ccw     => "",
    +#            rotate45cw      => "",
    +#            changescale     => L("Scale…"),
    +#            split           => L("Split"),
    +#            cut             => L("Cut…"),
    +#            settings        => L("Settings…"),
    +#            layer_editing   => L("Layer editing"),
    +#        );
    +#        $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
    +#        for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) {
    +#            $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
    +#            $self->{btoolbar}->Add($self->{"btn_$_"});
    +#        }
    +#        $self->{"btn_layer_editing"} = Wx::ToggleButton->new($self, -1, $tbar_buttons{'layer_editing'}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
    +#        $self->{btoolbar}->Add($self->{"btn_layer_editing"});
    +#    }
     
         ### Panel for right column
         $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
    @@ -439,39 +439,39 @@ sub new {
         EVT_BUTTON($self, $self->{btn_reslice}, \&reslice);
         EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl);
         
    -    if ($self->{htoolbar}) {
    -        EVT_TOOL($self, TB_ADD, sub { $self->add; });
    -        EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove
    -        EVT_TOOL($self, TB_RESET, sub { $self->reset; });
    -        EVT_TOOL($self, TB_ARRANGE, sub { $self->arrange; });
    -        EVT_TOOL($self, TB_MORE, sub { $self->increase; });
    -        EVT_TOOL($self, TB_FEWER, sub { $self->decrease; });
    -        EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45, Z, 'relative') });
    -        EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45, Z, 'relative') });
    -        EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); });
    -        EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; });
    -        EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog });
    -        EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
    -        EVT_TOOL($self, TB_LAYER_EDITING, sub {
    -            my $state = Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D});
    -            $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, ! $state);
    -            $self->on_layer_editing_toggled(! $state);
    -        });
    -    } else {
    -        EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; });
    -        EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove
    -        EVT_BUTTON($self, $self->{btn_reset}, sub { $self->reset; });
    -        EVT_BUTTON($self, $self->{btn_arrange}, sub { $self->arrange; });
    -        EVT_BUTTON($self, $self->{btn_increase}, sub { $self->increase; });
    -        EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; });
    -        EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45, Z, 'relative') });
    -        EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45, Z, 'relative') });
    -        EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); });
    -        EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; });
    -        EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog });
    -        EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog });
    -        EVT_TOGGLEBUTTON($self, $self->{btn_layer_editing}, sub { $self->on_layer_editing_toggled($self->{btn_layer_editing}->GetValue); });
    -    }
    +#    if ($self->{htoolbar}) {
    +#        EVT_TOOL($self, TB_ADD, sub { $self->add; });
    +#        EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove
    +#        EVT_TOOL($self, TB_RESET, sub { $self->reset; });
    +#        EVT_TOOL($self, TB_ARRANGE, sub { $self->arrange; });
    +#        EVT_TOOL($self, TB_MORE, sub { $self->increase; });
    +#        EVT_TOOL($self, TB_FEWER, sub { $self->decrease; });
    +#        EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45, Z, 'relative') });
    +#        EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45, Z, 'relative') });
    +#        EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); });
    +#        EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; });
    +#        EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog });
    +#        EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
    +#        EVT_TOOL($self, TB_LAYER_EDITING, sub {
    +#            my $state = Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D});
    +#            $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, ! $state);
    +#            $self->on_layer_editing_toggled(! $state);
    +#        });
    +#    } else {
    +#        EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; });
    +#        EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove
    +#        EVT_BUTTON($self, $self->{btn_reset}, sub { $self->reset; });
    +#        EVT_BUTTON($self, $self->{btn_arrange}, sub { $self->arrange; });
    +#        EVT_BUTTON($self, $self->{btn_increase}, sub { $self->increase; });
    +#        EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; });
    +#        EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45, Z, 'relative') });
    +#        EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45, Z, 'relative') });
    +#        EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); });
    +#        EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; });
    +#        EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog });
    +#        EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog });
    +#        EVT_TOGGLEBUTTON($self, $self->{btn_layer_editing}, sub { $self->on_layer_editing_toggled($self->{btn_layer_editing}->GetValue); });
    +#    }
         
         $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self))
             for grep defined($_),
    @@ -628,10 +628,10 @@ sub new {
             my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
             $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1);
             $hsizer->Add($self->{right_panel}, 0, wxEXPAND | wxLEFT | wxRIGHT, 3);
    -        
    +
             my $sizer = Wx::BoxSizer->new(wxVERTICAL);
    -        $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar};
    -        $sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar};
    +#        $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar};
    +#        $sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar};
             $sizer->Add($hsizer, 1, wxEXPAND, 0);
             
             $sizer->SetSizeHints($self);
    @@ -697,13 +697,13 @@ sub on_layer_editing_toggled {
         Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, $new_state);
         if ($new_state && ! Slic3r::GUI::_3DScene::is_layers_editing_enabled($self->{canvas3D})) {
             # Initialization of the OpenGL shaders failed. Disable the tool.
    -        if ($self->{htoolbar}) {
    -            $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0);
    -            $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0);
    -        } else {
    -            $self->{"btn_layer_editing"}->Disable;
    -            $self->{"btn_layer_editing"}->SetValue(0);
    -        }
    +#        if ($self->{htoolbar}) {
    +#            $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0);
    +#            $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0);
    +#        } else {
    +#            $self->{"btn_layer_editing"}->Disable;
    +#            $self->{"btn_layer_editing"}->SetValue(0);
    +#        }
             Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0);
         }
         $self->{canvas3D}->Refresh;
    @@ -1990,24 +1990,24 @@ sub on_config_change {
                 $self->Layout;
             } elsif ($opt_key eq 'variable_layer_height') {
                 if ($config->get('variable_layer_height') != 1) {
    -                if ($self->{htoolbar}) {
    -                    $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0);
    -                    $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0);
    -                } else {
    -                    $self->{"btn_layer_editing"}->Disable;
    -                    $self->{"btn_layer_editing"}->SetValue(0);
    -                }
    +#                if ($self->{htoolbar}) {
    +#                    $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0);
    +#                    $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0);
    +#                } else {
    +#                    $self->{"btn_layer_editing"}->Disable;
    +#                    $self->{"btn_layer_editing"}->SetValue(0);
    +#                }
                     Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 0);
                     Slic3r::GUI::_3DScene::enable_layers_editing($self->{canvas3D}, 0);
                     $self->{canvas3D}->Refresh;
                     $self->{canvas3D}->Update;
                 } elsif (Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D})) {
                     # Want to allow the layer editing, but do it only if the OpenGL supports it.
    -                if ($self->{htoolbar}) {
    -                    $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 1);
    -                } else {
    -                    $self->{"btn_layer_editing"}->Enable;
    -                }
    +#                if ($self->{htoolbar}) {
    +#                    $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 1);
    +#                } else {
    +#                    $self->{"btn_layer_editing"}->Enable;
    +#                }
                     Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, "layersediting", 1);
                 }
             } elsif ($opt_key eq 'extruder_colour') {
    @@ -2176,16 +2176,16 @@ sub object_list_changed {
             
         # Enable/disable buttons depending on whether there are any objects on the platter.
         my $have_objects = @{$self->{objects}} ? 1 : 0;
    -    if ($self->{htoolbar}) {
    -        # On OSX or Linux
    -        $self->{htoolbar}->EnableTool($_, $have_objects)
    -            for (TB_RESET, TB_ARRANGE);
    -    } else {
    -        # On MSW
    -        my $method = $have_objects ? 'Enable' : 'Disable';
    -        $self->{"btn_$_"}->$method
    -            for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode);
    -    }
    +#    if ($self->{htoolbar}) {
    +#        # On OSX or Linux
    +#        $self->{htoolbar}->EnableTool($_, $have_objects)
    +#            for (TB_RESET, TB_ARRANGE);
    +#    } else {
    +#        # On MSW
    +#        my $method = $have_objects ? 'Enable' : 'Disable';
    +#        $self->{"btn_$_"}->$method
    +#            for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode);
    +#    }
     
         for my $toolbar_item (qw(deleteall arrange)) {
             Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_objects);
    @@ -2207,43 +2207,43 @@ sub selection_changed {
         my $layers_height_allowed = $self->{config}->variable_layer_height && Slic3r::GUI::_3DScene::is_layers_editing_allowed($self->{canvas3D}) && $have_sel;
     
         $self->{right_panel}->Freeze;
    -    if ($self->{htoolbar}) {
    -        # On OSX or Linux
    -        $self->{htoolbar}->EnableTool($_, $have_sel)
    -            for (TB_REMOVE, TB_MORE, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS);
    -
    -        $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, $layers_height_allowed);
    -
    -        if ($have_sel) {
    -            my $model_object = $self->{model}->objects->[$obj_idx];
    -            $self->{htoolbar}->EnableTool(TB_FEWER, $model_object->instances_count > 1);
    -        } else {
    -            $self->{htoolbar}->EnableTool(TB_FEWER, 0);
    -        }
    -            
    -    } else {
    -        # On MSW
    -        my $method = $have_sel ? 'Enable' : 'Disable';
    -        $self->{"btn_$_"}->$method
    -            for grep $self->{"btn_$_"}, qw(remove increase rotate45cw rotate45ccw changescale split cut settings);
    -
    -        if ($layers_height_allowed) {
    -            $self->{"btn_layer_editing"}->Enable;
    -        } else {
    -            $self->{"btn_layer_editing"}->Disable;
    -        }
    -
    -        if ($have_sel) {
    -            my $model_object = $self->{model}->objects->[$obj_idx];
    -            if ($model_object->instances_count > 1) {
    -                $self->{"btn_decrease"}->Enable;
    -            } else {
    -                $self->{"btn_decrease"}->Disable;
    -            }            
    -        } else {
    -            $self->{"btn_decrease"}->Disable;
    -        }
    -    }
    +#    if ($self->{htoolbar}) {
    +#        # On OSX or Linux
    +#        $self->{htoolbar}->EnableTool($_, $have_sel)
    +#            for (TB_REMOVE, TB_MORE, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS);
    +#
    +#        $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, $layers_height_allowed);
    +#
    +#        if ($have_sel) {
    +#            my $model_object = $self->{model}->objects->[$obj_idx];
    +#            $self->{htoolbar}->EnableTool(TB_FEWER, $model_object->instances_count > 1);
    +#        } else {
    +#            $self->{htoolbar}->EnableTool(TB_FEWER, 0);
    +#        }
    +#            
    +#    } else {
    +#        # On MSW
    +#        my $method = $have_sel ? 'Enable' : 'Disable';
    +#        $self->{"btn_$_"}->$method
    +#            for grep $self->{"btn_$_"}, qw(remove increase rotate45cw rotate45ccw changescale split cut settings);
    +#
    +#        if ($layers_height_allowed) {
    +#            $self->{"btn_layer_editing"}->Enable;
    +#        } else {
    +#            $self->{"btn_layer_editing"}->Disable;
    +#        }
    +#
    +#        if ($have_sel) {
    +#            my $model_object = $self->{model}->objects->[$obj_idx];
    +#            if ($model_object->instances_count > 1) {
    +#                $self->{"btn_decrease"}->Enable;
    +#            } else {
    +#                $self->{"btn_decrease"}->Disable;
    +#            }            
    +#        } else {
    +#            $self->{"btn_decrease"}->Disable;
    +#        }
    +#    }
         
         for my $toolbar_item (qw(delete more fewer ccw45 cw45 scale split cut settings)) {
             Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_sel);
    
    From 8c7cc73da66608ecd3cfa8ecd10e2851006d42bd Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Wed, 15 Aug 2018 13:59:33 +0200
    Subject: [PATCH 135/185] Update extruder value for the object from the
     beginning
    
    ---
     xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 22 +++++++++++++++-------
     xs/src/slic3r/GUI/wxExtensions.cpp    |  9 ++++++---
     xs/src/slic3r/GUI/wxExtensions.hpp    |  7 +++++--
     3 files changed, 26 insertions(+), 12 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    index 8c29976765..7838bb10e6 100644
    --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    @@ -648,12 +648,15 @@ void add_object_to_list(const std::string &name, ModelObject* model_object)
     
         if (model_object->volumes.size() > 1) {
             for (auto id = 0; id < model_object->volumes.size(); id++)
    -            m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false);
    +            m_objects_model->AddChild(item, 
    +                                      model_object->volumes[id]->name, 
    +                                      m_icon_solidmesh, 
    +                                      model_object->volumes[id]->config.option("extruder")->value,
    +                                      false);
             m_objects_ctrl->Expand(item);
         }
     
    -// 	part_selection_changed();
    -#ifndef __WXOSX__ //#ifdef __WXMSW__
    +#ifndef __WXOSX__ 
     	object_ctrl_selection_changed();
     #endif //__WXMSW__
     }
    @@ -1257,13 +1260,18 @@ void on_btn_split(const bool split_part)
     
             for (auto id = 0; id < model_object->volumes.size(); id++)
                 m_objects_model->AddChild(parent, model_object->volumes[id]->name,
    -            model_object->volumes[id]->modifier ? m_icon_modifiermesh : m_icon_solidmesh, false);
    +                                      model_object->volumes[id]->modifier ? m_icon_modifiermesh : m_icon_solidmesh,
    +                                      model_object->volumes[id]->config.option("extruder")->value, 
    +                                      false);
     
             m_objects_ctrl->Expand(parent);
         }
         else {
             for (auto id = 0; id < model_object->volumes.size(); id++)
    -            m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, false);
    +            m_objects_model->AddChild(item, model_object->volumes[id]->name, 
    +                                      m_icon_solidmesh,
    +                                      model_object->volumes[id]->config.option("extruder")->value, 
    +                                      false);
             m_objects_ctrl->Expand(item);
         }
     }
    @@ -1519,7 +1527,7 @@ void update_rotation_values()
     
         auto rotation_z = (*m_objects)[m_selected_object_id]->instances[0]->rotation;
         auto deg = int(Geometry::rad2deg(rotation_z));
    -    if (deg > 180) deg -= 360;
    +//     if (deg > 180) deg -= 360;
     
         og->set_value("rotation_z", deg);
     }
    @@ -1529,7 +1537,7 @@ void update_rotation_value(const double angle, const std::string& axis)
         auto og = get_optgroup(ogFrequentlyObjectSettings);
         
         int deg = int(Geometry::rad2deg(angle));
    -    if (deg>180) deg -= 360;
    +//     if (deg>180) deg -= 360;
     
         og->set_value("rotation_"+axis, deg);
     }
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index 6c330a3d62..294eea454a 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -385,15 +385,18 @@ wxDataViewItem PrusaObjectDataViewModel::Add(wxString &name, int instances_count
     wxDataViewItem PrusaObjectDataViewModel::AddChild(	const wxDataViewItem &parent_item,
     													const wxString &name,
     													const wxIcon& icon,
    -                                                    bool  create_frst_child/* = true*/)
    +                                                    const int extruder/* = 0*/,
    +                                                    const bool create_frst_child/* = true*/)
     {
     	PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID();
     	if (!root) return wxDataViewItem(0);
     
    +    wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder);
    +
         if (root->GetChildren().Count() == 0 && create_frst_child)
     	{
     		auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG);
    -		auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, 0);
    +		auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, extruder_str, 0);
     		root->Append(node);
     		// notify control
     		wxDataViewItem child((void*)node);
    @@ -401,7 +404,7 @@ wxDataViewItem PrusaObjectDataViewModel::AddChild(	const wxDataViewItem &parent_
     	}
     
     	auto volume_id = root->GetChildCount();
    -	auto node = new PrusaObjectDataViewModelNode(root, name, icon, volume_id);
    +	auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, volume_id);
     	root->Append(node);
     	// notify control
     	wxDataViewItem child((void*)node);
    diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
    index b536232b81..32ae0a4f97 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.hpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.hpp
    @@ -176,7 +176,8 @@ public:
     	PrusaObjectDataViewModelNode(	PrusaObjectDataViewModelNode* parent,
     									const wxString& sub_obj_name, 
     									const wxIcon& icon, 
    -									int volume_id=-1) {
    +                                    const wxString& extruder, 
    +									const int volume_id=-1) {
     		m_parent	= parent;
     		m_name		= sub_obj_name;
     		m_copy		= wxEmptyString;
    @@ -184,6 +185,7 @@ public:
     		m_icon		= icon;
     		m_type		= "volume";
     		m_volume_id = volume_id;
    +        m_extruder  = extruder;
     		set_part_action_icon();
     	}
     
    @@ -352,7 +354,8 @@ public:
     	wxDataViewItem AddChild(const wxDataViewItem &parent_item, 
     							const wxString &name, 
                                 const wxIcon& icon,
    -                            bool  create_frst_child = true);
    +                            const int = 0,
    +                            const bool create_frst_child = true);
     	wxDataViewItem Delete(const wxDataViewItem &item);
     	void DeleteAll();
         void DeleteChildren(wxDataViewItem& parent);
    
    From 3391ea050dcbc5b397f1cca9d69f0e2d6a6627d8 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Thu, 16 Aug 2018 09:12:24 +0200
    Subject: [PATCH 136/185] Try to handle wxEVT_CHAR_HOOK on OSX
    
    ---
     xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 8 +++++++-
     1 file changed, 7 insertions(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    index 7838bb10e6..1ab4a84adc 100644
    --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    @@ -274,7 +274,13 @@ wxBoxSizer* create_objects_list(wxWindow *win)
     		event.Skip();
     	});
     
    -    m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { object_ctrl_key_event(event); });
    +    m_objects_ctrl->Bind(
    +#ifdef __WXOSX__
    +        wxEVT_CHAR_HOOK,
    +#else
    +        wxEVT_CHAR,
    +#endif //__WXOSX__
    +        [](wxKeyEvent& event) { object_ctrl_key_event(event); });
     
     #ifdef __WXMSW__
     	m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); });
    
    From 5de933b77e7a9d841385af358ca78110db637dfa Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Thu, 16 Aug 2018 09:35:21 +0200
    Subject: [PATCH 137/185] Try to fix evt_motion on OSX & GTK
    
    ---
     xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 20 ++++++++++++++++++++
     1 file changed, 20 insertions(+)
    
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    index 1ab4a84adc..3fc42bd4d0 100644
    --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    @@ -209,6 +209,15 @@ wxPoint get_mouse_position_in_control() {
                        pt.y - win->GetScreenPosition().y);
     }
     
    +bool is_mouse_position_in_control(wxPoint& pt) {
    +    pt = get_mouse_position_in_control();
    +    const wxSize& cz = m_objects_ctrl->GetSize();
    +    if (pt.x > 0 && pt.x < cz.x &&
    +        pt.y > 0 && pt.y < cz.y)
    +        return true;
    +    return false;
    +}
    +
     wxDataViewColumn* object_ctrl_create_extruder_column(int extruders_count)
     {
         wxArrayString choices;
    @@ -260,8 +269,10 @@ void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz)
     wxBoxSizer* create_objects_list(wxWindow *win)
     {
         wxBoxSizer* objects_sz;
    +    // create control
         create_objects_ctrl(win, objects_sz);
     
    +    // describe control behavior 
         m_objects_ctrl->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [](wxEvent& event) {
     		object_ctrl_selection_changed();
     #ifndef __WXMSW__
    @@ -283,6 +294,7 @@ wxBoxSizer* create_objects_list(wxWindow *win)
             [](wxKeyEvent& event) { object_ctrl_key_event(event); });
     
     #ifdef __WXMSW__
    +    // Extruder value changed
     	m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); });
     
         m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    @@ -290,7 +302,15 @@ wxBoxSizer* create_objects_list(wxWindow *win)
              event.Skip();
         });
     #else
    +    // equivalent to wxEVT_CHOICE on __WXMSW__
         m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { object_ctrl_item_value_change(event); });
    +
    +    get_right_panel()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    +        event.Skip();
    +        wxPoint pt;
    +        if (is_mouse_position_in_control(pt))
    +            set_tooltip_for_item(pt);
    +    });
     #endif //__WXMSW__
     
         m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG,    [](wxDataViewEvent& e) {on_begin_drag(e);});
    
    From e6fce6e1f63fd988515fb39f978107ccd902518b Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Thu, 16 Aug 2018 09:46:34 +0200
    Subject: [PATCH 138/185] Try to handle wxEVT_KEY_DOWN on OSX
    
    ---
     xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    index 3fc42bd4d0..5715fdeefb 100644
    --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    @@ -287,7 +287,7 @@ wxBoxSizer* create_objects_list(wxWindow *win)
     
         m_objects_ctrl->Bind(
     #ifdef __WXOSX__
    -        wxEVT_CHAR_HOOK,
    +        wxEVT_KEY_DOWN,
     #else
             wxEVT_CHAR,
     #endif //__WXOSX__
    
    From eae7752d300227386ad0927defa377db8b5f7209 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Thu, 16 Aug 2018 10:43:56 +0200
    Subject: [PATCH 139/185] Corrections for the last commit
    
    ---
     xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 38 +++++++++++++++++++--------
     1 file changed, 27 insertions(+), 11 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    index 5715fdeefb..3e4fe906e3 100644
    --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    @@ -293,24 +293,40 @@ wxBoxSizer* create_objects_list(wxWindow *win)
     #endif //__WXOSX__
             [](wxKeyEvent& event) { object_ctrl_key_event(event); });
     
    +    m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    +#ifdef __WXMSW__
    +        set_tooltip_for_item(event.GetPosition());
    +#else
    +        printf("wxEVT_MOTION from GetMainWindow\n");
    +        wxPoint pt;
    +        if (is_mouse_position_in_control(pt))
    +            set_tooltip_for_item(pt);
    +#endif
    +        event.Skip();
    +    });
    +
    +#ifndef __WXMSW__
    +    win->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    +        printf("wxEVT_MOTION from win\n");
    +        wxPoint pt;
    +        if (is_mouse_position_in_control(pt))
    +            set_tooltip_for_item(pt);
    +        event.Skip();
    +    });
    +#endif
    +
     #ifdef __WXMSW__
         // Extruder value changed
     	m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); });
     
    -    m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    -         set_tooltip_for_item(event.GetPosition());
    -         event.Skip();
    -    });
    +//     m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    +//          set_tooltip_for_item(event.GetPosition());
    +//          event.Skip();
    +//     });
    +
     #else
         // equivalent to wxEVT_CHOICE on __WXMSW__
         m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { object_ctrl_item_value_change(event); });
    -
    -    get_right_panel()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    -        event.Skip();
    -        wxPoint pt;
    -        if (is_mouse_position_in_control(pt))
    -            set_tooltip_for_item(pt);
    -    });
     #endif //__WXMSW__
     
         m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG,    [](wxDataViewEvent& e) {on_begin_drag(e);});
    
    From 2c9b41623a3d003134501fa66bfe9c1947ca2db4 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Thu, 16 Aug 2018 13:22:02 +0200
    Subject: [PATCH 140/185] Fixed wipe tower loosing selection after displacement
    
    ---
     xs/src/slic3r/GUI/GLCanvas3D.cpp | 4 ++++
     1 file changed, 4 insertions(+)
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index 92bde34ce7..f5122539e3 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -3017,6 +3017,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 }
                 
                 _on_move(volume_idxs);
    +
    +            // force re-selection of the wipe tower, if needed
    +            if ((volume_idxs.size() == 1) && m_volumes.volumes[volume_idxs[0]]->is_wipe_tower)
    +                select_volume(volume_idxs[0]);
             }
             else if (!m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled())
             {
    
    From d38816bd9c32badaf2f2946f0bfe395c4632858c Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Thu, 16 Aug 2018 13:35:56 +0200
    Subject: [PATCH 141/185] GLVolume use a pointer to ModelVolume's convex hull
     instead of a copy of it
    
    ---
     xs/src/slic3r/GUI/3DScene.cpp | 8 ++++----
     xs/src/slic3r/GUI/3DScene.hpp | 4 ++--
     2 files changed, 6 insertions(+), 6 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 0709271b8c..171f4dbe85 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -204,6 +204,7 @@ GLVolume::GLVolume(float r, float g, float b, float a)
         , m_scale_factor(1.0f)
         , m_transformed_bounding_box_dirty(true)
         , m_transformed_convex_hull_bounding_box_dirty(true)
    +    , m_convex_hull(nullptr)
         , composite_id(-1)
         , select_group_id(-1)
         , drag_group_id(-1)
    @@ -293,7 +294,7 @@ void GLVolume::set_scale_factor(float scale_factor)
     
     void GLVolume::set_convex_hull(const TriangleMesh& convex_hull)
     {
    -    m_convex_hull = convex_hull;
    +    m_convex_hull = &convex_hull;
     }
     
     std::vector GLVolume::world_matrix() const
    @@ -322,8 +323,8 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box() const
     {
         if (m_transformed_convex_hull_bounding_box_dirty)
         {
    -        if (m_convex_hull.stl.stats.number_of_facets > 0)
    -            m_transformed_convex_hull_bounding_box = m_convex_hull.transformed_bounding_box(world_matrix());
    +        if ((m_convex_hull != nullptr) && (m_convex_hull->stl.stats.number_of_facets > 0))
    +            m_transformed_convex_hull_bounding_box = m_convex_hull->transformed_bounding_box(world_matrix());
             else
                 m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix());
     
    @@ -747,7 +748,6 @@ int GLVolumeCollection::load_wipe_tower_preview(
         v.drag_group_id = obj_idx * 1000;
         v.is_wipe_tower = true;
         v.shader_outside_printer_detection_enabled = ! size_unknown;
    -    v.set_convex_hull(mesh.convex_hull_3d());
         return int(this->volumes.size() - 1);
     }
     
    diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
    index 1265bc20de..5cd144c680 100644
    --- a/xs/src/slic3r/GUI/3DScene.hpp
    +++ b/xs/src/slic3r/GUI/3DScene.hpp
    @@ -264,8 +264,8 @@ private:
         mutable BoundingBoxf3 m_transformed_bounding_box;
         // Whether or not is needed to recalculate the transformed bounding box.
         mutable bool          m_transformed_bounding_box_dirty;
    -    // Convex hull of the original mesh, if any.
    -    TriangleMesh          m_convex_hull;
    +    // Pointer to convex hull of the original mesh, if any.
    +    const TriangleMesh*   m_convex_hull;
         // Bounding box of this volume, in unscaled coordinates.
         mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
         // Whether or not is needed to recalculate the transformed convex hull bounding box.
    
    From 1fff2252bce88b4a7099f6347d7b264ecb793571 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Thu, 16 Aug 2018 13:42:35 +0200
    Subject: [PATCH 142/185] Detection of out of print volume disabled for wipe
     tower of unknown size
    
    ---
     xs/src/slic3r/GUI/3DScene.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 171f4dbe85..7053470943 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -833,7 +833,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
     
         for (GLVolume* volume : this->volumes)
         {
    -        if ((volume != nullptr) && !volume->is_modifier)
    +        if ((volume != nullptr) && !volume->is_modifier && (!volume->is_wipe_tower || (volume->is_wipe_tower && volume->shader_outside_printer_detection_enabled)))
             {
                 const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
                 bool contained = print_volume.contains(bb);
    
    From d10cdeb25f5f1ac0ff7924efa142ff98c39aecd6 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Thu, 16 Aug 2018 16:43:16 +0200
    Subject: [PATCH 143/185] Delete previous experiments
    
    ---
     xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 39 ++++-----------------------
     1 file changed, 5 insertions(+), 34 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    index 3e4fe906e3..e9d08ede52 100644
    --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp
    @@ -285,45 +285,16 @@ wxBoxSizer* create_objects_list(wxWindow *win)
     		event.Skip();
     	});
     
    -    m_objects_ctrl->Bind(
    -#ifdef __WXOSX__
    -        wxEVT_KEY_DOWN,
    -#else
    -        wxEVT_CHAR,
    -#endif //__WXOSX__
    -        [](wxKeyEvent& event) { object_ctrl_key_event(event); });
    -
    -    m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    -#ifdef __WXMSW__
    -        set_tooltip_for_item(event.GetPosition());
    -#else
    -        printf("wxEVT_MOTION from GetMainWindow\n");
    -        wxPoint pt;
    -        if (is_mouse_position_in_control(pt))
    -            set_tooltip_for_item(pt);
    -#endif
    -        event.Skip();
    -    });
    -
    -#ifndef __WXMSW__
    -    win->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    -        printf("wxEVT_MOTION from win\n");
    -        wxPoint pt;
    -        if (is_mouse_position_in_control(pt))
    -            set_tooltip_for_item(pt);
    -        event.Skip();
    -    });
    -#endif
    +    m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { object_ctrl_key_event(event); }); // doesn't work on OSX
     
     #ifdef __WXMSW__
         // Extruder value changed
     	m_objects_ctrl->Bind(wxEVT_CHOICE, [](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); });
     
    -//     m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    -//          set_tooltip_for_item(event.GetPosition());
    -//          event.Skip();
    -//     });
    -
    +    m_objects_ctrl->GetMainWindow()->Bind(wxEVT_MOTION, [](wxMouseEvent& event) {
    +         set_tooltip_for_item(event.GetPosition());
    +         event.Skip();
    +    });
     #else
         // equivalent to wxEVT_CHOICE on __WXMSW__
         m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [](wxDataViewEvent& event) { object_ctrl_item_value_change(event); });
    
    From b6e0458201128b5db4db5faaeb41d307e395edd6 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 17 Aug 2018 09:16:34 +0200
    Subject: [PATCH 144/185] Fixed lost selection of imported objects
    
    ---
     lib/Slic3r/GUI/Plater.pm | 6 +++++-
     1 file changed, 5 insertions(+), 1 deletion(-)
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index a0eef72fea..4fd9a2692a 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -1291,7 +1291,9 @@ sub async_apply_config {
     
             # We also need to reload 3D scene because of the wipe tower preview box
             if ($self->{config}->wipe_tower) {
    -	       Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1) if $self->{canvas3D}
    +            my $selections = $self->collect_selections;
    +            Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
    +	        Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1) if $self->{canvas3D}
             }
         }
     }
    @@ -1507,6 +1509,8 @@ sub on_process_completed {
         $self->{preview3D}->reload_print if $self->{preview3D};
     
         # in case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth:
    +    my $selections = $self->collect_selections;
    +    Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections);
         Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1);
         
         # if we have an export filename, start a new thread for exporting G-code
    
    From 048f3a03fe884566b2ec290164e46ca3fce39b28 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 17 Aug 2018 10:12:43 +0200
    Subject: [PATCH 145/185] Fixed scale to size of objects with multiple
     instances
    
    ---
     lib/Slic3r/GUI/Plater.pm | 5 ++---
     1 file changed, 2 insertions(+), 3 deletions(-)
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index 4fd9a2692a..b4677b1af6 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -1126,8 +1126,7 @@ sub changescale {
         my $model_object = $self->{model}->objects->[$obj_idx];
         my $model_instance = $model_object->instances->[0];
         
    -    my $object_size = $model_object->bounding_box->size;
    -    my $bed_size = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape})->bounding_box->size;
    +    my $object_size = $model_object->instance_bounding_box(0)->size;
         
         if (defined $axis) {
             my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
    @@ -1135,7 +1134,7 @@ sub changescale {
             if ($tosize) {
                 my $cursize = $object_size->[$axis];
                 my $newsize = $self->_get_number_from_user(
    -                sprintf(L('Enter the new size for the selected object (print bed: %smm):'), unscale($bed_size->[$axis])), 
    +                L('Enter the new size for the selected object:'), 
                     L("Scale along ").$axis_name, L('Invalid scaling value entered'), $cursize, 1);
                 return if $newsize eq '';
                 $scale = $newsize / $cursize * 100;
    
    From 53914e05c6ef769271849f5556973b5d92abd3f3 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Mon, 20 Aug 2018 10:23:17 +0200
    Subject: [PATCH 146/185] 1st installment of gizmo rotate 3D
    
    ---
     xs/src/slic3r/GUI/GLCanvas3D.cpp |  33 +-
     xs/src/slic3r/GUI/GLCanvas3D.hpp |   5 +-
     xs/src/slic3r/GUI/GLGizmo.cpp    | 588 +++++++++++++++++++++++--------
     xs/src/slic3r/GUI/GLGizmo.hpp    | 122 +++++--
     4 files changed, 569 insertions(+), 179 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index a55ba51ae8..1b24e37335 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -1139,7 +1139,11 @@ bool GLCanvas3D::Gizmos::init()
     
         m_gizmos.insert(GizmosMap::value_type(Scale, gizmo));
     
    -    gizmo = new GLGizmoRotate;
    +#if ENABLE_GIZMOS_3D
    +    gizmo = new GLGizmoRotate3D;
    +#else
    +    gizmo = new GLGizmoRotate(GLGizmoRotate::Z);
    +#endif // ENABLE_GIZMOS_3D
         if (gizmo == nullptr)
         {
             _reset();
    @@ -1294,14 +1298,14 @@ bool GLCanvas3D::Gizmos::grabber_contains_mouse() const
         return (curr != nullptr) ? (curr->get_hover_id() != -1) : false;
     }
     
    -void GLCanvas3D::Gizmos::update(const Pointf& mouse_pos)
    +void GLCanvas3D::Gizmos::update(const Linef3& mouse_ray)
     {
         if (!m_enabled)
             return;
     
         GLGizmoBase* curr = _get_current();
         if (curr != nullptr)
    -        curr->update(mouse_pos);
    +        curr->update(mouse_ray);
     }
     
     void GLCanvas3D::Gizmos::refresh()
    @@ -1374,7 +1378,11 @@ float GLCanvas3D::Gizmos::get_angle_z() const
             return 0.0f;
     
         GizmosMap::const_iterator it = m_gizmos.find(Rotate);
    -    return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_angle_z() : 0.0f;
    +#if ENABLE_GIZMOS_3D
    +    return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_angle_z() : 0.0f;
    +#else
    +    return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_angle() : 0.0f;
    +#endif // ENABLE_GIZMOS_3D
     }
     
     void GLCanvas3D::Gizmos::set_angle_z(float angle_z)
    @@ -1384,7 +1392,11 @@ void GLCanvas3D::Gizmos::set_angle_z(float angle_z)
     
         GizmosMap::const_iterator it = m_gizmos.find(Rotate);
         if (it != m_gizmos.end())
    -        reinterpret_cast(it->second)->set_angle_z(angle_z);
    +#if ENABLE_GIZMOS_3D
    +reinterpret_cast(it->second)->set_angle_z(angle_z);
    +#else
    +        reinterpret_cast(it->second)->set_angle(angle_z);
    +#endif // ENABLE_GIZMOS_3D
     }
     
     void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const
    @@ -3096,9 +3108,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         else if (evt.Dragging() && m_gizmos.is_dragging())
         {
             m_mouse.dragging = true;
    -
    -        const Pointf3& cur_pos = _mouse_to_bed_3d(pos);
    -        m_gizmos.update(Pointf(cur_pos.x, cur_pos.y));
    +        m_gizmos.update(mouse_ray(pos));
     
             std::vector volumes;
             if (m_mouse.drag.gizmo_volume_idx != -1)
    @@ -4128,10 +4138,15 @@ Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
     }
     
     Pointf3 GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
    +{
    +    return mouse_ray(mouse_pos).intersect_plane(0.0);
    +}
    +
    +Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
     {
         float z0 = 0.0f;
         float z1 = 1.0f;
    -    return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1)).intersect_plane(0.0);
    +    return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1));
     }
     
     void GLCanvas3D::_start_timer()
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    index e1b323a748..585c2526e0 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    @@ -365,7 +365,7 @@ public:
     
             bool overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const;
             bool grabber_contains_mouse() const;
    -        void update(const Pointf& mouse_pos);
    +        void update(const Linef3& mouse_ray);
             void refresh();
     
             EType get_current_type() const;
    @@ -694,6 +694,9 @@ private:
         // Convert the screen space coordinate to world coordinate on the bed.
         Pointf3 _mouse_to_bed_3d(const Point& mouse_pos);
     
    +    // Returns the view ray line, in world coordinate, at the given mouse position.
    +    Linef3 mouse_ray(const Point& mouse_pos);
    +
         void _start_timer();
         void _stop_timer();
     
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 7207022bd3..1ee8f6f378 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -3,21 +3,28 @@
     #include "../../libslic3r/Utils.hpp"
     #include "../../libslic3r/BoundingBox.hpp"
     
    +#include 
    +
     #include 
     
     #include 
     
    +static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f };
    +static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f };
    +static const float DEFAULT_HIGHLIGHT_COLOR[3] = { 1.0f, 0.38f, 0.0f };
    +
     namespace Slic3r {
     namespace GUI {
     
     const float GLGizmoBase::Grabber::HalfSize = 2.0f;
    -const float GLGizmoBase::Grabber::HoverOffset = 0.5f;
    -const float GLGizmoBase::BaseColor[3] = { 1.0f, 1.0f, 1.0f };
    -const float GLGizmoBase::HighlightColor[3] = { 1.0f, 0.38f, 0.0f };
    +const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f;
     
     GLGizmoBase::Grabber::Grabber()
    -    : center(Pointf(0.0, 0.0))
    +    : center(Pointf3(0.0, 0.0, 0.0))
    +    , angle_x(0.0f)
    +    , angle_y(0.0f)
         , angle_z(0.0f)
    +    , dragging(false)
     {
         color[0] = 1.0f;
         color[1] = 1.0f;
    @@ -26,17 +33,41 @@ GLGizmoBase::Grabber::Grabber()
     
     void GLGizmoBase::Grabber::render(bool hover) const
     {
    -    float min_x = -HalfSize;
    -    float max_x = +HalfSize;
    -    float min_y = -HalfSize;
    -    float max_y = +HalfSize;
    +    float render_color[3];
    +    if (hover)
    +    {
    +        render_color[0] = 1.0f - color[0];
    +        render_color[1] = 1.0f - color[1];
    +        render_color[2] = 1.0f - color[2];
    +    }
    +    else
    +        ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float));
     
    -    ::glColor3f((GLfloat)color[0], (GLfloat)color[1], (GLfloat)color[2]);
    +    render(render_color);
    +}
    +
    +void GLGizmoBase::Grabber::render_for_picking() const
    +{
    +    render(color);
    +}
    +
    +void GLGizmoBase::Grabber::render(const float* render_color) const
    +{
    +    float half_size = dragging ? HalfSize * DraggingScaleFactor : HalfSize;
    +    float min_x = -half_size;
    +    float max_x = +half_size;
    +    float min_y = -half_size;
    +    float max_y = +half_size;
    +
    +    ::glColor3f((GLfloat)render_color[0], (GLfloat)render_color[1], (GLfloat)render_color[2]);
     
    -    float angle_z_in_deg = angle_z * 180.0f / (float)PI;
         ::glPushMatrix();
    -    ::glTranslatef((GLfloat)center.x, (GLfloat)center.y, 0.0f);
    -    ::glRotatef((GLfloat)angle_z_in_deg, 0.0f, 0.0f, 1.0f);
    +    ::glTranslatef((GLfloat)center.x, (GLfloat)center.y, (GLfloat)center.z);
    +
    +    float rad_to_deg = 180.0f / (GLfloat)PI;
    +    ::glRotatef((GLfloat)angle_x * rad_to_deg, 1.0f, 0.0f, 0.0f);
    +    ::glRotatef((GLfloat)angle_y * rad_to_deg, 0.0f, 1.0f, 0.0f);
    +    ::glRotatef((GLfloat)angle_z * rad_to_deg, 0.0f, 0.0f, 1.0f);
     
         ::glDisable(GL_CULL_FACE);
         ::glBegin(GL_TRIANGLES);
    @@ -49,48 +80,18 @@ void GLGizmoBase::Grabber::render(bool hover) const
         ::glEnd();
         ::glEnable(GL_CULL_FACE);
     
    -    if (hover)
    -    {
    -        min_x -= HoverOffset;
    -        max_x += HoverOffset;
    -        min_y -= HoverOffset;
    -        max_y += HoverOffset;
    -
    -        ::glBegin(GL_LINE_LOOP);
    -        ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
    -        ::glVertex3f((GLfloat)max_x, (GLfloat)min_y, 0.0f);
    -        ::glVertex3f((GLfloat)max_x, (GLfloat)max_y, 0.0f);
    -        ::glVertex3f((GLfloat)min_x, (GLfloat)max_y, 0.0f);
    -        ::glEnd();
    -    }
    -
         ::glPopMatrix();
     }
     
     GLGizmoBase::GLGizmoBase()
    -    : m_state(Off)
    +    : m_group_id(-1)
    +    , m_state(Off)
         , m_hover_id(-1)
    +    , m_is_container(false)
     {
    -}
    -
    -GLGizmoBase::~GLGizmoBase()
    -{
    -}
    -
    -bool GLGizmoBase::init()
    -{
    -    return on_init();
    -}
    -
    -GLGizmoBase::EState GLGizmoBase::get_state() const
    -{
    -    return m_state;
    -}
    -
    -void GLGizmoBase::set_state(GLGizmoBase::EState state)
    -{
    -    m_state = state;
    -    on_set_state();
    +    ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float));
    +    ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float));
    +    ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 3 * sizeof(float));
     }
     
     unsigned int GLGizmoBase::get_texture_id() const
    @@ -103,31 +104,45 @@ int GLGizmoBase::get_textures_size() const
         return m_textures[Off].get_width();
     }
     
    -int GLGizmoBase::get_hover_id() const
    -{
    -    return m_hover_id;
    -}
    -
     void GLGizmoBase::set_hover_id(int id)
     {
    -    if (id < (int)m_grabbers.size())
    +    if (m_is_container || (id < (int)m_grabbers.size()))
    +    {
             m_hover_id = id;
    +        on_set_hover_id();
    +    }
    +}
    +
    +void GLGizmoBase::set_highlight_color(const float* color)
    +{
    +    if (color != nullptr)
    +        ::memcpy((void*)m_highlight_color, (const void*)color, 3 * sizeof(float));
     }
     
     void GLGizmoBase::start_dragging()
     {
    +    for (int i = 0; i < (int)m_grabbers.size(); ++i)
    +    {
    +        m_grabbers[i].dragging = (m_hover_id == i);
    +    }
    +
         on_start_dragging();
     }
     
     void GLGizmoBase::stop_dragging()
     {
    +    for (int i = 0; i < (int)m_grabbers.size(); ++i)
    +    {
    +        m_grabbers[i].dragging = false;
    +    }
    +
         on_stop_dragging();
     }
     
    -void GLGizmoBase::update(const Pointf& mouse_pos)
    +void GLGizmoBase::update(const Linef3& mouse_ray)
     {
         if (m_hover_id != -1)
    -        on_update(mouse_pos);
    +        on_update(mouse_ray);
     }
     
     void GLGizmoBase::refresh()
    @@ -145,24 +160,13 @@ void GLGizmoBase::render_for_picking(const BoundingBoxf3& box) const
         on_render_for_picking(box);
     }
     
    -void GLGizmoBase::on_set_state()
    +float GLGizmoBase::picking_color_component(unsigned int id) const
     {
    -    // do nothing
    -}
    +    int color = 254 - (int)id;
    +    if (m_group_id > -1)
    +        color -= m_group_id;
     
    -void GLGizmoBase::on_start_dragging()
    -{
    -    // do nothing
    -}
    -
    -void GLGizmoBase::on_stop_dragging()
    -{
    -    // do nothing
    -}
    -
    -void GLGizmoBase::on_refresh()
    -{
    -    // do nothing
    +    return (float)color / 255.0f;
     }
     
     void GLGizmoBase::render_grabbers() const
    @@ -173,54 +177,65 @@ void GLGizmoBase::render_grabbers() const
         }
     }
     
    +void GLGizmoBase::render_grabbers_for_picking() const
    +{
    +    for (int i = 0; i < (int)m_grabbers.size(); ++i)
    +    {
    +        m_grabbers[i].render_for_picking();
    +    }
    +}
    +
     const float GLGizmoRotate::Offset = 5.0f;
     const unsigned int GLGizmoRotate::CircleResolution = 64;
     const unsigned int GLGizmoRotate::AngleResolution = 64;
    -const unsigned int GLGizmoRotate::ScaleStepsCount = 60;
    +const unsigned int GLGizmoRotate::ScaleStepsCount = 72;
     const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount;
    -const unsigned int GLGizmoRotate::ScaleLongEvery = 5;
    +const unsigned int GLGizmoRotate::ScaleLongEvery = 2;
     const float GLGizmoRotate::ScaleLongTooth = 2.0f;
     const float GLGizmoRotate::ScaleShortTooth = 1.0f;
     const unsigned int GLGizmoRotate::SnapRegionsCount = 8;
     const float GLGizmoRotate::GrabberOffset = 5.0f;
     
    -GLGizmoRotate::GLGizmoRotate()
    +GLGizmoRotate::GLGizmoRotate(GLGizmoRotate::Axis axis)
         : GLGizmoBase()
    -    , m_angle_z(0.0f)
    -    , m_center(Pointf(0.0, 0.0))
    +    , m_axis(axis)
    +    , m_angle(0.0f)
    +    , m_center(Pointf3(0.0, 0.0, 0.0))
         , m_radius(0.0f)
         , m_keep_initial_values(false)
     {
     }
     
    -float GLGizmoRotate::get_angle_z() const
    +float GLGizmoRotate::get_angle() const
     {
    -    return m_angle_z;
    +    return m_angle;
     }
     
    -void GLGizmoRotate::set_angle_z(float angle_z)
    +void GLGizmoRotate::set_angle(float angle)
     {
    -    if (std::abs(angle_z - 2.0f * PI) < EPSILON)
    -        angle_z = 0.0f;
    +    if (std::abs(angle - 2.0f * PI) < EPSILON)
    +        angle = 0.0f;
     
    -    m_angle_z = angle_z;
    +    m_angle = angle;
     }
     
     bool GLGizmoRotate::on_init()
     {
    +#if !ENABLE_GIZMOS_3D
         std::string path = resources_dir() + "/icons/overlay/";
     
         std::string filename = path + "rotate_off.png";
         if (!m_textures[Off].load_from_file(filename, false))
             return false;
    -
    +    
         filename = path + "rotate_hover.png";
         if (!m_textures[Hover].load_from_file(filename, false))
             return false;
    -
    +    
         filename = path + "rotate_on.png";
         if (!m_textures[On].load_from_file(filename, false))
             return false;
    +#endif // !ENABLE_GIZMOS_3D
     
         m_grabbers.push_back(Grabber());
     
    @@ -232,16 +247,22 @@ void GLGizmoRotate::on_set_state()
         m_keep_initial_values = (m_state == On) ? false : true;
     }
     
    -void GLGizmoRotate::on_update(const Pointf& mouse_pos)
    +void GLGizmoRotate::on_update(const Linef3& mouse_ray)
     {
    +    Pointf mouse_pos = mouse_position_in_local_plane(mouse_ray);
    +
         Vectorf orig_dir(1.0, 0.0);
    -    Vectorf new_dir = normalize(mouse_pos - m_center);
    +    Vectorf new_dir = normalize(mouse_pos);
    +
         coordf_t theta = ::acos(clamp(-1.0, 1.0, dot(new_dir, orig_dir)));
         if (cross(orig_dir, new_dir) < 0.0)
             theta = 2.0 * (coordf_t)PI - theta;
     
         // snap
    -    if (length(m_center.vector_to(mouse_pos)) < 2.0 * (double)m_radius / 3.0)
    +    double len = length(mouse_pos);
    +    double in_radius = (double)m_radius / 3.0;
    +    double out_radius = 2.0 * (double)in_radius;
    +    if ((in_radius <= len) && (len <= out_radius))
         {
             coordf_t step = 2.0 * (coordf_t)PI / (coordf_t)SnapRegionsCount;
             theta = step * (coordf_t)std::round(theta / step);
    @@ -250,7 +271,7 @@ void GLGizmoRotate::on_update(const Pointf& mouse_pos)
         if (theta == 2.0 * (coordf_t)PI)
             theta = 0.0;
     
    -    m_angle_z = (float)theta;
    +    m_angle = (float)theta;
     }
     
     void GLGizmoRotate::on_refresh()
    @@ -266,47 +287,83 @@ void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
         {
             const Pointf3& size = box.size();
             m_center = box.center();
    +#if !ENABLE_GIZMOS_3D
    +        m_center.z = 0.0;
    +#endif // !ENABLE_GIZMOS_3D
    +
    +#if ENABLE_GIZMOS_3D
    +        m_radius = Offset + box.radius();
    +#else
             m_radius = Offset + ::sqrt(sqr(0.5f * size.x) + sqr(0.5f * size.y));
    +#endif // ENABLE_GIZMOS_3D
             m_keep_initial_values = true;
         }
     
    +    ::glPushMatrix();
    +    transform_to_local();
    +
         ::glLineWidth(2.0f);
    -    ::glColor3fv(BaseColor);
     
    -    _render_circle();
    -    _render_scale();
    -    _render_snap_radii();
    -    _render_reference_radius();
    +#if ENABLE_GIZMOS_3D
    +    ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_base_color);
    +#else
    +    ::glColor3fv(m_drag_color);
    +#endif // ENABLE_GIZMOS_3D
     
    -    ::glColor3fv(HighlightColor);
    -    _render_angle_z();
    -    _render_grabber();
    +    render_circle();
    +#if ENABLE_GIZMOS_3D
    +    if (m_hover_id != -1)
    +    {
    +#endif // ENABLE_GIZMOS_3D
    +        render_scale();
    +        render_snap_radii();
    +        render_reference_radius();
    +#if ENABLE_GIZMOS_3D
    +    }
    +#endif // ENABLE_GIZMOS_3D
    +
    +    ::glColor3fv(m_highlight_color);
    +#if ENABLE_GIZMOS_3D
    +    if (m_hover_id != -1)
    +#endif // ENABLE_GIZMOS_3D
    +        render_angle();
    +
    +    render_grabber();
    +
    +    ::glPopMatrix();
     }
     
     void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const
     {
         ::glDisable(GL_DEPTH_TEST);
     
    +    ::glPushMatrix();
    +    transform_to_local();
    +
         m_grabbers[0].color[0] = 1.0f;
         m_grabbers[0].color[1] = 1.0f;
    -    m_grabbers[0].color[2] = 254.0f / 255.0f;
    -    render_grabbers();
    +    m_grabbers[0].color[2] = picking_color_component(0);
    +
    +    render_grabbers_for_picking();
    +
    +    ::glPopMatrix();
     }
     
    -void GLGizmoRotate::_render_circle() const
    +void GLGizmoRotate::render_circle() const
     {
         ::glBegin(GL_LINE_LOOP);
         for (unsigned int i = 0; i < ScaleStepsCount; ++i)
         {
             float angle = (float)i * ScaleStepRad;
    -        float x = m_center.x + ::cos(angle) * m_radius;
    -        float y = m_center.y + ::sin(angle) * m_radius;
    -        ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f);
    +        float x = ::cos(angle) * m_radius;
    +        float y = ::sin(angle) * m_radius;
    +        float z = 0.0f;
    +        ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
         }
         ::glEnd();
     }
     
    -void GLGizmoRotate::_render_scale() const
    +void GLGizmoRotate::render_scale() const
     {
         float out_radius_long = m_radius + ScaleLongTooth;
         float out_radius_short = m_radius + ScaleShortTooth;
    @@ -317,17 +374,19 @@ void GLGizmoRotate::_render_scale() const
             float angle = (float)i * ScaleStepRad;
             float cosa = ::cos(angle);
             float sina = ::sin(angle);
    -        float in_x = m_center.x + cosa * m_radius;
    -        float in_y = m_center.y + sina * m_radius;
    -        float out_x = (i % ScaleLongEvery == 0) ? m_center.x + cosa * out_radius_long : m_center.x + cosa * out_radius_short;
    -        float out_y = (i % ScaleLongEvery == 0) ? m_center.y + sina * out_radius_long : m_center.y + sina * out_radius_short;
    -        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f);
    -        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f);
    +        float in_x = cosa * m_radius;
    +        float in_y = sina * m_radius;
    +        float in_z = 0.0f;
    +        float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short;
    +        float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short;
    +        float out_z = 0.0f;
    +        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z);
    +        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z);
         }
         ::glEnd();
     }
     
    -void GLGizmoRotate::_render_snap_radii() const
    +void GLGizmoRotate::render_snap_radii() const
     {
         float step = 2.0f * (float)PI / (float)SnapRegionsCount;
     
    @@ -340,57 +399,306 @@ void GLGizmoRotate::_render_snap_radii() const
             float angle = (float)i * step;
             float cosa = ::cos(angle);
             float sina = ::sin(angle);
    -        float in_x = m_center.x + cosa * in_radius;
    -        float in_y = m_center.y + sina * in_radius;
    -        float out_x = m_center.x + cosa * out_radius;
    -        float out_y = m_center.y + sina * out_radius;
    -        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, 0.0f);
    -        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, 0.0f);
    +        float in_x = cosa * in_radius;
    +        float in_y = sina * in_radius;
    +        float in_z = 0.0f;
    +        float out_x = cosa * out_radius;
    +        float out_y = sina * out_radius;
    +        float out_z = 0.0f;
    +        ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z);
    +        ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z);
         }
         ::glEnd();
     }
     
    -void GLGizmoRotate::_render_reference_radius() const
    +void GLGizmoRotate::render_reference_radius() const
     {
         ::glBegin(GL_LINES);
    -    ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f);
    -    ::glVertex3f((GLfloat)m_center.x + m_radius + GrabberOffset, (GLfloat)m_center.y, 0.0f);
    +    ::glVertex3f(0.0f, 0.0f, 0.0f);
    +    ::glVertex3f((GLfloat)(m_radius + GrabberOffset), 0.0f, 0.0f);
         ::glEnd();
     }
     
    -void GLGizmoRotate::_render_angle_z() const
    +void GLGizmoRotate::render_angle() const
     {
    -    float step_angle = m_angle_z / AngleResolution;
    +    float step_angle = m_angle / AngleResolution;
         float ex_radius = m_radius + GrabberOffset;
     
         ::glBegin(GL_LINE_STRIP);
         for (unsigned int i = 0; i <= AngleResolution; ++i)
         {
             float angle = (float)i * step_angle;
    -        float x = m_center.x + ::cos(angle) * ex_radius;
    -        float y = m_center.y + ::sin(angle) * ex_radius;
    -        ::glVertex3f((GLfloat)x, (GLfloat)y, 0.0f);
    +        float x = ::cos(angle) * ex_radius;
    +        float y = ::sin(angle) * ex_radius;
    +        float z = 0.0f;
    +        ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
         }
         ::glEnd();
     }
     
    -void GLGizmoRotate::_render_grabber() const
    +void GLGizmoRotate::render_grabber() const
     {
         float grabber_radius = m_radius + GrabberOffset;
    -    m_grabbers[0].center.x = m_center.x + ::cos(m_angle_z) * grabber_radius;
    -    m_grabbers[0].center.y = m_center.y + ::sin(m_angle_z) * grabber_radius;
    -    m_grabbers[0].angle_z = m_angle_z;
    +    m_grabbers[0].center.x = ::cos(m_angle) * grabber_radius;
    +    m_grabbers[0].center.y = ::sin(m_angle) * grabber_radius;
    +    m_grabbers[0].center.z = 0.0f;
    +    m_grabbers[0].angle_z = m_angle;
    +
    +#if ENABLE_GIZMOS_3D
    +    ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_base_color);
    +#else
    +    ::glColor3fv(m_drag_color);
    +#endif // ENABLE_GIZMOS_3D
     
    -    ::glColor3fv(BaseColor);
         ::glBegin(GL_LINES);
    -    ::glVertex3f((GLfloat)m_center.x, (GLfloat)m_center.y, 0.0f);
    -    ::glVertex3f((GLfloat)m_grabbers[0].center.x, (GLfloat)m_grabbers[0].center.y, 0.0f);
    +    ::glVertex3f(0.0f, 0.0f, 0.0f);
    +    ::glVertex3f((GLfloat)m_grabbers[0].center.x, (GLfloat)m_grabbers[0].center.y, (GLfloat)m_grabbers[0].center.z);
         ::glEnd();
     
    -    ::memcpy((void*)m_grabbers[0].color, (const void*)HighlightColor, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float));
         render_grabbers();
     }
     
    +void GLGizmoRotate::transform_to_local() const
    +{
    +    ::glTranslatef((GLfloat)m_center.x, (GLfloat)m_center.y, (GLfloat)m_center.z);
    +
    +    switch (m_axis)
    +    {
    +    case X:
    +    {
    +        ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    +        ::glRotatef(90.0f, 0.0f, 0.0f, 1.0f);
    +        break;
    +    }
    +    case Y:
    +    {
    +        ::glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
    +        ::glRotatef(180.0f, 0.0f, 0.0f, 1.0f);
    +        break;
    +    }
    +    default:
    +    case Z:
    +    {
    +        // no rotation
    +        break;
    +    }
    +    }
    +}
    +
    +Pointf GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray) const
    +{
    +    Eigen::Transform m = Eigen::Transform::Identity();
    +
    +    switch (m_axis)
    +    {
    +    case X:
    +    {
    +        m.rotate(Eigen::AngleAxisf(-0.5f * (float)PI, Eigen::Vector3f::UnitZ()));
    +        m.rotate(Eigen::AngleAxisf(-0.5f * (float)PI, Eigen::Vector3f::UnitY()));
    +        break;
    +    }
    +    case Y:
    +    {
    +        m.rotate(Eigen::AngleAxisf(-(float)PI, Eigen::Vector3f::UnitZ()));
    +        m.rotate(Eigen::AngleAxisf(-0.5f * (float)PI, Eigen::Vector3f::UnitX()));
    +        break;
    +    }
    +    default:
    +    case Z:
    +    {
    +        // no rotation applied
    +        break;
    +    }
    +    }
    +
    +    m.translate(Eigen::Vector3f((float)-m_center.x, (float)-m_center.y, (float)-m_center.z));
    +
    +    Eigen::Matrix world_ray;
    +    Eigen::Matrix local_ray;
    +    world_ray(0, 0) = (float)mouse_ray.a.x;
    +    world_ray(1, 0) = (float)mouse_ray.a.y;
    +    world_ray(2, 0) = (float)mouse_ray.a.z;
    +    world_ray(0, 1) = (float)mouse_ray.b.x;
    +    world_ray(1, 1) = (float)mouse_ray.b.y;
    +    world_ray(2, 1) = (float)mouse_ray.b.z;
    +    local_ray = m * world_ray.colwise().homogeneous();
    +
    +    return Linef3(Pointf3(local_ray(0, 0), local_ray(1, 0), local_ray(2, 0)), Pointf3(local_ray(0, 1), local_ray(1, 1), local_ray(2, 1))).intersect_plane(0.0);
    +}
    +
    +GLGizmoRotate3D::GLGizmoRotate3D()
    +    : GLGizmoBase()
    +    , m_x(GLGizmoRotate::X)
    +    , m_y(GLGizmoRotate::Y)
    +    , m_z(GLGizmoRotate::Z)
    +{
    +    m_is_container = true;
    +
    +    m_x.set_group_id(0);
    +    m_y.set_group_id(1);
    +    m_z.set_group_id(2);
    +}
    +
    +float GLGizmoRotate3D::get_angle_x() const
    +{
    +    return m_x.get_angle();
    +}
    +
    +void GLGizmoRotate3D::set_angle_x(float angle)
    +{
    +    m_x.set_angle(angle);
    +}
    +
    +float GLGizmoRotate3D::get_angle_y() const
    +{
    +    return m_y.get_angle();
    +}
    +
    +void GLGizmoRotate3D::set_angle_y(float angle)
    +{
    +    m_y.set_angle(angle);
    +}
    +
    +float GLGizmoRotate3D::get_angle_z() const
    +{
    +    return m_z.get_angle();
    +}
    +
    +void GLGizmoRotate3D::set_angle_z(float angle)
    +{
    +    m_z.set_angle(angle);
    +}
    +
    +bool GLGizmoRotate3D::on_init()
    +{
    +    if (!m_x.init() || !m_y.init() || !m_z.init())
    +        return false;
    +
    +    float red[3] = { 1.0f, 0.0f, 0.0f };
    +    float green[3] = { 0.0f, 1.0f, 0.0f };
    +    float blue[3] = { 0.0f, 0.0f, 1.0f };
    +
    +    m_x.set_highlight_color(red);
    +    m_y.set_highlight_color(green);
    +    m_z.set_highlight_color(blue);
    +
    +    std::string path = resources_dir() + "/icons/overlay/";
    +
    +    std::string filename = path + "rotate_off.png";
    +    if (!m_textures[Off].load_from_file(filename, false))
    +        return false;
    +
    +    filename = path + "rotate_hover.png";
    +    if (!m_textures[Hover].load_from_file(filename, false))
    +        return false;
    +
    +    filename = path + "rotate_on.png";
    +    if (!m_textures[On].load_from_file(filename, false))
    +        return false;
    +
    +    return true;
    +}
    +
    +void GLGizmoRotate3D::on_set_state()
    +{
    +    m_x.set_state(m_state);
    +    m_y.set_state(m_state);
    +    m_z.set_state(m_state);
    +}
    +
    +void GLGizmoRotate3D::on_set_hover_id()
    +{
    +    m_x.set_hover_id(m_hover_id == 0 ? 0 : -1);
    +    m_y.set_hover_id(m_hover_id == 1 ? 0 : -1);
    +    m_z.set_hover_id(m_hover_id == 2 ? 0 : -1);
    +}
    +
    +void GLGizmoRotate3D::on_start_dragging()
    +{
    +    switch (m_hover_id)
    +    {
    +    case 0:
    +    {
    +        m_x.start_dragging();
    +        break;
    +    }
    +    case 1:
    +    {
    +        m_y.start_dragging();
    +        break;
    +    }
    +    case 2:
    +    {
    +        m_z.start_dragging();
    +        break;
    +    }
    +    default:
    +    {
    +        break;
    +    }
    +    }
    +}
    +
    +void GLGizmoRotate3D::on_stop_dragging()
    +{
    +    switch (m_hover_id)
    +    {
    +    case 0:
    +    {
    +        m_x.stop_dragging();
    +        break;
    +    }
    +    case 1:
    +    {
    +        m_y.stop_dragging();
    +        break;
    +    }
    +    case 2:
    +    {
    +        m_z.stop_dragging();
    +        break;
    +    }
    +    default:
    +    {
    +        break;
    +    }
    +    }
    +}
    +
    +void GLGizmoRotate3D::on_update(const Linef3& mouse_ray)
    +{
    +    m_x.update(mouse_ray);
    +    m_y.update(mouse_ray);
    +    m_z.update(mouse_ray);
    +}
    +
    +void GLGizmoRotate3D::on_refresh()
    +{
    +    m_x.refresh();
    +    m_y.refresh();
    +    m_z.refresh();
    +}
    +
    +void GLGizmoRotate3D::on_render(const BoundingBoxf3& box) const
    +{
    +    if ((m_hover_id == -1) || (m_hover_id == 0))
    +        m_x.render(box);
    +
    +    if ((m_hover_id == -1) || (m_hover_id == 1))
    +        m_y.render(box);
    +
    +    if ((m_hover_id == -1) || (m_hover_id == 2))
    +        m_z.render(box);
    +}
    +
    +void GLGizmoRotate3D::on_render_for_picking(const BoundingBoxf3& box) const
    +{
    +    m_x.render_for_picking(box);
    +    m_y.render_for_picking(box);
    +    m_z.render_for_picking(box);
    +}
    +
     const float GLGizmoScale::Offset = 5.0f;
     
     GLGizmoScale::GLGizmoScale()
    @@ -440,8 +748,9 @@ void GLGizmoScale::on_start_dragging()
             m_starting_drag_position = m_grabbers[m_hover_id].center;
     }
     
    -void GLGizmoScale::on_update(const Pointf& mouse_pos)
    +void GLGizmoScale::on_update(const Linef3& mouse_ray)
     {
    +    Pointf mouse_pos = mouse_ray.intersect_plane(0.0);
         Pointf center(0.5 * (m_grabbers[1].center.x + m_grabbers[0].center.x), 0.5 * (m_grabbers[3].center.y + m_grabbers[0].center.y));
     
         coordf_t orig_len = length(m_starting_drag_position - center);
    @@ -470,7 +779,8 @@ void GLGizmoScale::on_render(const BoundingBoxf3& box) const
         m_grabbers[3].center.y = max_y;
     
         ::glLineWidth(2.0f);
    -    ::glColor3fv(BaseColor);
    +    ::glColor3fv(m_drag_color);
    +
         // draw outline
         ::glBegin(GL_LINE_LOOP);
         for (unsigned int i = 0; i < 4; ++i)
    @@ -482,24 +792,22 @@ void GLGizmoScale::on_render(const BoundingBoxf3& box) const
         // draw grabbers
         for (unsigned int i = 0; i < 4; ++i)
         {
    -        ::memcpy((void*)m_grabbers[i].color, (const void*)HighlightColor, 3 * sizeof(float));
    +        ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float));
         }
         render_grabbers();
     }
     
     void GLGizmoScale::on_render_for_picking(const BoundingBoxf3& box) const
     {
    -    static const GLfloat INV_255 = 1.0f / 255.0f;
    -
         ::glDisable(GL_DEPTH_TEST);
     
         for (unsigned int i = 0; i < 4; ++i)
         {
             m_grabbers[i].color[0] = 1.0f;
             m_grabbers[i].color[1] = 1.0f;
    -        m_grabbers[i].color[2] = (254.0f - (float)i) * INV_255;
    +        m_grabbers[i].color[2] = picking_color_component(i);
         }
    -    render_grabbers();
    +    render_grabbers_for_picking();
     }
     
     } // namespace GUI
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 839969090d..fafd8091ab 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -6,30 +6,38 @@
     
     #include 
     
    +#define ENABLE_GIZMOS_3D 1
    +
     namespace Slic3r {
     
     class BoundingBoxf3;
     class Pointf3;
    +class Linef3;
     
     namespace GUI {
     
     class GLGizmoBase
     {
     protected:
    -    static const float BaseColor[3];
    -    static const float HighlightColor[3];
    -
         struct Grabber
         {
             static const float HalfSize;
    -        static const float HoverOffset;
    +        static const float DraggingScaleFactor;
     
    -        Pointf center;
    +        Pointf3 center;
    +        float angle_x;
    +        float angle_y;
             float angle_z;
             float color[3];
    +        bool dragging;
     
             Grabber();
    +
             void render(bool hover) const;
    +        void render_for_picking() const;
    +
    +    private:
    +        void render(const float* render_color) const;
         };
     
     public:
    @@ -42,30 +50,40 @@ public:
         };
     
     protected:
    +    int m_group_id;
         EState m_state;
         // textures are assumed to be square and all with the same size in pixels, no internal check is done
         GLTexture m_textures[Num_States];
         int m_hover_id;
    +    float m_base_color[3];
    +    float m_drag_color[3];
    +    float m_highlight_color[3];
         mutable std::vector m_grabbers;
    +    bool m_is_container;
     
     public:
         GLGizmoBase();
    -    virtual ~GLGizmoBase();
    +    virtual ~GLGizmoBase() {}
     
    -    bool init();
    +    bool init() { return on_init(); }
     
    -    EState get_state() const;
    -    void set_state(EState state);
    +    int get_group_id() const { return m_group_id; }
    +    void set_group_id(int id) { m_group_id = id; }
    +
    +    EState get_state() const { return m_state; }
    +    void set_state(EState state) { m_state = state; on_set_state(); }
     
         unsigned int get_texture_id() const;
         int get_textures_size() const;
     
    -    int get_hover_id() const;
    +    int get_hover_id() const { return m_hover_id; }
         void set_hover_id(int id);
     
    +    void set_highlight_color(const float* color);
    +
         void start_dragging();
         void stop_dragging();
    -    void update(const Pointf& mouse_pos);
    +    void update(const Linef3& mouse_ray);
         void refresh();
     
         void render(const BoundingBoxf3& box) const;
    @@ -73,15 +91,18 @@ public:
     
     protected:
         virtual bool on_init() = 0;
    -    virtual void on_set_state();
    -    virtual void on_start_dragging();
    -    virtual void on_stop_dragging();
    -    virtual void on_update(const Pointf& mouse_pos) = 0;
    -    virtual void on_refresh();
    +    virtual void on_set_state() {}
    +    virtual void on_set_hover_id() {}
    +    virtual void on_start_dragging() {}
    +    virtual void on_stop_dragging() {}
    +    virtual void on_update(const Linef3& mouse_ray) = 0;
    +    virtual void on_refresh() {}
         virtual void on_render(const BoundingBoxf3& box) const = 0;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0;
     
    +    float picking_color_component(unsigned int id) const;
         void render_grabbers() const;
    +    void render_grabbers_for_picking() const;
     };
     
     class GLGizmoRotate : public GLGizmoBase
    @@ -97,33 +118,76 @@ class GLGizmoRotate : public GLGizmoBase
         static const unsigned int SnapRegionsCount;
         static const float GrabberOffset;
     
    -    float m_angle_z;
    +public:
    +    enum Axis : unsigned char
    +    {
    +        X,
    +        Y,
    +        Z
    +    };
     
    -    mutable Pointf m_center;
    +private:
    +    Axis m_axis;
    +    float m_angle;
    +
    +    mutable Pointf3 m_center;
         mutable float m_radius;
         mutable bool m_keep_initial_values;
     
     public:
    -    GLGizmoRotate();
    +    explicit GLGizmoRotate(Axis axis);
     
    -    float get_angle_z() const;
    -    void set_angle_z(float angle_z);
    +    float get_angle() const;
    +    void set_angle(float angle);
     
     protected:
         virtual bool on_init();
         virtual void on_set_state();
    -    virtual void on_update(const Pointf& mouse_pos);
    +    virtual void on_update(const Linef3& mouse_ray);
         virtual void on_refresh();
         virtual void on_render(const BoundingBoxf3& box) const;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const;
     
     private:
    -    void _render_circle() const;
    -    void _render_scale() const;
    -    void _render_snap_radii() const;
    -    void _render_reference_radius() const;
    -    void _render_angle_z() const;
    -    void _render_grabber() const;
    +    void render_circle() const;
    +    void render_scale() const;
    +    void render_snap_radii() const;
    +    void render_reference_radius() const;
    +    void render_angle() const;
    +    void render_grabber() const;
    +
    +    void transform_to_local() const;
    +    Pointf mouse_position_in_local_plane(const Linef3& mouse_ray) const;
    +};
    +
    +class GLGizmoRotate3D : public GLGizmoBase
    +{
    +    GLGizmoRotate m_x;
    +    GLGizmoRotate m_y;
    +    GLGizmoRotate m_z;
    +
    +public:
    +    GLGizmoRotate3D();
    +
    +    float get_angle_x() const;
    +    void set_angle_x(float angle);
    +
    +    float get_angle_y() const;
    +    void set_angle_y(float angle);
    +
    +    float get_angle_z() const;
    +    void set_angle_z(float angle);
    +
    +protected:
    +    virtual bool on_init();
    +    virtual void on_set_state();
    +    virtual void on_set_hover_id();
    +    virtual void on_start_dragging();
    +    virtual void on_stop_dragging();
    +    virtual void on_update(const Linef3& mouse_ray);
    +    virtual void on_refresh();
    +    virtual void on_render(const BoundingBoxf3& box) const;
    +    virtual void on_render_for_picking(const BoundingBoxf3& box) const;
     };
     
     class GLGizmoScale : public GLGizmoBase
    @@ -144,7 +208,7 @@ public:
     protected:
         virtual bool on_init();
         virtual void on_start_dragging();
    -    virtual void on_update(const Pointf& mouse_pos);
    +    virtual void on_update(const Linef3& mouse_ray);
         virtual void on_render(const BoundingBoxf3& box) const;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const;
     };
    
    From 1b26cd414d41a89c3c68e0e2590257666a28c1cb Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Tue, 21 Aug 2018 02:03:10 +0200
    Subject: [PATCH 147/185] DoubleSlider prototype
    
    ---
     xs/src/slic3r/GUI/GUI.cpp          |  10 +
     xs/src/slic3r/GUI/wxExtensions.cpp | 320 ++++++++++++++++++++++++++++-
     xs/src/slic3r/GUI/wxExtensions.hpp |  80 +++++++-
     3 files changed, 408 insertions(+), 2 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
    index 088cbf80bd..e50c7220a0 100644
    --- a/xs/src/slic3r/GUI/GUI.cpp
    +++ b/xs/src/slic3r/GUI/GUI.cpp
    @@ -1052,6 +1052,16 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
     	// Object List
     	add_objects_list(parent, sizer);
     
    +    // experiment with slider
    +    PrusaDoubleSlider* slider_h = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 200, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL);
    +    sizer->AddSpacer(5);
    +    sizer->Add(slider_h, 0, wxEXPAND | wxLEFT, 20);
    +    sizer->AddSpacer(5);
    +    PrusaDoubleSlider* slider_v = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 100, wxDefaultPosition, wxSize(wxDefaultSize.x ,150), wxSL_VERTICAL);
    +    sizer->AddSpacer(5);
    +    sizer->Add(slider_v, 0, wxLEFT, 20);
    +    sizer->AddSpacer(5);
    +
     	// Frequently Object Settings
     	add_object_settings(parent, sizer);
     }
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index 294eea454a..710d91b496 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -5,6 +5,7 @@
     
     #include 
     #include 
    +#include 
     
     const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200;
     const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200;
    @@ -747,6 +748,323 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent,
     
     	return count;
     }
    -// ************************************** EXPERIMENTS ***************************************
     
    +// ************************************** EXPERIMENTS ***************************************
    +PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
    +                                        wxWindowID id,
    +                                        int lowerValue, 
    +                                        int higherValue, 
    +                                        int minValue, 
    +                                        int maxValue,
    +                                        const wxPoint& pos,
    +                                        const wxSize& size,
    +                                        long style,
    +                                        const wxValidator& val,
    +                                        const wxString& name) : 
    +    wxControl(parent, id, pos, size, wxBORDER_NONE),
    +    m_lower_value(lowerValue), m_higher_value (higherValue), 
    +    m_min_value(minValue), m_max_value(maxValue),
    +    m_style(style)
    +{
    +    SetDoubleBuffered(true);
    +
    +    if (m_style != wxSL_HORIZONTAL && m_style != wxSL_VERTICAL)
    +        m_style = wxSL_HORIZONTAL;
    +
    +    m_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("right_half_circle.png")) :
    +                                                         Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG);
    +    m_thumb_lower  = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) :
    +                                                         Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG);
    +    
    +    
    +    m_selection = ssUndef;
    +
    +    // slider events
    +    Bind(wxEVT_PAINT,       &PrusaDoubleSlider::OnPaint,    this);
    +    Bind(wxEVT_LEFT_DOWN,   &PrusaDoubleSlider::OnLeftDown, this);
    +    Bind(wxEVT_MOTION,      &PrusaDoubleSlider::OnMotion,   this);
    +    Bind(wxEVT_LEFT_UP,     &PrusaDoubleSlider::OnLeftUp,   this);
    +    Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeftUp,   this);
    +    Bind(wxEVT_MOUSEWHEEL,  &PrusaDoubleSlider::OnWheel,    this);
    +
    +    // control's view variables
    +    SLIDER_MARGIN     = 2 + (style == wxSL_HORIZONTAL ? m_thumb_higher.GetWidth() : m_thumb_higher.GetHeight());
    +
    +    DARK_ORANGE_PEN   = wxPen(wxColour(253, 84, 2));
    +    ORANGE_PEN        = wxPen(wxColour(253, 126, 66));
    +    LIGHT_ORANGE_PEN  = wxPen(wxColour(254, 177, 139));
    +
    +    DARK_GREY_PEN     = wxPen(wxColour(128, 128, 128));
    +    GREY_PEN          = wxPen(wxColour(164, 164, 164));
    +    LIGHT_GREY_PEN    = wxPen(wxColour(204, 204, 204));
    +
    +    line_pens = { &DARK_GREY_PEN, &GREY_PEN, &LIGHT_GREY_PEN };
    +    segm_pens = { &DARK_ORANGE_PEN, &ORANGE_PEN, &LIGHT_ORANGE_PEN };
    +}
    +
    +void PrusaDoubleSlider::SetLowerValue(const int lower_val)
    +{
    +    m_lower_value = lower_val;
    +    Refresh();
    +    Update();
    +}
    +
    +void PrusaDoubleSlider::SetHigherValue(const int higher_val)
    +{
    +    m_higher_value = higher_val;
    +    Refresh();
    +    Update();
    +}
    +
    +void PrusaDoubleSlider::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos)
    +{
    +    int width;
    +    int height;
    +    GetSize(&width, &height);
    +
    +    wxCoord line_beg_x = is_horizontal() ? SLIDER_MARGIN : width*0.5 - 1;
    +    wxCoord line_beg_y = is_horizontal() ? height*0.5 - 1 : SLIDER_MARGIN;
    +    wxCoord line_end_x = is_horizontal() ? width - SLIDER_MARGIN + 1 : width*0.5 - 1;
    +    wxCoord line_end_y = is_horizontal() ? height*0.5 - 1 : height - SLIDER_MARGIN + 1;
    +
    +    wxCoord segm_beg_x = is_horizontal() ? lower_pos : width*0.5 - 1;
    +    wxCoord segm_beg_y = is_horizontal() ? height*0.5 - 1 : lower_pos-1;
    +    wxCoord segm_end_x = is_horizontal() ? higher_pos : width*0.5 - 1;
    +    wxCoord segm_end_y = is_horizontal() ? height*0.5 - 1 : higher_pos-1;
    +
    +    for (int id = 0; id < line_pens.size(); id++)
    +    {
    +        dc.SetPen(*line_pens[id]);
    +        dc.DrawLine(line_beg_x, line_beg_y, line_end_x, line_end_y);
    +        dc.SetPen(*segm_pens[id]);
    +        dc.DrawLine(segm_beg_x, segm_beg_y, segm_end_x, segm_end_y);
    +        if (is_horizontal())
    +            line_beg_y = line_end_y = segm_beg_y = segm_end_y += 1;
    +        else
    +            line_beg_x = line_end_x = segm_beg_x = segm_end_x += 1;
    +    }
    +}
    +
    +double PrusaDoubleSlider::get_scroll_step()
    +{
    +    const wxSize sz = GetSize();
    +    const int& slider_len = m_style == wxSL_HORIZONTAL ? sz.x : sz.y;
    +    return double(slider_len - SLIDER_MARGIN * 2) / (m_max_value - m_min_value);
    +}
    +
    +void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos)
    +{
    +    const double step = get_scroll_step();
    +    if (is_horizontal()) {
    +        lower_pos = SLIDER_MARGIN + int(m_lower_value*step + 0.5);
    +        higher_pos = SLIDER_MARGIN + int(m_higher_value*step + 0.5);
    +    }
    +    else {
    +        lower_pos = SLIDER_MARGIN + int((m_max_value - m_lower_value)*step + 0.5);
    +        higher_pos = SLIDER_MARGIN + int((m_max_value - m_higher_value)*step + 0.5);
    +    }
    +}
    +
    +void PrusaDoubleSlider::OnPaint(wxPaintEvent& event)
    +{
    +    SetBackgroundColour(GetParent()->GetBackgroundColour());
    +
    +    wxPaintDC dc(this);    
    +    int width, height;
    +    GetSize(&width, &height);
    +
    +    int lower_pos, higher_pos;
    +    get_lower_and_higher_position(lower_pos, higher_pos);
    +
    +    // draw line
    +    draw_scroll_line(dc, lower_pos, higher_pos);
    +
    +    //lower slider:
    +    wxPoint pos = is_horizontal() ? wxPoint(lower_pos, height*0.5) : wxPoint(0.5*width, lower_pos);
    +    draw_lower_thumb(dc, pos);
    +
    +    //higher slider:
    +    pos = is_horizontal() ? wxPoint(higher_pos, height*0.5) : wxPoint(0.5*width, higher_pos);
    +    draw_higher_thumb(dc, pos);
    +}
    +
    +void PrusaDoubleSlider::draw_lower_thumb(wxDC& dc, const wxPoint& pos)
    +{
    +    // Draw thumb
    +    wxCoord x_draw, y_draw;
    +    const wxSize thumb_size = m_thumb_lower.GetSize();
    +    if (is_horizontal()) {
    +        x_draw = pos.x - thumb_size.x;
    +        y_draw = pos.y - int(0.5*thumb_size.y);
    +    }
    +    else {
    +        x_draw = pos.x - int(0.5*thumb_size.x);
    +        y_draw = pos.y;
    +    }
    +    dc.DrawBitmap(m_thumb_lower, x_draw, y_draw);
    +
    +    // Draw thumb text
    +    wxCoord text_width, text_height;
    +    dc.GetTextExtent(wxString::Format("%d", m_lower_value), &text_width, &text_height);
    +    wxPoint text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + thumb_size.x) :
    +                                         wxPoint(pos.x + thumb_size.y, pos.y - 1 - text_height);
    +    dc.DrawText(wxString::Format("%d", m_lower_value), text_pos);
    +
    +    // Update thumb rect
    +    m_rect_lower_thumb = wxRect(x_draw, y_draw, thumb_size.x, thumb_size.y);
    +}
    +
    +
    +void PrusaDoubleSlider::draw_higher_thumb(wxDC& dc, const wxPoint& pos)
    +{
    +    wxCoord x_draw, y_draw;
    +    const wxSize thumb_size = m_thumb_higher.GetSize();
    +    if (is_horizontal()) {
    +        x_draw = pos.x;
    +        y_draw = pos.y - int(0.5*thumb_size.y);
    +    }
    +    else {
    +        x_draw = pos.x - int(0.5*thumb_size.x);
    +        y_draw = pos.y - thumb_size.y;
    +    }
    +    dc.DrawBitmap(m_thumb_higher, x_draw, y_draw);
    +
    +    // Draw thumb text
    +    wxCoord text_width, text_height;
    +    dc.GetTextExtent(wxString::Format("%d", m_higher_value), &text_width, &text_height);
    +    wxPoint text_pos = is_horizontal() ? wxPoint(pos.x - text_width-1,                pos.y - thumb_size.x - text_height) :
    +                                         wxPoint(pos.x - text_width-1 - thumb_size.y, pos.y + 1);
    +    dc.DrawText(wxString::Format("%d", m_higher_value), text_pos);
    +
    +    // Update thumb rect
    +    m_rect_higher_thumb = wxRect(x_draw, y_draw, thumb_size.x, thumb_size.y);
    +}
    +
    +int PrusaDoubleSlider::position_to_value(wxDC& dc, const wxCoord x, const wxCoord y)
    +{
    +    int width, height;
    +    dc.GetSize(&width, &height);
    +    const double step = get_scroll_step();
    +    
    +    if (is_horizontal()) 
    +        return int(double(x - SLIDER_MARGIN) / step + 0.5);
    +    else 
    +        return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5);
    +}
    +
    +void PrusaDoubleSlider::detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel /*= false*/)
    +{
    +    if (is_mouse_wheel)
    +    {
    +        if (is_horizontal()) {
    +            m_selection = pt.x <= m_rect_lower_thumb.GetRight() ? ssLower :
    +                          pt.x >= m_rect_higher_thumb.GetLeft() ? ssHigher : ssUndef;
    +        }
    +        else {
    +            m_selection = pt.y >= m_rect_lower_thumb.GetTop() ? ssLower :
    +                          pt.y <= m_rect_higher_thumb.GetBottom() ? ssHigher : ssUndef;            
    +        }
    +        return;
    +    }
    +
    +    m_selection = is_point_in_rect(pt, m_rect_lower_thumb) ? ssLower :
    +                  is_point_in_rect(pt, m_rect_higher_thumb) ? ssHigher : ssUndef;
    +}
    +
    +bool PrusaDoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect)
    +{
    +    if (rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() && 
    +        rect.GetTop()  <= pt.y && pt.y <= rect.GetBottom())
    +        return true;
    +    return false;
    +}
    +
    +void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event)
    +{
    +    m_is_left_down = true;
    +    wxClientDC dc(this);
    +    wxPoint pos = event.GetLogicalPosition(dc);
    +    detect_selected_slider(pos);
    +    event.Skip();
    +}
    +
    +void PrusaDoubleSlider::correct_lower_value()
    +{
    +    if (m_lower_value < m_min_value)
    +        m_lower_value = m_min_value;
    +    else if (m_lower_value >= m_higher_value && m_lower_value <= m_max_value)
    +        m_higher_value = m_lower_value;
    +    else if (m_lower_value > m_max_value)
    +        m_lower_value = m_max_value;
    +}
    +
    +void PrusaDoubleSlider::correct_higher_value()
    +{
    +    if (m_higher_value > m_max_value)
    +        m_higher_value = m_max_value;
    +    else if (m_higher_value <= m_lower_value && m_higher_value >= m_min_value)
    +        m_lower_value = m_higher_value;
    +    else if (m_higher_value < m_min_value)
    +        m_higher_value = m_min_value;
    +}
    +
    +void PrusaDoubleSlider::OnMotion(wxMouseEvent& event)
    +{
    +    if (!m_is_left_down || m_selection == ssUndef)
    +        return;
    +
    +    wxClientDC dc(this);
    +    wxPoint pos = event.GetLogicalPosition(dc);
    +
    +    if (m_selection == ssLower) {
    +        m_lower_value = position_to_value(dc, pos.x, pos.y);
    +        correct_lower_value();
    +    }
    +    else if (m_selection == ssHigher) {
    +        m_higher_value = position_to_value(dc, pos.x, pos.y);
    +        correct_higher_value();
    +    }
    +
    +    Refresh();
    +    Update();
    +    event.Skip();
    +}
    +
    +void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event)
    +{
    +    m_is_left_down = false;
    +    event.Skip();
    +
    +    wxCommandEvent e(wxEVT_SCROLL_CHANGED);
    +    e.SetEventObject(this);
    +    ProcessWindowEvent(e);
    +}
    +
    +void PrusaDoubleSlider::OnWheel(wxMouseEvent& event)
    +{
    +    wxClientDC dc(this);
    +    wxPoint pos = event.GetLogicalPosition(dc);
    +    detect_selected_slider(pos, true);
    +
    +    if (m_selection == ssUndef)
    +        return;
    +
    +    int delta = event.GetWheelRotation() > 0 ? -1 : 1;
    +    if (is_horizontal())
    +        delta *= -1;
    +    if (m_selection == ssLower) {
    +        m_lower_value -= delta;
    +        correct_lower_value();
    +    }
    +    else if (m_selection == ssHigher) {
    +        m_higher_value -= delta;
    +        correct_higher_value();
    +    }
    +    Refresh();
    +    Update();
    +
    +    wxCommandEvent e(wxEVT_SCROLL_CHANGED);
    +    e.SetEventObject(this);
    +    ProcessWindowEvent(e);
    +}
     // *****************************************************************************
    diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
    index 32ae0a4f97..584882d96d 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.hpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.hpp
    @@ -8,7 +8,7 @@
     #include 
     #include 
     #include 
    -#include 
    +#include 
     
     #include 
     #include 
    @@ -496,7 +496,85 @@ private:
     	wxString m_value;
     };
     // ******************************* EXPERIMENTS **********************************************
    +enum SelectedSlider {
    +    ssUndef,
    +    ssLower,
    +    ssHigher
    +};
    +class PrusaDoubleSlider : public wxControl
    +{
    +public:
    +    PrusaDoubleSlider(
    +        wxWindow *parent,
    +        wxWindowID id,
    +        int lowerValue, 
    +        int higherValue, 
    +        int minValue, 
    +        int maxValue,
    +        const wxPoint& pos = wxDefaultPosition,
    +        const wxSize& size = wxDefaultSize,
    +        long style = wxSL_HORIZONTAL,
    +        const wxValidator& val = wxDefaultValidator,
    +        const wxString& name = wxEmptyString);
     
    +    int GetLowerValue() {
    +        return m_lower_value;
    +    }
    +    int GetHigherValue() {
    +        return m_higher_value;
    +    }
    +    void SetLowerValue(int lower_val);
    +    void SetHigherValue(int higher_val);
    +
    +    wxSize DoGetBestSize(){ return wxDefaultSize; }
    +
    +    void OnPaint(wxPaintEvent& event);
    +    void OnLeftDown(wxMouseEvent& event);
    +    void OnMotion(wxMouseEvent& event);
    +    void OnLeftUp(wxMouseEvent& event);
    +    void OnWheel(wxMouseEvent& event);
    +
    +protected:
    +    void    correct_lower_value();
    +    void    correct_higher_value();
    +    void    draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
    +    double  get_scroll_step();
    +    void    get_lower_and_higher_position(int& lower_pos, int& higher_pos);
    +    void    draw_lower_thumb (wxDC& dc, const wxPoint& pos);
    +    void    draw_higher_thumb(wxDC& dc, const wxPoint& pos);
    +    int     position_to_value(wxDC& dc, const wxCoord x, const wxCoord y);
    +    void    detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false);
    +    bool    is_point_in_rect(const wxPoint& pt, const wxRect& rect);
    +    bool    is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
    +
    +private:
    +    int         m_min_value;
    +    int         m_max_value;
    +    int         m_lower_value;
    +    int         m_higher_value;
    +    wxBitmap    m_thumb_higher;
    +    wxBitmap    m_thumb_lower;
    +    SelectedSlider  m_selection;
    +    bool        m_is_left_down = false;
    +
    +    wxRect      m_rect_lower_thumb;
    +    wxRect      m_rect_higher_thumb;
    +    long        m_style;
    +
    +// control's view variables
    +    wxCoord SLIDER_MARGIN; // margin around slider
    +
    +    wxPen   DARK_ORANGE_PEN;
    +    wxPen   ORANGE_PEN;
    +    wxPen   LIGHT_ORANGE_PEN;
    +
    +    wxPen   DARK_GREY_PEN;
    +    wxPen   GREY_PEN;
    +    wxPen   LIGHT_GREY_PEN;
    +
    +    std::vector line_pens;
    +    std::vector segm_pens;
    +};
     // ******************************************************************************************
     
     
    
    From 743eee8b6e28f13e8ae645dfa7e8a3e91301ace5 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Tue, 21 Aug 2018 08:50:35 +0200
    Subject: [PATCH 148/185] 1st installment of gizmo scale 3D
    
    ---
     xs/src/libslic3r/Point.hpp       |   1 +
     xs/src/slic3r/GUI/GLCanvas3D.cpp |  12 +
     xs/src/slic3r/GUI/GLGizmo.cpp    | 447 ++++++++++++++++++++++---------
     xs/src/slic3r/GUI/GLGizmo.hpp    | 124 +++++++--
     4 files changed, 430 insertions(+), 154 deletions(-)
    
    diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
    index 87104674f4..8aee65a936 100644
    --- a/xs/src/libslic3r/Point.hpp
    +++ b/xs/src/libslic3r/Point.hpp
    @@ -287,6 +287,7 @@ inline Pointf3 operator*(double scalar, const Pointf3& p) { return Pointf3(scala
     inline Pointf3 operator*(const Pointf3& p, double scalar) { return Pointf3(scalar * p.x, scalar * p.y, scalar * p.z); }
     inline Pointf3 cross(const Pointf3& v1, const Pointf3& v2) { return Pointf3(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); }
     inline coordf_t dot(const Pointf3& v1, const Pointf3& v2) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; }
    +inline double length(const Vectorf3 &v) { return ::sqrt(sqr(v.x) + sqr(v.y) + sqr(v.z)); }
     inline Pointf3 normalize(const Pointf3& v)
     {
         coordf_t len = ::sqrt(sqr(v.x) + sqr(v.y) + sqr(v.z));
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index 1b24e37335..5a6fe65ced 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -1130,7 +1130,11 @@ GLCanvas3D::Gizmos::~Gizmos()
     
     bool GLCanvas3D::Gizmos::init()
     {
    +#if ENABLE_GIZMOS_3D
    +    GLGizmoBase* gizmo = new GLGizmoScale3D;
    +#else
         GLGizmoBase* gizmo = new GLGizmoScale;
    +#endif // ENABLE_GIZMOS_3D
         if (gizmo == nullptr)
             return false;
     
    @@ -1359,7 +1363,11 @@ float GLCanvas3D::Gizmos::get_scale() const
             return 1.0f;
     
         GizmosMap::const_iterator it = m_gizmos.find(Scale);
    +#if ENABLE_GIZMOS_3D
    +    return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_scale_x() : 1.0f;
    +#else
         return (it != m_gizmos.end()) ? reinterpret_cast(it->second)->get_scale() : 1.0f;
    +#endif // ENABLE_GIZMOS_3D
     }
     
     void GLCanvas3D::Gizmos::set_scale(float scale)
    @@ -1369,7 +1377,11 @@ void GLCanvas3D::Gizmos::set_scale(float scale)
     
         GizmosMap::const_iterator it = m_gizmos.find(Scale);
         if (it != m_gizmos.end())
    +#if ENABLE_GIZMOS_3D
    +        reinterpret_cast(it->second)->set_scale(scale);
    +#else
             reinterpret_cast(it->second)->set_scale(scale);
    +#endif // ENABLE_GIZMOS_3D
     }
     
     float GLCanvas3D::Gizmos::get_angle_z() const
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 1ee8f6f378..8ead590c96 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -1,7 +1,6 @@
     #include "GLGizmo.hpp"
     
     #include "../../libslic3r/Utils.hpp"
    -#include "../../libslic3r/BoundingBox.hpp"
     
     #include 
     
    @@ -13,6 +12,10 @@ static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f };
     static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f };
     static const float DEFAULT_HIGHLIGHT_COLOR[3] = { 1.0f, 0.38f, 0.0f };
     
    +static const float RED[3] = { 1.0f, 0.0f, 0.0f };
    +static const float GREEN[3] = { 0.0f, 1.0f, 0.0f };
    +static const float BLUE[3] = { 0.0f, 0.0f, 1.0f };
    +
     namespace Slic3r {
     namespace GUI {
     
    @@ -46,11 +49,6 @@ void GLGizmoBase::Grabber::render(bool hover) const
         render(render_color);
     }
     
    -void GLGizmoBase::Grabber::render_for_picking() const
    -{
    -    render(color);
    -}
    -
     void GLGizmoBase::Grabber::render(const float* render_color) const
     {
         float half_size = dragging ? HalfSize * DraggingScaleFactor : HalfSize;
    @@ -94,16 +92,6 @@ GLGizmoBase::GLGizmoBase()
         ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 3 * sizeof(float));
     }
     
    -unsigned int GLGizmoBase::get_texture_id() const
    -{
    -    return m_textures[m_state].get_id();
    -}
    -
    -int GLGizmoBase::get_textures_size() const
    -{
    -    return m_textures[Off].get_width();
    -}
    -
     void GLGizmoBase::set_hover_id(int id)
     {
         if (m_is_container || (id < (int)m_grabbers.size()))
    @@ -145,21 +133,6 @@ void GLGizmoBase::update(const Linef3& mouse_ray)
             on_update(mouse_ray);
     }
     
    -void GLGizmoBase::refresh()
    -{
    -    on_refresh();
    -}
    -
    -void GLGizmoBase::render(const BoundingBoxf3& box) const
    -{
    -    on_render(box);
    -}
    -
    -void GLGizmoBase::render_for_picking(const BoundingBoxf3& box) const
    -{
    -    on_render_for_picking(box);
    -}
    -
     float GLGizmoBase::picking_color_component(unsigned int id) const
     {
         int color = 254 - (int)id;
    @@ -206,11 +179,6 @@ GLGizmoRotate::GLGizmoRotate(GLGizmoRotate::Axis axis)
     {
     }
     
    -float GLGizmoRotate::get_angle() const
    -{
    -    return m_angle;
    -}
    -
     void GLGizmoRotate::set_angle(float angle)
     {
         if (std::abs(angle - 2.0f * PI) < EPSILON)
    @@ -242,11 +210,6 @@ bool GLGizmoRotate::on_init()
         return true;
     }
     
    -void GLGizmoRotate::on_set_state()
    -{
    -    m_keep_initial_values = (m_state == On) ? false : true;
    -}
    -
     void GLGizmoRotate::on_update(const Linef3& mouse_ray)
     {
         Pointf mouse_pos = mouse_position_in_local_plane(mouse_ray);
    @@ -274,11 +237,6 @@ void GLGizmoRotate::on_update(const Linef3& mouse_ray)
         m_angle = (float)theta;
     }
     
    -void GLGizmoRotate::on_refresh()
    -{
    -    m_keep_initial_values = false;
    -}
    -
     void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
     {
         ::glDisable(GL_DEPTH_TEST);
    @@ -488,20 +446,22 @@ void GLGizmoRotate::transform_to_local() const
     
     Pointf GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray) const
     {
    +    float half_pi = 0.5f * (float)PI;
    +
         Eigen::Transform m = Eigen::Transform::Identity();
     
         switch (m_axis)
         {
         case X:
         {
    -        m.rotate(Eigen::AngleAxisf(-0.5f * (float)PI, Eigen::Vector3f::UnitZ()));
    -        m.rotate(Eigen::AngleAxisf(-0.5f * (float)PI, Eigen::Vector3f::UnitY()));
    +        m.rotate(Eigen::AngleAxisf(-half_pi, Eigen::Vector3f::UnitZ()));
    +        m.rotate(Eigen::AngleAxisf(-half_pi, Eigen::Vector3f::UnitY()));
             break;
         }
         case Y:
         {
             m.rotate(Eigen::AngleAxisf(-(float)PI, Eigen::Vector3f::UnitZ()));
    -        m.rotate(Eigen::AngleAxisf(-0.5f * (float)PI, Eigen::Vector3f::UnitX()));
    +        m.rotate(Eigen::AngleAxisf(-half_pi, Eigen::Vector3f::UnitX()));
             break;
         }
         default:
    @@ -540,48 +500,14 @@ GLGizmoRotate3D::GLGizmoRotate3D()
         m_z.set_group_id(2);
     }
     
    -float GLGizmoRotate3D::get_angle_x() const
    -{
    -    return m_x.get_angle();
    -}
    -
    -void GLGizmoRotate3D::set_angle_x(float angle)
    -{
    -    m_x.set_angle(angle);
    -}
    -
    -float GLGizmoRotate3D::get_angle_y() const
    -{
    -    return m_y.get_angle();
    -}
    -
    -void GLGizmoRotate3D::set_angle_y(float angle)
    -{
    -    m_y.set_angle(angle);
    -}
    -
    -float GLGizmoRotate3D::get_angle_z() const
    -{
    -    return m_z.get_angle();
    -}
    -
    -void GLGizmoRotate3D::set_angle_z(float angle)
    -{
    -    m_z.set_angle(angle);
    -}
    -
     bool GLGizmoRotate3D::on_init()
     {
         if (!m_x.init() || !m_y.init() || !m_z.init())
             return false;
     
    -    float red[3] = { 1.0f, 0.0f, 0.0f };
    -    float green[3] = { 0.0f, 1.0f, 0.0f };
    -    float blue[3] = { 0.0f, 0.0f, 1.0f };
    -
    -    m_x.set_highlight_color(red);
    -    m_y.set_highlight_color(green);
    -    m_z.set_highlight_color(blue);
    +    m_x.set_highlight_color(RED);
    +    m_y.set_highlight_color(GREEN);
    +    m_z.set_highlight_color(BLUE);
     
         std::string path = resources_dir() + "/icons/overlay/";
     
    @@ -600,20 +526,6 @@ bool GLGizmoRotate3D::on_init()
         return true;
     }
     
    -void GLGizmoRotate3D::on_set_state()
    -{
    -    m_x.set_state(m_state);
    -    m_y.set_state(m_state);
    -    m_z.set_state(m_state);
    -}
    -
    -void GLGizmoRotate3D::on_set_hover_id()
    -{
    -    m_x.set_hover_id(m_hover_id == 0 ? 0 : -1);
    -    m_y.set_hover_id(m_hover_id == 1 ? 0 : -1);
    -    m_z.set_hover_id(m_hover_id == 2 ? 0 : -1);
    -}
    -
     void GLGizmoRotate3D::on_start_dragging()
     {
         switch (m_hover_id)
    @@ -666,20 +578,6 @@ void GLGizmoRotate3D::on_stop_dragging()
         }
     }
     
    -void GLGizmoRotate3D::on_update(const Linef3& mouse_ray)
    -{
    -    m_x.update(mouse_ray);
    -    m_y.update(mouse_ray);
    -    m_z.update(mouse_ray);
    -}
    -
    -void GLGizmoRotate3D::on_refresh()
    -{
    -    m_x.refresh();
    -    m_y.refresh();
    -    m_z.refresh();
    -}
    -
     void GLGizmoRotate3D::on_render(const BoundingBoxf3& box) const
     {
         if ((m_hover_id == -1) || (m_hover_id == 0))
    @@ -692,13 +590,6 @@ void GLGizmoRotate3D::on_render(const BoundingBoxf3& box) const
             m_z.render(box);
     }
     
    -void GLGizmoRotate3D::on_render_for_picking(const BoundingBoxf3& box) const
    -{
    -    m_x.render_for_picking(box);
    -    m_y.render_for_picking(box);
    -    m_z.render_for_picking(box);
    -}
    -
     const float GLGizmoScale::Offset = 5.0f;
     
     GLGizmoScale::GLGizmoScale()
    @@ -708,16 +599,6 @@ GLGizmoScale::GLGizmoScale()
     {
     }
     
    -float GLGizmoScale::get_scale() const
    -{
    -    return m_scale;
    -}
    -
    -void GLGizmoScale::set_scale(float scale)
    -{
    -    m_starting_scale = scale;
    -}
    -
     bool GLGizmoScale::on_init()
     {
         std::string path = resources_dir() + "/icons/overlay/";
    @@ -810,5 +691,309 @@ void GLGizmoScale::on_render_for_picking(const BoundingBoxf3& box) const
         render_grabbers_for_picking();
     }
     
    +const float GLGizmoScale3D::Offset = 5.0f;
    +
    +GLGizmoScale3D::GLGizmoScale3D()
    +    : GLGizmoBase()
    +    , m_scale_x(1.0f)
    +    , m_scale_y(1.0f)
    +    , m_scale_z(1.0f)
    +    , m_starting_scale_x(1.0f)
    +    , m_starting_scale_y(1.0f)
    +    , m_starting_scale_z(1.0f)
    +{
    +}
    +
    +bool GLGizmoScale3D::on_init()
    +{
    +    std::string path = resources_dir() + "/icons/overlay/";
    +
    +    std::string filename = path + "scale_off.png";
    +    if (!m_textures[Off].load_from_file(filename, false))
    +        return false;
    +
    +    filename = path + "scale_hover.png";
    +    if (!m_textures[Hover].load_from_file(filename, false))
    +        return false;
    +
    +    filename = path + "scale_on.png";
    +    if (!m_textures[On].load_from_file(filename, false))
    +        return false;
    +
    +    for (unsigned int i = 0; i < 7; ++i)
    +    {
    +        m_grabbers.push_back(Grabber());
    +    }
    +
    +    float half_pi = 0.5f * (float)PI;
    +
    +    // x axis
    +    m_grabbers[0].angle_y = half_pi;
    +    m_grabbers[1].angle_y = half_pi;
    +
    +    // y axis
    +    m_grabbers[2].angle_x = half_pi;
    +    m_grabbers[3].angle_x = half_pi;
    +
    +    return true;
    +}
    +
    +void GLGizmoScale3D::on_start_dragging()
    +{
    +    if (m_hover_id != -1)
    +        m_starting_drag_position = m_grabbers[m_hover_id].center;
    +}
    +
    +void GLGizmoScale3D::on_update(const Linef3& mouse_ray)
    +{
    +    if ((m_hover_id == 0) || (m_hover_id == 1))
    +        do_scale_x(mouse_ray);
    +    else if ((m_hover_id == 2) || (m_hover_id == 3))
    +        do_scale_y(mouse_ray);
    +    else if ((m_hover_id == 4) || (m_hover_id == 5))
    +        do_scale_z(mouse_ray);
    +    else if (m_hover_id == 6)
    +        do_scale_uniform(mouse_ray);
    +}
    +
    +void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
    +{
    +    ::glDisable(GL_DEPTH_TEST);
    +
    +    Vectorf3 offset_vec((coordf_t)Offset, (coordf_t)Offset, (coordf_t)Offset);
    +
    +    m_box = BoundingBoxf3(box.min - offset_vec, box.max + offset_vec);
    +    const Pointf3& center = m_box.center();
    +
    +    // x axis
    +    m_grabbers[0].center.x = m_box.min.x;
    +    m_grabbers[0].center.y = center.y;
    +    m_grabbers[0].center.z = center.z;
    +    m_grabbers[1].center.x = m_box.max.x;
    +    m_grabbers[1].center.y = center.y;
    +    m_grabbers[1].center.z = center.z;
    +
    +    // y axis
    +    m_grabbers[2].center.x = center.x;
    +    m_grabbers[2].center.y = m_box.min.y;
    +    m_grabbers[2].center.z = center.z;
    +    m_grabbers[3].center.x = center.x;
    +    m_grabbers[3].center.y = m_box.max.y;
    +    m_grabbers[3].center.z = center.z;
    +
    +    // z axis
    +    m_grabbers[4].center.x = center.x;
    +    m_grabbers[4].center.y = center.y;
    +    m_grabbers[4].center.z = m_box.min.z;
    +    m_grabbers[5].center.x = center.x;
    +    m_grabbers[5].center.y = center.y;
    +    m_grabbers[5].center.z = m_box.max.z;
    +
    +    // uniform
    +    m_grabbers[6].center = m_box.min;
    +
    +    ::memcpy((void*)m_grabbers[0].color, (const void*)RED, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[1].color, (const void*)RED, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[2].color, (const void*)GREEN, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[3].color, (const void*)GREEN, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[4].color, (const void*)BLUE, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[5].color, (const void*)BLUE, 3 * sizeof(float));
    +    ::memcpy((void*)m_grabbers[6].color, (const void*)m_highlight_color, 3 * sizeof(float));
    +
    +    ::glLineWidth(2.0f);
    +
    +    if (m_hover_id == -1)
    +    {
    +        // draw box
    +        ::glColor3fv(m_base_color);
    +        render_box_x_faces();
    +        render_box_y_faces();
    +        render_box_z_faces();
    +
    +        // draw grabbers
    +        render_grabbers();
    +    }
    +    else if ((m_hover_id == 0) || (m_hover_id == 1))
    +    {
    +        ::glColor3fv(m_drag_color);
    +        render_box_x_faces();
    +        m_grabbers[0].render(true);
    +        m_grabbers[1].render(true);
    +    }
    +    else if ((m_hover_id == 2) || (m_hover_id == 3))
    +    {
    +        ::glColor3fv(m_drag_color);
    +        render_box_y_faces();
    +        m_grabbers[2].render(true);
    +        m_grabbers[3].render(true);
    +    }
    +    else if ((m_hover_id == 4) || (m_hover_id == 5))
    +    {
    +        ::glColor3fv(m_drag_color);
    +        render_box_z_faces();
    +        m_grabbers[4].render(true);
    +        m_grabbers[5].render(true);
    +    }
    +    else if (m_hover_id == 6)
    +    {
    +        ::glColor3fv(m_drag_color);
    +        render_box_x_faces();
    +        render_box_y_faces();
    +        render_box_z_faces();
    +        m_grabbers[6].render(true);
    +    }
    +}
    +
    +void GLGizmoScale3D::on_render_for_picking(const BoundingBoxf3& box) const
    +{
    +    ::glDisable(GL_DEPTH_TEST);
    +
    +    for (unsigned int i = 0; i < 7; ++i)
    +    {
    +        m_grabbers[i].color[0] = 1.0f;
    +        m_grabbers[i].color[1] = 1.0f;
    +        m_grabbers[i].color[2] = picking_color_component(i);
    +    }
    +
    +    render_grabbers_for_picking();
    +}
    +
    +void GLGizmoScale3D::render_box_x_faces() const
    +{
    +    ::glBegin(GL_LINE_LOOP);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    +    ::glEnd();
    +    ::glBegin(GL_LINE_LOOP);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    +    ::glEnd();
    +}
    +
    +void GLGizmoScale3D::render_box_y_faces() const
    +{
    +    ::glBegin(GL_LINE_LOOP);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    +    ::glEnd();
    +    ::glBegin(GL_LINE_LOOP);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    +    ::glEnd();
    +}
    +
    +void GLGizmoScale3D::render_box_z_faces() const
    +{
    +    ::glBegin(GL_LINE_LOOP);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    +    ::glEnd();
    +    ::glBegin(GL_LINE_LOOP);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    +    ::glEnd();
    +}
    +
    +Linef3 transform(const Linef3& line, const Eigen::Transform& t)
    +{
    +    Eigen::Matrix world_line;
    +    Eigen::Matrix local_line;
    +    world_line(0, 0) = (float)line.a.x;
    +    world_line(1, 0) = (float)line.a.y;
    +    world_line(2, 0) = (float)line.a.z;
    +    world_line(0, 1) = (float)line.b.x;
    +    world_line(1, 1) = (float)line.b.y;
    +    world_line(2, 1) = (float)line.b.z;
    +    local_line = t * world_line.colwise().homogeneous();
    +
    +    return Linef3(Pointf3(local_line(0, 0), local_line(1, 0), local_line(2, 0)), Pointf3(local_line(0, 1), local_line(1, 1), local_line(2, 1)));
    +}
    +
    +void GLGizmoScale3D::do_scale_x(const Linef3& mouse_ray)
    +{
    +    // calculates the intersection of the mouse ray with the plane parallel to plane XY and passing through the box center
    +    const Pointf3& center = m_box.center();
    +    Eigen::Transform m = Eigen::Transform::Identity();
    +    m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)center.z));
    +
    +    Pointf mouse_pos = transform(mouse_ray, m).intersect_plane(0.0);
    +
    +    coordf_t orig_len = length(m_starting_drag_position - center);
    +    coordf_t new_len = length(mouse_pos);
    +    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    +
    +    m_scale_x = m_starting_scale_x * (float)ratio;
    +}
    +
    +void GLGizmoScale3D::do_scale_y(const Linef3& mouse_ray)
    +{
    +    // calculates the intersection of the mouse ray with the plane parallel to plane XY and passing through the box center
    +    const Pointf3& center = m_box.center();
    +    Eigen::Transform m = Eigen::Transform::Identity();
    +    m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)center.z));
    +
    +    Pointf mouse_pos = transform(mouse_ray, m).intersect_plane(0.0);
    +
    +    coordf_t orig_len = length(m_starting_drag_position - center);
    +    coordf_t new_len = length(mouse_pos);
    +    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    +
    +    m_scale_x = m_starting_scale_y * (float)ratio;
    +//    m_scale_y = m_starting_scale_y * (float)ratio;
    +}
    +
    +void GLGizmoScale3D::do_scale_z(const Linef3& mouse_ray)
    +{
    +    // calculates the intersection of the mouse ray with the plane parallel to plane XZ and passing through the box center
    +    const Pointf3& center = m_box.center();
    +    Eigen::Transform m = Eigen::Transform::Identity();
    +    m.rotate(Eigen::AngleAxisf(0.5f * (float)PI, Eigen::Vector3f::UnitX()));
    +    m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)center.z));
    +
    +    Pointf mouse_pos = transform(mouse_ray, m).intersect_plane(0.0);
    +
    +    coordf_t orig_len = length(m_starting_drag_position - center);
    +    coordf_t new_len = length(mouse_pos);
    +    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    +
    +    m_scale_x = m_starting_scale_z * (float)ratio;
    +//    m_scale_z = m_starting_scale_z * (float)ratio;
    +
    +    if (m_scale_x > 10.0)
    +    {
    +        int a = 0;
    +    }
    +}
    +
    +void GLGizmoScale3D::do_scale_uniform(const Linef3& mouse_ray)
    +{
    +    // calculates the intersection of the mouse ray with the plane parallel to plane XY and passing through the box min point
    +    const Pointf3& center = m_box.center();
    +    Eigen::Transform m = Eigen::Transform::Identity();
    +    m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)m_box.min.z));
    +
    +    Pointf mouse_pos = transform(mouse_ray, m).intersect_plane(0.0);
    +
    +    coordf_t orig_len = length(m_starting_drag_position - center);
    +    coordf_t new_len = length(Vectorf3(mouse_pos.x, mouse_pos.y, m_box.min.z - center.z));
    +    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    +
    +    m_scale_x = m_starting_scale_y * (float)ratio;
    +    m_scale_y = m_starting_scale_y * (float)ratio;
    +    m_scale_z = m_starting_scale_z * (float)ratio;
    +}
    +
     } // namespace GUI
     } // namespace Slic3r
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index fafd8091ab..35fc56b916 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -3,10 +3,11 @@
     
     #include "../../slic3r/GUI/GLTexture.hpp"
     #include "../../libslic3r/Point.hpp"
    +#include "../../libslic3r/BoundingBox.hpp"
     
     #include 
     
    -#define ENABLE_GIZMOS_3D 1
    +#define ENABLE_GIZMOS_3D 0
     
     namespace Slic3r {
     
    @@ -34,7 +35,7 @@ protected:
             Grabber();
     
             void render(bool hover) const;
    -        void render_for_picking() const;
    +        void render_for_picking() const { render(color); }
     
         private:
             void render(const float* render_color) const;
    @@ -73,8 +74,8 @@ public:
         EState get_state() const { return m_state; }
         void set_state(EState state) { m_state = state; on_set_state(); }
     
    -    unsigned int get_texture_id() const;
    -    int get_textures_size() const;
    +    unsigned int get_texture_id() const { return m_textures[m_state].get_id(); }
    +    int get_textures_size() const { return m_textures[Off].get_width(); }
     
         int get_hover_id() const { return m_hover_id; }
         void set_hover_id(int id);
    @@ -84,10 +85,10 @@ public:
         void start_dragging();
         void stop_dragging();
         void update(const Linef3& mouse_ray);
    -    void refresh();
    +    void refresh() { on_refresh(); }
     
    -    void render(const BoundingBoxf3& box) const;
    -    void render_for_picking(const BoundingBoxf3& box) const;
    +    void render(const BoundingBoxf3& box) const { on_render(box); }
    +    void render_for_picking(const BoundingBoxf3& box) const { on_render_for_picking(box); }
     
     protected:
         virtual bool on_init() = 0;
    @@ -137,14 +138,14 @@ private:
     public:
         explicit GLGizmoRotate(Axis axis);
     
    -    float get_angle() const;
    +    float get_angle() const { return m_angle; }
         void set_angle(float angle);
     
     protected:
         virtual bool on_init();
    -    virtual void on_set_state();
    +    virtual void on_set_state() { m_keep_initial_values = (m_state == On) ? false : true; }
         virtual void on_update(const Linef3& mouse_ray);
    -    virtual void on_refresh();
    +    virtual void on_refresh() { m_keep_initial_values = false; }
         virtual void on_render(const BoundingBoxf3& box) const;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const;
     
    @@ -169,25 +170,50 @@ class GLGizmoRotate3D : public GLGizmoBase
     public:
         GLGizmoRotate3D();
     
    -    float get_angle_x() const;
    -    void set_angle_x(float angle);
    +    float get_angle_x() const { return m_x.get_angle(); }
    +    void set_angle_x(float angle) { m_x.set_angle(angle); }
     
    -    float get_angle_y() const;
    -    void set_angle_y(float angle);
    +    float get_angle_y() const { return m_y.get_angle(); }
    +    void set_angle_y(float angle) { m_y.set_angle(angle); }
     
    -    float get_angle_z() const;
    -    void set_angle_z(float angle);
    +    float get_angle_z() const { return m_z.get_angle(); }
    +    void set_angle_z(float angle) { m_z.set_angle(angle); }
     
     protected:
         virtual bool on_init();
    -    virtual void on_set_state();
    -    virtual void on_set_hover_id();
    +    virtual void on_set_state()
    +    {
    +        m_x.set_state(m_state);
    +        m_y.set_state(m_state);
    +        m_z.set_state(m_state);
    +    }
    +    virtual void on_set_hover_id()
    +    {
    +        m_x.set_hover_id(m_hover_id == 0 ? 0 : -1);
    +        m_y.set_hover_id(m_hover_id == 1 ? 0 : -1);
    +        m_z.set_hover_id(m_hover_id == 2 ? 0 : -1);
    +    }
         virtual void on_start_dragging();
         virtual void on_stop_dragging();
    -    virtual void on_update(const Linef3& mouse_ray);
    -    virtual void on_refresh();
    +    virtual void on_update(const Linef3& mouse_ray)
    +    {
    +        m_x.update(mouse_ray);
    +        m_y.update(mouse_ray);
    +        m_z.update(mouse_ray);
    +    }
    +    virtual void on_refresh()
    +    {
    +        m_x.refresh();
    +        m_y.refresh();
    +        m_z.refresh();
    +    }
         virtual void on_render(const BoundingBoxf3& box) const;
    -    virtual void on_render_for_picking(const BoundingBoxf3& box) const;
    +    virtual void on_render_for_picking(const BoundingBoxf3& box) const
    +    {
    +        m_x.render_for_picking(box);
    +        m_y.render_for_picking(box);
    +        m_z.render_for_picking(box);
    +    }
     };
     
     class GLGizmoScale : public GLGizmoBase
    @@ -202,8 +228,8 @@ class GLGizmoScale : public GLGizmoBase
     public:
         GLGizmoScale();
     
    -    float get_scale() const;
    -    void set_scale(float scale);
    +    float get_scale() const { return m_scale; }
    +    void set_scale(float scale) { m_starting_scale = scale; }
     
     protected:
         virtual bool on_init();
    @@ -213,6 +239,58 @@ protected:
         virtual void on_render_for_picking(const BoundingBoxf3& box) const;
     };
     
    +class GLGizmoScale3D : public GLGizmoBase
    +{
    +    static const float Offset;
    +
    +    mutable BoundingBoxf3 m_box;
    +
    +    float m_scale_x;
    +    float m_scale_y;
    +    float m_scale_z;
    +
    +    float m_starting_scale_x;
    +    float m_starting_scale_y;
    +    float m_starting_scale_z;
    +
    +    Pointf3 m_starting_drag_position;
    +
    +public:
    +    GLGizmoScale3D();
    +
    +    float get_scale_x() const { return m_scale_x; }
    +    void set_scale_x(float scale) { m_starting_scale_x = scale; }
    +
    +    float get_scale_y() const { return m_scale_y; }
    +    void set_scale_y(float scale) { m_starting_scale_y = scale; }
    +
    +    float get_scale_z() const { return m_scale_z; }
    +    void set_scale_z(float scale) { m_starting_scale_z = scale; }
    +
    +    void set_scale(float scale)
    +    {
    +        m_starting_scale_x = scale;
    +        m_starting_scale_y = scale;
    +        m_starting_scale_z = scale;
    +    }
    +
    +protected:
    +    virtual bool on_init();
    +    virtual void on_start_dragging();
    +    virtual void on_update(const Linef3& mouse_ray);
    +    virtual void on_render(const BoundingBoxf3& box) const;
    +    virtual void on_render_for_picking(const BoundingBoxf3& box) const;
    +
    +    void render_box_x_faces() const;
    +    void render_box_y_faces() const;
    +    void render_box_z_faces() const;
    +
    +    void do_scale_x(const Linef3& mouse_ray);
    +    void do_scale_y(const Linef3& mouse_ray);
    +    void do_scale_z(const Linef3& mouse_ray);
    +    void do_scale_uniform(const Linef3& mouse_ray);
    +};
    +
     } // namespace GUI
     } // namespace Slic3r
     
    
    From edf03e00dd00936e7581a3eee4600ec9c88a7ab4 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Tue, 21 Aug 2018 09:03:38 +0200
    Subject: [PATCH 149/185] 3D rotate gizmo colored by axis in preview mode
    
    ---
     xs/src/slic3r/GUI/GLGizmo.cpp | 8 ++++----
     xs/src/slic3r/GUI/GLGizmo.hpp | 2 +-
     2 files changed, 5 insertions(+), 5 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 8ead590c96..ca4c4a2034 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -260,11 +260,11 @@ void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
         ::glPushMatrix();
         transform_to_local();
     
    -    ::glLineWidth(2.0f);
    -
     #if ENABLE_GIZMOS_3D
    -    ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_base_color);
    +    ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f);
    +    ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color);
     #else
    +    ::glLineWidth(2.0f);
         ::glColor3fv(m_drag_color);
     #endif // ENABLE_GIZMOS_3D
     
    @@ -403,7 +403,7 @@ void GLGizmoRotate::render_grabber() const
         m_grabbers[0].angle_z = m_angle;
     
     #if ENABLE_GIZMOS_3D
    -    ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_base_color);
    +    ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color);
     #else
         ::glColor3fv(m_drag_color);
     #endif // ENABLE_GIZMOS_3D
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 35fc56b916..736d05c275 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -7,7 +7,7 @@
     
     #include 
     
    -#define ENABLE_GIZMOS_3D 0
    +#define ENABLE_GIZMOS_3D 1
     
     namespace Slic3r {
     
    
    From dd1fd66a47f64ca8bfba736728787d2781a983e1 Mon Sep 17 00:00:00 2001
    From: Martin Loidl 
    Date: Sun, 8 Jul 2018 14:32:48 +0200
    Subject: [PATCH 150/185] Added possibility for upload to Duet
    
    Further changes:
    - Added new configuration option Host Type
    - Added abstract base class for future printer hosts
    - Moved location of upload dialog (also made it a little bit more configureable)
    - added possibility to send file via postfield instead a new frame
    ---
     lib/Slic3r.pm                               |   2 +
     lib/Slic3r/GUI/Plater.pm                    |  15 +-
     resources/profiles/PrusaResearch.ini        |   4 +-
     xs/CMakeLists.txt                           |   9 +-
     xs/src/libslic3r/PrintConfig.cpp            |  32 ++-
     xs/src/libslic3r/PrintConfig.hpp            |  27 +-
     xs/src/perlglue.cpp                         |   3 +-
     xs/src/slic3r/GUI/Field.cpp                 |   2 +
     xs/src/slic3r/GUI/GUI.cpp                   |   2 +
     xs/src/slic3r/GUI/OptionsGroup.cpp          |   6 +-
     xs/src/slic3r/GUI/Preset.cpp                |   4 +-
     xs/src/slic3r/GUI/Tab.cpp                   |  63 +++--
     xs/src/slic3r/GUI/Tab.hpp                   |   3 +-
     xs/src/slic3r/Utils/Duet.cpp                | 281 ++++++++++++++++++++
     xs/src/slic3r/Utils/Duet.hpp                |  46 ++++
     xs/src/slic3r/Utils/Http.cpp                |  24 ++
     xs/src/slic3r/Utils/Http.hpp                |   3 +
     xs/src/slic3r/Utils/OctoPrint.cpp           |  78 ++----
     xs/src/slic3r/Utils/OctoPrint.hpp           |   8 +-
     xs/src/slic3r/Utils/PrintHost.hpp           |  31 +++
     xs/src/slic3r/Utils/PrintHostFactory.cpp    |  21 ++
     xs/src/slic3r/Utils/PrintHostFactory.hpp    |  24 ++
     xs/src/slic3r/Utils/PrintHostSendDialog.cpp |  54 ++++
     xs/src/slic3r/Utils/PrintHostSendDialog.hpp |  40 +++
     xs/xsp/Utils_OctoPrint.xsp                  |  13 -
     xs/xsp/Utils_PrintHost.xsp                  |  10 +
     xs/xsp/Utils_PrintHostFactory.xsp           |  13 +
     xs/xsp/my.map                               |   8 +-
     xs/xsp/typemap.xspt                         |   1 +
     29 files changed, 699 insertions(+), 128 deletions(-)
     create mode 100644 xs/src/slic3r/Utils/Duet.cpp
     create mode 100644 xs/src/slic3r/Utils/Duet.hpp
     create mode 100644 xs/src/slic3r/Utils/PrintHost.hpp
     create mode 100644 xs/src/slic3r/Utils/PrintHostFactory.cpp
     create mode 100644 xs/src/slic3r/Utils/PrintHostFactory.hpp
     create mode 100644 xs/src/slic3r/Utils/PrintHostSendDialog.cpp
     create mode 100644 xs/src/slic3r/Utils/PrintHostSendDialog.hpp
     delete mode 100644 xs/xsp/Utils_OctoPrint.xsp
     create mode 100644 xs/xsp/Utils_PrintHost.xsp
     create mode 100644 xs/xsp/Utils_PrintHostFactory.xsp
    
    diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
    index 0c6c81bb57..5eaa0e522a 100644
    --- a/lib/Slic3r.pm
    +++ b/lib/Slic3r.pm
    @@ -167,6 +167,8 @@ sub thread_cleanup {
         *Slic3r::GUI::PresetHints::DESTROY      = sub {};
         *Slic3r::GUI::TabIface::DESTROY         = sub {};
         *Slic3r::OctoPrint::DESTROY             = sub {};
    +    *Slic3r::Duet::DESTROY                  = sub {};
    +    *Slic3r::PrintHostFactory::DESTROY                  = sub {};
         *Slic3r::PresetUpdater::DESTROY         = sub {};
         return undef;  # this prevents a "Scalars leaked" warning
     }
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index a0eef72fea..89f803228e 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -53,7 +53,7 @@ sub new {
         my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
         $self->{config} = Slic3r::Config::new_from_defaults_keys([qw(
             bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height
    -        serial_port serial_speed octoprint_host octoprint_apikey octoprint_cafile
    +        serial_port serial_speed host_type print_host printhost_apikey printhost_cafile
             nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width
     	wipe_tower_rotation_angle extruder_colour filament_colour max_print_height printer_model
         )]);
    @@ -1569,7 +1569,7 @@ sub on_export_completed {
                 $message = L("File added to print queue");
                 $do_print = 1;
             } elsif ($self->{send_gcode_file}) {
    -            $message = L("Sending G-code file to the OctoPrint server...");
    +            $message = L("Sending G-code file to the Printer Host ...");
                 $send_gcode = 1;
             } else {
                 $message = L("G-code file exported to ") . $self->{export_gcode_output_file};
    @@ -1585,9 +1585,10 @@ sub on_export_completed {
     
         # Send $self->{send_gcode_file} to OctoPrint.
         if ($send_gcode) {
    -        my $op = Slic3r::OctoPrint->new($self->{config});
    -        if ($op->send_gcode($self->{send_gcode_file})) {
    -            $self->statusbar->SetStatusText(L("OctoPrint upload finished."));
    +        my $host = Slic3r::PrintHostFactory::get_print_host($self->{config});
    +
    +        if ($host->send_gcode($self->{send_gcode_file})) {
    +            $self->statusbar->SetStatusText(L("Upload to host finished."));
             } else {
                 $self->statusbar->SetStatusText("");
             }
    @@ -1914,8 +1915,8 @@ sub on_config_change {
             } elsif ($opt_key eq 'serial_port') {
                 $self->{btn_print}->Show($config->get('serial_port'));
                 $self->Layout;
    -        } elsif ($opt_key eq 'octoprint_host') {
    -            $self->{btn_send_gcode}->Show($config->get('octoprint_host'));
    +        } elsif ($opt_key eq 'print_host') {
    +            $self->{btn_send_gcode}->Show($config->get('print_host'));
                 $self->Layout;
             } elsif ($opt_key eq 'variable_layer_height') {
                 if ($config->get('variable_layer_height') != 1) {
    diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini
    index 32ec800e7f..7e358c77e2 100644
    --- a/resources/profiles/PrusaResearch.ini
    +++ b/resources/profiles/PrusaResearch.ini
    @@ -1007,8 +1007,8 @@ max_layer_height = 0.25
     min_layer_height = 0.07
     max_print_height = 200
     nozzle_diameter = 0.4
    -octoprint_apikey = 
    -octoprint_host = 
    +printhost_apikey = 
    +print_host = 
     printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n
     printer_settings_id = 
     retract_before_travel = 1
    diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
    index d4306c525c..998d44cb05 100644
    --- a/xs/CMakeLists.txt
    +++ b/xs/CMakeLists.txt
    @@ -251,8 +251,14 @@ add_library(libslic3r_gui STATIC
         ${LIBDIR}/slic3r/Utils/Http.hpp
         ${LIBDIR}/slic3r/Utils/FixModelByWin10.cpp
         ${LIBDIR}/slic3r/Utils/FixModelByWin10.hpp
    +    ${LIBDIR}/slic3r/Utils/PrintHostSendDialog.cpp
    +    ${LIBDIR}/slic3r/Utils/PrintHostSendDialog.hpp
         ${LIBDIR}/slic3r/Utils/OctoPrint.cpp
         ${LIBDIR}/slic3r/Utils/OctoPrint.hpp
    +	${LIBDIR}/slic3r/Utils/Duet.cpp
    +	${LIBDIR}/slic3r/Utils/Duet.hpp
    +	${LIBDIR}/slic3r/Utils/PrintHostFactory.cpp
    +	${LIBDIR}/slic3r/Utils/PrintHostFactory.hpp
         ${LIBDIR}/slic3r/Utils/Bonjour.cpp
         ${LIBDIR}/slic3r/Utils/Bonjour.hpp
         ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp
    @@ -411,7 +417,8 @@ set(XS_XSP_FILES
         ${XSP_DIR}/Surface.xsp
         ${XSP_DIR}/SurfaceCollection.xsp
         ${XSP_DIR}/TriangleMesh.xsp
    -    ${XSP_DIR}/Utils_OctoPrint.xsp
    +	${XSP_DIR}/Utils_PrintHostFactory.xsp
    +	${XSP_DIR}/Utils_PrintHost.xsp
         ${XSP_DIR}/Utils_PresetUpdater.xsp
         ${XSP_DIR}/AppController.xsp
         ${XSP_DIR}/XS.xsp
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index a78e73fb53..794c276085 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -1137,24 +1137,36 @@ PrintConfigDef::PrintConfigDef()
         def->cli = "nozzle-diameter=f@";
         def->default_value = new ConfigOptionFloats { 0.5 };
     
    -    def = this->add("octoprint_apikey", coString);
    -    def->label = L("API Key");
    -    def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain "
    -                   "the API Key required for authentication.");
    +    def = this->add("host_type", coEnum);
    +    def->label = L("Host Type");
    +    def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain "
    +                   "the kind of the host.");
    +    def->cli = "host-type=s";
    +    def->enum_keys_map = &ConfigOptionEnum::get_enum_values();
    +    def->enum_values.push_back("octoprint");
    +    def->enum_values.push_back("duet");
    +    def->enum_labels.push_back("OctoPrint");
    +    def->enum_labels.push_back("Duet");
    +    def->default_value = new ConfigOptionEnum(htOctoPrint);
    +
    +    def = this->add("printhost_apikey", coString);
    +    def->label = L("API Key / Password");
    +    def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
    +                   "the API Key or the password required for authentication.");
         def->cli = "octoprint-apikey=s";
         def->default_value = new ConfigOptionString("");
         
    -    def = this->add("octoprint_cafile", coString);
    +    def = this->add("printhost_cafile", coString);
         def->label = "HTTPS CA file";
         def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
                        "If left blank, the default OS CA certificate repository is used.";
         def->cli = "octoprint-cafile=s";
         def->default_value = new ConfigOptionString("");
     
    -    def = this->add("octoprint_host", coString);
    +    def = this->add("print_host", coString);
         def->label = L("Hostname, IP or URL");
    -    def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain "
    -                   "the hostname, IP address or URL of the OctoPrint instance.");
    +    def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
    +                   "the hostname, IP address or URL of the printer host instance.");
         def->cli = "octoprint-host=s";
         def->default_value = new ConfigOptionString("");
     
    @@ -2107,10 +2119,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
             std::ostringstream oss;
             oss << "0x0," << p.value.x << "x0," << p.value.x << "x" << p.value.y << ",0x" << p.value.y;
             value = oss.str();
    -// Maybe one day we will rename octoprint_host to print_host as it has been done in the upstream Slic3r.
    -// Commenting this out fixes github issue #869 for now.
    -//    } else if (opt_key == "octoprint_host" && !value.empty()) {
    -//        opt_key = "print_host";
         } else if ((opt_key == "perimeter_acceleration" && value == "25")
             || (opt_key == "infill_acceleration" && value == "50")) {
             /*  For historical reasons, the world's full of configs having these very low values;
    diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
    index b18603d877..438e90681f 100644
    --- a/xs/src/libslic3r/PrintConfig.hpp
    +++ b/xs/src/libslic3r/PrintConfig.hpp
    @@ -27,6 +27,10 @@ enum GCodeFlavor {
         gcfSmoothie, gcfNoExtrusion,
     };
     
    +enum PrintHostType {
    +    htOctoPrint, htDuet,
    +};
    +
     enum InfillPattern {
         ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
         ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral,
    @@ -61,6 +65,15 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_
         return keys_map;
     }
     
    +template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() {
    +    static t_config_enum_values keys_map;
    +    if (keys_map.empty()) {
    +        keys_map["octoprint"]       = htOctoPrint;
    +        keys_map["duet"]            = htDuet;
    +    }
    +    return keys_map;
    +}
    +
     template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() {
         static t_config_enum_values keys_map;
         if (keys_map.empty()) {
    @@ -789,18 +802,20 @@ class HostConfig : public StaticPrintConfig
     {
         STATIC_PRINT_CONFIG_CACHE(HostConfig)
     public:
    -    ConfigOptionString              octoprint_host;
    -    ConfigOptionString              octoprint_apikey;
    -    ConfigOptionString              octoprint_cafile;
    +    ConfigOptionEnum host_type;
    +    ConfigOptionString              print_host;
    +    ConfigOptionString              printhost_apikey;
    +    ConfigOptionString              printhost_cafile;
         ConfigOptionString              serial_port;
         ConfigOptionInt                 serial_speed;
         
     protected:
         void initialize(StaticCacheBase &cache, const char *base_ptr)
         {
    -        OPT_PTR(octoprint_host);
    -        OPT_PTR(octoprint_apikey);
    -        OPT_PTR(octoprint_cafile);
    +        OPT_PTR(host_type);
    +        OPT_PTR(print_host);
    +        OPT_PTR(printhost_apikey);
    +        OPT_PTR(printhost_cafile);
             OPT_PTR(serial_port);
             OPT_PTR(serial_speed);
         }
    diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
    index c8aadc8c35..997938a910 100644
    --- a/xs/src/perlglue.cpp
    +++ b/xs/src/perlglue.cpp
    @@ -64,9 +64,10 @@ REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
     REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
     REGISTER_CLASS(TabIface, "GUI::Tab");
     REGISTER_CLASS(PresetUpdater, "PresetUpdater");
    -REGISTER_CLASS(OctoPrint, "OctoPrint");
     REGISTER_CLASS(AppController, "AppController");
     REGISTER_CLASS(PrintController, "PrintController");
    +REGISTER_CLASS(PrintHost, "PrintHost");
    +REGISTER_CLASS(PrintHostFactory, "PrintHostFactory");
     
     SV* ConfigBase__as_hash(ConfigBase* THIS)
     {
    diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp
    index 85fa790a5e..757a18f115 100644
    --- a/xs/src/slic3r/GUI/Field.cpp
    +++ b/xs/src/slic3r/GUI/Field.cpp
    @@ -586,6 +586,8 @@ boost::any& Choice::get_value()
     			m_value = static_cast(ret_enum);
     		else if (m_opt_id.compare("seam_position") == 0)
     			m_value = static_cast(ret_enum);
    +		else if (m_opt_id.compare("host_type") == 0)
    +			m_value = static_cast(ret_enum);
     	}	
     
     	return m_value;
    diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
    index 8cd7ed7768..8555f0b921 100644
    --- a/xs/src/slic3r/GUI/GUI.cpp
    +++ b/xs/src/slic3r/GUI/GUI.cpp
    @@ -604,6 +604,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
     				config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value)));
     			else if (opt_key.compare("seam_position") == 0)
     				config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value)));
    +			else if (opt_key.compare("host_type") == 0)
    +				config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value)));
     			}
     			break;
     		case coPoints:{
    diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp
    index d5cc29e19f..a2d6559a9a 100644
    --- a/xs/src/slic3r/GUI/OptionsGroup.cpp
    +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp
    @@ -459,8 +459,12 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
     		else if (opt_key.compare("support_material_pattern") == 0){
     			ret = static_cast(config.option>(opt_key)->value);
     		}
    -		else if (opt_key.compare("seam_position") == 0)
    +		else if (opt_key.compare("seam_position") == 0){
     			ret = static_cast(config.option>(opt_key)->value);
    +		}
    +		else if (opt_key.compare("host_type") == 0){
    +			ret = static_cast(config.option>(opt_key)->value);
    +		}
     	}
     		break;
     	case coPoints:
    diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp
    index 9f51f7b978..8335e48b58 100644
    --- a/xs/src/slic3r/GUI/Preset.cpp
    +++ b/xs/src/slic3r/GUI/Preset.cpp
    @@ -329,8 +329,8 @@ const std::vector& Preset::printer_options()
         static std::vector s_opts;
         if (s_opts.empty()) {
             s_opts = {
    -            "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", 
    -            "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
    +            "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "host_type",
    +            "print_host", "printhost_apikey", "printhost_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
                 "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
                 "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction",
                 "cooling_tube_length", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits",
    diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
    index 7c4322c5a0..13b0ece5f5 100644
    --- a/xs/src/slic3r/GUI/Tab.cpp
    +++ b/xs/src/slic3r/GUI/Tab.cpp
    @@ -5,7 +5,8 @@
     #include "../../libslic3r/Utils.hpp"
     
     #include "slic3r/Utils/Http.hpp"
    -#include "slic3r/Utils/OctoPrint.hpp"
    +#include "slic3r/Utils/PrintHostFactory.hpp"
    +#include "slic3r/Utils/PrintHost.hpp"
     #include "slic3r/Utils/Serial.hpp"
     #include "BonjourDialog.hpp"
     #include "WipeTowerDialog.hpp"
    @@ -1521,10 +1522,12 @@ void TabPrinter::build()
     			optgroup->append_line(line);
     		}
     
    -		optgroup = page->new_optgroup(_(L("OctoPrint upload")));
    +		optgroup = page->new_optgroup(_(L("Printer Host upload")));
     
    -		auto octoprint_host_browse = [this, optgroup] (wxWindow* parent) {
    -			auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
    +		optgroup->append_single_option_line("host_type");
    +
    +		auto printhost_browse = [this, optgroup] (wxWindow* parent) {
    +			auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
     			btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
     			auto sizer = new wxBoxSizer(wxHORIZONTAL);
     			sizer->Add(btn);
    @@ -1532,47 +1535,52 @@ void TabPrinter::build()
     			btn->Bind(wxEVT_BUTTON, [this, parent, optgroup](wxCommandEvent e) {
     				BonjourDialog dialog(parent);
     				if (dialog.show_and_lookup()) {
    -					optgroup->set_value("octoprint_host", std::move(dialog.get_selected()), true);
    +					optgroup->set_value("print_host", std::move(dialog.get_selected()), true);
     				}
     			});
     
     			return sizer;
     		};
     
    -		auto octoprint_host_test = [this](wxWindow* parent) {
    -			auto btn = m_octoprint_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), 
    +		auto print_host_test = [this](wxWindow* parent) {
    +			auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), 
     				wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
     			btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG));
     			auto sizer = new wxBoxSizer(wxHORIZONTAL);
     			sizer->Add(btn);
     
     			btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
    -				OctoPrint octoprint(m_config);
    -				wxString msg;
    -				if (octoprint.test(msg)) {
    -					show_info(this, _(L("Connection to OctoPrint works correctly.")), _(L("Success!")));
    -				} else {
    -					const auto text = wxString::Format("%s: %s\n\n%s",
    -						_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required."))
    -					);
    +				PrintHost *host = PrintHostFactory::get_print_host(m_config);
    +				if (host == NULL) {
    +					const auto text = wxString::Format("%s",
    +						_(L("Could not get a valid Printer Host reference")));
     					show_error(this, text);
    +					return;
     				}
    +				wxString msg;
    +				if (host->test(msg)) {
    +					show_info(this, host->get_test_ok_msg(), _(L("Success!")));
    +				} else {
    +					show_error(this, host->get_test_failed_msg(msg));
    +				}
    +
    +				delete (host);
     			});
     
     			return sizer;
     		};
     
    -		Line host_line = optgroup->create_single_option_line("octoprint_host");
    -		host_line.append_widget(octoprint_host_browse);
    -		host_line.append_widget(octoprint_host_test);
    +		Line host_line = optgroup->create_single_option_line("print_host");
    +		host_line.append_widget(printhost_browse);
    +		host_line.append_widget(print_host_test);
     		optgroup->append_line(host_line);
    -		optgroup->append_single_option_line("octoprint_apikey");
    +		optgroup->append_single_option_line("printhost_apikey");
     
     		if (Http::ca_file_supported()) {
     
    -			Line cafile_line = optgroup->create_single_option_line("octoprint_cafile");
    +			Line cafile_line = optgroup->create_single_option_line("printhost_cafile");
     
    -			auto octoprint_cafile_browse = [this, optgroup] (wxWindow* parent) {
    +			auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) {
     				auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
     				btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG));
     				auto sizer = new wxBoxSizer(wxHORIZONTAL);
    @@ -1582,17 +1590,17 @@ void TabPrinter::build()
     					static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"));
     					wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
     					if (openFileDialog.ShowModal() != wxID_CANCEL) {
    -						optgroup->set_value("octoprint_cafile", std::move(openFileDialog.GetPath()), true);
    +						optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true);
     					}
     				});
     
     				return sizer;
     			};
     
    -			cafile_line.append_widget(octoprint_cafile_browse);
    +			cafile_line.append_widget(printhost_cafile_browse);
     			optgroup->append_line(cafile_line);
     
    -			auto octoprint_cafile_hint = [this, optgroup] (wxWindow* parent) {
    +			auto printhost_cafile_hint = [this, optgroup] (wxWindow* parent) {
     				auto txt = new wxStaticText(parent, wxID_ANY, 
     					_(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")));
     				auto sizer = new wxBoxSizer(wxHORIZONTAL);
    @@ -1602,7 +1610,7 @@ void TabPrinter::build()
     
     			Line cafile_hint { "", "" };
     			cafile_hint.full_width = 1;
    -			cafile_hint.widget = std::move(octoprint_cafile_hint);
    +			cafile_hint.widget = std::move(printhost_cafile_hint);
     			optgroup->append_line(cafile_hint);
     
     		}
    @@ -1897,7 +1905,10 @@ void TabPrinter::update(){
     			m_serial_test_btn->Disable();
     	}
     
    -	m_octoprint_host_test_btn->Enable(!m_config->opt_string("octoprint_host").empty());
    +	PrintHost *host = PrintHostFactory::get_print_host(m_config);
    +	m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test());
    +	m_printhost_browse_btn->Enable(host->have_auto_discovery());
    +	delete (host);
     	
     	bool have_multiple_extruders = m_extruders_count > 1;
     	get_field("toolchange_gcode")->toggle(have_multiple_extruders);
    diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp
    index 8b4eae7de5..230fe659e5 100644
    --- a/xs/src/slic3r/GUI/Tab.hpp
    +++ b/xs/src/slic3r/GUI/Tab.hpp
    @@ -321,7 +321,8 @@ class TabPrinter : public Tab
     	bool		m_rebuild_kinematics_page = false;
     public:
     	wxButton*	m_serial_test_btn;
    -	wxButton*	m_octoprint_host_test_btn;
    +	wxButton*	m_print_host_test_btn;
    +	wxButton*	m_printhost_browse_btn;
     
     	size_t		m_extruders_count;
     	size_t		m_extruders_count_old = 0;
    diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
    new file mode 100644
    index 0000000000..aabc8eb403
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/Duet.cpp
    @@ -0,0 +1,281 @@
    +#include "Duet.hpp"
    +#include "PrintHostSendDialog.hpp"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include "libslic3r/PrintConfig.hpp"
    +#include "slic3r/GUI/GUI.hpp"
    +#include "slic3r/GUI/MsgDialog.hpp"
    +#include "Http.hpp"
    +
    +namespace fs = boost::filesystem;
    +namespace pt = boost::property_tree;
    +
    +namespace Slic3r {
    +
    +Duet::Duet(DynamicPrintConfig *config) :
    +	host(config->opt_string("print_host")),
    +	password(config->opt_string("printhost_apikey"))
    +{}
    +
    +bool Duet::test(wxString &msg) const
    +{
    +	bool connected = connect(msg);
    +	if (connected) {
    +		disconnect();
    +	}
    +
    +	return connected;
    +}
    +
    +wxString Duet::get_test_ok_msg () const
    +{
    +	return wxString::Format("%s", _(L("Connection to Duet works correctly.")));
    +}
    +
    +wxString Duet::get_test_failed_msg (wxString &msg) const
    +{
    +	return wxString::Format("%s: %s",
    +						_(L("Could not connect to Duet")), msg);
    +}
    +
    +bool Duet::send_gcode(const std::string &filename) const
    +{
    +	enum { PROGRESS_RANGE = 1000 };
    +
    +	const auto errortitle = _(L("Error while uploading to the Duet"));
    +	fs::path filepath(filename);
    +
    +	PrintHostSendDialog send_dialog(filepath.filename(), true);
    +	if (send_dialog.ShowModal() != wxID_OK) { return false; }
    +
    +	const bool print = send_dialog.print(); 
    +	const auto upload_filepath = send_dialog.filename();
    +	const auto upload_filename = upload_filepath.filename();
    +	const auto upload_parent_path = upload_filepath.parent_path();
    +
    +	wxProgressDialog progress_dialog(
    +	 	_(L("Duet upload")),
    +	 	_(L("Sending G-code file to Duet...")),
    +		PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
    +	progress_dialog.Pulse();
    +
    +	wxString connect_msg;
    +	if (!connect(connect_msg)) {
    +		auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
    +		GUI::show_error(&progress_dialog, std::move(errormsg));
    +		return false;
    +	}
    +
    +	bool res = true;
    +
    +
    +	auto upload_cmd = get_upload_url(upload_filepath.string());
    +	BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
    +		% filepath.string()
    +		% upload_filename.string()
    +		% upload_parent_path.string()
    +		% print
    +		% upload_cmd;
    +
    +	auto http = Http::post(std::move(upload_cmd));
    +	http.postfield_add_file(filename)
    +		.on_complete([&](std::string body, unsigned status) {
    +			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
    +			progress_dialog.Update(PROGRESS_RANGE);
    +
    +			int err_code = get_err_code_from_body(body);
    +			switch (err_code) {
    +				case 0:
    +					break;
    +				default:
    +					auto msg = format_error(body, L("Unknown error occured"), 0);
    +					GUI::show_error(&progress_dialog, std::move(msg));
    +					res = false;
    +					break;
    +			}
    +
    +			if (err_code == 0 && print) {
    +				wxString errormsg;
    +				res = start_print(errormsg, upload_filepath.string());
    +				if (!res) {
    +					GUI::show_error(&progress_dialog, std::move(errormsg));
    +				}
    +			}
    +		})
    +		.on_error([&](std::string body, std::string error, unsigned status) {
    +			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
    +			auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
    +			GUI::show_error(&progress_dialog, std::move(errormsg));
    +			res = false;
    +		})
    +		.on_progress([&](Http::Progress progress, bool &cancel) {
    +			if (cancel) {
    +				// Upload was canceled
    +				res = false;
    +			} else if (progress.ultotal > 0) {
    +				int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
    +				cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1));    // Cap the value to prevent premature dialog closing
    +			} else {
    +				cancel = !progress_dialog.Pulse();
    +			}
    +		})
    +		.perform_sync();
    +
    +	disconnect();
    +
    +	return res;
    +}
    +
    +bool Duet::have_auto_discovery() const
    +{
    +	return false;
    +}
    +
    +bool Duet::can_test() const
    +{
    +	return true;
    +}
    +
    +bool Duet::connect(wxString &msg) const
    +{
    +	bool res = false;
    +	auto url = get_connect_url();
    +
    +	auto http = Http::get(std::move(url));
    +	http.on_error([&](std::string body, std::string error, unsigned status) {
    +			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
    +			msg = format_error(body, error, status);
    +		})
    +		.on_complete([&](std::string body, unsigned) {
    +			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
    +
    +			int err_code = get_err_code_from_body(body);
    +			switch (err_code) {
    +				case 0:
    +					res = true;
    +					break;
    +				case 1:
    +					msg = format_error(body, L("Wrong password"), 0);
    +					break;
    +				case 2:
    +					msg = format_error(body, L("Could not get resources to create a new connection"), 0);
    +					break;
    +				default:
    +					msg = format_error(body, L("Unknown error occured"), 0);
    +					break;
    +			}
    +
    +		})
    +		.perform_sync();
    +
    +	return res;
    +}
    +
    +void Duet::disconnect() const
    +{
    +	auto url =  (boost::format("%1%rr_disconnect")
    +			% get_base_url()).str();
    +
    +	auto http = Http::get(std::move(url));
    +	http.on_error([&](std::string body, std::string error, unsigned status) {
    +		// we don't care about it, if disconnect is not working Duet will disconnect automatically after some time
    +		BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
    +	})
    +	.perform_sync();
    +
    +}
    +
    +std::string Duet::get_upload_url(const std::string &filename) const
    +{
    +	return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%")
    +			% get_base_url()
    +			% filename 
    +			% timestamp_str()).str();
    +}
    +
    +std::string Duet::get_connect_url() const
    +{
    +	return (boost::format("%1%rr_connect?password=%2%&%3%")
    +			% get_base_url()
    +			% (password.empty() ? "reprap" : password)
    +			% timestamp_str()).str();
    +}
    +
    +std::string Duet::get_base_url() const
    +{
    +	if (host.find("http://") == 0 || host.find("https://") == 0) {
    +		if (host.back() == '/') {
    +			return host;
    +		} else {
    +			return (boost::format("%1%/") % host).str();
    +		}
    +	} else {
    +		return (boost::format("http://%1%/") % host).str();
    +	}
    +}
    +
    +std::string Duet::timestamp_str() const
    +{
    +	auto t = std::time(nullptr);
    +    auto tm = *std::localtime(&t);
    +    std::stringstream ss;
    +	ss << "time=" << std::put_time(&tm, "%Y-%d-%mT%H:%M:%S");
    +
    +	return ss.str();
    +}
    +
    +wxString Duet::format_error(const std::string &body, const std::string &error, unsigned status)
    +{
    +	if (status != 0) {
    +		auto wxbody = wxString::FromUTF8(body.data());
    +		return wxString::Format("HTTP %u: %s", status, wxbody);
    +	} else {
    +		return wxString::FromUTF8(error.data());
    +	}
    +}
    +
    +bool Duet::start_print(wxString &msg, const std::string &filename) const {
    +	bool res = false;
    +	auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"")
    +			% get_base_url()
    +			% filename).str();
    +
    +	auto http = Http::get(std::move(url));
    +	http.on_error([&](std::string body, std::string error, unsigned status) {
    +			BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body;
    +			msg = format_error(body, error, status);
    +		})
    +		.on_complete([&](std::string body, unsigned) {
    +			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body;
    +			res = true;
    +		})
    +		.perform_sync();
    +
    +	return res;
    +}
    +
    +int Duet::get_err_code_from_body(const std::string &body) const
    +{
    +	pt::ptree root;
    +	std::istringstream iss (body); // wrap returned json to istringstream
    +	pt::read_json(iss, root);
    +
    +	return root.get("err", 0);
    +}
    +
    +
    +}
    diff --git a/xs/src/slic3r/Utils/Duet.hpp b/xs/src/slic3r/Utils/Duet.hpp
    new file mode 100644
    index 0000000000..83ba0cbbbc
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/Duet.hpp
    @@ -0,0 +1,46 @@
    +#ifndef slic3r_Duet_hpp_
    +#define slic3r_Duet_hpp_
    +
    +#include 
    +#include 
    +
    +#include "PrintHost.hpp"
    +
    +
    +namespace Slic3r {
    +
    +
    +class DynamicPrintConfig;
    +class Http;
    +
    +class Duet : public PrintHost
    +{
    +public:
    +	Duet(DynamicPrintConfig *config);
    +
    +	bool test(wxString &curl_msg) const;
    +	wxString get_test_ok_msg () const;
    +	wxString get_test_failed_msg (wxString &msg) const;
    +	// Send gcode file to duet, filename is expected to be in UTF-8
    +	bool send_gcode(const std::string &filename) const;
    +	bool have_auto_discovery() const;
    +	bool can_test() const;
    +private:
    +	std::string host;
    +	std::string password;
    +	
    +	std::string get_upload_url(const std::string &filename) const;
    +	std::string get_connect_url() const;
    +	std::string get_base_url() const;
    +	std::string timestamp_str() const;
    +	bool connect(wxString &msg) const;
    +	void disconnect() const;
    +	bool start_print(wxString &msg, const std::string &filename) const;
    +	int get_err_code_from_body(const std::string &body) const;
    +	static wxString format_error(const std::string &body, const std::string &error, unsigned status);
    +};
    +
    +
    +}
    +
    +#endif
    diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp
    index 37eb59a00f..f5407b9fb6 100644
    --- a/xs/src/slic3r/Utils/Http.cpp
    +++ b/xs/src/slic3r/Utils/Http.cpp
    @@ -4,6 +4,7 @@
     #include 
     #include 
     #include 
    +#include 
     #include 
     #include 
     
    @@ -42,6 +43,7 @@ struct Http::priv
     	// Used for storing file streams added as multipart form parts
     	// Using a deque here because unlike vector it doesn't ivalidate pointers on insertion
     	std::deque form_files;
    +	std::string postfields;
     	size_t limit;
     	bool cancel;
     
    @@ -60,6 +62,7 @@ struct Http::priv
     	static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
     
     	void form_add_file(const char *name, const fs::path &path, const char* filename);
    +	void postfield_add_file(const fs::path &path);
     
     	std::string curl_error(CURLcode curlcode);
     	std::string body_size_error();
    @@ -187,6 +190,16 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha
     	}
     }
     
    +void Http::priv::postfield_add_file(const fs::path &path)
    +{
    +	std::ifstream f (path.string());
    +	std::string file_content { std::istreambuf_iterator(f), std::istreambuf_iterator() };
    +	if (!postfields.empty()) {
    +		postfields += "&";
    +	}
    +	postfields += file_content;
    +}
    +
     std::string Http::priv::curl_error(CURLcode curlcode)
     {
     	return (boost::format("%1% (%2%)")
    @@ -229,6 +242,11 @@ void Http::priv::http_perform()
     		::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
     	}
     
    +	if (!postfields.empty()) {
    +		::curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields.c_str());
    +		::curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, postfields.size());
    +	}
    +
     	CURLcode res = ::curl_easy_perform(curl);
     
     	if (res != CURLE_OK) {
    @@ -338,6 +356,12 @@ Http& Http::form_add_file(const std::string &name, const fs::path &path, const s
     	return *this;
     }
     
    +Http& Http::postfield_add_file(const fs::path &path)
    +{
    +	if (p) { p->postfield_add_file(path);}
    +	return *this;
    +}
    +
     Http& Http::on_complete(CompleteFn fn)
     {
     	if (p) { p->completefn = std::move(fn); }
    diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp
    index ce4e438cad..cf5712d966 100644
    --- a/xs/src/slic3r/Utils/Http.hpp
    +++ b/xs/src/slic3r/Utils/Http.hpp
    @@ -73,6 +73,9 @@ public:
     	// Same as above except also override the file's filename with a custom one
     	Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
     
    +	// Add the file as POSTFIELD to the request, this can be used for hosts which do not support multipart requests
    +	Http& postfield_add_file(const boost::filesystem::path &path);
    +
     	// Callback called on HTTP request complete
     	Http& on_complete(CompleteFn fn);
     	// Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup,
    diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp
    index 97b4123d44..c62f9b55c7 100644
    --- a/xs/src/slic3r/Utils/OctoPrint.cpp
    +++ b/xs/src/slic3r/Utils/OctoPrint.cpp
    @@ -1,21 +1,11 @@
     #include "OctoPrint.hpp"
    +#include "PrintHostSendDialog.hpp"
     
     #include 
    -#include 
     #include 
     #include 
     
    -#include 
    -#include 
    -#include 
    -#include 
    -#include 
    -#include 
    -#include 
    -
     #include "libslic3r/PrintConfig.hpp"
    -#include "slic3r/GUI/GUI.hpp"
    -#include "slic3r/GUI/MsgDialog.hpp"
     #include "Http.hpp"
     
     namespace fs = boost::filesystem;
    @@ -23,47 +13,10 @@ namespace fs = boost::filesystem;
     
     namespace Slic3r {
     
    -
    -struct SendDialog : public GUI::MsgDialog
    -{
    -	wxTextCtrl *txt_filename;
    -	wxCheckBox *box_print;
    -
    -	SendDialog(const fs::path &path) :
    -		MsgDialog(nullptr, _(L("Send G-Code to printer")), _(L("Upload to OctoPrint with the following filename:")), wxID_NONE),
    -		txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())),
    -		box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))))
    -	{
    -		auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
    -		label_dir_hint->Wrap(CONTENT_WIDTH);
    -
    -		content_sizer->Add(txt_filename, 0, wxEXPAND);
    -		content_sizer->Add(label_dir_hint);
    -		content_sizer->AddSpacer(VERT_SPACING);
    -		content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
    -
    -		btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
    -
    -		txt_filename->SetFocus();
    -		wxString stem(path.stem().wstring());
    -		txt_filename->SetSelection(0, stem.Length());
    -
    -		Fit();
    -	}
    -
    -	fs::path filename() const {
    -		return fs::path(txt_filename->GetValue().wx_str());
    -	}
    -
    -	bool print() const { return box_print->GetValue(); }
    -};
    -
    -
    -
     OctoPrint::OctoPrint(DynamicPrintConfig *config) :
    -	host(config->opt_string("octoprint_host")),
    -	apikey(config->opt_string("octoprint_apikey")),
    -	cafile(config->opt_string("octoprint_cafile"))
    +	host(config->opt_string("print_host")),
    +	apikey(config->opt_string("printhost_apikey")),
    +	cafile(config->opt_string("printhost_cafile"))
     {}
     
     bool OctoPrint::test(wxString &msg) const
    @@ -91,6 +44,17 @@ bool OctoPrint::test(wxString &msg) const
     	return res;
     }
     
    +wxString OctoPrint::get_test_ok_msg () const
    +{
    +	return wxString::Format("%s", _(L("Connection to OctoPrint works correctly.")));
    +}
    +
    +wxString OctoPrint::get_test_failed_msg (wxString &msg) const
    +{
    +	return wxString::Format("%s: %s\n\n%s",
    +						_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required.")));
    +}
    +
     bool OctoPrint::send_gcode(const std::string &filename) const
     {
     	enum { PROGRESS_RANGE = 1000 };
    @@ -98,7 +62,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const
     	const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
     	fs::path filepath(filename);
     
    -	SendDialog send_dialog(filepath.filename());
    +	PrintHostSendDialog send_dialog(filepath.filename(), true);
     	if (send_dialog.ShowModal() != wxID_OK) { return false; }
     
     	const bool print = send_dialog.print();
    @@ -161,6 +125,16 @@ bool OctoPrint::send_gcode(const std::string &filename) const
     	return res;
     }
     
    +bool OctoPrint::have_auto_discovery() const
    +{
    +	return true;
    +}
    +
    +bool OctoPrint::can_test() const
    +{
    +	return true;
    +}
    +
     void OctoPrint::set_auth(Http &http) const
     {
     	http.header("X-Api-Key", apikey);
    diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp
    index 1e2098ae3f..aea2ba58f9 100644
    --- a/xs/src/slic3r/Utils/OctoPrint.hpp
    +++ b/xs/src/slic3r/Utils/OctoPrint.hpp
    @@ -4,6 +4,8 @@
     #include 
     #include 
     
    +#include "PrintHost.hpp"
    +
     
     namespace Slic3r {
     
    @@ -11,14 +13,18 @@ namespace Slic3r {
     class DynamicPrintConfig;
     class Http;
     
    -class OctoPrint
    +class OctoPrint : public PrintHost
     {
     public:
     	OctoPrint(DynamicPrintConfig *config);
     
     	bool test(wxString &curl_msg) const;
    +	wxString get_test_ok_msg () const;
    +	wxString get_test_failed_msg (wxString &msg) const;
     	// Send gcode file to octoprint, filename is expected to be in UTF-8
     	bool send_gcode(const std::string &filename) const;
    +	bool have_auto_discovery() const;
    +	bool can_test() const;
     private:
     	std::string host;
     	std::string apikey;
    diff --git a/xs/src/slic3r/Utils/PrintHost.hpp b/xs/src/slic3r/Utils/PrintHost.hpp
    new file mode 100644
    index 0000000000..2047406356
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHost.hpp
    @@ -0,0 +1,31 @@
    +#ifndef slic3r_PrintHost_hpp_
    +#define slic3r_PrintHost_hpp_
    +
    +#include 
    +#include 
    +
    +
    +namespace Slic3r {
    +
    +
    +class DynamicPrintConfig;
    +
    +class PrintHost
    +{
    +public:
    +
    +	virtual bool test(wxString &curl_msg) const = 0;
    +	virtual wxString get_test_ok_msg () const = 0;
    +	virtual wxString get_test_failed_msg (wxString &msg) const = 0;
    +	// Send gcode file to print host, filename is expected to be in UTF-8
    +	virtual bool send_gcode(const std::string &filename) const = 0;
    +	virtual bool have_auto_discovery() const = 0;
    +	virtual bool can_test() const = 0;
    +};
    +
    +
    +
    +
    +}
    +
    +#endif
    diff --git a/xs/src/slic3r/Utils/PrintHostFactory.cpp b/xs/src/slic3r/Utils/PrintHostFactory.cpp
    new file mode 100644
    index 0000000000..173c5d743c
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostFactory.cpp
    @@ -0,0 +1,21 @@
    +#include "PrintHostFactory.hpp"
    +#include "OctoPrint.hpp"
    +#include "Duet.hpp"
    +
    +#include "libslic3r/PrintConfig.hpp"
    +
    +namespace Slic3r {
    +
    +
    +PrintHost * PrintHostFactory::get_print_host(DynamicPrintConfig *config)
    +{
    +	PrintHostType kind = config->option>("host_type")->value;
    +	if (kind == htOctoPrint) {
    +		return new OctoPrint(config);
    +	} else if (kind == htDuet) {
    +		return new Duet(config);
    +	}
    +	return NULL;
    +}
    +
    +}
    diff --git a/xs/src/slic3r/Utils/PrintHostFactory.hpp b/xs/src/slic3r/Utils/PrintHostFactory.hpp
    new file mode 100644
    index 0000000000..4c9ff2bf24
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostFactory.hpp
    @@ -0,0 +1,24 @@
    +#ifndef slic3r_PrintHostFactory_hpp_
    +#define slic3r_PrintHostFactory_hpp_
    +
    +#include 
    +#include 
    +
    +
    +namespace Slic3r {
    +
    +class DynamicPrintConfig;
    +class PrintHost;
    +
    +class PrintHostFactory
    +{
    +public:
    +	PrintHostFactory() {};
    +	~PrintHostFactory() {};
    +	static PrintHost * get_print_host(DynamicPrintConfig *config);
    +};
    +
    +
    +}
    +
    +#endif
    diff --git a/xs/src/slic3r/Utils/PrintHostSendDialog.cpp b/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    new file mode 100644
    index 0000000000..b1dd86961f
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    @@ -0,0 +1,54 @@
    +#include "PrintHostSendDialog.hpp"
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include "slic3r/GUI/GUI.hpp"
    +#include "slic3r/GUI/MsgDialog.hpp"
    +
    +
    +namespace fs = boost::filesystem;
    +
    +namespace Slic3r {
    +
    +PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print) :
    +	MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE),
    +	txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())),
    +	box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))),
    +	can_start_print(can_start_print)
    +{
    +	auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed.")));
    +	label_dir_hint->Wrap(CONTENT_WIDTH);
    +
    +	content_sizer->Add(txt_filename, 0, wxEXPAND);
    +	content_sizer->Add(label_dir_hint);
    +	content_sizer->AddSpacer(VERT_SPACING);
    +	content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
    +
    +	btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL));
    +
    +	txt_filename->SetFocus();
    +	wxString stem(path.stem().wstring());
    +	txt_filename->SetSelection(0, stem.Length());
    +
    +	if (!can_start_print) {
    +		box_print->Disable();
    +	}
    +
    +	Fit();
    +}
    +
    +fs::path PrintHostSendDialog::filename() const 
    +{
    +	return fs::path(txt_filename->GetValue().wx_str());
    +}
    +
    +bool PrintHostSendDialog::print() const 
    +{ 
    +	return box_print->GetValue(); }
    +}
    diff --git a/xs/src/slic3r/Utils/PrintHostSendDialog.hpp b/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    new file mode 100644
    index 0000000000..7d2040d976
    --- /dev/null
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    @@ -0,0 +1,40 @@
    +#ifndef slic3r_PrintHostSendDialog_hpp_
    +#define slic3r_PrintHostSendDialog_hpp_
    +
    +#include 
    +
    +#include 
    +
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +#include 
    +
    +#include "slic3r/GUI/GUI.hpp"
    +#include "slic3r/GUI/MsgDialog.hpp"
    +
    +
    +namespace Slic3r {
    +
    +class PrintHostSendDialog : public GUI::MsgDialog
    +{
    +
    +private:
    +	wxTextCtrl *txt_filename;
    +	wxCheckBox *box_print;
    +	bool can_start_print;
    +
    +public:
    +
    +	PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print);
    +	boost::filesystem::path filename() const;
    +	bool print() const;
    +};
    +
    +}
    +
    +#endif
    diff --git a/xs/xsp/Utils_OctoPrint.xsp b/xs/xsp/Utils_OctoPrint.xsp
    deleted file mode 100644
    index 28610cb01e..0000000000
    --- a/xs/xsp/Utils_OctoPrint.xsp
    +++ /dev/null
    @@ -1,13 +0,0 @@
    -%module{Slic3r::XS};
    -
    -%{
    -#include 
    -#include "slic3r/Utils/OctoPrint.hpp"
    -%}
    -
    -%name{Slic3r::OctoPrint} class OctoPrint {
    -    OctoPrint(DynamicPrintConfig *config);
    -    ~OctoPrint();
    -
    -    bool send_gcode(std::string filename) const;
    -};
    diff --git a/xs/xsp/Utils_PrintHost.xsp b/xs/xsp/Utils_PrintHost.xsp
    new file mode 100644
    index 0000000000..0c3fea137c
    --- /dev/null
    +++ b/xs/xsp/Utils_PrintHost.xsp
    @@ -0,0 +1,10 @@
    +%module{Slic3r::XS};
    +
    +%{
    +#include 
    +#include "slic3r/Utils/PrintHost.hpp"
    +%}
    +
    +%name{Slic3r::PrintHost} class PrintHost {
    +	bool send_gcode(std::string filename) const;
    +};
    diff --git a/xs/xsp/Utils_PrintHostFactory.xsp b/xs/xsp/Utils_PrintHostFactory.xsp
    new file mode 100644
    index 0000000000..2b083c957d
    --- /dev/null
    +++ b/xs/xsp/Utils_PrintHostFactory.xsp
    @@ -0,0 +1,13 @@
    +%module{Slic3r::XS};
    +
    +%{
    +#include 
    +#include "slic3r/Utils/PrintHostFactory.hpp"
    +%}
    +
    +%name{Slic3r::PrintHostFactory} class PrintHostFactory {
    +    PrintHostFactory();
    +    ~PrintHostFactory();
    +
    +   static PrintHost * get_print_host(DynamicPrintConfig *config);
    +};
    diff --git a/xs/xsp/my.map b/xs/xsp/my.map
    index 4a14f483fc..aefe7b3454 100644
    --- a/xs/xsp/my.map
    +++ b/xs/xsp/my.map
    @@ -239,9 +239,11 @@ Ref 				O_OBJECT_SLIC3R_T
     PresetUpdater*              O_OBJECT_SLIC3R
     Ref          O_OBJECT_SLIC3R_T
     
    -OctoPrint*                  O_OBJECT_SLIC3R
    -Ref              O_OBJECT_SLIC3R_T
    -Clone            O_OBJECT_SLIC3R_T
    +PrintHostFactory*            O_OBJECT_SLIC3R
    +Ref        O_OBJECT_SLIC3R_T
    +Clone      O_OBJECT_SLIC3R_T
    +
    +PrintHost*                   O_OBJECT_SLIC3R
     
     Axis                  T_UV
     ExtrusionLoopRole     T_UV
    diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt
    index b576b1373e..cee75fe261 100644
    --- a/xs/xsp/typemap.xspt
    +++ b/xs/xsp/typemap.xspt
    @@ -270,3 +270,4 @@
     };
     %typemap{AppController*};
     %typemap{PrintController*};
    +%typemap{PrintHost*};
    
    From d4b73701d939f6832d5b86818a318d409a25b508 Mon Sep 17 00:00:00 2001
    From: Martin Loidl 
    Date: Fri, 13 Jul 2018 16:10:55 +0200
    Subject: [PATCH 151/185] some code formatting
    
    ---
     lib/Slic3r.pm                |  2 +-
     xs/CMakeLists.txt            | 12 ++++++------
     xs/src/slic3r/Utils/Duet.cpp |  9 ++++-----
     3 files changed, 11 insertions(+), 12 deletions(-)
    
    diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
    index 5eaa0e522a..7aacd1fd90 100644
    --- a/lib/Slic3r.pm
    +++ b/lib/Slic3r.pm
    @@ -168,7 +168,7 @@ sub thread_cleanup {
         *Slic3r::GUI::TabIface::DESTROY         = sub {};
         *Slic3r::OctoPrint::DESTROY             = sub {};
         *Slic3r::Duet::DESTROY                  = sub {};
    -    *Slic3r::PrintHostFactory::DESTROY                  = sub {};
    +    *Slic3r::PrintHostFactory::DESTROY      = sub {};
         *Slic3r::PresetUpdater::DESTROY         = sub {};
         return undef;  # this prevents a "Scalars leaked" warning
     }
    diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
    index 998d44cb05..3558b6d3c3 100644
    --- a/xs/CMakeLists.txt
    +++ b/xs/CMakeLists.txt
    @@ -255,10 +255,10 @@ add_library(libslic3r_gui STATIC
         ${LIBDIR}/slic3r/Utils/PrintHostSendDialog.hpp
         ${LIBDIR}/slic3r/Utils/OctoPrint.cpp
         ${LIBDIR}/slic3r/Utils/OctoPrint.hpp
    -	${LIBDIR}/slic3r/Utils/Duet.cpp
    -	${LIBDIR}/slic3r/Utils/Duet.hpp
    -	${LIBDIR}/slic3r/Utils/PrintHostFactory.cpp
    -	${LIBDIR}/slic3r/Utils/PrintHostFactory.hpp
    +    ${LIBDIR}/slic3r/Utils/Duet.cpp
    +    ${LIBDIR}/slic3r/Utils/Duet.hpp
    +    ${LIBDIR}/slic3r/Utils/PrintHostFactory.cpp
    +    ${LIBDIR}/slic3r/Utils/PrintHostFactory.hpp
         ${LIBDIR}/slic3r/Utils/Bonjour.cpp
         ${LIBDIR}/slic3r/Utils/Bonjour.hpp
         ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp
    @@ -417,8 +417,8 @@ set(XS_XSP_FILES
         ${XSP_DIR}/Surface.xsp
         ${XSP_DIR}/SurfaceCollection.xsp
         ${XSP_DIR}/TriangleMesh.xsp
    -	${XSP_DIR}/Utils_PrintHostFactory.xsp
    -	${XSP_DIR}/Utils_PrintHost.xsp
    +    ${XSP_DIR}/Utils_PrintHostFactory.xsp
    +    ${XSP_DIR}/Utils_PrintHost.xsp
         ${XSP_DIR}/Utils_PresetUpdater.xsp
         ${XSP_DIR}/AppController.xsp
         ${XSP_DIR}/XS.xsp
    diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
    index aabc8eb403..86573ff300 100644
    --- a/xs/src/slic3r/Utils/Duet.cpp
    +++ b/xs/src/slic3r/Utils/Duet.cpp
    @@ -82,7 +82,6 @@ bool Duet::send_gcode(const std::string &filename) const
     
     	bool res = true;
     
    -
     	auto upload_cmd = get_upload_url(upload_filepath.string());
     	BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
     		% filepath.string()
    @@ -196,7 +195,6 @@ void Duet::disconnect() const
     		BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error disconnecting: %1%, HTTP %2%, body: `%3%`") % error % status % body;
     	})
     	.perform_sync();
    -
     }
     
     std::string Duet::get_upload_url(const std::string &filename) const
    @@ -231,8 +229,8 @@ std::string Duet::get_base_url() const
     std::string Duet::timestamp_str() const
     {
     	auto t = std::time(nullptr);
    -    auto tm = *std::localtime(&t);
    -    std::stringstream ss;
    +	auto tm = *std::localtime(&t);
    +	std::stringstream ss;
     	ss << "time=" << std::put_time(&tm, "%Y-%d-%mT%H:%M:%S");
     
     	return ss.str();
    @@ -248,7 +246,8 @@ wxString Duet::format_error(const std::string &body, const std::string &error, u
     	}
     }
     
    -bool Duet::start_print(wxString &msg, const std::string &filename) const {
    +bool Duet::start_print(wxString &msg, const std::string &filename) const 
    +{
     	bool res = false;
     	auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"")
     			% get_base_url()
    
    From ee9f7eaef69cbb84e3f5b54e6b624e317d16f7a3 Mon Sep 17 00:00:00 2001
    From: Martin Loidl 
    Date: Mon, 20 Aug 2018 20:19:22 +0200
    Subject: [PATCH 152/185] Host upload backwards compatibility
    
    * Added legacy code to preserve backwards compatibility
    * renamed some cli option names  to better match option names
    ---
     resources/profiles/PrusaResearch.ini |  4 ++--
     xs/src/libslic3r/PrintConfig.cpp     | 12 +++++++++---
     2 files changed, 11 insertions(+), 5 deletions(-)
    
    diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini
    index 7e358c77e2..32ec800e7f 100644
    --- a/resources/profiles/PrusaResearch.ini
    +++ b/resources/profiles/PrusaResearch.ini
    @@ -1007,8 +1007,8 @@ max_layer_height = 0.25
     min_layer_height = 0.07
     max_print_height = 200
     nozzle_diameter = 0.4
    -printhost_apikey = 
    -print_host = 
    +octoprint_apikey = 
    +octoprint_host = 
     printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n
     printer_settings_id = 
     retract_before_travel = 1
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index 794c276085..943db2a302 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -1153,21 +1153,21 @@ PrintConfigDef::PrintConfigDef()
         def->label = L("API Key / Password");
         def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
                        "the API Key or the password required for authentication.");
    -    def->cli = "octoprint-apikey=s";
    +    def->cli = "printhost-apikey=s";
         def->default_value = new ConfigOptionString("");
         
         def = this->add("printhost_cafile", coString);
         def->label = "HTTPS CA file";
         def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
                        "If left blank, the default OS CA certificate repository is used.";
    -    def->cli = "octoprint-cafile=s";
    +    def->cli = "printhost-cafile=s";
         def->default_value = new ConfigOptionString("");
     
         def = this->add("print_host", coString);
         def->label = L("Hostname, IP or URL");
         def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
                        "the hostname, IP address or URL of the printer host instance.");
    -    def->cli = "octoprint-host=s";
    +    def->cli = "print-host=s";
         def->default_value = new ConfigOptionString("");
     
         def = this->add("only_retract_when_crossing_perimeters", coBool);
    @@ -2129,6 +2129,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
         } else if (opt_key == "support_material_pattern" && value == "pillars") {
             // Slic3r PE does not support the pillars. They never worked well.
             value = "rectilinear";
    +    } else if (opt_key == "octoprint_host") {
    +        opt_key = "print_host";
    +    } else if (opt_key == "octoprint_cafile") {
    +        opt_key = "printhost_cafile";
    +    } else if (opt_key == "octoprint_apikey") {
    +        opt_key = "printhost_apikey";
         }
         
         // Ignore the following obsolete configuration keys:
    
    From 889d0f14346285a97c55877043e9565cbe3b6d4a Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Tue, 21 Aug 2018 14:27:36 +0200
    Subject: [PATCH 153/185] 3D gizmo grabbers
    
    ---
     resources/icons/bed/mk2_top.png  | Bin 33621 -> 45562 bytes
     resources/icons/bed/mk3_top.png  | Bin 30647 -> 36806 bytes
     xs/src/slic3r/GUI/GLCanvas3D.cpp |  46 ++++--
     xs/src/slic3r/GUI/GLCanvas3D.hpp |   6 +-
     xs/src/slic3r/GUI/GLGizmo.cpp    | 260 +++++++++++++++++++------------
     xs/src/slic3r/GUI/GLGizmo.hpp    |  17 +-
     6 files changed, 212 insertions(+), 117 deletions(-)
    
    diff --git a/resources/icons/bed/mk2_top.png b/resources/icons/bed/mk2_top.png
    index cab4b966f86544dd5fa1f4736ee0052056810873..e93430b0989551ec14c0e2b46642af80db61856a 100644
    GIT binary patch
    literal 45562
    zcmZU*1yocE^e=n}1rZUYB?SZ|MN(-2Dd`d^1?ldFF;GA{rIBu=J5)lXl#m8#X#wf_
    z_IUsAz3;8ZU3baM9Om@izuG5IMM>rg-VHniL9WQjN~s|TCj5$t;9i7({<;qy!9N#`
    z6lA23bM${{jX6>9&Lw+U9VZ0gWI+Fq;hrVt3UA^#%PC6Z%;J*ZV{$T+SXv_p9U>|LE3l^%h
    zho-Y&NDw|}r6uBSVjvcpfj5KFSybEL{hJbmQlB3(hHT=t6DYrLu6%ljJlsSl>|n#?
    z%rv@oYoye8+JTHyPtdi8Z@9a&v-5``K1;}wNAd1((YZ-*>0XoVq1#}tYHvyN+0g?@
    zNy%Kz@Ty}s8?lncK5KJxT*dgiS!+erQ)3-F!ov|_Hs@6$(@#X_LWMSd?<|R(A3nG8
    z;t;bs7N8zAKdnt?aTGi3R4x;+9dI#_!y^V>b$XmmT3WG(JAGne(?51Amy~5vmv#<6pYIx;YaZ24pW2pmKEE#XE~2W!
    zCMx!P+Iy!*hmVhMO2liAw}R~Ly3b_Ql_!2m>grM3hVVC7T7{1t#B&C=CfB1v+fn)X
    zV~KW69-Ave1!AWw1tpgEgC;}UmArh0CMKwS|NfA9cmC(C7?s6%yM9PiRa3RM-9(Lp
    zztzvedq#K2UvC@?8l+aArHopWz9pbeXmVNo`K@=YawwjE2{d9xN@31TKxw(JVC+mkLv$EY&%!J%F
    zoYhBjJ2iJ;`&}KU0sFA(6JN|uY^n5dY1b7`luS}(NJokgk0+oYN1$F11ATE($bom
    z_a_N(u;#PMc6UW>M|c`T3b!$;rt!gwhcVd#ZC3+#0vS_!o#n#$@CZ
    zXYAig3ngS*));TSu9Q%6l<{De2GS#~tI<-Ka&oDgPd#={mtPL@xR@Juis{fM>EsVi0hlLyrq@GorU-dhX43iRPAy%8T70A=LgpGMq
    z-b*x9dF%M}xdLJJco9p`1E0Q+^4>j`EX3(m6qyxd_!qxKpMEAc_H9n(b^Q!wi5&k;
    zMlH1n&DCW-uSNqtLR=}YX7j<%9h>`X(R;+ws*MI*E^FaO(7QbU_xQ-r6kW6b8
    z4Bf!Lh~?WXCQdO8MNXsiOQDukWXGHZ0nt$6ExE{`prDKE)6IT3s)km1d3hF4Rrxe@
    z-;}W{l(Dbgo*Q3wUW9uzDLu$8nIpM^OZSs`AG`b(QOV6>MDo1cE4N9%#>qiK34Vmhf~}2Oi^-i(b3NZ-8S?jbyM$`
    z4I5Badvg)uvcQSi(64t*{=V#N1l>ZFwsQDfM@Q$jXeyVAq2Z>@ae3(3v<@W>CWgtM
    zU!Od@_BTX0a51M1)?9}Si~O`TH90sqdYa(vS8y~pHuThGdA(goGBl<9S^4+Z?s$!sY}y;p&Dq
    z>3JS!=zEx~ltbjTy-z(##cQsTS^+;c<5lbHa#XH=cc2G1L0$GX^gG>E)rs{lHp*^v
    zwK?{>ckf=+n>TMvtH-H-Z_H+78~_@y3f#17KHZCko6F7dnRdl|JE<3S;LnYU4(YA0
    zW~Qd5erJbE$Io?0>k_NS%7@gJ9cuOBp(fuSzCNA2&|f?jJtwFXYjP9z)pKK__9%3=
    zu!t-9)BsNXwEdSWAGn$hX2r)3HoBE$b}n}~=*i0mML_$ns`@#gdbVLZZJv-lG(3EK
    zyuaz!vyb`f1rCip`uH^N{?s+}Io%!JEzmA$nu$^t&C7-24T#~kdiL6i5~7$QT>GoM
    zPqZN2TWe=~p-XWfNBR8Cx2Zy%@*pxU1Mm0w6Kl@*U7!@DE@j0T+<&kz2Yu~B!=KNW
    zaq`wCJy(k?e2)HdH*U34=HyzR^_`!DJ%heAtu~q{?BOUOE$weMS?8ju!EflXKyn&P
    z!Lz3_di*2Imlvvl=)Ee>-Ia0;epnH^5&#N;m`3S$q!v78y^QsXv$IBX9Muz%rPiZm
    zL>m0Vg*s6u6Ha~WS!LDE%WB1GX>@;}azs|n{aPlG>>
    zL-*m0Y4sc>mm>Z8J>nrDS#g^?l(^lEed2=nWN2Ulr7_0J0m0iZmKDd);ik0_*UhPQ
    zTai(_>83`9o&-J;+$)ID<(2W<3=Wh}>8lvT*Q6+G$Z(#1Ps2pMhoS_NB@o1Zm&o>W
    zV9;YiHFp9eU`LOXNV)Da3pVl~U!8u7<*D!`La7LH#gT6e*hs+fA)z9I{FVxiT*6Jl
    zKh!Z{ZROK`VPYiy0t;)X6aB+Y=&m8*PFKbZ4KOE}%Q!uSkEY1UiiC6e;Yu
    zvq`d4t%_e&Up0zoz^XX0N~7IAUJ$QjY@MQsq!_!#ws-KDgg#DsnfDd220~Nw4%YyG
    zkz?4gmvrL1Sh%n`vb;Ah{Ge();>I+(ibGarHyfRT`Gw$9_K3-0eTP_C{;`FO&J2RgY&?$<562d_G?{R3-L!^@&c9ir~;_bT)E&nf*t_NyEbG(UGV0
    z059LIThh^tfvYU{D$nYFo^O6_>U=({$V4>#qt9y=O8fjRxmn$@TppqIO6l`E=shQ~d
    z+%VRpsd2<&({60fYrLcz$8|QxE`t|gT?#^D
    z*8F*mjDwnD6{jb$cR#?=PiE^2+^zf0f9_qm_^M^+BM4iwX4U5v`&pE{iE%8x#%hTeA#8u50eOH8mr>oHVBI?>u|(;?vXcIyF;i
    zY3XGs<8m}%%(A7|0c!+xEqqH6_9)XOZZqJbxhhuOoICB?tEXr0HZ4s`=7xTpBtJ6;w@rg^pVRs++uj-bukT1X
    z#u|(}LXWf!JleX+jW3h39~29{VOC6tZZ_$P&4-fgk@t;QDTl{ms=)&%cdh3=x1`5%
    zRvbY=zPuVMV8MO>$J1t=k+)>>yp!%ZncDwImwpSq$gzu~O4R4*y;iZFN5VH~x!Epf
    zCx>ry*F??^<_=?GVy3Q&oqP=XX6or#ci2W|C@gDY_xJaYVZZH8W@g{><7sV`QSZZr
    zI2Wg--brN>JNRSFtj5ouAAC{v-Hz%W1{i=HqZro}^K&w(pPCeU7F*DDU^;uEtch?v
    z@ypZZJc?JVfwKOFL$_*BE%Xzaf%^;grc~b}i*q2a%0oYYzF}FrLL)qK*YmHrfQe8O
    zbW!02Z~OJi;U~pF0eQb2aNfS{z^+#lzLwAtOcC{IM|l4e4sB9S**&vfs`cv__9q8h
    zJFqLC4BmyQ
    zb{d&R0jd|LR@*xa!ducZEn?fc;kj4kq-P&t?7CY7ZkadPhRzA<#CCc&2$I>W(oC!R
    zwfir5a9(%nzJ>WKkU8am**G|a>fW}`Me(&)l4=NwP2{5SCMWW}o`nB5b&UwoI<9z9
    z@Td3gBDb)SYZ30USWup!x3ri9ZAsNXwZ=dzItWuSwlEe7I1khr^)Ld||J;9yv<%4#Q*Ah6(4C|H)fHJI!6-g_I!GTvbNADp4yYDfZ=j*MPVKey}wSF6~JamWX>oC{f<;MQ-fay-L)sxx
    zpd(KuC9(3t04vkXHZf6ifjn@DS3n@jz{%K{AusHOxVS2@zjL?B5;fFQ;7-`-_nIbc
    z-9DC;MHZIyrHawwzWs7jo|a3mM%=S!sV|i|%&vNTond2%&c*h5Fn4iX-BG}#ZR*f=
    zNC>4sXw!PtxaiShq8&}?lcG){rbq6p1Il7WerW1yemDNFa34?TT+PpdVA9GAiv-j1
    z(MU>4%ET{ZarnT=lTUrS`tBbI5aZo$YipC9_Sv6wP$fPL9n>;Q9q`o5%+HU7OI=k{
    z)8)gueBS!BoA&aSaNoFfRrJcrN|__WGqI>oT)+^+Ho
    z2WW8yCBwr8YZXf}Uek7^>pO>-ur$|w?}I-MIJ(kv0rQ!H-(pKAmL-8q``8vvLXv>>PWF&`x!wqFf|nROfSZ_*t=L
    zcV^V5B{
    zJZp5-9qJQ1KQUnS0s{SX59BcsKdAq!dDgx0JWD4a|L&`gjyL&uwX>BpY=?!39_1!{
    zTLdj|`22tR##n_#ah!qYGH#4Wqd_0~$_<@9Kieny$2)%VLgS`>eJ{NbdO^G!d}_*?
    z_>PRr_WgaA6;4(!xJ|xJmYXS`_9hEWYVa@hB)a(mM?X&I3%i%BH2Jeg_g#A!?fIpc
    zo}5(QI&*ZVyX`}D&+$3%ljg(4#3>nCpT7#MUScORSJkm4epOqKswoI|=&>T$5`^gP
    zcT`Z)9w{pesE&1Ybq(kiY4uxG
    zJ!a1|9(#Hx0IsN3(Urh;IavOFJ9#`JD<{WIc+3LOCEk*V5Yy0*TEG5pP(!
    zi;K|z;Y1kqDvZ4~fk}m2L?PlAkTgt`Yw!(>?
    z2K$4MBoN08`Wd>#rBO{OzzNE&t^uOe$`c{@_7x6>w8IkW_qr`FY|+mh0vc>=q-K_J
    zr3;1pz95KAUMWKK;)|(lICdmOO#EvL8DMbljp>kCikD?FTebyf6z54rJX
    z)A7btZGg#h3}M3kIG4(->~ZgS)>C4Vks$2Egwv%;t*KZaf%YEz?G_kLI;$dUMFj;r
    zc7Yi7bHjwwBhkwg*CL$K>nA_phrg20YGV
    zm7*6=+pkPOG0QUWb)1aPi$YnMwj234V;0O^1=@SU@<7h{pHZ_^pH%Ml6z}cM2SBJ?
    zwd&M0V`9*15*2|H{HSOSDaiY*`;_nq(}YC=5C^2m8^;nP+?GZo>~7x+`o;JHURJ&D
    zw=#C{B91|3a1ZJ?)z?opB*b>;&y+iaOMD~LQ2#J7CB+u5+9RM4rwfHY$Hr`JZ3$}i
    zI6LM$qb_%wmP=g==8R)&gm&`c?DUvfeZ*?Z;al`={~~{DGn=9N)fK17;&f?G*kk7@
    zZEHTogXo+sy9l*)qQp=PN7I`vD1C2zT)+y-!&F!4u)#PSv)cj!0&SL+--x%MpF~4t
    z4vrnMu-HkN52_OD>g$8%x>rHLWBS+X&{x?>aeSn+udfdd1oLG@rYPBKVbB>VJUl!)
    zCEwu7Wdv=0{`|QcT@|3AAOV-9q=DBbWQsouX=Bb%=_HOhzUpvF~S}KJm7JgK#go_wU~J{=%5c{bV^n
    z_kd`%g1GfnRcB^AQiz0DON7r)c230rO55o+p^^GdB%_Ot=QZhwb1;x}d}coTi_6Hr
    zSvs_hv%f(}8Pnh2AKZMpZnspob2$0QsPy;G5<|Z#W?$zwpPmwu8(V*QbA@_rt&mZa-H7~30*x-
    ztEYX1+-5#smg(h2-H1h9*
    zA*3J;nwwMv^Y+e;`mSeVaXI(MfbVUPQqR`y{Dg*+4>m&kpbj!CC7GY>joVS?8RcYV
    zwl;xM>J-W7&{(@cIvw&b
    zVKlqoCp{(_kMI5)%*}5-@-ZFcTRZc^GdQTilib2atpVbinlXMLmD4K)Zl
    zN^T>mk03%SFK6WEuY8bv+Yo)LAA|}z7a=vY-8lo2a`>(Scs@VYU84-jvn^KOj>pPT
    zP9-3`)c=%@kdFEl)DV#GGH6d(+~FcZT-H?D_j?4Usf1ltqD#uk?#ez>1}`u_|L!;U
    z%w8ejzF&KKvS@tRV5euitC8fi_7vTG$)38X|z#GKa<^yzr^
    z56chVMZ`Z71u|o_f3`t^F7}t+)o7N~OBfdyb)X=sdd}D)RfwQkZ`VxpU+OkOrzcdd@4rsnYh;mQ58sVLOb>))K^{b>0
    zE+DO-&(Te37e8*pK#WA=q-Py={(HlnkP!JLzjSxImPGfzKB%n#;$K~A*Vn6AX_&AE
    z3=M=Zj1GehiGbGCdfe*YNeFF`jYbsc6oQCjl48&y*8^m!z9WG=gmPTuSyo8##x^Y%
    zmOMMXgE$ruS%5aZ!?Y-(8r@~IgO6gAn`WNrp!E}`)xT7nX4#%%^|Hd4F=YY=`R$Lq
    z7xOzyL*p({Q0S`drsw?q{X4COiyIFuOJ;Lz9H}}ky%e|P8LLK&p1FkJi91&@>^=_U
    zeRF9WTIs(mKn$KZ<2FJ2BWD9A>}|J4?53?h&qu3lbQb_?I>NPYGhJyl{9<-?wh$;7
    zu$5BKkQw@R^t40FOCQe%2jhzv^O|e{;tpq&Yv@|ID;p^l$p_ZYNRBdXBOPwzo+=@>
    z>D_p#@=^A<*w_S?;@bRt)~^m!3UYGS(D;d16s+&Br0@K@I~EsBr9rM25YRT`Pm~VA
    zFy~LqnxB7umlpU9NwO02;=;mm$V1~28Fw(^E=Ul99w#*!I!R$Tsi$LA#(o(}y63w#
    zdX-kCCr@s8tOcaAK(D(8l3{uPHT?Oq^Zh)i9#RnufOaLp=~Bv32YW?|bQ%hj48=5j
    z1021J$x1Sjlt2yUp^*4Id6FP?O$aQdyL~%UnnDlho7P?Hd!OHpSfrMVl${g0eOnH6
    z&Wt~>lU07_zFfwSj;jeqKR)~74*+;4DBT=N{%D3w*cp_^#0?s>BZoG&V}F=O-&fzr
    z&o688IUa+iG{1jL5`_BeE-bI5r8V}R+t|@WXTv4@?U!dofOq$VHfk7Hg6KQbpl@u#
    zd2avxBSSMNM$yNoF?Gpf`RsVJPfkwmhx!2^%3#oP?cjjpdG+-4d{|ps`wQN#P=__Y
    z0x|#2&dvas*(XXl1CO5dt-InlIXd=l)Gnn^H5yE>t`cBlJ_8k%M$rB#lV&qE<_mjh
    z9EOG`OnlmeV_vk4T&-PZhFppR(<9cE=2uDN=xi0n^9
    zMZtNzJ`Ec|3e>o}`w@eGBUtCfhmm;VUC*_k;f~k4ZEgX>hpY6iaWIEY*}S1Kl6DL1hNFacj$!zrZ=?%Bf~bPyf&|JLZ|>YCTwT1=Q3zf
    zYuGW3su>SQ!O8Do(<&5{HYv}|{rYpV<~IQvDC9y0pisBE7^E@KDOVJ@8l`@6&9#Rd
    ze*!SmS7^|9#2(WFchAnt3zxsndO?x){AT4^tM^9Nw)WP}?yw>AyAC*0g;@%5G#UTg
    z83+Ue0F*oyW(fK#R
    zGEX`}D3?B1cO334t+JGO{QmyoCuo(FCfp4nvPg?{<$=u=x;!SFUc$eg`R%n
    zI>tx0%_()Sy)}#8uU}=)ASgnEhEZY-N!PIuky{UGu!)Jx^jOxVXYs|^Q9QSQ2$>qu
    zK{e+@DPXq%f~-&EZU?uoI3z$9HID`HorfGN#xsO;8}b;90o1}c2vS%h?xiw)kvwo5
    z?^+n$lravdLv8-9!5;Lgs^Y>g-NUGH5yH)U3OQj3CTm0m0|mrjx}84qDiW~ynECqK
    z)tiX;AS%*omhf^I=9};bDFIBh3|mbgP9ZHMIBpmy_lr=bkw<#mdKgHWzzx(G9a_E$
    z20sUdCoWtvE3Oe_@96)I10kG_q_`x&dJX$UhLUKh3I=F4P{y151rw0g`eJdSzrOU3
    zTA4AJRv5xDI*s0hi3>k~FRptesw(Et1|8!qB4PKL8S?>f288gKhzv-`tp3{!c!g*K
    z&4Eo}A(|1yh7HqB|MxQY$yvwORTS(1Z{DITCUq%lwicnAo}Cx?B0O)=c<;M4H?0))6a?RS3KTafJi
    zum0Z}lN2?2rq(j$;(R@@^!nwu2t$P19n*CFcA_#vsP#IHp1T~N>lg)>0!vIioqflp
    zbb7YmT=F~cQ&13I-&CWQNVJlcrY4{2n2^(A@c7(TxJ|AGI`P8BCRcKgiU8sZXuq#}
    zN7zC|OIuq&eGDCdNfPxLQ=k&EmG|;(o_5!h3jV
    zC}PaY+WIdf6X>#fAdz4?>$^uIcSTEx;H
    ztYzd)@*%gj(~y>L`FpfYY2J&|=^Yd?p&gI79-{^2SX;c3~TsqM2JGgb~
    zGex&X7IT+|%Rpe1eBQ$#SlpXu){2z#Od*CcZ^!NZl827w5MA4Qi}s5B$rvZlCrqhe$V2h_UwR4}@m0bP!tb~2|u{K>Gj<1=j$-v^Q9-%}
    ztJ7jP#T~beiBpZejplP-Gi5ePeQw{AgF;3(qZ1Uu;L3m&pTnTnce(M@>
    zlO3r3tLJdmX)?vlNd-Lyf&fP;fs2mvDjxpqp#m
    zn=0n_9nA2itnO=&jw%#$oDT^5wurXJK^*mCd9w2!{4>{$i9;zk+lE+(&
    z(JIyUeP9r-ld$UM>@orOKTqE=%1r=i`IIShFdBx+!paXk*WbN>M+J3H_0
    z^1$0c8O4E-TT$KX)zfF%py2SDf2Rb9l3!GGMPDiJ<%nv?m_d5u3eSsF%UJti#mV8o
    zqbC!x^VGpsgo=Okqj4XY(-Y3oU&zVKAe^Ye3GYhR1GD?qCk{mfCD>^)@5DxP0Y5DP
    z<25nzBem-fzc?bk#(;}S7C4WEmSj)?K@PkUCE7Gk7@^8;$rozO&>`uMlQ9xX1DFE*
    z>~jejQG{<1+05P-mKWnB=~J>=#xF=Ta0!3B0KP=`Tl1F&%rx6PNa>wtlHo6F$a>8&
    zky|uoWRHX${s;C8)t1H2qm1W}_Jnwko&aTs~6U4rn|
    zO3a6Bi$Mj&5YLy8s*^1&&hU98!_t&tkR<)IA$+M_cHVB5Ke$!>IDJZ{ot#Mb+^jX=
    zniZr%|M0YvAAg}e`;&^6lBVz8y*m#D?fc*5fn&%%<*~gwJ=!f_T3lp;G+0`Ge(=(v
    z(yNN{RPbsW3`%AlYs*r-HfotviM{kf%xwS^k`lbFv?{J;S{iOXs~j1}7%U6BEf=W*
    z;478KEOcVKv9}m}@G;+#($XJXLFvM1^=H&Nc6`jrOHp4Rt8WKXk9hc>Q%GohBBCfo
    zuexLI5(!wFeebz5i&)me>+!>9r{QrC^&)7T${$;
    z&4s5u;b6~c0vfQKlw02XgwbSTUuH_%^-hs6F7u(4(%8Ztf7(!2cmFY*Hr*!g!yjFv
    zq2@Nt($XT^Um2_yzQuH~Xixwdv$V`VC{I0QKLEg;WPTVNTAbIoYIEEOfcN{_X!$Fy
    z#=rg7at89Oj(4ht2a|nkL5_(6_SFOQ{@kmId42i^AEfyB#KgpQ0egyBmOgGx8MEAU
    z+XU|L6ts;^aI9;^Bf2MSt0N))*5G}(ZH(4;^2BT|`9D|z!g2#G@dP!W?Uuw4i~a)z}90*0y1*J$N6W_1}fydRJ`JlP6DB
    z+;@kx!_Z-hYBa?_e_apw+-QI;?_dbJo)$U;P>Wu@&G?q1kN2NXI5D7y@cM#YvpZ=~
    zUM!mP;e!O|eaapXat&3r84?98YUTWF^E}0|ev|tHXaDS0d3yNcfWp`T3JbnR46g5j#36JVy8Nm
    zpb6+~1-HEm!AL}>x8m@SSD(_xD=gF@G?>Wz=)KAw)bO7??T3M0w;b$G+Dzbcz9J)N_9SWiM*8h
    z)+x7fu+3;$)*kpb=hix%`_G@ME597%&)csx!l1&(YK(uLYr_PNKxsH-L|6K7hinoL
    zcn{X&RYe~bR?)O7{H!Gg(+%S(h&Z){KJ*R+_iVS`&Gvq3l{O~j7+)P(%+;nvYbx5!
    zm?%~9wqz{xR>6d{;nE`2%X)x-={EIz=m7Tzq+BD4=Rg|paS?mzz?K~u^qWYUZYRz-
    z4QM;!Xo!!{(R?By4ddpnegNG9ddU%3->4J-*U@^a?IG>0ufV530fF%9434zoy?k5E
    zo($qts}zv`YH7VN`#|;sCL-R~0&^5hoXE`!;+UZl;6@ha5pccTX4U^
    z6DJ1G;f4FH>&lpq1{n!p^O*ki$YktIKTegKeAPxhA@B$GT6J(xQk;!Wac-W@F+;Lqd?k8IvJ98CID)iFL
    zY1H*{jJ8;ySh*YawI(6%kUuOrhbWadSKrRWSfy1*9OPs;_3J85EUzZuifFvJFZa;r
    zZiXm}v<=q-64Yy
    z*o64JH46NTqI`Tqf*^|$<9UUIga9~x{Os9>PjJiMwqBf{f4}A`6dwlHd*Q%xCYqgs
    zZ6^18Y~7WpZ|5Z78pBKCJ?LpLXwbURZmSLV0c3>%u@h+u)lJ$;=L`f2Ot~AQ1sVUa
    z!Be#0o|Tgm)uwcZTmu>o&Q%)g(#P!d{)z=dmb^DYF0#@#8kEp?mC25VqGL6weL2D8
    z-1LtYGQg{5`8Si|(1OAsjq8#wd%Ny@|B7(mj>bBy)Rgyz96C9C!%nzh^>3v?G=7bwqPvVN)R4E#1jYNmelV>eLSDQ-Hv4(tnbvrcN_fk-|{|a
    zLjfv7KMYr-+YiFc44&RU1}+^AcPv1|Rj7-JKdbOs9H@rN@N7<9w7-_I!#-Pic+?g#
    zw2eg#LA5A?w!W2}Xe$re*^k|yG!R*ZiJBjll`Bx8BqvW3dquyj?n2@9>i_lWl{$knifs=
    zY3@D`2$`+>GV^iKUN90?V54!V-<($-V#ad7ATaW|lM@mw8~4Vn)O$?J2MxS-Gp|5s
    z3oLb5ud3Mdui+X&BoG&O`f6Gp(YD{#IYf_(X?BNP3Nqf~>pyQwzR8r2WK@}N@9yr-
    z1nl5NB7HNjJ-)|OF4iPIpW2IZdc92(OqC6zGXj-`_ULG01qE;Q3S+P{V#>B7Vqz9%
    z1Vf3&Z@RyH82>hfRyNw3a1ZyVTSD!Aa5gDkqXw#6Q8K_n2xCAh)M
    zvI9Mhu3W%ld*1&41aW~*t)ZucE`j?kc49wJtY6<-@=xPIXPdxwF98u}$@zXy^zfQv
    zEq=zskZmsAs^IR$D*_YW-`*vK!&C%5he90ZEvt@aU*4Fgx9>;aRnZ(gxo&e!%(yet|u*eyRZ-99AF$CKxN^{<9J=U|Y
    zWeX>_C&KxHD?5m@&j^DjZB;*GP)Ffbg$
    zOkNfz#^vRe_?yoI=BQBzD`c09?z({_4%l^svW*SSUAElzBe7z8C6o8H-8@wTnd%
    z66eaka0jIb$|;_BPV47-guXNj>{+O(bPt))sap_lp3pa4Mygp~vUHh1jSQ!7ZY;_a#3kA?sI03@nt|fxKrY
    zcJS-f@ZZ*_#>U2f00CmiGXs$Dj@|>`$p^0Z!izeps@Lr6Cne;8H{FWy0*&Mo2r6o{
    zls9fPMDGDaK#v9tibl3WU1hZEfc$O0Yauw+>SID4+nLRId4YcupdVO(OpI}h7%w7}
    zVx?iuwO$fL6pY!XY?@auAWd1;gw9yK{xUF?)$tgB0zTU^W(
    zLjC_6XnE394xQ%*h~(ok!Q6Wg4Vh>LNj6OE=y%2n;N+9Hqt^T%Ic*EdMy)$-tLyKP
    z`5%=GuYyNAF)}hD)+(rVBPG=Z^T=9@6P^#N83#>8lj
    zQu2L;T|qUSSZP3O#YIInX*UJ{Ag#fgnj|9`<%mlm63fFus?ZO%A_4bHT`|cID!G}N
    znbYvPDdP{!KlTp{MEQuOPOt7B1ncOJ+iBc?z38hqKA3q++k3cbc<$L$RTcYamqV}SqbC4r
    zuiQ1o1m4?zP+*u@vIbiNiFRVS41FV27XD?1I5hT(8~2_l0B1Y=HwX~ZLuTk*x&rdk
    zxPr+hBr>~2z4i*LZ!NOC2T=%x9%ITWtM)l|JqGjfDD1zS=^~6nPM`ae83;nq6jpjl
    z%Bgoi9U}rV=ma`2G*qCSnpIW{;|=Xl`q$~YubBV`bON0$G0z$V{2Jet!Mu8iuL6Ja
    zLC+X9LwRIkO+?>Af}nHzXzbB=t(A(}0X4bJ>IEuIU6|Ne&2Jznr?0;)LJm*A;(2Fe
    zh6Y+W`YSN$9A$%^HqhXQ$f#mp2Bey9#Wd2M{+7uDe;T4*#aU%K>gqcK3R<{9>91vi
    z$s=a%1=nDLg8B@m%XXIoRlyfCH8JT7CIq-bSCuocy89f54*Zi|=(W+J+rr9qGsfkk
    z&)>B@7~_H*@xxi)2naf6neVB_nv7ajMjpM7jC`Iy`
    zkCh&`ekK)XZ_q`aa)U*S)@+7C0c@kHFF*M8Fg|P^14{6Fb0pv$_9gM8B|9`Emkoyy
    z=^hPQ5=W5qBw1^WG)yR^O52}E0k7BMqO~7{P>t?~sUJv5u>36D%{B&5{#$}B6Okc-
    zg7^T=TUtqWenFV2)r|8MNJ$15-@k`8VnNJ_PV@!5E>5^t!4N<%f%k7#12sZ-T^A|+
    z^VgQDzb@;bx&4{~K`JuKadEf4
    z;-R3+hS2g^4$^_p15<_0U0>juj_Pvg-`p=~B~!9gRwa(!+J#5Vx>6O9m-uCnidf1Sac%>Fn$GD1!$
    ztttkQSPX!f=^x`GBaQAR_8fstOzAHIY7ih5wj?39;=ZSM!Vlg<&}ebj6AO*tNn^qQ
    z-*6@g*cm`F^Br~|eG-gUokgXdWWqcZdoqi}8vyfqAD@20R3yX>rBlrX+<5_}jFwp1
    zFB=B~&FGx5XCxygW>gPZ23EfS(!Lv_xrQDHUWQ=?XU(0w@x9l|JBM7(~M
    z1A@H4N(+;CCLZVvWUkQ?-~xw3u%JU5KyQiB5aon8d@7V!8rCy+%vFF*|FQtIhTdD!
    z>;Ee95Er`kfx&4~__%bIcZko-%q&Nxy*FL@onTxS2?#zHb%ek+V$)oM>HyA@zwTrK`FEZY
    zT;vE5ZQV2FaFckLeSJpn1?(+zxSMn1rs`<4NqijR?XhG3IFoz%J}|UMI;NUd0rGrj
    zi4GxlO1})`qGQ`Ge^7ieCbM#oy$5QeL99vWw-lwExzVFwPT-`xT+eI<&(YmzqTry6+D3XBE%>n708GuSSy+=?J)3xv|h
    zKHo0&+EdM@5%tcMlfoZHE_W-u8(|7t;4_xqZF*-i2^-#GlF#h?{kduTf7Uu$R-
    z*Ad1e#&^abg^1P(kj4Sf4=!PY;aUmSA}2cExQvfhH@XMiuhWfPgN5i2BMymKpJi2D
    z3~`~oNLjR2f`0eT4+OuJh50%cR*?TVqAG{6CED>6#?b^J=>PNsa_8brD21ST0Cc5`
    z{KbiCRqcV=c@@HsC|s(@&)@hA{D}!L0BXZ!g5j^h2)`3@U<#=EpkZt41^0?O*qkWz
    z#G3aD5;aW5GTaHR0G`|bgM&*SU;+-N0|X3Z7=;%7TUz~NRsjhMt6fD)BCX$BFPOJ{
    z5FuQuWoN=XxsIXo__6uZ;L^y*>jOYEE~c|#1aPRxVIamIJ1~DGyGE>Z
    z{iJOij5G0B!U(089;fyS2-W-6lXbf(=(fEH^QPkh{trHIMp-}-!xFHZk02G(vb(fu
    zG^m10)DX7h?Qme!C579=0{{~L>q|$Qe+!*ru
    z#QZ>zAv3ZJ7ygag#|yx}bEH%mJ>-MG%*gI}HhzK#9oL7jS}1nHk~0|)=K_2*cMbg{
    zf~LHJ0xPC%ZXk_G=#@R^#d%u+aq(b4Ey+1Sb`pew^^+-jz{!l#oF(rW=mR6{wxyyA1==B~WjLsWV7J
    zQh1~+fi@{ZyazJzIgHDa@7wjlr21Qk>w+@V0DXnK{Rt->#03>piL=V3q1>{=#8{_M
    z>3eW@19G{Cpp<&C5aVqLPp`fxvbces90$KlHBVoH5E}qe1fyK$<5q~PNfS%gLcO`C
    z(J2Ga9VWox(Gf55Ar4Y6LWqkktvVn0aw4B42$a=aY*YdSRImC!z*Dwgd8DbSSTdXl
    zv6s71QBiW(@E{CJ^i)%}#SLk1I6wEOLLar0%RJjO@_q-C73O!yoi$F4%175A$3v5U
    zprov<%tMa92#>MAX(QJlzLf-D&BemVMq)?M0BWL#yy?yfo+ik1ORNCOMWWfcx
    z>U{#2F%vW#T|p14-T?|HrU30Qv6VyX4hfojT`bZq`H>7Z-sLbXNR-!tap9>?zPHx-V(s
    zv^Cqd09bWW+Qe?QHP8bb)!jnF<}=Kg9vCUUk^+Nawya(-4fv5AqB3*;8O^mYLs0fV
    zGX!>14KK>g`le(|xFFQ!Hf~dG()8a@bFp6SHVz0k2QR-(+4&uh--P2!;WpvW@sKqU
    zEu?&ez}pEMs~0*-gC0Ns0s($Vt{lgkr5uvQG{T@Caq*MNmFMc}M4c(h78ZFHa62n~
    z)8hvW9|=>d)M;hfT3V(w(33;%HcbO{K6i(kQZ%Pb06gk?Q0_q|%Fng_IXDnmz|@JAqm?QiUcJz0pu7knDPs4d;{?H3
    z{`pJQ!sN!mkX4H4Phkm4V((I2hkV>iMmfN0aK8ke&gGTF=kt!`%YRZ$z4@V6=p|iv
    zhd^Z*bSiM3yR#v>n_FeeKruadm{?~>AJI%+4b<<5Zg){h
    z4ZJ*n;eS}3qVQUVa54zR10`kkxA2f&DB|d0WvyapJrZ$IbU$|PLb(n|kFk2EQ(=8V
    z6c~hAm3f`X^?Fq*L{%C}Y6L5c9~92sK_O7|b3qz`h3oY$VxUN_qia06)}vuBf{WUC
    z@kQjnH&AyZg)ot?X}%cOu@TwV60c-1WbELbIkEJW82<6z
    z-RM`7&-^9A&C*wrDCE_}EuXePe>V&|Wt2H45RmsZ$ZO)UYjklEh!l~#;eGLK2_Z~E
    z1#d3Inm@4uB9NDWMV^?*$pwN_%R{f4g2C%S#iosj7lwTbn7V+DN_>K=>+C5!;l~QV
    zm*~9X6@GsHi~k;r;#`)O*C}IB($pjhj$Z}JwbxvBW`B#O68VmASodl_>piZ>I(;ZaYe}jJtv9hM)y>%g0Ikm7Vb`8zO`3eU?
    zDtJ=Cc3Cd~$rP3NOHf8D)6-`kC@Tq6FHN^e%`}54O>^?=)m2|~xy7S}$f88fKqu6W
    z5UJDaH$Z)DMn9@Sh?N#NLL}A;@$c)dUU}Z^92_q2T#oZx_AVnkAT%HTe2rj`fez#r
    z1`&Og=m5zN;ZIfNTR
    zFzEFtlJSGUy`|Yc-kbr8lH%fGU{s8k8C8kf&JV)QYdpYW_v0hSzpLuKL<3nCUQP5<
    zPe5;?w6e6k5zuP>2cm40f59O2Q$Qnen8H?s={-n?#>)zNLh?%Jp>YReERI?X*W;b$-8e;=yBkK|3qSdr-z28
    zE80G>U`W3Fwg}-%mnF%oB7fajz0e9DOda!<=#*!}M*zhW;KDt-64Q8zh-n_CW`CAL
    z>3afd#!{c%KW3q^2MnTM;{T)SJ>a?Q+xPK{6b({oSP>GD60$|v(Lg1$P??dL6=^A%
    zk&tAMl9`zmWmIIZkg{dZ%>QxS-{1HD`u(2Q>v^8LZa(9>-q$&f<2a8qgzxg&3+4djBEUKP$h;T8mFO~WTz!ss0&^M1IV+%Xbt!tC*s&?S-E!yWEm2X|;2Cia%NJaP*10556-Nwi&{q+|PSZ82fK=#4HDMF3(UqM;_
    zVSVQ0de#^y_~Kz(T42P@n3s5ciDuLNE%87m5}|m=Jm?J`lR@EtegoWIb{b|eZF#6{
    zwY0iMA76xksWA_rVIw6)L)(RmA}p|Us{iRH6XoB8@tKPo?qRGVVEzo|J(4c#H#wu7
    zYzF%lD_#uTHV^nXDQF+Cp|WM1yR#XtO(SivMCg@em%Y0s;D19nP*!r35vD;#SFW^c
    z_*c$Yv*p+iH`wBsI>lQeQdT8R$WolZa)F_ZO(V5mFwG&LKQMa)7%Ll?SGt6f7O^Bb0Y
    zicfncwj=Yocyqr$54v#_#&F^(fDgUoSV`}hI<_@{y&d|OFSn{)(1)sk_1IO}ZZt#Z
    zp2k#Zwv9sUGYOu#wV56WK)fjrj|LdG~_n4%o&kK}Sp>Mp7TcF@VGN6M#)KB0J}1+b{cE
    z`$I0jcZCkD7@|WEajj@P{m-|PlMEhHK8Ah8AVVvYEg^m>HpyyF_@7B0RXMqP|8N9l
    zP)`px#P=g%w<}zq$f(>ustH}XK8CC}9*#hZHvJ2boTF$+5v!!56bUl>qq%bRs~Lbo
    zyaqmI`HyR0-Y~PN78iysL>g+ILKqPV{f{%lKurjr$FXal9vn^jyNY1TirL)Y+m)bXARYlh3-}h2sH{c+2vux)k9IS{LP-Hb
    z2A_p)d>iTl`V@p&hoHT?YfO&w?)O)+_wuh8`i`J_deBCUA=yRRz`)4;;iDc!2V)+|udeB~>i->1YI8lUZQ0WDOo
    zH+X9Agf3+doe^Vy!?>dI*ljKE&MkpC2Gmmmge806-bub=TXlX;{<*GKN&@^A@6zj%9l`xbshDn42X4O4%bhQAVROSKem7t`t~c9*1u
    z9=$qIa(=LJX4zC2b2p+Y09LNJaO-s?dS
    ze@dKPT|0Rhwy0md`bJ&1qIV1XNAj2co>^DQerNOpWQS8}9ub+MQQdzSY-XV5g^fN9&L-xO}XouE~kno<-UvI~IO@
    zSu(66e3v!rq@<;-ppZn1)IQf=nC$qrcp^0=MWem5b7nN>J8iqJyF=aNx6aPayT3~B
    zHPg0V(fs>kGE0{Is$iI!VwYnKZ@=YX>)HA5Y@)Yb?TYxGAbM%fTzl-4tD~bMKc2)+
    zC{RT;oqM#~n^s?A6fFdAy?|8Ygptb|Ir5_AGaza=;|SnCNeQ#gzH^M{UP~q4{eqGO
    zxQfyca-H=FVXwHxVXdwU_j+%l00bN$jM#t=Q1hdgN$SL(1*V{DD<`4&sfT_D|Ho)@
    zLiiN^8B$KBldY!05=DN(5|L|qT&0UnPh(zBofxeVic_DEYZHwiltVvs^%c<&)%fPl?ak*`O;d{b_xMnH-s}_kz(4Z#oWrQ9
    zaVC>%?bTy2rttk@l#%phBs)VrEXYDniT7zrNLR3}sj^3rRU^}x!ej}f6klO;MKks9
    zxnDYz_V~=5`!aO>#EYCS)2E_ZxyL>V7BN+KcGQW)OEEP>em1)BXh|ct$o+zA-0Y?s
    zDX*AoJjxjYZ@b=1I{N5tbg@iyaslI7w{}Dac{?J%-_Ru9xPGBP|M$2KX>X@GuPsWM
    zm4pWjmX?;L4(7?yo&(Dp5h^RqLz?^
    z2d>ayKU_>#q+Jb-X$}q!c|2flK@#M&$_xg+4|5#^Lgmb#p{=K`F@SFEXM}m7`Zb_l
    zBIv05xn?o|G2TLP5cx9rBgU(!gUseSeS=(GP;Zk@}_pehX@9>h8}zdwl@0g>d!FjWnNmqmit|^;P27
    zvGelBzmwy2P;==7%}WEy8-ZmXLtv2TwNF$xak!R2yL-no6R-7?Y|ue*QL^rxdMFfk
    z9TN?aim?Z{8(CU~!#TIBSQl{|sbk48HeV`hFs1#c-0`LJkCkU?w3Vg5=?5Ks3)AxN
    zcUdRF?F)6DzW3+wu;WDm;cu0DM5PK^(v`QEv1MOQtYmQrIJMzay52Fi9aI^AB~0gO
    z2(Roi8p81r4dOAuK31Pr%Aa2P2$m4CHplLhSxQ4vOZ423&f|G2=j=xco@7e;YfAr&
    zc;n7VGp$jk;E4nh;L%kKoa}o;uF5re3b{ipte&Bl#PZa)ux8~v70;p2St?>M_4nS`
    za@wBg(ClS5S&_E4xn0ryLg_XQ-3{cb^gk(y0!FnQG+szFZ(5)I^Je#Me|rF#VtSer
    z>4N>gq~XWl#eO60eyWk~770Q*BFzU0C-oXL`|!nOpl4=Z9nKqWuct_F`XtAnn+5r2@3pIEP9AfoCjg{%TkG_x7)`Q^PoK>=-KxC2~h
    zbHS3fXB_c2wRzC=Uh(}M2Ai$Xjl;s(N=-W&f6jNXKI(mpR(;8^$VCLjaPDy#nc_u-
    zjVNdwu3QNVj|(~uHd4a?a%_$0^YpqR7{ZDR{@O3!sOG|E1~^}b`}vOM3j(3Lj)abk
    zm|t{o5V^pLbon$+3PbW4vu<3eh8P-Gjd8D
    zqKe99K~|{!E=olDndU3UYs8od96fqD@Ky;h5bqauc=M-F&Co>*lcTLy{QUg*FkbV#
    zlvJYWo;r1ElYgZ#IbxMC6+!xDHrLcVHD)OgdiPOeEkcduphHQIwy37Z+r;Q3XP`i_XJI%8UgenYN$bNCqicGXWfU%y4$g=J6x0Wy
    zW2PEaP#`?}?YzL++FB&^G;FB-6i;8T*S@>&wU0c9ZoEcvkTO@Z!%RWO@na#k@JBit
    znxR>6HkJ0VyM-Mbj>Y@)79!qvkg6Oo3$Nkbd?mHs)PAl#n|D>6l$ADObg6x{xBT%r
    z3eT3GnMup~_`sb&;OYGLOV`9Bahx7D8!CL}ar1cCwtKQyFGBc!ZDhu`T~+^|YP<$3
    z14CaZ$OITX>`SPLFP?u>yfoI^+iMOjS42>dZHY`5Q;MBKiD9x<3izIJ*TLGzZv_(J
    znR8!iUd8a(*x00pR+~)Y2spc2ozk?bKTlm~Km0o+EbL!Jo}K9}9pC=KygV9c7HdS9
    zd~*)Ct9|fIsPP5Ex(U?zBB}H?&d^rV{MVx0b9JReF}B{t+1cR^T+)Q*yR-{64W(mv
    zwm_TeQ#UyCY^*TaF{L=Cyv@<;{{0P5rc9t3xM3`D!esPO$yO>#8HhnRl5eSY_Go)(
    z!=W~UY*KW}FC}Y*1JF4HX_3e%%DG;L-Ov2|idz^$jWB634l7|E2A|I&aX*7GYO6)s
    zd7t2Qo+w!_UO)rM78F$3Ym+oIWLgIkTxpJjcCef?1{8MpVem%?0ow1ly6URRX(+?%4c$~?2XYM(1uno*qR)TGnm6gsIAu0(B
    zB2DnBM(G~toU*ihcNsVKA0$b9%GEzH6!4jv%`HGxwHsHsXnwpaB2^>V`$4+9n_C`?
    zlGZ7ebFP04Jvxzz(uz8+4~KgS0N5d^JCnaOHI9QDObP>GfAN13*|tshNeg0pK}_~8
    zD5e&s<4VqA==dIdbz5iWMq3YkOgb+RdL}nF_h!H|&B;4bd#*XFp&R0V#lf%z_0{vC
    z-zrZh5A#}BSZsnjnT`PZnRQ;b*8kN4nC1gSds;WBo9%};E9gxfDiJ
    zZ`A#N(`U2dmuACvP|5Mf9x3l6c2rP)yutGM0L^ro=*_r|zpXs?Y!e8|)VukS6)zj)
    z#cpMdgytFV)fva1^XJo7PkJh|Iji;(Th?PgMjGT+4;zq*IctH0q=*?muX%R}L535+91C}Iur0WI6lQm|WnT>ZjJP?bIP
    ze(5#KK%C|MFK4;TE4x}&eWSln%F7#fZ~z(A>#Bmh4lm^EtQvg^)It@fHS%caM_CW=E1qvSA7;-xQgaf0ABATBcAm=zQb^;K?J`*Z
    zcZVkk5e0CO-{@#*KYaNl_hXmRa*Mx|>76DKuWacD+{c
    zxA%ZCm^=j|RcPQ;A+_UsQIc7KaHx!y!ReA}#%M>5!8W1Pyn5q)4763%Ix-En>}
    zL`N3fM)S}P&%>E`jU7U^=(l2do<`T3YCVuQN9z;`erAhGd>F6+;zzblYKO+xhp^Fv
    zRurD(<}@Q!7Y~QnYDn1T>oLb`Tv2b31L4mSqNkh7&*6z!x+`AQGKq>lHW~IJ=FK{p}Hvew~hjt?E{E>~P
    z8vimV2okbNHIB%Fh?|GaISFV>-@~P8ax)%icci^*D2-RY-+91CPt)DVtbE@Kg~ssy
    zy?Zb7?D2VW$Ng}KYA)P`0#JMQz>DL^33gw_4KN9v>;^v8!_`MYAT-xQp*oTQfr?UG
    zgC6STFl8K6wGwzm!AbWeEk_j0J2fP3wWUWd
    zKi$nY9`NHryvFd(ojVgSIn@6f-ZWd4hZixqO!Xd;cmdvJgM8cgp8tYsh)--Rr$t{?
    zU@dg(H3vclpuU=eS>rq{5Fs$lyD^X!il+AUp$u(g#$m^?j%GWQ?77OrVleNl!>7Ht
    zZ~J?llt0imdBM2-zLti>moHx&C2W4l=HOf%_neuU>W#!uMowDV#0d=t84JlODw{P1rO?iN&QQ9S_W=8OLcP
    z{{@L98pRxB{y}1Oop#HO3~hTv(E``z2kA;_ZDv$V2ejN4>`y2gHWDCDIdI_@NQcohD22+wxM
    zi%Q^U0-JV}n$niST6-Gj{YvVkt(!M*4mm<|gx=6zK_>y#N}yC
    zrR>`L%5Hx!`DXL%NWE~xAK6zeHuh=a9js?sGenvg8_Zhpy&J6Z#OWxHE1fJqw$_g{
    z72=tF-cMzc@313_07lln*?PA-A36nHwtNs0-Ur(`y*Bvor94L|kN$;y
    zNcq6n564LsyfCdIu{IaT2XSsM!(N0?l@H1H={T?Zh)&k^Fbe>$xVd|UM7jjoB4k+kI
    zuGa{7lIY5{OPn-D!qcEmXe9Eier)#mLIG*ZEFKR2^y~-Ei$f#u%E~psA@+DM97Ejt
    zP|2qaK;VkY=UZgoSFF-%Ce+NBvb&A0dCSO~&$&Bpn3jC6zZsZgeRbbD=_vF}3LWRs
    z1OpWD^S^Aw>`yBhNj<)~;tB{W7O7r{pI=RXL0a=seVg7J;P)tx%gE9t-1skT_5A$&
    z9*n%Imzf-YqRov<@^5)r@_W=t!%mGZg1|!(6vN{JKqvx@uyAf>SkPEsUq#l35B>O=
    zC^?Srpf${ul$1JA(%eB&plJ8CnSp|W0vxKN7A!!^MieYR2almtYx`1BaRa*Ip>P*I
    z(^)z?Iw?a#L*|_hsHomXzIsKKk&$5#*O6ITDw~K&P|Z$_LczX=xMa`EuBJ18txR~4
    zi%;P&^rJUlqASQ2KYfk-NW#s<5gLi3SrdCRl%B?vHaA~{Z>*Sv#I*7d27I|1hwayU
    zD6}zsbPn>;d{l04&-0i-44U%nBt&sMgHv~WZ`=WTrw&m=OiGHX%Bc%z0WR?{)OdMO
    zir+6Yy}cp1-n6u}H8&wW{XB3SWpM(s;fw0f+&_Ep5g9!mA4?h`JE7jx``ruZny`0-
    zftU6A@i0Y!&{m8wi&hJSdleQI#$;w{$f+vf<-wA_=jGuEuoHW=ClrWhOLDV$2ugbV
    z9ebGnfA73`yJ703(LOk2A3b_bF-7l=)c(_4J9qHy1>v8XoP6QQXQMD#cGHa0ssPH+
    z!y<=;a9QES4}Uhw0Q2+IyHO87DXLd?^B$y9mCPeZyD7W^0+#~Joq|;l>VmFU{;&(x
    z(6+gU@{yZ}^T}E|2)QS!ZE*!E{xTDub%R-G3^gP$URXuQUVi%%0q^ytix-nIEiU!X
    zO$I&I(l=`88#J33%5Xz}K6vmT2--Mvjp(Ns=HNr$ctuwAby`}Q6_92X^v;1#)ZsFi
    zgH0zBkJ;{49f~JAJ2G{>3jXNrO%wR|@#CO4KTgGGljbSoG{cR>X=Ak;mjBpPVjP41dF$KDM{fr8{~jEyM4H$ZWQLswA4!MQ&vAWo
    z<6WH!?DRG&BVsTy`3!mXli{J?zdr%FOv|(BJ+-+^B8mNqp`N~e>hC{)d|>Dl^1$-K
    zn|jkGt}#J87J(*_Nw}A0F4X=k1e_n2Up~QEE$~)&i
    zs_ilwSqsN6w`c3;If(%q9#s9=rH(oZJx@^|lWl;GJ5?!*<`&>F;3-m#LJ-9tl%l#e
    zlcd||l}$~?=cTIMX&Z7X9wo9Gs68aJg)-lGUI*uZ7!8fmUz(ij-`_tb|I%)he-P2jVP!Y;4ex}%@AeV*+2Og@!7Y6@FlQUdN%%FD?5AL-Mdkhia@+^(FD{r(
    zvUtuP3lc03ri?sW$xBoim5Sg8S4MYuwlrMJtwdmV{6S$F+s$~1>9`SE6+mPG(*K8D
    zS-zx4Wm>kIc{^q1&)N7YpI!A+()uDKNZb>f{OR>
    zVGTS>JJf}S#p57Ta=3VLcfFey4}*Loe~4=H&L{Hf+Db8IEr5@cwN!<>$Y)r4|Ap13
    zK0X)mFBS6ifEs+vfZW^3t(c9YC+3k|PyOQTwQKiWJ
    zoEI-GJ_7^s$G|x5Kkx%J?uMza+Pb=z-NLG)O1*45STi4a4K#C*{m)gAn=|mI#xBl0c%Q%8qrqm*JNn^ec?Vb_$Df<
    zI|+%2Cl4IhPI!L5A%vKz4rlAl2RiF#$GapazxXnn#r^FB!qC;--93&!CUcFvfm{}Y
    z$CAGwi(F7?%PqFDe+Lh96oNI%&1JuS{X%oadj(Ctd?yWQlWmldr=5Cf*KjtHrv;?G
    z<$mOFn*0?DVW!lmsHmGz0i1ZUMi8Ere{Q3wOogf08Ra%VfYUI-799dFn~cxU-EDw`
    z6&xH)Z^n$Fj@_iEoz^Hp`ui6_HW#v+jm^*z^Hsf23;Db5C=(PEd`+&Qfj@2ZtKiT~
    z^q{YyKPpsE?QV!a<1Pd72EWGigHs3W@Jql`tUZ)fZ$Jkzh~namay5qNvSubH(_vuz
    z0|*GNdiNiglbz!@c$Ay_ix2?PC4{oiaVB>$q*iw31-}aS!3h%BMpH9CimqY$e_)p%
    z{|9zK$5D`~^Wm^>uoR-{S$X*fGoaElRjQ@%0ZY-Q7#=Y5iIi}#CADAc_+98uONNGq
    zM&tTWwDc^GcNO+eO-)?~_S2TZXZI3U;=U3_BPXtsFE?xqt`
    zTOOXm;OG)9d)1jwYFlsG?3ER|Qtaj&igrp-LPElYATD0I#4U6CxBO4EHz@-?ogi6CY>J(x9@$h~GXe0XZnT+g;3Mb_jbp
    zN-kq$NjF)m2L7oNB-*ToWgj8EMKA*Cqa^rsva1r|Hf(^1vi619`jx2EI$UNrQFTb*
    zcVmio%wS7ZduLgh92SsVo`-wlp)p7XZ+AJNG2GWTHa1oT4_g>&<-d%IvbIf`8I1Dq
    z`(Y{^4yoZQ3{S~Gyo6Mom2e)9bC)g;gH9{!Hmu@G$Q&G^Q)}r(2lEbE=;CR}ZuAv|hN1CkNAWLU@S-l|q!hYR^CEIj;hDxVXq!_vT7J2^uR61kL+1NX6y
    z-qj|y6lAmUuJ~A5?=l7>es80bwPVD4Dr4Q@`qFE(80C;nTEn087N|7|!ZSVnSZGyM
    zl^JcdubSiijg|!n&K~
    zBg9$#4n1rzM{vTzx;Q+j9@%h^bV=05HIT%aDq|)}5vX%;0I{krirg2Ih%agkS)koE
    zIzr(>HttI2m(QQwvAN|P?F1&V=n*mI<{oU`b@y|^4pdKQJBLtSFE;)7hX9QSr^WZ#
    zvu75n@tI6SX<1pcoq<93`+}&(6Spc1}9g>$o1I|IwVlCbu^*<8ehx+d-!X@|rt;X!zklgE}`Li3**
    zpQ&Gwo){bJ8ifVcSKsLj6`uupjz9p~9TMde;3!)e7y+YQe|4E4NZL|xdY|1k};LU$OV=#s&Vs)
    zWJWlRcF>YFR(2;%DMAMadM!5!0#A9G;J#-(hK4;>plu}EEaxSkJb5xmK`gozlga7A
    zoIRZmlkW-4$MV)p)^&HUr9y9W_l9r^YwJq^7P`ke*{1c6m;92sj~}0w-n%zc1PbJy
    zKYe}KwlFf^+GPG=3&DQkgyRLfM$E4)o8P&Um!2OLA?@n8kB~+F#^;NO(7nw4+-#oa
    zxjYB&vD(Y7uFJWPwlVJ1G%$F*f%r8(zE*=9_`0*82&y-SFW0Xb8w>TrD&pm#5LF)v
    zPi}@SMchzQG-#}nK1sQ>Cza&oD+&q=J@D~ckB3Il$QdV3tc)%6ctllQJ=R9ZxZPWn9{w#MQdj(yME}Z~Sf-zX%O|`{&P}uiR=c)5|7L;i%Xc
    zUq2A2ER2Ct0X=|mC(38Y3wSHp7D&MCMxqMOo}Z78>y3Ne5Pqd$fJvrBiO)cA?q7O(
    z`n{#I%WBTf|1=3tav;TgWC1HS&ff*ZXZt606r~!*dIb!N-9Dw;7JvMxw+e3NZ=+=9
    zr+J%^Bjhm?u0mwruLek%AV@{LR*x(EA}@?_kQUq9Zq+)0)|Wo9zgKkj7QVvc4Mbmm
    zf4m1tES0b1T!@p)1=(Z|fBDAN;D=6fP%-Lba#b&@Z{{%t6
    zkX6-nx=#h}*LUtLS{!}*6Aj$gZGOstKEvJakn&CQR-FK`e(24z2m*^~-t3n?dfmii
    zB`hMswWrAr0?hYI3**rABo0YV-cw&)gRyY&JPyP5TRp}4k{>W~0OLb40!z(ABB`dZ
    zhdGjzNoHBB2J^M5fCJHwqL?N8f1$QLN85`^)OEqK)(IGkH?bBLYi+tbhQ{?Fq#wUwE=`=I++EN|Xi_{s6_<;#6_;K7_+(G(R?5Gb4nvjKwBN1tC*6TSv4
    zs9QJp*4VXIk0vK=dZD&IrL0y+(nsnW5(%a<$jkJ50#2I!?ZOmY2?YTwBv;KDY=LV4
    z$^B?p>8dDm&`F~TnseoKp#u8m4VO{|UJdrbtr{0~ylBaOnJgI#d80z3_`pzcWT1$|I&{-xN7QQdd52kcJ=-`S(Ghwz(8#i8I7O&TLL_u~N%&ZU|C%yN%{Zpvxe->ej-*)f=r`0m%mGx`lg0jBxaG}27Nm!Y
    zLHZ`Dk*`ck-^c@uE{KjG$N~s%UU%;nXJ%*5W}N4led_JKkBSieeo+R6VFrf7Ye7r$
    z69ifrM^n?J+-RW(BVx9jvUZ(pGpIg7Dify>xK%DoHamT{d)|
    z8zldS)?LdBjJ+@y507cpp(wYQ>TlmZPPOP-yBoXRx=*ovyZ=@9?8Nzs&+t<3`U{XJ
    z%Am-FmdbNIN2>&9f7`3$*Hz#3E}&myYGW9I3jU>e-|zU!D@k{-+u6>-m(+d4WNx6#q%oah%5
    zrY03`ro!eDk+lS|59zcC7M>*|ORq4NfY^0uLFyU0yoK|i>|i55Evy7!SRY+Vq0Gtd
    z%gI_O4&ncDjPOL-o7hB%p#czxJS%z?m1i`UzrV+hj(!GNi~P5YUmvQiKN9ae2ACse
    z!j#Il$m06-gFl>x5}4%lp)%CKwx5(WLkPd+9^KWda>Mq^T;!rlwAG@;^50rpTT^Oj
    zs(!s0B4lrBvuOJZ&}NcpqhCm>rj?aC}%UiUL%~bx5OxbK5)#S@|Ql_KH&3)I%wW7IUGS+X{u%X1{
    zc$g-XY+VQO3M)|nSD2*@jaTj>yjTzf#MJ(%Pn#dPb){cBof&BSqa>c0e);VGY61QU
    zqsBYAZ`&7LbmrEW+W)+Pc!q}|5;R$B;qF+}P@W;a9Zx1nI6VOQwJ>p0jeI{m40+JMN5-SIfS#U1VKIs
    z-@ks93=a!)784ShK_q#c%hb(`qiHd4xDHPOhlj^HBi4iXLc44F7d~}sbv->E5Ip(Z
    z8j>%>7!>P?!#;D67>Ut)4VWOw73?(s_66I<0ve&&Dd*o~u|)PI
    z6a}whrBh^~;X2|FkO#9D%v=9MwIr+ZKvlLd)oJ%h))p)JAd;
    zaJM10uKV~D`T|FNDN6aK+|BZ3MMVX}Do^sHdoji*UGM*IpR997*&i>{^_
    zdo8oX-Y99m!Cs>4cH~*7kHzI7C=|3tlQwSJBr(EytSfwH{;ntTkw1U_Z2MlF2BSiI
    z7|gao#hmfIX$I{ef6)=gq;roTffmKv9AIaE<8Odv!34(70!&{X7fL{Q0#%`MPnO;0
    zABhpDRik1pKt}whsGzWFsG?FGOX?v`gSY!8#r}og^d$`WTLemVP^!i)bxQ(|penD!
    zt5~&MOho+jpXKB=FrHvie1^`_J#~COMS@PnXRtdI6ViK$Qb>?0k8y}3pdmzQibd%{
    zumqa&AWfRAS0+F!KYD1NjfTL{iniY!Wo(BKBar@lf^eX#7Zi^a1SN2&+?OLmg5HSJ7>sD
    zhNSuPXIEF0-F~1))W4!cSm&ytoS!9ka!bImWqzWk#}TCbD#q_eyAEoeKOcazBV-s`
    zPj0A@Y;4-|2~*w0#mGA6Aia1kAtvT#x;e)arzzVGG>C6+oD6!kVp0Hj>p&0Sw;%DK
    zLt?l2>f22mgf>mRIAwt}zd?QI;<9uop33DE{iW_cs8@J#2F4L#*GEkIu$=CVV`>??S~jM?+X#dXYSE*pX$(XS;RqIGfYpYy
    z#4+rFr}(bY19OA@j0(XVBQKKC|KR~Z8~*N5-#&o{PoF$-{@mD@+QXW
    z7lT=D=T^c7*!sHAUBF5uB^dA*KO!PB7ZVxTWppNE7``!Y~?ZaiC;Wo2c@^!{R%$O+NQcOkpfI=Pf-^|uQT${wzR2O}p?
    zSkh2aQ(Ncj6|a+fRCzf$IqMo5HHERDD3FY4bJsh6)6~7Yk~L5E*>zUuR6gxJNK_)r6y9T{2z6Cx9BQmgRoeM|{J*%+H@=3W1E9Jamsa+fayqSnAG#
    zs-IZN`eg$PFzdyC|K|LCeOIre_W`q3H@^?o>We6XSC0W>j^a=5#xzUDzHiwy0gwK*
    zi2?qWgpZ~3qce_(C8tQ>3RS&F^6f!bef*6_t<^jyc4baN*Eav-Cc+cCAX3Xm5MK)E
    zM&Ikw(9*6B0O?&jGa=meU$#V8NqvPqhAibVQphDv;iG+FI&3?Dn!iBO=^A1s7T0rE
    zVVDuR?{U?rEsoJBvHsUyJKHqSe@bYN&*$AVRAkM1)?uB|P_xr}X6e*fOZP$vJf;@0
    zI}vbF^-U4(Yae40TX&2?ytug79Tzll4$CaFi)YC4i2P7(+TA$z=KylWF*iuU0O<+#
    z2QN*+z)t^aUTq#WP2quFL{AL|wPr=tOKAq~xiYIB=vX?Aw_}N*=D1$c_e6>fo7}Fq
    z-J{>XU*<}U51fB+i+)orl!s3y5lQ%knF3_xgB1i24N
    zC$8Dt0i}FclGDz28X6h{z*oPKU*Huvp*FgIvmFc70|XWJH~2YJ`KcgkA_8B%T6v2z
    zx=*^%ggh3LlP?P((;pm=0~b02iHm;usgs(h7dcWP*+0#{r$;~QhPpM`t;SS+{aSeW
    z{P{`Q=<`yo`<53ME!&Zdb09tYDG{6b3@5oH0u8MzrqNT;_8n5Jds<;MMRtHUKNW(g
    z3WJf~0$LfhTZ~w5u_TJW+D`reY6m^{$F;aaPNe*D)Arc*yxYcJYguN<@!OBNMQQUF
    zcm5~==UPcy{T;jLnoB~vwke&xh{*aHXI3CYRsHPAzP`RDDV*LH`03Fb#ooop5v<(M7IKmJL1~j8<$|taU9#?uk7Jvg(xtmt+R7i1Rs-(Ti%bZuC5xb
    z4Qny~aZo)N;Ve}7V4}8+Ct`1}L
    zxGHgd*s;x<6fLwGp^6)VMd8^m_E(&uj6PpA3-K7zi^?|YckBZXV|a9wmAx-QX)nRY
    zcc#0kZZj2SL0a0;w759=kjThX(>mv>!I@$8C&I#C00zFo?AiB4_V^b#+SBbqJ3N83
    zWPo{jJ!k#%wlBLw*@kc$=eY+78d!8559snD6u9HV!_^0pjRDA6hR@uE8uIXg1JRkq
    zwI{{I+FY=8Z=TEHFG=sKI0Iw;`N&b^BTaO4bW5CraP~S1@q4&>Yl9ZPck)IZzW`7W<=jUzqxn~z`YC*)$Qo&R~pH#_LGy|
    zSBxE8yLO;HByH~Dm=60n74zBclXdyO5Ea!mDic_~cCeslxOhD5S(4w*Cl8XGjy|t$
    za`5-C;sw!R8#E2NrRI+At^=%328@p>wc!u9p_+xafPcY7Y^2X}=>)GE$UrTj&O
    zE*TiixNFxgwY;;WV#GT~67xg1w*100thE4kGy$vmBvi=?ZZYjB1`|$m`lLYJ@fTd(
    zL4*+XnC+Txn@8|OI2I#>a6HSJX@GtZoDhOg>nEtgf3OmgL$A#Cnfeg
    zPxeI+(A9&g(e`&j~R)#}>XZKY79G=XI2-ONhL^O2qQqZd4<
    z#(J#bd|1;ig?blPS0^x*y_%ZS261}Y{CmowcuWBt8Y2U3RuB2QV4|G>XxbwpS(=6O
    zKBe5gefwpwCxswcdp1&TJ&9qCVDvs~=-3$(?A-$0?CtHdQJL97lyk_WCk4u;iP_oN
    zO8ZlM6{tWJurc#Gw6SMmx~?7D_YCiK04({xhu?4HE)I1T%y;SO3dVE75D-eo8L<9*
    z${}a4ySSC?{tpJ;cp9_qu~0qgYzNKGiAU235Eq@m_Npg!Vgv})U6{uT{t@)=J5XzT
    zDW`G0d)LaSHe2SV=;bflUp)i_~_Bhjj0bPYUSpbkXizZJ0=7tf8LZYY>Vm$5p%2Kl6xJV$X&hHcV|xPCoU*$5
    zkrL;b1dMo*&4*!z(PvhTX^dz6k;`Nym8Y+d_nOyf?v6s$!Ce?&dni7{oEqUiCK$=t9TqqznOkpg}9CzyOt(XxpB7*}|x
    zbLEO4zL&Rw?Nx|Kxq0hqYyGfy(|gR*=JxGt2um?$3ENH|wl%xK^nTaDHbEPjP@%?_
    zmKSE#636##OCXhMHU;7j8r8C2k&o6KE8lX=Q0$Y6t5_c=(MqqYR|xEe5pT-BwKcH+
    z_wQ3`SVney4upfpy*NLQl_@0(6=x{RMv$>>gN~!PkCfART~s7JC9{bz{cx?G+_|%c
    z4cN9&XpbSbYsiK_6wfiu7PZ;&=5t3*EsJYF{r)4&WT*N3`7XW#2jqJ#B*MZ^4n7@*CIzD=hXFH_$iTBrM%e|8
    zz3RP~WZ#$?{VXbM??J|JbL;HNO+mspBxsZ$|MR{oa{{|hYQJrxq1o5h+bh`I)a^kT
    z+h+!#rgtkkQb?WW9}k@Mhw2Wi2FEZ2vx3ymfAi*G$d@|EYFJ32fqsI_G7{>3W;nss
    zS*1Rk;<@^a$o}WZl2tm*E>#XSs1P~SH(=ShE%7Yue@xeXXWdW~^de(MfgXT0zDU-%
    z9W*c-7YF+)!F$|*r$S=A>979&X5+T>YnqsFxqOVAEFpzSqh>Z!5?8lAm=w5N@hm&U
    z?izq-+R~GiYNE3Sz?yWfUj20|w#st|jzorV@j6UNbOdkkRHEQ~A*5CLL&S^j;pch~
    zn|DE00?cv(Saio&Tl*y|Axk34sC>qtaQr|$qZV-@^|XS*=bWg!OJ<8cZmkHe)GF}}
    zT@sPwd0K~8N!R#Q-zYXoN6U11j&^{yeqT*8i(m=n`)*l}`}tV&uze*I8?9(bbT<&B
    zc7o(g$|=^`uQ?#zw~jWJ+4N`J!*vc$(mMPQ4fk=kH4xK*Adt7W;+M&M%qc4B)QrO2
    z-1-c5tero
    z9Ja^65uBbSzu438o_ObROpp26?|V55@b0CCX}n*^JX(lClt`I4P78&~T+_uUN%v!D
    zwh+XXKE{<>QK>)N*ByoMLGGS}3Guwhvgs(?O6Q{x2wX8GtE#FhaW1xgAK8GcfK6Q3
    zw0Lv52ZzrXQv%#refSa98%m0bXL}%QI7fJb>5pk8|21TieYnH7QJg_Rq3cSqCVoy4
    zvesEM4Ze_?M>)wS8|m^33$q~cP!b5eOhEvX6k7mq(h7RyCN}#jBX&1Y-YczcX}M&w
    zB970u7jo7EL@98C7-&XzUxA?#y!l-MEB`zpx7F6#2)2MaaFmdgTn6Frs{tj&zpujZJIORe^&pJ4?M|IOUDAvH*E)RBcWsLy
    zx~=oDXhDp<^ytx}yMO-vl_~+*L&DUN6a>6Nck4r?DbK?oFox##9%u_!!N{{WSI|j6
    zFL0VD9>n!38RleS6R|gVAj?ia-tpn>;A?5urSbNx#0m5axs-&7i3!&z_2FMocX?oR
    z*qw#$<^Y)tm?gcdF*ANH^%5Ui*u3$O%LeTwkhaj0CH>wbIt;>q{Lex$wfyyH2@7Wa
    z7CDX`n|zDo%F;h|$B`7?*}XUl8v}?C|1BPu*yH2GW1~94M%r&wPtL%s|2HP(S^f+T
    z?oDboU)Mz;**iSUNJ;z}8mhuL%9st5PwOHJl8?5t37BMP|2&D+&%skYC|-`vqCZmu
    z=X?xRrIbk(eI8t#21ym#U1WL5+S*z+@K107K)54U-mgiKQ}s_{exb(RnXhM1j~h=<
    zh}HJn0w_Yy_YqK@%h5br2M1aZb+o!g4nd4?F;Gq|DH6-n@f?
    zAjjIm8W$lh;X-wG)PA((y8x7S(Krk1qv*~1a7B$0)xs#TEGHlP5|-i+YC!AYAHxOB
    zo01s69M|T*lqIko->uLC5PKHlD+ckCV1NFisc85^Qqs@`JCpg*R_i)Ew=76=tFUmG
    zKt6WEDC2DnP!K05h>FjjeHr0r!xj4V!$u;Y+T{AWVmk&9*p=_UG^QmKHFUOP1u7Pb
    z=(*Bi*J2+cqAlTh@XQOn2q|aVZk!||g2ujs`{~kg~h>450GkKgvGd#=so`raW(Ds<(*Ca@zonjdHJ_d0T1@eIbIaY{;
    z1ZQ7~WlW%>D@k_RVuk|jd-0+=M_{mkb~<}Tv=d@b%Y&k(HOn>)Q!dRvzkg?bsjKtP
    zh5OYCX$0xec@j||3E0(J(%PmT#ZV^xyBXB%^Kbe&Lnke7me+tOtP
    z07m|_sgnm`6O~_|fp4~9_QJ*~@UJ}(KX%g)>7<7S#2bZ>lr=(Ps88!;N40UILlLT{
    zJ`x9l9=ito=-ov$o9ef2)t7juVkoJ3e>5A&O4AiR{yiQr+mt!V$$5GYbf2NhTuZR6
    z1)wS}EDUay9gxM=a%mpiE!J7TN3~Tulz`a=c;x;uPg9~`7A+pz2lTvnd5vr|`3;Ld
    zeuWWT3nf81zrN}P#r@#qM#_6IiP^O_{q^W&waxi=S6$-u1kyl!8ZV#-_x1PjVFm%w
    ztQoISjoltP6hr{hxdGQs=R=1MDUn;z{-C=wf_hAP=dN9K5DOp{0K&x5n0E@K6!O1$
    zXxzsVSc2jMgJVGRIPn&zl1<3?%QJF2&DK|uk*i06l6U^x5s6+u5s$=sGE#Vx-7-1S
    z`qnp16P=YUc~;P^v511D@&m}U%td%20GVKnjgcsY*Y(Z1aFMUl0s`Z-fMV|w1ooXw
    zAHUiAn*n&rxb%UyOn#!;*#M86TCT!ogFsybj%G4{WF^V2xzJxP%qs+nSvMszCv*Y$C3rLAnhh
    z{Q$1ZcoukZ8Djla2Mjoe)CuC=y?c`ILC{BC6$4l=1{+WR{PEDsx4q6p5Rc1y2?Az6
    zDX(X-H%g5=t8D8FQt}ri`hjG)$!R7>Akp)YP2u1-~!3G=}uCQ*V%r`%ew7bPPIl
    z>R|=Xfwql5x?2ex54X?#whO_Kui!P)wy{cNpVrg+*;vr(Fxc0}GrKN&M`RHZfDJrn
    zuC+VNm83x=+`!tHYKNl~)02~JlcawU=BrERu&(=?B)oz%rtHq0tMdi1XOGMnFpBu8
    zPdZL?y!F9U;aRSoMis`0}qx9p)KK0L?2T
    zYn{*@Pv{nhAs9vI*GSTM`xb)xLmUbI^sKBxGQR!CDcbV!yTZ#?t?M(kJ>ZK=D{kS%8Nk
    z!~ld&cCGTu0SGX}vF^^4@C1Ih+Vb)n_6yGek6E~UdIFy0IeE7#tx0U}DQ#pT@9gO2
    zin+MN1BWy&N7-FfbDuy+93!)|Z==ovYf<@hMbjX*8qbz4E+B#=Mif4SRZxdSUcgpK
    z%ZhE}tRfElL^(mA*F`Xh@XR-o`IbSKs4oF;oLqNW@A~2++od+A&q1B_z8FQ_I{Y?`
    zP=%IU^~*k`^ihAFCa+{K=@^my5^Ovx7GD4I|6(`x1!>6F18em}RqhVLUeQgthMZL;
    zg;(#nt}~4vV&B}o(eqc;Zv1M4Icfa_m7!D6aRrVYq1k@G+%1ESLoSf2d!34OH*=EB
    zQZU$bXRoKiAZPm{>Q(~k*ugLnf_!c(2WRq;(N^CdJk|LpBf9NcZj_0!OwOzMf=zFV
    zLOxu2`O}~fVxX%zClK2Cdt(|j
    z!e|pWg4;eDSECSp-W1k7tOH20>Yw&En}28l%p!Q=M2ipBzg#&%+CTzAnMXy%#eo-i
    z8yOin2GUX*;Zes@l1yjOr>UcXv`j+ey}&IP0)99cFb_x2JS#o@FQB6)yXon#Dm4_V
    zgnYm#4)l%r0rpz>^_M`FPN%#h-x4L>y*T&Ij*d)nOe^lM?%|LW7FO0VJP-W>^fcOB
    zCXPctOu4~`G&o2#3mag2@@~gwgD94lV8Yl~
    z2(xU|mAW>{rEna@i{1HliLd?>=3_Zm
    zCf3LDCc8n}uYrxcUA*{^&nix^VR#p1-fleK@o_buJ37LvZ)6Q)XcvQUv|G16=Q?yK
    z6gbST_9qO$mn?WGb-|OT4rYQ(-&gvJwg4jsw_J?IP?!M
    ziy2uNLryO&BJ$x?usiIoSs{uGY`}h8CHR}zZ`;*fT>Y8eA?<2CaRZ%#JZ`Riv=AFC_&GaDeX!{r)^JC<2W&~a}z>^GU
    z>aRN=JNA@w<<9Gl@i;|9>9_Oo9NaI#AjNex~p8+E;I`k?8qSq=i
    zSgNnatgjIRJ8}^6WjV)1RK24;fLP`D+2TU>I9b*2?y6%TVuxsCmIxctq`9x6fVBY*h8TrN8Ha7)0p!}c2udOcX(U2M4
    zS2OQkqCdm#EgMs{*Ycpl_r^HxM|DK~@S*07kKG|6%?6#j0i6}IL%kfYz2y6mQ%b9#
    z)bzjHgd8_l49J+25z=0(%HJoYTecgU@^s>3k5UuTn)NRiG4xYGY37TjKnf*h$M9-Z
    z4cGo~jzTQ~93MWNP&H4?h?9|X6jrdLRB??Ao5n$NHYta5ov-S?2R97wuh~dT`e`Q&
    zUCrI(9vkDn)dCtmzk6TRfDPN5TXV!!qM~}bI39tMyDhr(&BP)%u4(LA0U5|eqnQ~<0MGxhSsk)qVsmU(ChF7GJ=-#c
    z5|t9Y3U0YsK1`%vGgsTSDtJlAnG#dRc40E%)Qr%bKTo+H)UYmO?BM<3c?Z{*|2epo
    zncjY3C;Nc%=o*K!gMWN4gQ0x1IZnRGI8G{fbHsP7HK~3*a`T08Uf9@YdiUp>D>noO
    z4ut4j(*K`F0h1d1$J3?rwF1SSI57hc-Yyd?ITB)@%KCNKQXzfslZr$d=Mm<%fwdS9
    zvDx#RPoM#DYP0y%Ap?>b)zQ;<(@6zV#0>nSsgw$az+hvKp9l!{lit;?NJOo*@R&e@
    zDFV<=M@{WFsch?~A3|eW2=AeL6;K^c{HfvtZPU<+ieWYmvPm-_nz5<)00Woe2tppG
    zwVRt;5PHePnA-dI-8J;|3KyY^RfVBYGll#u7&QfthsE|;Fm?Y$N`$MA&wE#)MqM!;
    zFmgp`6v!VP{i~#^ipmUtVLl~pMvFV4o71&_R+KwFhH7Xici9h!0K^Iaf@gHlt|!G{
    z2#g9Da(83|&X96U7<9bGnt`XmhT+f!6u?V~C2`c3Ugl|<@6R__(1mm}`R(mIgZh}N
    ze7~y7TznjQK}Q4-J4zHQvB0g3Y;V=DRuPd4n?^6HN)T7z&RT@~l}<$CH2NVbYSZ-*
    zeE~G0e+Z5MHj?-C6i=Qeea9P>3PZTIL*;Sz4)@M~*^1eVC)2B8>UU3k?J!_n>a>0p
    ze7OBml7*aDvV~m7LZtB56Xt($N58VjwXMZ1Y*%tVyJ0gWasK^G7v--$yh)?Mz}ZXN
    zZ1sATcqDYv!%_e7#Zif2>O~ApDPw-gb}rVPJMwf&dBCC%eQxBAO|wEfHX;;rh93f3
    z7~Nwi&dHgOdVwNq8pkED^B)=rq)CL_E@PR=vkwba(i)^;p9=GjTjE(D8D+^_O!J2a$H()gCMP{E#2}Tw#;n-!DKNmj$`Wp_u88|E
    z^n^p;YO)$;rqiokRnsixZIwa;AN|cw?4VM^V(|+vrKB8-&D@>d9(?b)1iz#1=`&}V
    zM({IlWM$1HCWy+&EKZ%Js
    zbZJZ<0dxUHg5El0W%D*~!$Lz{{#jWmgia^-@Mlv~Pfyn7M8y&&`gnkSI>FOw36wYn
    z>GKzJawPg4kD=8tz&F$0-5n`NL?g}7X(%b`s1+kH3ecZ+a|vq$_{KJxbZUG9+bZlu
    zwNQx2<_Mt=CeJecwnjzs`lJc*Ys)oB|UZ7_k-=_qAkHCIhcxEljRlsEhZ21Z=~
    z9P=QCobBKLdk)kqBCxS9ln>X3l*@5T^!N8KqG=x_9ZBj+q2zZ8DuacyX`>8IeNGN7
    zOryh_D#Z`Hzm}2lfpXEjg&^PnotT~dIU^^ho|7MXjNn>`anU_nF&gm1F0{2G4Ve$o
    z2Cxa8@buh*`5C9MBp(*j(kxH9(diLBfErBl?c;!140LvPt4-|oLXo}rX)R*mle{d2
    zs{x}?ci;Jh&oV_sL{7t}wT8`s7Xu^$f-Eh$${HZT3G)N|7Mzxrb^{k(h(AyVMeV#b
    zRCRBCZ2;wBsxWMk-Pq*3aomTyE2rX1MiH{ygo*?7d>EJ@-zYb?*OZ%w{|4mr4W=uk
    zVx#o^4dse3b_yM;A7GLL5k|+$`e%92Zs19G>IWz(!wO5=qo$Vfae3JZBM&Qa>N>f(
    ztxI2S(_G6MB@GCu2lwDr#;sc?n2`Iar>3Ub0h(rZ3&9olj9kXXZ{NJph69%agSQ<3
    zLLAM_%ad`xn+z-_GOMug%y~rJLtX}pj{r0kQG*%Ln%SMTSe%E89r+3i3ViHK23bH~
    zsv!mKNz)@BTv(~AulKt(RC7a0s|jH-v`|BED`(g#o7;2GRtF;v`H&ZD&^=*VfifM6JBZ-^<-$4y9QU
    zUCqwM&aNpo232Zlrg)W&lK4%q-P?$0tan9%K*n!GTxD-PAd)3fA{w;T1wfu|p`mc6
    zEe{W8Z6%uVL|MVcmXexktewGp_3GbrYXw7i=T*qT9)AGXvOzuk6;Ooe9ZfS+(=sF$
    zHPKy{C^ymC=C-(JMbPH}PN!n_r8=fy?*-FPw*bB^SX@%lx-DIg{ldgyo>q653!IRk
    zGCnYH&OI`6?9I9s>E!GK(O&np9&3@lGwpBP3@|m*xFdT2N9D64XBi@B^r$ALy-*#F2Ckz+moH^zH8p&SO`}Xat#J^OMV7{Y?9&P>)CrdpXc47q%
    zvsAtW&p<6@A#Qb26WZX5lombkU&r7NeyFFbyWz!6$_&yoG6o?SQ7kGiZwf)`*#f>v
    zL10Sz?!21$@ZnoHjLPhULqtRa?qMq>*<$@DZ;-3&b)-1_rmUcF`W=w`{^)3eiCv);
    z*qOJ}TR{THbMUkl&iheR)yn{yb^e@y1=Odgq$I}x@Dc-(v%1OedlnE(Dv*>#h)07C
    zcz7b*cm*n`5TrhDCZ4(I-DYm-2LTD}i+Ax1k6-N2Lv8#%KAuBfbn5xrz$Q|B*WKH@
    zBtOP@EPNrV7?awyk>`YRAz~wH?L!};AmXDh=66bHX8Y7gzC%+h3l`hEtrce9!p^R)
    zS;)f1F;n7OenrKS)9KR+qWrL9r6F(P#mGolEBL#wd@@E2y!@$XKakN>0mX%)mR4ms
    zhaKzNb1M?LNy<3Q>p&?>OG}{U`8_@_;P}ERKR-L>{1*!x@*(gr
    zY3AFzK%f!p&A4oupT$PNJQ+piWW5A0ZJg7Abl-eDfRaCQaTPtl<+*lT*AOOzb$)X@0XCgq?XR{h1Q@gqWed}sz-R9-tX_#TLf^=nN
    zFElCh63G^3Lxd%xPmWZJ|NAE0Zp^eLC?uq|ytr5ZwxqQICNq`wb`&&_D2(#iRt#}k
    z)W%mOZVALnHI`IWoePH@f{FT0X6kLmwQF9uj8$kl$TOx@$>*Qt<(jOltk9&69+X|i
    zNI6|U{wQ!4svG0|E!WylHc5$#UpKF~#7^9g*qe@?9G54~@d|
    zBPuRFH##(=>#5A&sq>!z&!+tnZfhLj&p;WV4+o48IpYgsniegf
    zlsTUa=S3*gRbOyE_hY`6D=nb$6ZOcOE-MQx*4l!CH=HyYiW^`GIHrtTxhYr}9mzlc9{2!LXMBo4b
    
    literal 33621
    zcmX_I2Rv1c`#)633MoQ%8QEE7Bs(IMjEuTUS=pOV$jC}XMzTjC*}JS;A<3TC2yyMb
    z{hy=v_kYKyD|eoIp6B_l=LBnMs!)(JkRk{|aZOcG2SEtoCLuyX1b?i$b#B8SL>P4y
    zMdSee_kDF{H2lqRJ5>Wm1QFms|0i%uzv2RaN$hk@Ly35dn2wx^XL|6LGJ>!o*A%bb
    za_gT>G_Z@Ga^6p}{Ypv5D);p0hHj@oW93kYimZsZ
    zjNm>;W4iG1p#GNoA|wIwm~|$sAHE_*HZJ3M1ePO?PD%THj%91Nrfkyl9(=9^i-dx=
    zUaE`rKwuC4fp?GAfr~NSmKU?1obQ_Vvpq)NM{^01_JlwF{>|Dc_1P&^Kki`c%CptD
    z5aV-9Yk6m^=iNKz>o;!H3%#%+G@3cMEo+)RC8}|<6!%0boLPocN&Clk;j)kV*6sUt
    zc2p04CX_dsM7=(^`Lk(xN|*Lnb#?XT-_(d7vKfO5dZ*bD`anNhZ~Ps%^}dJH?dGYwh
    z*;(uFm-*KAe$anR@b}!9Z>(IX@oa4{5agPe^uv&($5>n(t)zo9aT&Sg?_M$De_(Cz
    z$y##BT2upU4+;&^IxK|KQ@BY_pFZ6%dnVA&>2h^i&STnIT!ehG@79*Pwyq_8lbHOB
    z@}fueFK6RU6(q&|v5k$AY!Y3>_}ExV7ERlP!;Hh-CALo?d6$D~V
    zhNU`rmlhYZCAEd_aQ87fmNPk&C-izyg()|V|E)vzD$!?fy2)-Ve?!j8T>ib{GGockgHxdwpCRRE=;i>~s~g&S5orG(-R0
    zN?SkFKhonVp|4%R`4%xYTEw49Ag}9rCau2~LRF{aXU~b=khh#W`ksM{)a$sf9p$h`
    zJS~!z$3;C|S1BX2y#+^3NG~GDd#JSt>1C2sO-Tz$^AH*NnNJIR$9>`4
    zfAQi4YkVtaV}8hObM(3WY_G7_%*~|!$g5cUy9RHvqBAT)2BN(LvmY3U9Sq+ZC~5S5
    zRdKD|fyHJZZ@NEQIWK*@?94LzoS7Iyp3_`!iSbIccE3vVXUiHFaRpqqGFpx58)a2q
    zTOuZIqsP$G^hqs?ZS9`8fJs%*eUanH=5|l5iS|fpJ2xCl{g+JGt9p8L>$80v;){yx
    z1x}R)$3-<1IBC8P4kqX?HH_PPZ8e*ouH0~BrSi?gV_bvoyrbh`;fNyPYliAMwNj9p
    zdZcv7CB8S@5k3>zKU~?NpUln8l|i{R&Q#{+QoxCyA1hRk
    zv(qqH)PoPeEzspI?C(DzCnpaIE8i>^(s8AOPEulXV1oQ{xHRb8WBeD7uTNHZP8F<}
    zxAk1%IptUT1m}fDTz`Ma+qZAiD=Tt0&JZg77)tH`%m@G6wYDbD$;orSG2!shD{p<>
    z<+`S(z`2LHJKZs#3xDhNI7g|a+bxYX>P84o;S39eG)|)5H6^oj&|RUjc^Q1qa`mQu
    zQrmO*iBqS$@}XxrJ)O_PxbsjNcH65KZqn~QoAJe@>TE?|$GK@qzugx(w_s{4;4*=+
    zHaI;wH6<)7TeC{QW!3m`aBntds=T`TEPPl2csMUZhhfrlttwkLAD=5f-^o;(E8M{k
    z4_~k<`0#;j>SyAewbAod-hp+7Vre|q%-@b)wcfQ@K?*F>FLL&ofx*fYQ|czh8yz|
    z8YlJgO&WW9dv)?`oo?Qo4YTqV(mdz*$bD{geSPd(6o-0Jw$`=Q*PnHbjHINb?Du!K
    z&aqtBBY)|zr`aK@nA-j_zOGpIV(Q>HU-Q#@^pa0@H*@+8nl;PUl(cF6<=MDHJ{K+Z
    z{+-hP$m9zBIMH=@%ah<&N5`|TU$0he&wPk|`}QlB?9%ZBDObuE0b@dhN!smG3LyhC
    zb5m#MtH0H8^`k$-+UaNp|Ao}}02r`T#}{*0-qsgkbq39%Y5{tR)*;`)GX
    zXdh{SylT3Rq~qj^zdQ-|=eR?jddWuM_U2tN;${W0SrHr@9I^57cV@D6xIAC3RXgKv
    zqc4^?qMWLY7C23=%0P+I3Qd@Ddw)N#Ar4Ob6j>K1Cplf+>({Rb_1)54+vq`B+S<$>
    zKc+;7;qH?wCQr5=pl9)B(!o{|1&f7Rp}~ByDSqFEh=sAhMbh*y2fnhVmoKUPlM~^m
    zPoL~sF;}i!5v*ut^B-hN%l~BCsT8KBB*ozC?(irNKd
    znywe9^fq%NBy3{CUO5H&j;f>w8GT*cM6Z%GK6x(U93JLmuiqS?QiH34lG=0PcC?4Yx7u@K
    zEIacZAtJrE+OgTdCF?ggP>Ngjb5yCPe|e|$=A+I_8Z3-vEn$O9fhV~%NAmY)^6kbNjzgj9UUf8f?a~9NWWi8nvY>O(a4WW$Mtj$D$+P+MNQHot
    zvEbXRv4uAWrse0$25T}beu?JJ;v~jWGc2@poq3e4`gl%-zQ!>I7ObwQ#alHx@DDUr
    zq0j3hcq(5<{LPi_lDTu06?yqT);xrbo3T2@H)niqPkK+RoVLpRYj&x&J7hquve8Sh
    zt@+31>(Hy&`n2O^4o`IBo;z$^X!`upgKy%%VW%1InOpJU{x0%nnGi9_FP)WC>*JZi
    zFK^epyHvWo&}@}?i>YV(AZ)cs&9mW0fTg>wQD5_IS=aKiH$NV<-@e2mAu^9Kc@yEC
    zZi<&t$*#(8QBQX)8^qnx4amt+FV?pZ__1e{(9yiJ;bCuT+me$bul?$!?CsA_a(Ck`
    z;yxVvqQc_sUpZevmZ_Oor&qhJTU1&qxM$P+hW|l)=Q5Xtm6d{{<3-Ydr{6RiKJ%@v
    zu8z!febDM+tiAu$*?4tr?d$kb&}Jq#DPSd$xBhKYCkqU?X-7783cPn*sN+LYSZPVw
    z=*d&Ev#&L%bcnX&a1nrj1djVMt_i=ro}$J5zPMPo1zS@i%O@ZZviVn%($LWG7u-vq
    z@BCQmxFmFc1%MfS!L;}RQ4MO(kd!auORUTh9Q5QNvzK9h!QgX&k^T5ab|)5KSW!s{
    z1{;2sU6vux@BgO}i~(p8@WDd6Ty1RyPt(!q*_h|WQ0z
    zd<8u{z3&49cD!E-H_1^#m~7}gw7LziQ(}v&;Y@qniG3tWiol6Wy(Ap{eHYiSI}>4&
    zPuF(kK-&D_!=-N=5ePEubFddeeu9GHo~0$}+w01+vFxWqM~7T-rwU=1XeLOqnYWEE
    z-MxDkfkuT9CkuF*6~WKX|H#s^ZgaCNw)K%{2n^gCEyKTlQHi`I&&bGVuof2+3*L4+
    z*xMLio$iqI*{_P#0EA|S4G-??5Y^~3VWpCL_>k*o!X*y#!ra{4v3EMhWs^?4c`*97
    z9bO{(d)mJS2c7u!1wF;7k;9%k;cy9h9RD1Z?JHHzr|{K;G->PVwy&`Q{(gHsq}%o3
    z!^}qqn&~=Pw8x^OqcP&fJ;waVc`mMd^hE1(1JB`&E`B+>j4IKSUbbgaX)(#I
    z4ZroeG&0Mwbw@wU@T0PfxS>g!w{_>Gn_xT
    zhp`_5JCa6@L(r$gBYh3M@oCgyN{uAD(p>{OUNSU0E&zCR(zSK5=$o`Ut5H-T=Fszu+1UKGdT%0jOO{!L
    zQ_l&g(cfEq2?z~kGgE~}^*UARHATALvpFgMI1brtc_@1X*{q|h@qL)&eY!yZg*N+O
    zwfN%G*61_x404FqykioeG(SK_EiL3VD~SsuUx`@;Bd^vAMYc^TCAOFsrmQb-Ni$)o
    z3-!2Z@6rcmMU>ct_FteS71FrbX?{MMy7(b|pc_-V_ebTtj%g>2wiqJ;y%qNYK6S63
    zs@MXC>Vj_k;o)wXHWJ{PHv_;=hiositS{gquq-}If%ophye&J}-&qnoSQbQlRhZq6
    z`pMljo){h+d=U_Etl;~jxbr2CheDvV9>uxT1FyWw`?@aAxU%7CmIcGulB9rX4PUog
    zTzvczaJoADT1fBeme=Bzm%hF})rD_110~6+sR}JvRZha@&kYQ=$y#5!bMLed4N<^g
    z)(IUbu~86+Q2-FyXE9I`2513Af}N!{11RiUTok&cJ9$dhxTn3d)4#m@QbB-SPo8m5
    zBZj>=m7BDGV4!_)kX%<+Ns|r^lgnIxfFe6VQX7}Z{N$wh{X&BR<;V0y%F4nG4slv#pd3VsO@?9;oG;MZ(amQ1ooL0w?FWnf|^3t
    z1(T!Cu0{zTkrSe)J(V6B8fs@u=aJ3+cVPkhA-^VsLnVCV?ZbCpH)=LyP==ZH&F;pH
    z6KH)Hyy+#B5)%smAaJK;cw~fCc6U)h`&<|FwxuO|Vb7$U!KuZuP{EMCP(^mM@B_%`
    z6jzpiN-YHS;ispsUQTS9o&6ZwN=7Fz(pzALS=Y&?eX{n!babpS6eI3BKN$S4LzLJI
    z*e(CLChVD$lTuiCa;hzfuJF>8D-k`WKE^G>bEUX&`0jRBGq0Q~yafF78S#mb^`N(J
    z&j?eSd|XIXiRiXwS=sWU2kPs0gt);v{KbpAPENNvvG+xI3~_I-6XjPehs0LHfmPDB
    zoA_}`INoP}%BRKDBwhGPm_E->G0pOw>iQ`?aDIdju8?pRjIaFz}X=CFwMpGf@f+P5*cn*
    zP?H|su=m8Lb>nO;PS6DBCnqN@sKe!XY
    zPwEln+PGt^Lw3iri`~o2100vQ;;X+fE?KU$3Qe|-aNzb;XH5Qx9EEOxR&`|m{DWOS
    zq%I(o3t_s*M$y~6X#m7AgmzmM3OoLonQEDW<$adp$;jQpyX4<9LO2?kN6iBXeUEd!
    zxOb!}3KOB_e-jZJ^>-hQMcayhsPp(-AhargJin>5VyOXcsp^i}yV(eIw~(=wK4Jb3
    zOIgUZUx&Ul`ZzI?DB&V4GH4+Z_=3ZmGmRh_84(B;zRzU4jcGMHiNdw)r6DogyK87C
    zinknPFDA3qyN>?gA9?|h6h8^dNRclb5zcQ{G6K*XPOU)fcy{IYIiTMWzt
    z5)!GUrF2oMiudIW-vq!=2O0=11h4~$L+7K*1w7L+<4bOls+=Qh9!fIMb6Sf}ry11jSFPQ&MiBZq_EZXc_q^+&3&@?b>Lw$YaIW@NT
    zwdba%{o1CU*$hZ@mWFP6$pR-KKXIade98Koa`u!H6Y2
    zfjq{!lK@?y(B-5jX9xNU4UnCz1YZG=OVV?FHqN~-XdzjPBv_FUX4FZ%RE(f$Q$s^T
    z&UVdkta+h9R!)d|jKI(*egsIkov~{%0&orSjf$e4#nm3f`DFQncm@2<=`z0ut={yC
    z-evpGeII+Nli<6%N1tLQux$~S5yW4MI|2P3zKQlK5FCU(8~dZywLiNU^?-auwE?%R
    z2b5tv71EIgG+<{d1YpoCZ&m5f(UKl(m#oMeuv;_crj2YhzegV!RGqT;=A+7VO{#^V
    z?qtz@ZwWE6tJ<{MEPga}1k93l2(tL&LjK3gME&2iUvheB6bgF{^5xHo88ThUoCY2!
    zyYg8Lp^c!2kxDL4lHJsWtuhuKO~}3qP2k?jC}r>l0V{1pty;bYLmrd4DbG!=}>|rKO7Yquz!~=JISxKp98t
    z8UsSh&1GH^<-pzR8Y9D=y5F0l0{W(7N)b~jDNnM)fx?@_3${lUNwS}tGy&D46L^Q%
    zwfKwP6&yc~jMgc+5q|OD{CE}~kD{RDNOttLJP8}xVUg>Sqhioc-yvGiB#p{Q95qPr
    zl~suT{3hVqi`TeQ=v5};A}{}8Ag~*-DIJ~8Od>*d_izU?04!wprf=XJCh7+QMFRhH
    zH%2+e$vU*G@z_CA0=A7pXJ{spF0=F%m6Zh`jU#g0v^m_u0C}I(n(~okSjcyX!ssq&
    z@RVjgN=U#R;!rQk8$xK@GrHzMP5E4@$(p|Dr7;Ybu~ZPEfVix*1D`tp_33TrZ&|}P
    z=VpKmKoN`f07mI33YpYPe-uXqrwi1DC3b2hxFqya$8lt@2w>p;f?{SJJ@v58+lj`=G
    z8yg#;U7A)_7cd3%+dDf-lke}*t9z^bzyjZbS>J#WF4z`m(*7(_k$0iw*_xX)3DGl*
    zJv2xVPJm@mopID&RZP_u_6)rD9t10IZ*O(7jWC*E#rNE#b)%nxsv8>{ySR|XM!uNl
    z(Pt-QeJ$eDuQ8rIoBPd9HVNC=Nh|X9MQ?aUaq%fG%>*i$ZAtfKPQc0+-b3Nv%+w6t
    zdPq-%(jkU=JINP-RkY(@cPpKPUd@rHX=Z7u)?&^c^#%0)##!&>=%fae2s=*w4DPcA
    zFt)Q$D`+kVM=kskuvdVvMa-3zl}c7zw4}g$s{ndo#HB7@R`eDnKjP@nP78h
    z;R!Laxp1&=ds|>sk&|NQ424%FVfIGYIk?M+pU!G}+ytPYazn8n-3ctYw|q
    zcp2JNeoGeD!&9(17uYS`57hEsISPVM6#T1?lO|yGw|lOL-jp~3eWR;oQUL%@&Efs6
    zBmM1uqU!z7;-rz0yV})nxu}*D!W=>?&tQze0JyRheT4v#UhBMxx<|k&yqn`sxRQlY
    zD@>N#FV}heNnk5w_TD<-vy1g1&+j2aV6gBtH8t6RzDaTBdS-`eW=D!93_#t^;kvq~
    ze{E!eiEqa8H`A~biUQV8S^qsPD)RQaMub#+Yj+i3B{=2Wyw!U&1!7=`h=GJZ#+`_V
    z5yOCm5kgBE5XZbSHpWiq=j)c2zQWGG4&to?pz{qY)1Cg`f-F9}_PbL)`&_aITZbwk
    zm~PKcur#&#>^ss27G#{v!-G7No=;PjN2bwncc3IlqsJL695}1jv>gUZZ0e_0zKUvS
    zvz&7!42|N-|s7)+6U#p9z;4}&l@spwC{ZO-CJ5(g8Bw4
    zoG)l5UOs1T>*hwEc_Zm`?gDfYqbfJ;mSK=i!b*od)LVv!t3BHRq0yOoX}3(;lnw;~
    z1q)ga>gzMOU)UFy`0Tg$bzNNm!n;TIr<3-twM_nxda-~6^2i(Kit~0G50hw^Rbr>+
    z4q^9T*v~)M&i^1t=wwWXe&+4z%%wlkH^#d3Oot-dp>xhig9H#0{qk#G$S4d6y%
    zGLsn@=a;G!*{Ol^?EI~mpE>E9wEKq(@wI)j5)Xq!NMofUoHy<4xfg6$Z>y%3{^W=>
    zSp!GLs`kdDNJA_0Dz!K=I<`NTk0NXmCk49wsn$QoKa2*?OYhrZ-Iz4pAvh_|%}p{Y
    zorbXfxt(R+6iKCXLJP5rX{e%-2ieu06q$cnreApaI0(kgn=}ZU5qVz=w<=H^R)3AE
    zZL`On@t)e!FFo?nk$$aSQYV)Bx9>_Es^Lb#699mi@zDDg5D1bkLgE_WkN&C3@ues3
    zINK~WDy&#E+&mV-+!S@FK8xFn+t&s7fm~B}{-^LwAVl0q@2^<8@VD#%RHVM_3JMD9
    zxQG`o{EXpYL2qtiuTUazOg4d4lW26z7YIWX6at{i#w==4uKa4!j7XVqxK0T3#
    zhlip!`tHGVRZ)5Vm&pjkm1bwSmwYmn^}y+Zsg*6%PaOmKl!%B3greTqBYp^bR1oy4
    z^#y%CqC>f|Am;o1+hoYdLHNDs@0>ss;040K5pHkSPPpWlkrMoXo(Po2$mWSRrw5Do
    zZL4b;1O32eYHx4HTShj|3F>KsTFH))*QNzj0k(Zd<_$p^m2%~n?nZOn
    zsGC~J-QM{jtot$s<{6yv2{N883QK|#L|A(uVM3QLR}_^!Zn&L|1&#Ll`DfG97E$b_
    zd}Khh=+B-tE;YC{Ryi&SMoc|W9JH@=^Tox*+0Arj>#ljrl-n~A1D*mOoc3fn*rEW0
    z#>dBd#1_=Hdb_#;VD9G{(&jyEL4cA1jzIefI|4efiV9Y2%{vu$VA=r^}$3)kLZ
    zMO)J^aj)s*+6>UMusnZYfMJXP*~@G3D=~19aY-n5#+(HQnR0p@e2}7|BI~T03)E|H
    z7_7QClxMQQ7zLdJutWnE%H9wG7G_l8bR5-!=gY<^jmjM!WM9%V{PX4kzl`6}@bNFA
    z8up$qRq}tEnY`r=PT?aZ`)h*1!JncNAmst*
    z-;~$`K!c1ffEFbTAnRt&NHD+Cp#mK?=b}8rrzN{V|3MGv8GI;LI8L2-oW2nfZj&N3Ed^e8vdN=7RS@x3b)l$^|F{?hKZs2dZbnBYg1-&$PA
    z2F~Y7&1$+J0rJjch6g3skk}}nhz`^fYu?nulPQRur-+xFA{+lE+*2Q?OA!yMY4};G
    zK#(HfJ4WT|FQ86Z4-g>$bF*f)^XWkb01(Ze$T`Rj-_ZEdt=-+%R3t~?G;El)hzo?b
    z;U+DBX%p1|+fk0ySB{kxa}m4|NK0%z8W5LeK9SPS4PSM^8OrA{@xiLp=ne-0;(T`0
    zPM-pzGBRM$)h@@cwCYaEEiCZ!O&mhzeLR6{4@`rVox3*|A~+6&4Vs$
    zTk_}iFoNxvR2oc#=>6d|wTB4@JVaFR>s_rX3AG+qNv6OQZj7vXVc!A%VY5&9r%wpz
    zHEmPqf7j1LT7?*a26l?#{OQoIpZVwm*>3-7e?$x30!9hdhwqD&=Zx}#^GjyD?|qIC
    zq`2hb;^L1E1K;MwW169%az5=h5Tu-H$CPR9CH|6SprjpM5vYzZfmRV$7i%ctIS7>e
    zoCaMozvqsK31e__3b#7p`$48b?!bGfD%d4~0J+G@;Vt2&of=Wbdsgocfo*#p0Ug6j
    zHc6iec$_qI?fkZYt{(J}0{xF;+j(gc0{OPtRjTih`yy`-J~Zlt8USJkiHMR((w8gA
    zi-!0FbHj6%C;IjZ@o{mWE>Sy{KzITLD}_@_Zg!I`XA0Oiw!G|^h*4t)@>juy58Fce
    zte|sCGBts>^T-77X9FtFI9(3Q&gK!0M`?#7pw?PTydNp
    zXfD=h@UB|_;O7USZuz>pr%s+cdFbJIQ;z_}L6vAY>btGDT?+67e2kS8zpc^U&#mt_
    zXS(z_ywqfSJm;h@ASRenb!377xaXH$YKTHE*4d1>24sFj%+=Z8oVH8+qr-LpKgqURn7W%-2aTw8dCg_`6$t
    zX?%^s4@ADVfyaYh1w}G+R?xDb6-f!m7pNTiKeVdee*$_CDhE713PnHS*{W!@!gEqbp-VxL7~#8;m$5G3|7Dclz2JVe(h
    z&H!E9@-pfC4tC7rHhU&v&j5LX0Qsw(XtH4L6zMT2GRuqcts(sv$rb+7$=_bj$iOBv
    zPgu$d!W?LOYuQ=qWcw=b?DY1}sTDDtE1VcG=BLU5P2@iV0%5uz8e`&dJmSi}Bm`3I
    z|9qoH&2+cX06GNmSyl1zmt*~uM;eSB2a5)EIyK=W&An5~{#xy7M-gKQM0?KN314~7E`)?}rREgQl6mGz?a1(WT&Xiij^?rUp29Nqz(Tx}(@*{oL
    zw_C7~&Tw*dRp@c1)-HiG1?UX-Mb1#OoW*|q@&!R916i*ofI{o*m`9IPXX~vGJK9&U
    zJ$e&{%N@Be1+n^b(3_#0K}U;9d>m`-I2_wN0XA79{!R)A455Hiz|@GM
    zc$vceOOyi57^3c5e$R#5xcd5fcC*{G$Cx9Cp_dlbxB~3CE5t2guB1&x8t~?5lHweg
    zMW_;ciyHZI@`kkE-p+=Zxj7NCSbMNjd+H484gLAUdA)=7CNC(Z|57F}Q~)(8-_oF<
    zq#Ub1afP;UBhzO)(`~7dt`X%HfbtJhAqjDDbx|BD4YO;jtE|!A`MMI0{y%{#E-f8T
    zk1?r(3`}gRaiz=X=F&K39kl3h$T9@=-PAn=GWfrK0CrT>{x%-7j^YY*UV(F!9oq_@
    z8KN{=Ehf;WzC{7NO8!UkW|~@PK+6w*5=>I
    zf)r*#)VvhTpDKy&Z6+`Y<1w1*JKV-nU5nITfC#}cKZ4Y3wc0+7j6Z&bvdn&2o}_$j
    z(Z)B;d?b^gdoq?Mh2$6y>U&VcL{kchq2;4Y`H}f#T3s}qg
    zM>61YZ=x^;fC+R3s^5P>4qZ|uK;tWP0`ii?@K`R2WFZdlo+F8jA2Dd(TI@g$o#oLR
    z2!s8`k7MzNO^hw=JY8Mur!pN?YUnqr3OB<7B112*m70j=(;!kVv-;alfS$bYI6L=<
    zUO}FuFQNM5Oex`$ne0rvow?E@2($O9w%2-LQnf%TV8P1<6rL}|)8}+K`rxO5BAa;G
    z?yQJnS#t}P4_Cz|opPu8ormHU!ir@lEnB87H%U;l7l6$r>GfVS#1}X-HAwa9J_FUB
    z5)g^0_>lns7SuQ)a)pMg9iqaLl0ilS#2OunA(=o`lC@O%g5olg;XdH5G?;)<%S!d-
    zos1{yqgV&Zvw^INs#?7l$&cJAvA&8%2_e+Mp466FS$U@u5HVP(93t5R{r#Vy`qBKw
    z<|BjCfzO|hN>bj^J+)*HCX%`{j~{mYDx7K3%yjV`dwY8+YePLf&tcRL+GEeMR|?h(
    z3Bmh9qY0gw1rnhAqe+pj4N#ED>#4pM<10{~2N+f_;Up-#p^Xg=HUr}DBFQ;9`rbQx
    zPoNON(O@%7)%^|0pKBJJH^_jMVPTSDre<%)n3wm5v$(gpA}w
    zX!8{IEH|P=>Bc}8YFR-=9onOZxeIkpntyHu__P4%X2wBl0L3UHGqceebU!8;k6SIn
    zG8==>fEHme82MXrHGAs=DL+gcY5+w-6{FQz#C*<5MVl531twW9{T6e+(x7q
    z$C#TdJ5j1ldf3}jo0*w`>)&7k+APQ?Tya+D#1In`O9tr_7!XDr4zr@SwXN;*6mC?f
    zk>XAz*lN(5dMbiNk
    zSZm#`S^G$XOgni@?*MnYOl5<848a2JAd$~In^mz*|cFO
    z<+Pt8l?XR8s#h41yR(4R0FJ4AJwSf+Mqa3JT4V~8B|5R$lFGUe=TkgLZZr{HY(5U0qUgqzoK@{gZMDYC&=jT=?Lg!E!gmI%Cfu*D7k(Q
    ze-)AvB)$Y1-+3J{K0w8Rbl73y*@j1g#_<fuKB{tMI|_|v_<=Uzwc4(N5nq54;t&s@fs_ArqG9)`L`kQ=k+Ic~)=Ju&eHa@X
    zOI7F&^JN_3A*TyVA>?w{ssJXasHhO51jQOEKB*z!XU|I56Y#$T*g>DbWD|}@iSv`4
    z0bEEQBZPNnXZg&{%@whG-FIw*`_TP~w-+DsUY^VK2jO&n{&BlWiRZ@SD(J14br5lW
    z9p+}yvUviDbf885->r^7VocvD&9Km&#ubC`
    zZI%}p5t8yzmH7gMp&?;Q*K`aHs_Qd?Zga|Au9R3A(rC@S6G>_ufparfW
    zSIB%L8Dd4l%IKHVY2{~b_^%WKP&vG7R$MvbaDsJU)h8D;0b*T7A(-|T9-|iluSXFh
    z9|ElnWR%vYVD&adi4uA&F@m|B?=^7&QcDL52YXile=9$4ed?Y}f6l*iy2^wGaUF7|
    zd$L*2f?Du?#LQpR1*@Hmi74vP-3@1*eiK+38kfYZlLin%2=p+37SRVWul~by8cg7b
    z)S?I=F;I8?V5p?5r!{)YCgpEc#b)q8qGv}Fwm@W)Y_m|ZGPa`R(bAQ*VA-iDCa$7R
    zh=%pi^U;3#clyi93}O~9DhQt)W=cT*oSdA5ZHnH8;Q}*aPzyvg1{M*$ZM-BUYWO~A
    zK3yOOaxKtg>!Au~1>W3OK8=JCjTlI|B=x48B_{%N6SP^u68!~O=vZsI5gHTeM3$4;ss2O;JXs0RA;=bGVP6oOj1V(=UetP{pM~NgYUh3`j
    z+eWwv55O-OIy?|pRj_`RhQACS6%d8)!3?l2K+!@nzT#Uf
    z0W3p>K0XZ`fgH{i1B_-qmgraS$*WScdLylzx
    zoKvd8%p6`&hEaV&IR1w8(or-?7Tg!whyjV{AHhJ?hhNDGn04S~QJR_dTgwlG;|mK5
    z(VTr&M1J);1Eks-tu=4l7)Jx8>uB6=9JIo|6*uX>v$Lpu%IVR+0tttpzDigdK~0(?
    z#FRnvW5aIa6vCcpXG=_Mgq4)oRv
    z*c0L(pf;tf%~c4XL3&|Nm_;CRcz77LOq%XCn(iKMiAZn#i3SS=_L?M$7}wpTQwZg@
    z|Muh8V4X<#HGauW(IxhRQUE7&#KU?n?$_K8uYnF0kqQocgc&rC#2CvL1|FJLap(6L
    zEW~C8#+N#sm_lXM&JMta9J-Ga81Z|pdgkcJ9o+V5l5*W!<4MC#>
    zE#%X+ZfEkhG5v$?5WyeZzNA2)Rj=_&>Zp@{SlBy*Hlqy1v&tM#V)
    z6Q&}({wYrvkNQR`QzO8y%g|jVkLh#LtG^U_Y<}4@0alnvE0XCD9hD5v@_D(QQ()Iu
    z!atvC5toAQCfW>$0r3k;1rODxt;7bk!}KYV^}_nI*M_<0QXd$M6>|)^=4j)(zJ(S=
    zAn)mFFL`f^GJ}1JzQSgcL`}hr-^QTh2`^b(#gA%$z;JqDu8C-z94NH~c~z?d226e0QT1EA#y4$eBzUsr^gBfyve
    zrXI9a21LTG+5}*K)HR5I1rI!~qyWbXbJ7zU1fY$y8k>P8s9TWef@BboY76wq0fS-O
    z`#`0D*7zY2P!>poa
    zKsX)rT&ME%(spO&B1-1td?ei#?JebNO=d<2jChSCX+?e8pL
    zp;ZeQS2!tD-s5>t;>E5ua1$1DCM@I5%bHL3sETQv>^q}-Q7|V5Tv|Bw8m1M+=B_2y
    z=yqT&jc&4nmA|bhHggA9Fo71t)CWE^f$Z=3TkJ{e@h=cud$NZ&<)8^hyQv)iSF&=F
    z?EJ0LGn1m;oNGVDmSLkTw9IXZJrzwpbzZ7Efn=Y_v41fTB*i&Tdip@|WYDH?C?Qq=;U-sqfbKw|BnC`h4oHbv
    z5;2qgdt%yCJ;x(@{-r?Zlumg6TZ?2b%CjQSxTx|
    z0Mk78s<1c#r~pU@rWfD6&hbh2NuL$CaUIkGJ-wd5{rtfd&`co+0vPzNkiN@{3uLK-
    zD}-{AOv>PGSj6GOo%wx2A>06YZZLL@dwN_0%RrA?iu&eZkjBP=U-uh(mOzz;|h5g*aJwA`t%OBDxLvcG^
    zvgrDecgjoYn2^@x?QT(_gF&S2-*AIW~Nv{B6V3=Bc~oPduyF%Dnb<<0{pb
    zYeZBeMD%#)C|<7?j14*(Zx|AMf?i#i>QCPq5+G@uuu;!BGO
    z#92YyfiO+M)W!}q$x*xc!AruZ8Vw6E4d{9yq>Qy$o(Bd(mpo_Q20$!PGqitpVIjZF
    z#*deMk9((<~5|$~naYQoQZSFAb^6r5FU{9yBw`eE&tgz*lb3>5&%k(Y9%^d)qw>LCMVrzd;d(HB6x)%SE~~
    zb;l}YcR0Wqkt{f)BCnd71RAL=X-p`(!-7zSkk&yW1hN~sUqcbbhxQZS$q%_q=J&?e
    zR1Rbw=)#~iEo{ZWkpFZIBAA9b_9
    zFg=MWnb2jnhPy8{k{%9|#;)YQ!wvy~E5E-o(SOqtH#$;Q3Dun63LcA)fxN&ZIMf#9i~*SP$md~Q-uth{7d
    z!2eO-F8m!+ynnl;8T@MKMNY=17wPbgshbeNri4W!%}l*Uk7}k@45beYAaJiN&1{;J
    z_*(YOO6IL0$e`};KN<8n*aOMufzthK58fDC(2)R$Zss0f@oCTJ`&!
    znYeKSeEFM$kNC?#_90%Nw|vm^;q~iv{^q)$iBh9WZjjvnUiAu3JTd9&>M8+SLoo~s
    z9>oQ>hmCpy9vGnJ4x!%{UKaJPZI4TkSqD!7#MeIS66+gTMW;g@?ErTzx$VZqJeIiJ
    zJ+Tfc7TA7q(Xi3e@tj!Bz+VOn3baw;2<%Of$M{@|=Gx2Q&j5lsIXfHQ#NpQx9adDe
    zX(6%?xnLk+sn(Bser)|%fOj`PRGG|8ib~?LdAj&U-1GW-h}`AGysVA?CA-nyTm)HmNyjy9!K$wItUE<04oo7-V@-L(YW{a{47%<05iyGB<8MI8oI#@WYVHW?;~}`J{(q+kR5LVtCo3yZ5$ZYd8k*P;Xgw$!JKW^~V_%msneiSRwTl-o
    z0^&MZcu7hswEp>t&~3PI0ZT}t91$FezCx9vm^om{pzzkjR>Q0W@x48!8o+I{U@f0{
    ze%aO7ckfOYZZ({|vAfUy1z6uWJi1A*X89Dl+}&eytE1>#I+v0?9bexsPC=JdH|d^P
    zp>)N7)!1}zaC%bKMKvnTOLarhwVx-owHer12uox6WHJGiU184+j3^#vbT+12v!CF(
    z!QTyVcnt0-_#5)t4s%Mrxb5yzxo^2oOA~Tig;(b6{5!e4+F00}iEzhYwGWz3wVfC*
    z3UL}#(2qo6k{T3F0WcVDRvA=&SHh79rTH%^x#?&rXPU=J>El;b%Up9AbsbZLZEaJ
    z+N8fmf>xyX`$r%ES)ca*PvM{1!sz!X^{RAB1tnBzgR^QN#&p(cY`A(
    z$2ua-<+PQ_QB@K;g}Q!tsrRT?Ra$4x0ZmlLCk4FR`i>h{!az)$dgnH~w9LV+~e5v_1Q*PlM6h
    zpOBP9R~!S@X{)(ag-cG9l|?~ySsAyisik)djHI?R7A$ay4#>Q=?mVIojID;0f|smm
    zhuKfKu*~y%F&W~*zK>;8uEumfAUQph?>%P){x`Z>1ifV+qWfpzgjPQRU%DHJ)4XbF
    zkgb1UkV0k+S;<66Nah!D>*@wTX5}Q-*%&oIAUmZwJ$)H+vKDb-@pKSO#o8+}x})26
    zr_j|Kkd*Anj}oSkeJqlh-X`(|rM5yF8Y*0eq-aSTl}YguaZXIKH=wQXDyQMACb^s5iEDqk3T!HvT|ahf7v6cPmS#!INnA4UxE{5BEvL{GK{CNA-|yeA3A4a8HbNm45VBQSZGi|}ykF2Z%to)`)oRbC4uhyRyc^Ferh
    z(O-tbRT??CW(eA*KqahN@3*qrANL4NhFFo5byAyNyQ8h5Ms^NwhJ&;5bAZeF)u(Y!
    zIUn5YasFE&FoWtTYuX*6OJMr`U2?M=R*x#+Ma3XFmAW9p7c97);
    z^TN9)AEM!uFGGZfjRKTcS2Ab}{-SxPaRq@hH!eA--Zdd&cP=@3c>Qk5dotiU!CJ9>
    z7t2N0I%rssba_lCFh2(L5VfXIV3ejkva}XfA7hU8u=^eSqObs%rph+hf?CRaiy$Ex
    zmFY|Queli$vc6Um{tf9k(mIcij(OBbT*fHN!4iv=z@_5-^1+alPA~1-^;qRyIXdyA
    zreL$&jl|Y5GmdVI*tVva^-QzR(pH-fGrCovE{lqU6?uY}KJQ9mw%Y!;Mv~`Y#6#RI
    z)eYA}0VD$SoRN3E*!sWA_GmCGX@jwGcVL}jhX@J02RH{(9UN37*1a)0B@m4T?G7Zh
    zGldTfAYTiD$QR!D*MEv>A=m_$ErHe6iabLeT`5SCfJg%uS-{+x*q@iAgh!#9gCs}Q
    zWX)5boF12itI3Fbh5d4dLeN97ffaeF>FMcXTc1tH!I=?dP=bg9Fq$}V-eAa|z)l{l
    z;C}^bsc2cy>#EkZu@)}%S`DOAHVA*UfSV0gu=pTj4=isS$-@uZ4bhr`Bs{vY(-UTv
    zz6C2ZaPg$Bgqo--zys*23ccYQ-IAL8OlDZcfmj`E{}Vl9hisfdS>BH4A-#amD0)pG
    zh>>Jin5;mOH!md^5-3rv=6ZVvVYKD<=wa=T9j=vyUP}v7J7a`7oL3v|)!{y1c%#cI
    zcnE|+l0}$>zuAzX@KqM*xNwBfm{|FrRK>irScA`?zBG*Kf3!VLFd|6_qB}fH-vo|s
    zYpBft&}rD>g9t@~d9s#%Na}su_xmCblzyyrmfla&pHICdcww8X%M~u`1pUU+<<{)o+{(r(vWi?#
    z;|aZD0VO3R1er8Wf{L7CKMM=pu;L6`11>HhljmqjAt2Z;Jmqi+0<+gzOd!k*c3D{j
    zIH;FLt^X
    zPd4XVbiNjFm4vRRl||)=8n`LP1=-H|Yt5p|awKdx5!a~LfdD-*Y%M`;fzqp$yN*CjTqpyivydj!uA2HC3r%?$GXD}0u&dMsw
    z4v-x&>%nt++EMWljvs8kgog?gB0DCtLLSt5pUIWYrx<
    zd61*IFSCq;&i4v*x{~N1B!>K2mA@|DJac?33@??2P_6#{H52zk^)!-leEqBr^5_#Q
    z$y3%iR``u=5slKcZ}y9$S0f4Kex=EYzI0&=J#%$;Yk>`(wOX`+Mb;qt^;6dU-Hk|E
    zWGrlcr06#%nVh#pi2ZtS#xc0h)eu@9?W4ft;N4u%2st9x>1jv%dr`HjZ{h!Xx(;wG
    z+xCBxl_)bS(RxBEtBkCW5VCh=lToCM5>iwm$%v4ZB6~$8k`+m0m1Jb+Eh{g|`2X(r
    zJC6T*yvO?$&-Xmfec#u0p673z=XGeoBUo?~AuL>QR)G!QQYy8*?$~u1o&4Xr?B_iO
    zcI;K=ZDZiA%RXqJz}D%bK_%#1Ov%dI7;K$Nz~^peVPTNR^7wYgh}KUPmh&=O`{=Wc
    z!cb0Rq^1(!i;kI?Y-M&XP51k`m)7M$gZa(hx`yi%-YDv}Tyx2P`d{-MIB>wj%PXgx
    zI@Kg6%^*8|>-{qMGK&vV?`~>k+MZVLj!v`43$@5|JWx{fAnMX9XQ_wkXBtB9^?ZJx
    z)$W|y<&wRfr*qed1d-Si^RX)*GH46R_43bR>$rG5T8O|48&{M6SOkfL}D42mzP^k{}U8Mwz5(j
    zI$g?U=H^s9JjCYa=Hg@YbGqIR3UEQbG3knYY^Ydo{Gk08ETwzHG`S+5l
    zj;gCyoTYdjSE0T0-MxEvqT}h7=y8*rE?mh5I1{3)(B(Mv<3||gK&546-7;6Wuxh!R
    zO5yZ`u*%Jt3{NW{XJ>uC;?j86JrS+=NK6o?Ry*C4kU(Z)l5fI`({s|N!4sAALp>VJI)3*CfQdDqUJV%BehcpcEiyMDO%
    z038jDb$6~gLALlB8xSfgDl^ODDW>;_r>7%5hZ}6Vo*9!|Kzo_u)j!t1C)x??#{@dw
    zXHt%bb6>;8HNJo3@^9x-*PhLqnwrMv+^?=Jj~5mfQ|nb-ng>#kuZ;Zr8j|Y_qw*;p
    zaa)?+^7ChYDq#L*4UB&^>jU4u@uOp>K;PTNMP%$lV>BqA7#b#NgmyP(?+BMUSDg
    zvrxlb{yQMNx-_T;M?UxV@_wtvG)P!c?$I6B6BG;<%xtu_9-#!rYK36Oe234q@+=0d
    zb%XM`k~!VgZ7=WnU2WH~8jqA0>EY+Tz2wnZlRn>HhejH=kyrS{O08xs_oSY&mIS-G
    z={}#F`$Io}emJ>Cux#IcdiZ>Too>O0lHWhNHbD!@@9-f~jdM)QUSBTL%q)7j8rZsF
    zV8-X4fBp%ltUrFd8>-cfa$P`3|M?Hn@X1pgpe)TrD-kzBLb|da$sB(bw%G?N-S~c?
    zgQqJ8*A+2qMQim*w#rt5OnT~fx7`NoL``6d)3t-uE1xF*0CkC97}1b9a>Nc@N|cj~
    zXUi;@xW(Q$3wBQ}O!SuRL**Sv5ad1r_hMQt!pgWjaB%}eOLwA;^0<}NHWZe?UpTDO0M#wBwqfA
    zP4TGR9wwf<)hnYI{WIC|)D9Rrw(6tK5+V@Goq9cySPxInn)&gMt_y!jr4TGxMDNh*
    z;=oNKkx|Z%;o(g1f1^v)oqSoYd5H(+P!tnw9-5V{>^&(v=5_3GJBa4wc@f<;qA+0f
    z@|t~3h3D|IOE*To%Vu@CAYnnLyqog-TVJ1x==+hT=m?I;u(1j3CYzzRAw=_HEv(F^
    z#ubaKYl7u^^s56_31XjV{>UoNpWl=7H_b0HO-b$DOP}IDw=wV}CIh*;ZOSvbgYUHh
    z*>j}e@87?PrxbNPE76A9KC*?_xN)P#i4z<2s&uTw5Jol-aJhf5WZt=Rr_1W!J$v@N
    zYibg|U^HqU0Bclb#$)x28uU<65xTm%91C$Ac=|8Be=Th3BW$0OuJPb#MhK7b%u{!ssBD>?nn}w+!
    zzvCUfZUr7{YHEYfcArM2+|0?@H*k|l+=vbRtinVc-{#jNhdN$g@mce_G|RAEDWHY^
    z?6axu&q_)ps~L07ow3R`n!8ee$eojEyFB;M(2&`4H}kyro)~Z0|N9KrKB7F`|IcsX
    zu&^-96>4%ZEeqs;z(xGvsAxo4u->wBq8nS-*Y$0deul`YbqsBoL9c8=sc1-O=v)fs
    zsC-CC_VMz%o{?df7Xb79hHtNYjwc>=_obZ&Q@?NPehZ@}zLcqQEHcVT4+Ld$&da#nVFwQP%UhilydE6B7Zbj$AsdvhP#(Ki|73L^TeBi
    z>NShu*L^{~>BsKK3EN;qC~Xk$;wk0~W|Q$l=QK`j`|0N9R%lz#NGMxd3$CoJ{CG@w
    zuve$iucVCXb?2BKTr0^IJZ#LbpCo2Oz5e1^M)D;acz9OEc_bYNasT6$0u_6aFou0(
    zs~OLIKY8NBaBRX$Gb*Oot4Q~x`1~pJ1X4o?2B^LBfG?;B;Tf-=(D)O97-X
    z2ZJhXMnOfz?O=q*P1{vNnQHFsP(^_q6(4^K!JVhxeUL1q2<~GZ^60!*QB`fP4h%#I
    z|Mj1_h3Nr-qN1Xeb!?YgF#ji3_7t99`1S4UgjpKuxAV#0pP#NUWKCjoadNtO$hBKg
    zNl8FoU!Mp<>axSaTtezB@*3CGekiqZmwLD|vT5>nTB$nQQ0!Fa?;cSbTubifA7H(s
    zESNRNfY$T37;`$s;gR`WZQzLxU$oN)NeKxtw4uu>%%gp($fZl9)a?r!zuZM~ljT%C
    zu)O`O%OxZBUpN}uo*C!Rl@l_uvc`Om-KV7>mw
    z*#!KOI0^KzInTs0lkJUgE$7!V1V14W5eY_TxJUb+yYmHIIBs(8Ehfnb4vZ6n7(b@s
    z>nropXL{`^JyZIABE4OCGH88UMeOtBZLfQ8EbR+`!Fs_jF=)fOQcO>9;
    zy)6o_caKL?)6=b+d9AZjCl)T_-?jCH+u!35wvJF*Q&OP;ND~J&kE>Z>-eSLk}+sj5o)<>oXc&>tqwXSQq}FXr=aHRT8Ioz(oF=0-+0Fl^o4$!XWVeK{t=AvqQ{7mg+Y);v7qYBG7*
    znDsAHfoJtV!5O99yLSuj*irLXl&Cb{wzTSd8h6trv+mFGa+hCbU$)_1_%=DwHX`nq
    ze23B`?EM65>@E0@CB=8FU<@YH#0LZyg_Lb_3)v^&*$tU
    zM`048bOXIoGbyr-dkndMm6&pFs1~U(j#5d>nmyf`mTzlR6IS^7%D65KZdSwNw>5d)
    zxThhQeL0LeM*7bpQqrAiC+vFP{k>nsC>zRqrR4?A80+mYH8ovpQF`#9y{*)pyS0Z!
    zwVJiH;CfCEy5VdgxWxXr)L-eh;`8G&?_gK{k|Cx${qKx`9MhTKzt!f)KKPxv5f4OoCs{$+u+*h!
    z0h9%fRh>*xq-1=h1$JnO7hkb9?U5=iGcUT})?0dez2(@soOg&M@$!FB+WQ&09dpnZ
    ziA_f&ue=`v+=6-c8G*(rdM>Cne#*`>>kHw17_8bfj
    z4sLtl<+R3-C5*yVa#zS4IT|oL|M;X
    zM@DWyQ6XdJiek}=T&owKMm!~JR|9@6r3fQ)hN4Q|DJseYBvRI12FS~|W5*84dvhx*
    ztDKgrx#HTKxke((T`R~T?fB}%Xj%#kY@R4GKY#t2fo}%}B=mGaBibaErioFf9~tTC
    zB+L;ASfWCoRxN%GXP}tdXR*@r1KTgZ-VTtW77#$z2aT5AOp)uxBbdU?En&|UE?JYeE(U&xd^?mz>f|j~!pp&v|5aED@pPv~+w|AW|&%FJ6y?6Lx5l5u%jlMHsuHu>knEQtj_M^wGH+4qu@
    zZxO6v&!xZodqRJ*YGT4!@rdtDc@kQ5Q_~^-;+4Z{z1@<>h6(a^*_K8{xj!
    zWHSmHob~qZ`PlhdR#tZ9hK=u*^7M}_Em9Iui4Wc6y#Haq^in(uK3cK+??;V)Wt+xc
    zD7Q7A%BeG1P^gVYAT1rtPy*>|i{w~omnrP3r>S|nJ>hby{0A>=d=_qQh8I?M(L6Bl
    zn0jfpQNYxq{md)xyym&R6pHP;dprEi+1S`zRx43f94kNf&Dy~sMA-0&b#sjHiUPb0
    z0%(KFyZaiG?QAj8iA45gT~h+JUZUdMH#yV`M>7p(b1ZMG1&@r_XAB2CfruytyrE}e5{h}}gFsYZJWOJvmbCNg
    zbY>^ocLA41x862Joc92Ln^^cGQZAr%;siP3&&=H1Sc|R{HBaB`D_ppQ(Q>7rxwaRi
    z#V(YYPg^$$nZ6Mfm>7b|uH&B&sNu5a#seQ+9|QG=uh3T=Sl#QSsjEBoH@vz!FqTo-
    z(UEdLI+~={)^+ks>=4;d8&YBU)GU%`RoLP#8H=1rx-L+jKF&d?Zn_8P_gBuTN$et%
    zKQbB{JA3=nlLAb9hnW-=6$!Feyl0~PqTH!ujvzW&%GL2!{#D$ggpEkYdmdq~V1coU
    z@r@fdI+xhGL%^X9!LQ*s`s1(~MsYPZK5bCQe96Ma78ZPyp$;UY(m$_+q#2;Wj;V?$o=1OSlTxHn2+41u3EUclfhTjGILE}MP
    za-_Glxdx^Y&Bs8}XD!FK)HO8F@kTUE$Or1c{SE;p%jntVdS~BgkVe{LJkrwAcnBHF
    zT4Tj$J1BO0-+-FgL7{}oKfSF)%lnz<>_T||07;{QtyW2Edn{ch+ew+Yx5K53*U!(-
    z_+M5&zC???k!Jw_ZfH~N9tA&t@}>Z&S6m5;ki>$W%5xPG~lG4NXR-#nt(FO?{@mvb<=|<
    zahxdsW7GK_o={dM4E;VNN;^Hfbt2OsBZ*dYQ2U4Id$R`@o6FLkWyD0B?Q5S}sQycM
    zj7@1qQxd`}J74*n*VuiZ*OUub1lQW;ETt{SR=#fMIk>no9zVXJIRNH;W^wVRO4{JOZ2X<*Xm)&ow2sO6@FP2;
    z7;p>S
    zp6VG&Jp7XzYG_~(Z%LT_QOGh9c@m%iuk^LVM7|ChX$J=f?bYLzuU|*u_yghaTv^B*
    zLzwd(;k~V-xL5^TZ`tvQA6kIVDR#lM7)ajQ+Uomzf>kmB2U}>Td}(iI1!D}h%$fpF
    zHMh7w2-{jU+bA7d(Zj+|O$v72WV^|68RFD?WmXl%>Gg5Mg(-S?zNL_8vw*PdkVnGUpj+K>_TkfLs^=;#cidW?j(ZkG39C<=G&IyU`-(TePaXU;U
    zeN|WayvAC@U;Y9C83-nrQvJ$r-qC;Yo6mVGK0`slhtAF{d$UeWBI1gN-zO*&4eGWWBJkFv11m8iHLzjIiOoM~_aso{@nzn9j;n5wkhI
    zS>qlX^Gd+sK#(O}{ST6o++sRvzygv$VI0$cWoUznso132SZMV307l`hkE^Iqr({qG
    zZCQVXT$Eu&kD$;9VO_*wC}%&A`Eh@Ld1U88B%X{L+JuxMiI4bLrV={m{2zMi=KkG&iegYHqYTb!vG!a+@q0NP?sIyx{BCi!M$Y
    zWVm5y6bqT5yjX})%k8MKfI+CAyL|};$2!*o(zfuzs1}*VK;D^^xfo|E8!?M3-ltZs
    zY|l|K5aD&=H+WUK-MP$yJSJyxdY};^!Vvg$bWd(Z;egammgAu10|UWQKa@Uyx}dEa
    zmD$qLa3stE8W_y5hy{=u%3bVHe$sh&YBqkDLR8
    zTQg_A{Sa?NRL7Z1e!L=gRpC1X6kLq*0+Xwu!7%nQB@#dt#XaPdxA+PZR}geTrxHN`
    zIJRK+p@S$Q|I=g|+(xxh+uY1Z6ym@H8ag_X8pnVMF;tW{Q5zm!TGFsY{jAV_kKKAW
    zSGAYgR{_ueGuQwedq9;c&uZxACfX3ra~(K*7+$esnAJy~NJJ>(4^h$4hIUU>4O;*W
    zF-((N`rNjl3x=x@OT+`${2_$#;&=^uy+@H55Ipz}yS)M5+5Ym9E7?5)1XGJ);WN;5
    zs0nfrLblu?BEkqMbR5_-6QjJ6~Y+{R|FZbSQ&|Cfkm^x91H=4z_i9PxF
    z5Do7pCMGo`uN2l9E1a0`K5EB-_$k&cWMtOV-Ycht%YOAW$T
    zI(>LULF&z>1TK$qA$O;v_5L#!-MRkCCQAYvZ3rqJSCc1X*+RCG{Y=$SuA^?RX#Us0XVNOJS
    zc-TM^Lq)%Xb0Hmd>JhvlpFf+eUB(keo}(g496!7TNDQ~{Jml8~lBx(F4>+kT$TC=e
    zhbva%HCVhZ%`tp2Qt!kS&&P$6Gx80hV@$g5Z56(k7Nc_jv8^&y+3BJ51;L7ZGS^#@!
    zQiu3Y<$m@lwb+lH0w2v?Olh-OS(=^be|_YMZgLpb#4eN$Kr^-LYt!pHLGedLMYTi=
    zB?KoC8-oQkX0z|3a-L6tZjh=cG&3`#N{lYEefso?1*j4*a4!1%F`1B>?(VpCzHz|^
    z(b2Vui?W6?^o#2lzw=T5r-P4o+M!=4~J}Z9=Dka_=$)OtE><-9IPoVR<^KnvTr96mTHjK(_
    zxUrEP6zYG44j0!jxHc##|B_G`Yxg^Nr8mHLL&L+h>n5n(S5{YNfq!f;BAbkJh@XC5
    zD}aJ+(Ec80W|D^m#Dmrb8eN_UVEz&*py+=KS}u8vo`$9-DGb`!?-4q1KpjjXWDjX_
    z=VLF?5%Rt>LFMOKQj`P+0@oD~uG{gB#OUoQUI{IT3^YgEe!L7bK4lnsV!et?XOJRS
    z25=Z$xPJai>f@b3U}Z4@?{b;#~*g*B}k3Q|G8T0#=>XbqxUttVyrO-!T6;UoB$9I
    zk2rpO^kH2ICHl$168k2$y88Mdv}wJ8x<(LqzNGh@ipWrpc$<3kx|nmDAVHGsV(0o~
    zaxKg6mIt?qD+i}`?JL_9JG;f^OR|Q0l+zSH$$_kRxyB8I84YlA3*piibPA)FH
    zw>LJ<{QGa{_3In(Ikp##BqSu*sHms_Tl^_CFi^(F-ntFQo7xmB_#;I3>G0k{JJsCh*o-bk
    z>ONI9Fu31dVlskTpssai2j@I{p{}qtBhfx0c>I71SGo;S5n2lciPXYE7LeCZwERBw
    z^N>Cz<__hfKUF$;;FLj?oc8!JIrJ!v(T_RJE{H}al;&vX9Wrd-xvF3J8HzG#Z2Sw#
    zfDcBGVBdmlVwhkR5ZsV26-_4k=tFr@8^3=fPqh2`H8ETJz_rcqCAvqJ&zM$--Y7_>
    z4ce5g!4fDHvnh2_NWnf~$1U|IM7V%bRli@-*{);khZQ8db}2|INSDn|U5{e7Mmf`e
    zb@5cRG~}l4_&FA#E$%|GS_zqAMmb$b(#~DaI7{AagO;jCIxKb|O6TY2x5!;V8I!^p
    zw&}FZ0qEv9cyEh#P5LP?OM}D18<4dq68MXrfx+7re~hdEQ)M(&Wyx-lxApn+=ddf;
    zbsxZ=Q8+`ey&V<=#lqV!V2Mgc>DV%IR*IJty88S3wf|%QH5g>yFvuRM
    zeO5eiL@V?D*>&$rm(X}J$X-<_v}@}5*uQVhBOKtoM)hHCi8;iO(X3
    zoE0aCjAwp*6AWAC=Hl-MAH!_b?tCvf`GZ@!cubSa_sh$3d%zLKYv)@H2p>3*&*5B`
    zbejUt3MeG)UWikoI3)#9Z=?bn<`wA{7r?
    z6ZQOQcoHP|_L8W5Zth-6%E#RBgJ-JLyuA;XNLW}}?t&X8ff`&K$)tdimAFFB&(ELm
    z@A=lJK@RH&goLbnt7Xk%iz+I((Z&IbM69Hm`z|(F8JSeTc8~}y+b!};=4aE*%?W^k
    z`1(I_TA4zkqV|V9J^5jXkB?;k@K&ov_(CqZ2K`^#MgD$ym&+7JOh5CCHF2+|e0L#j2AUX~`3(!7a!Ce#fHYv2Zl{5a}0WDd=jB
    zO?^&b2G{tx$j&TmV5DxKF`112iy#B9;Kpai4O-v?WC*5v_eAc+CpFdoK42L5DVfwz#oHL1E>4&gb;
    z%ge`0Rl;}?5y1@YDc*bcl`DTOdh~Swslgf{j*u2U33R1m`QFN&frtwTa<>3xBCVO3P&HGVe$!xxAk@Wn`;X&8`4^3W*41fQ=tZ-3|AODq1VD7?I6>3IAfIWxxk?rUUrWApbg<(Y7M7OQh4V;?U7XM>c@J9uWX58H
    z6}>o!eR4%uTG~M|L8TV_#lfv7pP#Uv8LYW=`?mPsZ?H;A5`yyb+hEw){zN;$HctLO
    zX6B{+nJk}_hlaeOf(5l>_3uKfYLGYM2tro=N1DlgAA!PllN
    zx>eHfGI`)C{}FF&1DpWZdXLv#1G`wYzIFwj
    zqaC@ItxBC)k*@&+f{`xWJ{OYqNk?IJu<*15q)uyC1~(k(%IuZ*{({2J
    z^6ju2y)XX>5p~Kx<_HXxI1Q*GU{wwZ(!IF2(25Ef^ooVOruLZX-VcT)KA{qrvKCw(
    zEiJ9~$e!K1-+ID0Dy;9-iBz%hz4f
    zf5`*4Lo8|m0y0_VgEy&%gCbr*deOF6&zBUl2>7gUYQxn(ouUco$sOAp3>(QHcn`;>
    zBY>YkcJzc9ieosbTMXKYi;D}v!+-wr#Ri-VS)AeX^bkYOC%|<)63GP~H-5DrZ^r?7
    z?ArgzM_l4e`*DE;<4Anb;elE}9&BA6m$H}ra|Q+m9)oYFGH#F8-DY2iGc_;>=GZ_!o1QwYZ
    zSS3-OgC44YUotuN5isE}UcsA)f(Mdle;(PxuFjDCG_uufL_;2M-opHrG(XBqFlC09gsRr1sCk6e|Zu
    zrx+u#&rpW+;R*^1Z$xym-}&*w2WFBNg)Y6!nJfz+SRtf_X?
    zC_V;9WCP&;Rt5nHBc9uHaxIX4x2u1C*I>6L#I!?@o%i4l
    zkPldiPq_c~r!mS!5J&ekrIc%%g6ff8JU>0w#9BIfb2ER-BMwZsB(dhbdn^|xKh^)^
    zE&#m|S%FkINz>1{zKReW<2bz<)gXRR
    zrX$b-48;tdrBLj6TQY-0a#{`C#@@hMT5|SW*a3Q`dSz}C99^EPe=d;&-nbExahs?1
    zM;}RVb@wtiGmGbRbAsKO1c=}d#34#z9zHFcehbevU}sRKMAyI_2rPy5bIjQ}%lxN=
    z&o3*S7O)ksF)Oy;2yu}d4JOw_Mn}W}L~eQ#9UuRIQN3q(>F5e6iP{u^ilwuF*z~{5foyncvPe8k0Nb(lolIaShc#j(g`hU
    ziaYHw^eoHoa-oWuo+{Q`Vsa>+P)%ZjE)SbJcJq#d2S1AXSv@W#DFGaOR=F{y{?*&L
    zgliWXPkQZjRZRehhP^}j%BQgrPkjUP${uWMlsMRCz&P)kn{B(fHc(T0{QcLxq=Ao~
    zo*w0ty`$q>7##t-9ECYiBzk*$V-w)D5?7eMH-kQo0@#{fRl>B1eu?CyBpk{*GdFtT
    z^pK>}(OnPT+hDUn4LPsSy^u^!vj7DFk13jGpc>!|ynE5n8)~3O|AzXI_Ky`=F14$*
    vO)aosCmIZ3n@|#@lgUEq;ZDSW`t=P$VrToVaPiC#_($`Yu3El|b@2ZIb}^#M
    
    diff --git a/resources/icons/bed/mk3_top.png b/resources/icons/bed/mk3_top.png
    index b44007ad440da38ff179263ee1f2f4ec2f56e229..9674484979d36ffdbb8b4aaaf76511a59ce2c0f3 100644
    GIT binary patch
    literal 36806
    zcmZsD2RxPk-~Mf@q!Kx{FOpG4R6?S%_c)c2k?gEQ$T%frMD`||WA7PC$lhe{WF;KoQI_`6?bME_deXjR)UGLjRMM?VXY1-2Wf}E9=d7y?M`0x@RAti?Y
    zHXZx+;6LK0^3o5GW89xlpVLEN$tfEdO*;fRa}oC!4~dRN!9tRkvWk)<)1*{n1e~AD
    zI|2}d8IgS;q2V~Z{Q0$G=v>^f``xD73D-mHuFr~N*x2aN56r3aB{Ne^QyU~h&)men
    zOXNAATV^VuvHdS*;41jN0q0u&<}?6)>OE!C2@F1zKLbGb2N_c
    zT@5s@G>ewS-}M&_ajMO2m1l0gvNPrC{(HyOF@B1iNv!%Vk$FG1q2O`$am9SR2
    zLbfmbNHe)^K1$;FO8D4-s@`_A*sNEMr)s${b!XJBvvzpJ0Wat2S$cBsm#x>zM%y@j
    zDb9G;4@$FKplW~0z!LQR&aF|K`uZw|l|^xj$KI~{+fVEc{;qXcyUfrXdk*B2E8jib
    zjKF4PwenQ|`I?Y)BfEYeN1Lwn`A}hDW#t}Ev6p@1P0Q$wm9hyploAa_fUxQ~{E2PS
    z1w<%B$BRARN=!iUqKPqFI(_&HKLjWL;;tov)FBFp(5N}?ZonOfz#9jvMHr7+{H0%8E{#vP`?g
    z${3h_w!Vwx1#MfAq>%k$XbWnl`~gch!v`s4)tu+o19R0G^`1ZIqXN>oACZ^M)BsoKkwiyi-AK?Xe*G
    z$GujOiuYH1la|*UTPmxn?ixH8j6QvBbvDDldT{C5h1&ra;nJi=ynMn`hBxUHPlS(G
    zmc(H$%BZXwsy1lAkcK1Bq#7T9%fU4F!fx
    zEE-u+S$Q?iTT6MhHCJdqu)i<8)sDqtr$eqN$EUqFiB3~}^5n^jOvV!VOSI*n7XFB?GL6SzXSMU;#j-#!za%bia+-QN6D;Z0bVrD%lJu^*#
    z3G6ieqMfvKbW7xnqJO0|!!KhUva_%
    z%T8g~ymaLRv0eBacdnA+H;XJ2yurw$(RYGmI5eEnYieqyLzKmQRLo3TLOE}ElQZl^
    z@;9n}%gW8=i4$|(b2{ElINsIMVCvIAqp6bGM997GmyWEI#>dCIak<{QaijnEXs7n>
    zy;SRnRwHUj)#arnKh;pxa3h1=^|`DE0WO7%MfxwCjq6-u|Mp`(Cd{r|3qT
    z3FVp&PlQeTjX@2|iJHTU*z4mi8|_=?#QrjC#?80K%x~*mOw)ZP}cxzDa
    zm>69Rqs23j=`N$@XXh>Mhn7uEjIR|eEG$N(D+uz9TEm;qiTtiYPM~`4YI|>#kA}v
    zRpHrsH!X)XcK+0#bALdUmzmkzKRUXcdN3NsAmV_6yCqM(K|5dn&BTg>A8*ZHmgai~
    z!^LJD+_~jXsX9@H4~Spo8z$B)OVBExCuAqZm)0>cw*T|^A4LJJ&v}{qQS(mK;g#Zh
    z(KA8;&SK)@1S-wtQ6GmTJTC
    zjg^Q#89^Z!DgsRD8I=)KCqi7uGD%wJJZWhaS&blku+aRAUkQ%-=?RS<}r
    zMuww45!Wq7RJ?rBbOA%v@LnE+AXoj@N07&dS0nZ$sckA@OMPVhor@5_LzWN%M-FB~
    zE)p+W;Q?5DWW&S32j(8}DjtT02woxzr|a%b3Z0%Ig4Z8K_^0{Bk&8sI6@rBP1E7MT
    zuO|KOEKC0i768OhCZTKzRrn@*5xn6G#FMdasPXV@e%D<^sgg>AP41%)`Kx3;#-g4x#Q=H?1VR@j|
    zi
    z2$Ff+j#Ki%c2sl)u*u?gZrl^1FCBV*Gd@Z0fhwh(oE#f=6m{BjxiVLP%k|tWL%f^_
    z!5P*XEVd;V%hl{jAJKaG1$~ohNwNW|&8jTKJUu(RO)AO)cT`e*lai;ET9U^-E$LO?$iNB4vYARl>j*s2BbLUXQFE==tLXVANAyunAG(3Dy
    zL|E9_$3b1FYK&+2`OS*FJYJ>#034RNA0O`AP2)&5i~dPn`?p~iCc>Rpwfkcv8pE8t
    zygRQP9JbYRd}Cr_mfUx$CKw8bo>QP{xVgFg#P*kq81JnR3d5c=9Hx!iqx*kt>>j+{
    z`8_7x9Z;DYbM4x-t<{?S-v+9bvX>a$YA3>E*m@RTYF+MA`cza@v}4
    zO}!D-Y$a)AWMtYAdz<>O$^D?oU1e@ng=2PQd3iaxC*W#paO+&**5>9n&c!>Q;wo`y
    zONx$TAoo@bW)lMl&m`MGKYCK4H7gWwjNAMcYY?dq2XDn}u>RFcz1yo3%GYVGH~#2|
    z6Y(r9Ep^pcdzh=E={8ySW}H)mUZ8)Y1OxT?Hy@K*CnHqm6{x&lZEZ!dx#n`f9@UnSK0S_nOX`wf9x8R(~h?Rv0#s2dIhcZ?9zCb~|u<
    zwlZ9jr`j-DXw*7O!ss}8|LfYN&ofhs_hlPFL6=E!OXpif&yFNIn-#ililI{b9Y|8
    zz~%!slQ&Tfcysz&fLG5*u;E%5rupjkt^E&Wzgs`|tZ$G0j+^HM_GAb~0(Jqoa~f|MsawVaWJ1`9kRlR3U3in@T}sTk
    zRXY-7h6eVYjml`ae=y3(NI;0duOCTJj|&O-49M=_&f?Tr3_XSw_N0G-P@-9UdQZ{t==I9Hi#__ky+1=ED!A$-XR
    zOe}`}lF6!tG^(nUcNhaLLOuHgKM3k_h4t+3@5>sphsgFgdKsV=UPdpgh%yE)`6hi7
    zPI$nQsMhG$G$Y)(k>a6^MthX&y?iowmfOO+(#6Xlvd^eZGA@$|=5q9v+@cG94Ohnk
    zuvqhg$Nt$2y$i)sW4vpQv4X2?6h1m_n!>hm_Oij>Ul_W(*LI+09yCVUHZi;^HjOqW
    zBipXk`Vc2Ou_oFHI8+^-W(I^o>5ph9S-pPL@wua#L|c;Mk;$-6NgTtWoa$KX+QaP(
    z4&Q^WRVJ;DZa3=AjpscnvR`WVt7?~&*+Ui*@7^k=_i%6O+WzixCr-)2ZoVTY6N_Cp
    z<{q@+9-C@u+Yt4WN;XUHUDzKUtQoNRLPQ;EEZg$DycD%CK}Sc|N=r+7Ff{D;>$FRX
    z*mcF;*xPo-pnTRJ2nlH;&n;$klFHp}66|-OL
    z!;Vf=x7IraQ64eDy|0R!(;IrVV|Q
    zQP!%=p}^+rrKKfq>x#K>;@SyoCfRfKW-rU#1>g+CT%C>V?MqC`>4OvlhmNdJUi=mV
    z+0liC*UPT99>~l*E3WBu(Nk1Zl;oh~syN&pi80drDZSJhv>Df}bpQVSt(>y);a@L>
    z8=f0JEg#F8YJ}SXt)byaCE4x#`t?Oav-;<2*WDjdN>)w2J+s)-+WO=zvkhy%tx)^9
    zvL&Dt#f$Do^X>sKQyE-!71m<9ar5Rwp*W>Z%d)WnHrZgl=o;_+Ue5zlX5iK(HZrR5SBrjd7}QIv=Gu94!GnRX`~1-UsC
    zKk0AllnrKs8^G6}|ENP`^}tn>@rv!$GncOZX_~P;oc!W(s9|-Po}79NZk|=(GBqwv
    z3pK9$l>m-WA*1;Q_g|c#+O*|RVe0Cm+P!}7PO<&xS0qK_=PPbl3~c@$t=K@JX>M7K
    zqO7g0k0ZIp{b>Y7fvWb5e}4Ts<7sJG*=vrHA~>M|;LcJt`(vv$7U&gV&h>T^t~(5A
    zI&V~Vwi-XsT?}1|hY60NSMY30O}wC)+T`!5)guho_*c&}1PlxenfJZPFK0X*cbxgq
    z!Wt-gZy+F0bp8Ddp!}gY8XY{-q!@FvVZI~o^?2N?@m&(^^<2GLu}WZx${ExBeSJT)
    zwY5KY-r3Q4$Mv(+a-=$R`1bL^#%G=g`A;n$Zrr%B4FvUgmmJeN$0FiVGP3fv-I(=G
    z8x`Rj!T2$&c^Kd_^y;7dn>IwR$Wmy2W*{fwZM|+rles@MWkVW8m545>Kc?wu8E%${
    zyS;e*CyTU7ZFF=c1Kcw(0g2Ox2nPBjaR||q7dp!av#BHusn7GL_j3Mteshuk`<%GQ
    zGgr&95vRsKj0kuA6a{l+#v}`MG*}}UyepJidQ#F)JyP5
    z5`~EkIk$?(A;dpTiiA)jT`XkcxnBUk>$u3pMPOfbpAb`$lUE3m|AGjXoFX`NR%`YX
    zL$(ecP&^q^q}fF5acP%m6iOL`h@Vh(%|81=he6`=jW_cT>HroSd!8A+q*A+oJG%
    z@!2U(0VG__cUT!V*9<4}jcjjkZvY1O`{>9mj3qvaV-IfFmVrFIytwuhp<+cqZ?tUi
    z^NpYF+~+*+Ir#c$ohKZCQCMnFPaG_(J!oH)nwt8&J^BV`)tFKbZ&+9uV@x~yz`%fW
    zN6KfZ#fZYf!lbEUfdSP0C3;dq-{9tyLfe@}E#H(NrSvUW&9|t(!v^>k@x$!Ls*GgB
    zmKsudxINZ;ofQsq(9+g!1?eG%(M%z*Bv7GOu(hSdchsuv)mWvyfo$;KwSik$Bo|dI>K8cG)sF$9j6y#s>z{0=Crb*tczJJ!XV*;iOC(e5t}_
    zEbA4jhl)%d$py2i&sjeiY>yH!3D46klCl?~pLT3wZnD+kpe*#h_s>q$>~PSY3>uwW
    zT|KIt8D}H3Y~)`M9Zh>nQZ%rM)rbs%;nSJ0yt2~Ifv*RMU+_}cl3vu2Pwqlqdb+fB
    zwWIAAuEd+s&5KCk2UMJJnvWX?K|>?)J@p5vlIiKE`%s#>zMbE-y|MyzSmm{+nNin{vIw_A`GM3+CN-ZSH5Hab?!wF3NtY9@D=mm|Tj^|(0Fp9ll
    z@z5PU<<9buUG#UAAFGv1JnZDzLM6D$yWd7Q?&i}1lxDU6k~+FS#W$0~_tFO`+13m!
    zw!61Cng1T#cuHs~C^Fb6PK6bAM5jghCZRN%S;FLKfJ7AQ!M(-N`rVd3eW_AuXb_l6
    zx2cAS|H6Z{HvgcM_W*S19KK3-bUF4L_{h?hDlY{#ZNX<$H*4Y}E36JtV879#6JsX*
    zbR{3gktH*`74B>fCh=SU2c4vELl*S%M7h(R#jbxNnn
    zkVp){g+M%ff9gL@yrTLDLP6{RB$5{}>GT_ya>B=^O4K5VXL7?mLCVJ!_$b5=BZ3SN
    zh#-+<28=*c>&V6Na)Up9J40e_Sucuz!V#ANaz}wx0`!XNHW-|g=m_#q7WsJr@RAtA
    z2yae~nqByL35T8YWB|D0gnVVVY8gnSm~;`z20$Sq9t=#CPnAquZ1iVZ4Z%mhe!ho4
    z^pk?mDSw=OdT{D3rbAj+l+JZNrGv$=?Vu;3Mk81*^`$Qc3g8-GlMgjCpc=kRaq7-_
    z(sJI~gY~C?O4pr1Qu2#cN*A&ixEhA_2ehl<&Z{mAO-kMabc>(&OiN2k%F3;5Nud?A
    zx3@onmso$6_%t(Ps5c@C^|-jWbU+n?+mu4G8tQ2(DyoWzYuXQ4TCXdsnpBNh?d-Z(
    zVQYZh6p)}U;#Z4w}>CnNTVhZ~*v?Uzpn|3xF@w=g$Tr{QRIbshhtgt}iWB3HE#zj6FYxr+4
    z?Sk!h=`fk<>BJd44Pl|7I72Id4~1T1tFbHpyI}_G)WV5(lm7Jptg$
    zqEPgnQ!7`OR27ss{|LQk40`Ew~>-&I^Ib|b6Z&fw|fJ~9pqXb)(m6V_pwT2u;8Fxl2?CvY2tEVk4
    zK^1OoXIFF&)hS@u=zl)U0jh;wA^m(t4}0Apyq#p~(zpKDONvLB%4F6mA(dl6k6#6n4(DE`%u5
    z{&f+%h1)1QBjYLJHTo3im068p#{p-b89j$j2(oVa@)D}q5GV@7L#+AJk%^ECI*@!ei-Odz<2jvXYiKnwj^e~zEEGBo!X^p)OUxzA5
    z6|eCOP_&Bs=igNeZF3aAdvlK9t2v+-6u5k{Pf3t01LPz1#whaG^|Hy9pTI82T()(H
    zEIl*tS7f$d4zwv3T4rJvA#bgB4oKuDVrHhgbUb{_DY`oUuL#2SgzN#PhWNW5@HBm9
    zI0>@4cLV^ki19fDg~4aj8z;b6KKf6VIM2AqaJrf_!)A}RN*mxQ_Tu^+IigEM#?IyG
    zn2w~8tdJ7uhx7@QCPBks#l>3G}yebf7Nx@vsvyxG#bzsKAm|o!vqL
    zj4}b?#r|cgX>!EjPFDbt0g@fSO#0Y>0I^K&FqOvPwT0*U6es5E!jemu5>F%3)-y^dxb@MzjD%ZG<@!1Q@6Y}7*O4jMjOV-e12;C9zgWR*BTdRjCj(>D&|YaM3P
    z<)NZp$~CpjP+j2qFZJLWp&EPBolHR8-Rq_LEn(RrH
    z-v{+s=0Z_gTH`g@;K?m#BVWfgN1Zdo9^-SZ5fO61-_-ktUu5_iZf5@
    zR=zaj533QVD1cdCF*PnaI+_J545od`=(s=@bIHe{K
    zJ6EjAUQ_jx*B$I`xWaemJ|Fk$sRq>q)LEW%H=jFtaE*grCcm>XGRWZbKm!}OG?AX{^z?nH|+KeI4wk(c9qu+|i)8fFWe_y&}pB63tfksGK;31=Vaxt4fy
    z{e%MW#nFdb<9{!qtAPlwEE+ZiNaoh;Ek@)F2lUNcrrt}--2fA$!Vz=~qmB8_V;XeZ
    z_V%_D9Tcj{RMy=|l9EjU4DyU(E<&#_Q{Pg4_z|ue09&P0*UofA4`+@FOjVzTl6vB*9(y7fgg0egk
    z#40`P)GHV@xO7=W=7n%vQvDS3oycG#%@ltVsPqDx?$d^tXPKO*?|0}F-*p7d^>IZ9
    zW1#d)A$rjQ2bra|jcV}{^j(?2w+%~eATu|&3|}C8Gw;KT4@G2;uzc{hiZLtsc?Kvw
    z)H+CDu1=|um$;(~m4eJzKoW;qrzU^fc0>c2#Elh49YwN>450g!TXbol3?Eo_9atxbK&0_ktk?j};1tIlq#
    z-YdAMpd{S=tFnWvKL21=^7fa-N-Hhbej}8ZY-WSbrk`0fyM3EZXi%lz`j^0;;(;k^
    zFM?iW><7A8ij*ZD9bhvr+%|$3b_qLXnq+^P22h23~$Ur@)i$-q&
    zBs4T>%LL#&(8PxiA6}Zkd0L=unP@dm9pxk@COU#HFA0N%L7qk`3p~m_sE;0P#jUzX
    zqZUjWhrN6r+zVrCF14BZDhpgsjH|n>xHuGyijl{y{1m|Ei;UZ`KdPMsibhuOCH1JO
    zsYL*!1i>GiG_|~?#UK(8Is(KmtmNR_8o>lWNOUlf;_D{w6KcM0G@z_=}=?bqw8b94(9
    zxZ|62=2q|ij2W7IDgXOBg8Z8W;CE%;V%Yl(gz>Ui0&l+k(g3k4WkpfHKD#l{qpYl~
    z`?tm&4Rd-ILL0Tk06o3yj~qqgwF;lf2DA8Z`zCcp38b4Z4dldf_>MZ3KzTwk4e
    z=Y~${gvbx0z)cYv!{sHtW9Q>lx1FkSDEA)I&>NN~Fzl5~IVh0T^I2D~NzDXFGwwQ?
    z(s*Yr*|;;&uD+hw*L|@%{yE`bJ)(BGE!6DSaZ$fDjB^rqFhx@wmVd07l@^jcihsTa
    zg!-1{aLV0-KlMx??w+M)9qA;0sBYdP=ee*)l|^Aj*{)%nhVZjS0Q_uf
    z(v8MK(|Zwa-HjIRJpTtQLBGMC_V)H3FeyKx$?WhL?xr6N7}~QdKI%MxX|XwRWu(mg
    zdhjK0It$hhO(2cY;Rus-a*07{*y`jDj}lf{id$M$=MFb)jA8{$lu9^!zaH*PxI5a2
    z1lVVjFM17;xns3`7KRk}wzSv|tYHBYQV%hb@p7Hm7M~XX0?MWOj
    zqI|X$M)*cnTdG$LY-;^Wvg;e>opirnvWD+nUXvuk_Nr^pwTC}D>-(Lqu0&q5Vz*6D
    zq%+>vk`k%k+A`guA?XGp2LxY5fW!#&jMg
    zJ-l89d~RLx22w2(CTjC-R^w6I@};XZktw@Vdvgj+>oU9h3j4+~LgA%!@*|YX
    zewcN@%XTZQJ0WVKv=cpEC4p)Wjn$XV+6P*
    zpgQvcK4N#~q{_*M>+m(14)?Jl(diov8(P>ud
    zLxnrJ8(&gXN@pgWd|z}Vg}=)EsUE~{%%fXf{uteFu+s%NKtf%|M+Qza&&F}$gr1(>
    zsyrKoxum40tIZTi6U|=H&{O`#$6!lB5o@u~liJ`=6?Zqp>-)9{xG8=RC^`?Bizzqb
    zl)bakEJ0mY6r*Qx$~}lcqg}#hE$h2m=s!r^2$=cN7A0^$3eSmsj^_6(D2{<%c7q9;
    zN=D7AeUMmi-F-l?Jyy^xc(q5K_bf09+m!XQmc3I?X}t`*>J7@;jW0s@hEMHDQ3DOO
    z7iKbQ5CZ;7W#I820K^d6*a6)SjEe{;?se_^(MEJ}la5m~@164FLPXOW`N2;n%o>jG~xiI}La5_$K{=ecsWSVEqu&-KcT@z6b9m
    zzf2g{xz8={sO^&=|1hGv?HSF0tRLcl2WIapSUS1?Y5;^S~GF^t-<=
    ztetS~uq@FXC^hcPp)l}+&;BuIOuBbLA2tIKxh-d@GRX4z*|=7(eb=Zwn66+x9>l2_
    zEumoS1ZZq0&gEdSFK*M5cQ3p=UB9JxQ_*C}I-&E52^k`vs#clA!RBq+{7kJ^u$`WM
    zRe?Qkiv7`(!4J=_XBxGL=z@3|(wqY6r?-}vjm~aY6K2%oL6QYAlHl&aJ}f)Ac*CM
    z+C?yap2kRz&MPSDoD4`B(FQ2uuo{qb&(GCeQ*5#a)$^{kPac9%=4JG
    zP>Fy9${|XCp{}Hq3Qpl+dZ{jCO!@N?2ADO%?pZ{^>JR`sB8Gi2*bpM@*b}BM
    zPRI%tx<567vuqYFY3+jIDj@=jX*PJ}CzZh8t6-l)RRN9?-j*L%)Kz6xw^V~CC3hY$LRbTV-N?TwI*S7CHtqFMFZ_q
    zLYpi)(4%;13oa>|uWW8^ro7*m>=jhJGC*!Wu9|scd~h&=Wgvj|gH-cooD7?W#}BaO
    zhU4QIOVZQ5oyo8sO+pfPvos2bhgFRHZFR0kEx$h6op+a@8l8J_8*KZK=4ZZN*UY2S
    zKvqyXn)Em)MkyKejAm(LceLQ~)yySfpDz0+x8&sIJs`xgn3iD&3>ZWUhdDZ6fiD2`
    z{jt6G6F(k9M(MK#x99ZqbW%r02M-k4zK&o%u=^&R_iG5&2-)h(R9`yUOux&JTE3CE
    zROtjdRy>nmLz4SnNEymx#=%xr*w>tJo0ilbAFduhD;i=;x*iSTs6=?HmvZl>V((SR
    z+ARM1#oSG9=nr9@>(DEXl}QKXct!Hv=j%Aje%jPqG;R!GSdGJ7Mvz4r^v9~Yads=}N%
    z?!1)4(f_T)ZeGd$-;9iQ!BffI80(s$!-e#O;M>jr5hesm$qPgF@PppbdPE%M+OmU)
    z`a0CYX3558uW}L06zh-)-7~^Q08F&X2D4@*-{m1JdOr38}&VYWW9$
    zZBC9Q6S{hLV}2hh_;QF#@nD7Z9EOaz|Ki;ecToDWAsXRfcdp0rE%V2O_Sxwhr5ahu
    zZyxK2#Y(76pubDhek}V3&Kj344A{$zqBrecUx)fn^w!VmvmXkt+J&yY_#_=)uJ^*0
    znupr(<4ZApG7ty{qD{LpE)kV&e|a$QG*PU_-~S9BCpZ}=A(&HxWG^NLyU0%hdZ#LL
    z=Nn=W0~KV?`KjvrRpvJU4OefGuCa?Fb@O-Ped0xdBkISpBRD&{_=7M3U;_ij=b;#~
    z0z0fj4$kh{5bAFM*zKNPDMC))UU+(g>=VPuTE|qZ;{+;vveXbe)W-#VKwI=rI*ZE+
    z5ncN)JiN(+!$S@ja;*!8q1Z2Q#nunn1sWr<*k^BG)C(#KycdZT
    z1WP3ii%sGGKre(m8Ek%<)@v40`95?F7$DRrnZ=PbS1bN@UBNofWBsEXHBQ0I9pbE@
    z-qiXXR@l>Ee;Ocqf
    znNG5K(Z<+65Ny7D`7)zo?S@?%LKU^+N;?Qz+b)AE#`1n=dX_}mSUVfb$qELTJ
    zQtLVz)FyE(%GA1N+dra|#A&69va&vYU+0>h78JJ|Z|Jz~&Z&c^pni|v3v9!f=;#)(
    zb*d@X;8Vh4f#&%QaBBT1lgK>pDQSi`H26_VbGE?TJ>J!&;xuAeb^|gfu{pUUG1V#@
    z8edU{ug2^jwGMqlHAb4@qC}iLJPz?b4%cg6o6^9!1AU_^8+{?~F}PF`_^SHZBF@o9
    zYiTW}^eHi)&p)>kQIuL)Sy^3PUOxML{lU$ZzW#nR4m3XCO8EgPlcYDkzNhox6Tf=(
    zYWn#Np-3P}9Wl4^YQXc5D5>g#vjVrmH7fzD2c8Z$(CK|#4bCbV&j(rpI1J=q{9~(U
    zwwx^?$dU_nerW6pibhy>5yC`X<|eTf-Isln^7X&G{iV6gc%T>s7EmNVaYT{=WU4Ge9IV>gTX(Evo{j{KT4?b^Ao6Y1nBS)1DnO&d3(r2B(wBm?&s~{
    znkFXq1C&9WVW@pPzoFO2{Dv$*koh@u8`JUMrg$i(H_mCSUEyMqg`%e@F_W&
    z)&5skBB4QT3djB&h!wU6lO%VAQaDaGpWbeyv-WR~6M6Of$*VERfdG(d{KNn*6DT@(
    zw-&nc$zU@i#g+c?WH3>HglHU{>H4$Px6c
    z!`L_T=Wo1B;S;YcwmZAy&mSyct4e7EsW9m8Ng}@v_N^TBeX!Cutx>XT1+!IQv1W}Z
    z9~iiTkhJ2CQefw3fg`Kx>D^uW=5x_o1Fd)=EC>u7X|6Ciih&^@dV5YupI=Lye-J;v
    z`=;if?tqEab8uLD)$AID-e=&ihV(2H_l%8=-Ok6-JrZsFp;s(0?nqOuYn_pvJ}&?p
    za;-qY`|5g>ziNY>5Piq9>onTA5@
    zautDmpSVcyreEhgH$cf!n(w@gkPbj~iF6>N>#ORO$ckHXnG9eXFq2j@lNN$DxMD_T$HVr~_|DQKe})XsejU#hdm3OFw2`
    zLK!{>=Vk*m;ceA_7XW|+b;S7{PYv{jAhHZEH0vrntxkj%k(N-FbGvcZIlH4Z}b
    z&$cc}2Bp^=$mB@=n^=?&l+X-!YH>c-=FWcCROD1=gSP*@eL?YRl22eETfUDK2x^Vw
    zrJOayROZe|ilg4+6gD+TfXI!MIyCv#WN(rPZ3DtG(R{jB`ACk1?#wmznq{YbKu$2!9v9Z
    zfVkrKxF7I8rGrcm4wu=Azh((IAg7xqzw!~BI8-r6PQlG1A$-3LC9eW>)ij&b
    z#odG3D-TBrc!aM%{=slXm$*5ZiEV-T>wN=n=JM
    z8`QNp#0@Wi5>#;9B*8TtMq>&ObCAqOb7`nuRVme6VTClGCO^4s$q;B1SOc`ennkB>
    zDB-FQfV|VTB3xjB(tcRfSlip%Q-fI6H+%MWk)}YVM^KC!wM0S`-8{XhNFbHtw5AB7
    zh{M$e=+%36eQ0O7sPib+Tz+(EOBh_042rzmC1>cWsmC=)CNh(;tvP6-MFdPbVt+bR9o8PMSnsHAmgFu;$;-FS;#xoQnMolh1d6}9
    zDkWN#GE7bcY^}tA0CKLCNL*oh$K~%IRqR+fH1(-iSS(fR9WFjP=2aDm#$_v^$!|HS
    zCt%Df0$iq?swxqweqM-;+OO6gA6?*D0RmISCM$}zf{OQG5kk?5aGj^pVyyPA_4CwS
    zj?kg-Z+JRzCBp*sF25Y3Ktmj(7=Jt11S8qFSazwqZ0rPAL+@mk78f)P^
    zPbyzRc$wrRJj2Fpu(jCVf8u7cRn*(A@i`58PouYEXkK6JH7ypvQm7R#lL>zUKw2Wn|+
    z2uar3)PEy1Y!tFgqK^3!*p+es(Y`Xs^ps7nl`ElZ;65@N1jd)8VFW#{>SOhQY!}eg
    zX=&__AiA_O0lfFF0GY?Le-3^a2ATmWd>x1N$A!#af7V^_q}COZykL#%i-X3zoVFnb
    zB!2#y>lBeP-s3<~r_-1N`iQ;fLKg$g$$mb)O1;RM^5D$Z*oIa~1@k*UPh*?i^BHq=
    zO~jaJLoQf;ARkKm;z`eDD{mT9jUk8-@M-##S`tG2bxKHSP&Tlh#=iPqzhQiv$F3o=
    zUgu*-f-%)6fU@(WJ2(qO#n4r!FG`^DhZxCXTe>V*eJbCk_q&cD6_
    zBndgB=o#=%YG+XJ#&2?h)CRyphUgDWu&3VS_?MN5cQkvmV(z`Z$c%#-Ost1IW!#d)=3fBwXz8Y0dM}Q``CPH+Nli@Rkm3qU3Xs(@=@tB-!6kV~
    zAWDH2dR2=3K|o^-NQXu{ZA}qKh2HK;wptU8dC0_DnM+5!c8S7XqAmh2`weXwH1|)p@i%%2Z+9_N308oAneNkF@nA3hqS#6p#0
    zZeDXt^LkxA0jZxMs3oCwcO{5TmR^EavsLiNDqmx{IP)ZowL{dhsgf2LzN@DA?wHvBoUp#CIHnk#N=a9;bELQ
    zrDPmfu$Iux@``Z9-=3bo5jEJ?M|pJjc>b8pd$Tp_r%!FD?P6c15vYo$a@}XYZ(l_Zruk|5oU&M!k4ux=|H)r2_ef}VXApd3oIDYQz4;s;banytc
    z+1lD#z1JtXAS)vPHfpOM`BP`HjyN~xw#}rtwY0Q!G}q5c`=zgecea0|Ypmg#2di#A
    z|MBO0QK#65?1jL2r{B>QxtRye%5E=WvHt&&Ap$GEer01f<-fu~f$j%0XROMnyl!Pq
    zLkVs12shELw_Cey7-gBycS|#U{Q^I17atJb)?pFv4x`e{%x1PHo;s!|C@ln;c9*8VA*
    zzgAfR5y0s7LEOD_5*1Qycy4W7s}E75XHF)#un?r^p{u#f8{2_v(dLJ4Ves!iab=8I
    zQG+5;w;?)oTd^@mW$L9cQB1p3Wj}Phv$cQ`<`>Ks6xO}sumO{3%GKohsZRk{ppx!!
    zOMG7c#_a-*_iZWW#7R^!XxRUZRk~tE^Mr>w(*dYHk;JC~a;I_Iso7n=pc*k6@Oj72JU&tT@A%w4q^
    zb%we9Z(i*?<3QhJ>v=QH&K$`WqnVz;2b;;WPN~U+5E3x0nmV{t&>AULm~`7GsxI12^2Ek9jO`2K`XA{-C+A$)iO%FKh4kSVXjWw%u@
    zv-L4ZfgJv&Xnk2bqOA)^Kp=o-q>2dg@a~m{+g4Nh=R5QN+djsn9O1aQAE?AVFH7mn
    z!Vyu3zUv=G%vZdfJ6&FIg0pLce(#90lN#>zDb?t>^w~2=Va3mbE=To9M>Wxm)<FSF1&mK)*l*l
    z{h0;oMwEPduQMQgj!s1(m?g3^&F5&K;642O`Ev`TGzm2JtRO>~3vDOOqWyyL^!!}`
    z<9Y6fOLHb8UhXB8vrbkJj!I49kjr|QgDoC??7G%CjfFNP
    z71aefcxzN)HaE74SM!U@?7S)*Y3RDj)lA)+r
    zbZ9;AdV_PR4qV*aV;YAbq3Z3nJWA5d8{A)S2I{B?xB7
    zD@Nm)+1bTlY^7!5ys=G?ber5aVkTmfdOE$?F3D`np6$kC(
    z3KViAi=8#HRr$lxI!nGDcY1rlK40hpSe
    z{&;-3)$1)>lB%0i^zf{rpZaC-yZc@4ohoSboGKcf0IkMPf9(Vo)1qi^Z5%>m1vJJt
    zz#0@=-oGecr85iT|N9zd?KFib3h2tPX?V(SpCWXB=}vKVR4t@ATmcKI00?_Pv+=e_
    zPIPC?{T-DDEk9tVj({|zH?^~GIW%l*x|}#6Ah->FOl#4WVJT{8xSx+K8Ajw&DDlj^
    z>;EA?j2l|KrSs&v9u(ycAV3vVRu13Lq4CPnzIoPYFBPJpzaV{m)-UP%2aWJAhu1X*
    zqug(Si{vOeuEG(jTVjrO{59B=6p`qzl&Yo$O*swF4+6z?#dAYLw$EgTV^BN4fu>*$
    zsQPY?pcr%Rcpud?HZ}&MvmTn&n=L(Wbii!{eIF4JV0&7+Zj^Owvv-Y?lM}jt#$hE-
    zffb>i&VT`4=;q|*=HAHDF5`)f`qrqH7POZK0ez;xre4UMDj8c;6)d{tJ3P$M+IlqH
    z9B$%eF_BoCUR_=N3lxZq@3RF3v;9HE0?<0HrWw8hBAY=>`}VDnDI5Ft?bRsv7&yrc
    zrz;~oy*7qTt2-kwi`;=G!J9ogw+5~}0_~RMXSPk6KdafZtMCK{C%8D=&=Hl~uF1Nk
    z{nG-luLT05K3C#}Xd$g|+QjyMHO%xkLBad@GcKWc@(hV6A>g;G5C{*};0GD*?|0Tf
    z7X_+S&?po?L+YvNq~xDtew3%1qh6ZKCi^vjK}0Ok_m=ySOG4Ak1>EyGz^;a;UQj_q
    z{n_FAj{EVBYmiLJexf&{@I5M+dgXB0b+9REy!d}N%@}vYY99Ta31mvTQymbE*W=Mhamr>u+4TZZ_&o@#Sa94o&EY
    z`I`ZtTn%APjDf5=Xn^`}X)!K{GM>Ug*p0e(X91V~(ydRS>pX9A>VxFyfaHHB*OxK?
    zXZK8;FjV~T+#D#&fs?)I3i`%x!0H2<9atU+tb;4+5k3wR^56fbsW$yD5)u(9A)&}jhQxpEe&6@H-sie{
    zszc5`d*AnGSf91lP1NZ?Gf2kI{h$XD0?MGYU^v3aMWeEfZlt(Gg6;pf=!mkU6%Sdf
    z{vRU{LE0P|on!m2KA+wiE(9V0SbwR>JguuK-(c
    z)dN!cziU)v+HSr?Ycg@<}{P=glxW5&Lm{L&Kh5-hZC-hTOj&D$=y=wVT!QN&an>2+#Ec
    z58$=3jSl_=Jeu3U+#c6wulVH1YmjbWAcY22$JA5|==9ZN5r@*Wts>p@$CH5AVt98zs?jZ-&ws?td=|PR!o#iJ
    z20#-5;a_~;WWn301Ff?h25fYaEpF47R>-lnjaGtPs^e5nyMxy8@jb{|4AyTZzaRMQR^Mq?)+bfw$kz!1aM*#e>Vryx^wch!
    zf8Av2GUa>K%HmNclV;NsT0cKj*CzuS-@&#f*IE~im90(E@$17Lzt*@Sf!`zKe)L^x
    zNRAs{U@Vv&@RZLLT{|9Q8I@k-^f2nGqm!iGpNk)QHBxsiKU3Q`;_Ag-EcU4;qj3m;
    zK!5FjPKtWZA`{Kj+Q#n)YsTAN9aTx9N=$Z^UnR9I
    z%M^Et7G(TfAI+(NP=v0Na0
    z`TRZe?=Ktf)8hn(jQjTql0Q8T65dUnqHm;O&MYL;oO#Bt02X?AeXej>i@k4#-eHuH
    zmU;$==rf8p0bhq2Ul@jsr45?cL4EFPH)3pGHfS$W9iYZZKd$nJjS<@QtlDy88c
    zyn3s~il2!Y!6>g#UyeZWyf($O_I?Uq=P14J9WWKlef~T&Uw=DG0_F>tpDEx57oZ0aaBOA*(ywUE~oOE`uaW{
    z2P?GF4A2|dJ;Z6K0@?~MmQ>S9w$@}{-axL)k2xnKv
    zCxxk7>031DFP}Q9L$&oz|6_ZH-dD%cJB{<|4b-okD<5?*DJnM{yWe3_bo`22!ehOy
    zc{jE%WQ(Lw#~tAr`?|8aKgsMkO}3(K?Cf9v#ho6L3cd|bM`T)k6ZI$^v9}Al#^&9h
    zo=;CnIjeju!$@TKc0xjDY&;!-awTuiS@G!TX#enR+jzT-Q&
    zIeV7U*wSKfE54x9es7Usp`byu$G&4Pg1LF$^8_3A~kNred64Od^a|pr(gFcQeUeK
    z=J`SJ*z#kTxW$ubX62x^W&5ezTv4p+vxSy3D2bYpDwfKJ`eo%IR$pk~Xo4gMO?OYw
    z-r%9X#z?eNKc^=!@Rud%@qc{v{Q-#uH@)_s^}C6GKW|H&!M`ub5*nLs5d9p4=kG1F
    zN9l;8v1htJDgS!|HIHukH(#Q}#AsBSRq>uj>F(-ECecV5r^da+Pf1rNU!h!Hd$OH}
    z%5?{EL98;8SNt1V7;)yFufkHns7rM!;5
    zw)A2h;RjDiQ~UB%>theR7$nkKivPZo);jUQb6jfpGIT>&it0W;+g0uSdyS$gIwh1z
    zutLg>i#)u&AK#3MnnyZu9F$}$D9`Gn?EBo>+WPMGYjtB2lOiV}@%(ld;a>Z4V^%J%
    z2}P)EmK8+#K6wp&tS-2J|9Ji2v~LQ(
    zdD2sslaqyI_z)zXMMrr0Q}5qu_z^ShB`7FZiodR3zuvejDQtPhWzv
    z8=Ug`F!24m6|kBA*yMCGGgAD*v=U;Bi}KN4Iu&T8eA~P2=9BsAXHi!c2;xg`ZyqG~
    zJ%9=f5C2X-l)#iDIV$VUN%WcRbl^wcit8pAN>S5}*?P`)Xf~fMNAY5R-A7CWp6ctyGB1$D*
    zS8uDn?2`tBVLV~q8DY&gx0;43VRYu}{IyEvoN$7>#v2Y<&wIn*@y~qua(>4KU*Jl&
    zRd@$5<5Ng`Idkmk`Qmd=-ScD>6sr3drw3p0x4#*jHj>n?hkYvk@dsN;zfz;-zDh3#
    zF^b;?1_l_sZ8LRucW2w%yJ6ENkKV^l=c4oS_JpZqfC6EnYiums2i{VgJEm0Gxq1|H
    zjQVAA6Z6;8(LMVGr=hD_0)z3f>bkd0`M0@g4yClE1ETsK+n&y);I~L0a%=$1%w+Jo
    zyZJ}+;1wA<%Hkv1uJ}O!>dF?%;YU==?wDPU^*ZQ*BAN=$V7zig6
    zXJ;Q%KM-YHlxQf#{POWm$vMWG2|0K=wvz1ORvDf4<73KT3AueojVbzi$Md#nB;@E(`14~B5Dt02+3?9Y{JR_mBIG~%=ETw{kKe-~{L^go
    z@bt_jTp5GT23y+Dj~=?lqrHv{kyWk;U}UV}Su2Tr!t}q$nX#S>rgv$MFo$QSzOpzX
    z>U4*hj}WE1gYWx6R57PExz$OZ4FC5%K~*!18yTto{dtNvo9KRa=dxbtHtk6I5VW{rfkzS0$6`gC4MkRoyRB8DZ&TrR5|diK+wZX#}Zw>17pg
    zaXYE%=<8Y$GKnh84LNpRNM3KC&FKo=e
    zZR-b4<(#_p1XkjX_mY3VG$gfHKY|HGsXQWNbx6p%Q@nAmFE2=J?HvF_O%N!hxH4O9
    z;w}|DJujvXKcI^T*pgJ(A?Wi(xM8Na>kB6^$GjeleS1(<*AF4)acSv5+}#mY&TGUQdSJ^YSgJ!utpKx@ci%c{38P)=AOAZ&vlgjswJk?RS+7?Xf#GdM;jL83dQiY&UAnnmGSMnaxyq#tTHsmP67p6f$r26v%jbONzK)&hC28%Tph?}iN<+p4lqIYyxvrrnhPYY3SIbCZ8K-*i9Y@hi&F#2*_Duwv5@~jjGNx{*$!OPOAM(Fn!+ju{Ln67@Ld>=s|iyf|n2G9fG57Jx1{PGbvcU
    zb2v&(+3bmHmN|CoQbQj}HF3p~F`4(PR+N#EvFWS15|W=0gA0tjaf80%(IXE7ihd^)
    zT8n>3-Nks_RhqRYfiR0;<>cVFbgsgAJpaSbA4tQ!e^BAQC5YkH%)Xxb`ubE@`#=)u
    z`LSbCD|!F(YH-ZOL|7se00-6*1nI}!yO@DSuUkb`b%`cOQA%q5Xs^6PB<$X$4jgD_
    zAdCPqDwZG~6G7NFs+MDQ8V$8yx1vxk$j$xEi3_)F%}^+}i*2;}g1X5&SLp5ch+M-$
    zFVMXFTye9$Rd&Ipy*(jYNESbw>ekmv|D~a%=Xw-L*1AQ*P{tE3V&
    zHMNMIo*qljr%y{e11Ep{=qAZy!Kd}~80UYqSqAq0sDrkB|E3Jr)g=rVXL53K*3y!b
    zjT$5kG5<^1K=e;c)Og^TsYgvtw&jG0?A~n=r!VX!vHJDZt36XAnSt*P9~u4sxBy(9
    zt1C;E>9Sru7XG5B$(O4azNS$TM|3;RMAGbsi+j
    zcllfaR{{ApFfj*
    zcpSS=x1PksR%r=%1mbc2g`LCSuq8!AbYDC%vzaA-iP?1A?t-u|^*3;UQ?Q4l$O&z!
    zcbg+v#WQ|^2FgVA4-P)6#r21=pF4>m+w@{AsrV}E=-57AlSOF1yc+Q6FzzC#?;
    zSM*1u^F}h6$Hsszf842Hd46*3-X#dj+OUFZE!o##Fr$9<>{(XWoFq4X`2b`4qd4$9pg=Dg}`n%N=}0qE~N_IuC9oZvE;mhg24H*
    zhn$?8fAvnEUURtrANz4R?GO6BGt3dM?o?l1D7I!_L-js%(ZG=#BSL&}f!o%GIQvKm7G=Q77Udd_c<;#~(Zm95r)pQ%<1Hau
    zwCmTOG=KY{rNzk+@t^?Y@$|!zb&tu=WVnz*L&xao<>9f*;XJE^gv19pIl2=Bnwl}_
    zAdHnr;
    z7Ed^y4n3Ht!>`G}#euNTh;nn9j*h=rhM(a<{7%&1KS>ZUw?8Ywys?jA^X5Bub%es9
    zqCyy?>RV*_Md&bxG!f^HT&K3Dfy!`{XjP$^$CYm{Z(bn?xC+&f<(u7qE10Bjp1RetCM@^z)R|Biq3bvj0UTHeD#a
    zivhuj%hw>_rAke)>J}fVH(x;I&uOrT(
    z0-+;K%N{94>3jc9nCLq3)3%@7p-k=Z9j2@$A$PxRQ!B`o%^O|W@h9s
    zP2JT3-DcEx>4c@FWh>jy5IN!qXx{0e-vtB&679;3rD9%hFLUngp(9RyWZ0PE=ZvaP
    zA!UX%1^XGwE*Pg_#X!WSr1(~3l?gF#Jf5W^-}%drCF0dYXJJ=Bcy?&%C&zcd%5wnv
    zSB`16wqz(g6RaC-yn(CR6B;_{A}1%8c{F&vS0
    zh_b$ZhZPZ23tl!~elZmxFE3AvpSAj;-+$F}?nj#$+N{HGKYTd&OKENCRGF^Ih5iQ?
    z^#az|0#>0axVJ1%o<3d2_O{9zg^3xd*^6jfg7a9_YVOUlV?Y6{AALfTtU^NLgQN3)
    z3Q4anxT#0(bkoYy+v-N}^6?d6SY3v@eL7z6H{dbPD>%Pljdzbr_XCS)Q0a&G`M6QR
    zO_K)%Fg`c6h`}&?#qPiBtxeZAZ2tl=bcRi_FXP6I+nTHntRkk~bjl{T?mKeF$>_~s
    z5Y5+uZ5@EI(Wz7i8O4@A-}?LO2BBn9&N2LWgItm}CVEZfUv1}vA4ODo_vUU|7!@($tuRJ3w
    zzrQOhjH#mx?Ay7xzH$=%xRK=-$$qqqq}neoElDk@)U_(D$=_%MYXRk{^X$iSE}g~J
    z(l}1XzSpMGXOb%}BV*##N@mlLJ7B%46t)F)y6R%*pXg}TQN3Y9O%Mamxy!6@D;qg?
    zJv8(;69dCIIr;XowjN$$J81pOA~BF%Rouwb)YO6?pc9aH_r#nJSJs@O0`&=2LzW$|VtYn}&yZqYMUrKf3pWu~KPv6-DkjBO_NLD47N!
    z@kV~!s|%$#A}Q>)Jo@{s8$&qa?fBDe5MhgfEy+r&3(HzqJ?U<4=kpL%N7dw;I0Ivo
    z){8V8C)6Y}Mc$
    z^7SeWUBhFSzJL?Tob15g&k#Y2i;Ko|=Fkk-fhHG-yf}sWrM-9}|0q92yl=hw&ySFp
    zc(+`?$}~MSwV&)uYp
    z1rS^%9GV&$m!z%U#9GcPz`oXmi-qOmI>Mea=*Ep3t_jEU`e&C6^D}^Gmm8zVO~MR>
    zZRgHK?i>y6N#HBbnkUgkOeCSlj$y(R@?)n%xOCU(*`WL<43NPr)dJO}>79q~z!3Zt
    zhtI`FpZt|g{yHpbW$7?mql~_=>W=%`(b1v5lRfu0>`zlyuir~*cw@1nBZtp#xOMSa
    zLC3+r_>9Zji6a^s8nuu#7cNhLA9so`#RjLM-C^Cf?E)Kq`67xOu)eZ%V=JFu)epZ9
    z_T{5wi{k3zv-Y9`H(`cju<{M!Jf>}(q4En5Ds#?mRyyY*vxc9w4iknnTI%Q5ySuxe
    zB6O@3;(y~sVrL&()lC0IBK*Zj@FX?O0&Q!#?cne4Kfb>sTQ#Cm-bF?f-KIcN6DO!C
    z3KO7A2f`JeOa9UdVjaiWs<|ht<|3P~iYFh0ll~e7WKcs^vU#oo85RfUKGY
    zo_fi2Ip&@yR9obl*We@ek>o}i&Hd*afNUOfzjIUw)LF7=H#)sUMf`wM5R=+r?8~h-
    zqg?g->lU;Pl+ePaI&)+M%>9Tvv}ccMai{%QITLy$1zBYF@0EQ?2?<32O{ags-Fyoj
    zUD#i*u%o-&UyK#^n*+^{(|9@TDvf!p>cQx4uxB2EbaR`5xQrHdN!Q%moC%-J6uuLm
    zQBU*{ge^G4f&`JGHEgJ(^WgJs52VeQ2_jY{dou~{p}7BSglg+udwY8T(35H=9GchK
    zQ9V69JtFHfA%AJ$MF{5wOG`j8X~PJNOPIOH^$1UEWfG0ljrc^1p$IMs7q0_
    zZp|=}8Q^9PY!_$x#tK
    zs1{BL+&bG;&4smW=ityJt@@weDxz})j0Xn0^yhB@lGw)0ZPvU(K1(GE6?}dK#x7u^
    zKTBzaIzBoqYb#BTP;4$$3JYu^_$_cI8Vni@lt3a`nS<|8nbdqU!mmRs6|l)7IEdps
    zo@0qv$LzrA
    za|0OijGg1bJNxp=8B}~rUGkh)kC^`)7}$DWk0K1>a;Go4{hE9$95z(zg$Mcl7Vln6
    zM|fPhiAWuu7=#=RYrjiiO_cZ))Br??)1r9*md53%1Q&l-rp#8C{iBO81h_*@Y`LlO
    zkt{gP`mUg$9RFRl6n*oi3`LK^9GbD3=2a314*`>uR#ft9m)_q!%FVvUz#+XqQDPW3
    zT_IK3Wb$)`_@m-mM?RF*K&r<<4!0`ODI+8(1eA6&arfT6dv#l;De2u^rIzY_f`7en
    z&lre0K;(Q>TqXAr1(VNLoKHfNaoyMY)5D9W%DawPTU#e!LswK+Un#^8@vnpWQR|ue
    zhJ{5w36Rz8A?3Js*uzMiM~@y&M{rINzSX!muTmV3=5s$Q(N7+s*4?4NrxvO4efm-k
    zvg^}Yz+k%bG8+Mein4G}Uy^A~J1nkD(NCl%z(W>DJUimp475YNr>Dn{hTz|`=ao`$
    zy*68Z-)Im!Qaba2>rz+U>`=00D5*oXd`
    z@KBHDNO|k82pO+$(CdO-2mC&uh^hegG+UBa3K86oSN=G}M*s2ilplTMA02y
    zOg(V6<#+az$vqQ0g4{8YPr*A?|M{gLuZQ5FPR7@RN^d#DXHNFoPx4CfSBCFu4=Mv
    z^U_AiO=%IsOcJcF^!-EAje?o~(O&Yu)CBzKP;Opc6lNQzyHIS-p%*vn076zlMKEtv
    z)wN6%?F?ZPb$4}raHYK(oy~a$f@cf;e+Vg$ahQ*QHDoR6xU;-i*h(Yh0gK({{+XA#
    zV>WCT$Q4R>8|GDup~VhqS=qD$s2^2w$Ie-6hb!DaBBR$GW7kqMB
    zKK`p(Q@kdIhAzJL&w$FawRr@etrh58?Fjk{Mg@;yt;q9YYn7Odm~5Umv-R&7$J1OO
    z6|e^=wNZf-A+L}96?10iHf%Lz*;gGR+6*_nocr7r=4F@)(go{(m70s3s%|B2XN};k#wP&n?$_1TaiOLx^kNJS4&D<=fBhZD*UMt5inM&h
    z8*<{(1CA8w@T7!Pf(dt8y4;$_&C4*VuZMCpa9Lyq^gOPcuP&Seks?O0_ZbRH2WUKc
    zP<|F(o
    z)=HHJ@GXXitv}h4g?CU;kcRc=pMts|7Ad%Ye^ZQpq#C5NPu)04$sfry;VatnE^i>C
    zB>dwKE0i3ZDi1C&jBGwg|y?Yy<;)ZuN`)5FdW1_3u=9HRG!G7t{@d*f6rNJ~)*T^W@
    zLD&gTYI+G}aX$RLC34zPyNF@t@g%>~UdqeHd)KYRL_=~JKf(}r8%5vV>CG8m{DApT
    zb_+D3Vj?2{-JA5&)zu|m^ujClFYTJZ(>E;qiQsh(10J!xdnrC?eQ>_M6W?g{8x`l8
    zbgKmpU}{A4eoMB`Mo8a**4Y`r7;AhcO_$HPRG0U5RIc>*_ea6M`w26u{Y2D5bUQ;&
    z0}=X$WgE!At>}OF(R^h{NJwWe9aHhp$jIR*(!j9)<19DZ=($c!ayk*P#kk^AZg~HpxE9o_^^!)j5`B3kL-!Jd8U+RQEO2B`
    zk?rLOzaLW3eTJOvM!9YZAXa?>HJ8nAK@NkH&aM0#9fkVE`ADz;DJszLyCSG
    z?1=ggq)a+nk`Mn&zV_#xRrjk+)WwsNmqY+{JFrK=pq2b4M+}xQ4
    z4PCX%qlFVl3U|&g$bdTf^@-bHB{F(u;0~{CoWw5)^O7w`dylSC!9{SOPr%I>tiX$l
    z3n7tzM2*Yt38LiLgAQW_^xcva+aGpB(3dY?_QBtfiHZP^r2-qJ8jac^L|%o-l%`a@Lj)&PW1
    zhAEnqJbo3ufgWk)vaRh8=&#=wksZ`dZthwJ!W_a~lhLnZzkW3bre5JBNj(hXpZf;}
    zc4M$OoI^83ZHun7r8?=l8plynC$%W{q0^^NKN`n_l)HU=r~&oIG7XWNpC65ZZ(8VP
    zwr_P`Aet~blc)ETq@U1a3{?OU-vQpv;XW|)!j5Hid%!j#@E3P%MuwtkPTMast9X|i
    zSN}m^QI0B^goL<4E$5RP0F?3LdG51Vw&$n_94}*|hDOeehLHA|_;Me`ti^oZ)uy4<
    z9PQ>eZ{E-luF#s&5wT4}4w!WANJvhWuDW~Ie1XKnn}%je)^O6PZ}UNz92M#aeawVe
    z7(Y=55P^eR!LRHwzSHOA$#=!bXPp3EZhdTA$iH*vjtQm)&yYNm;=f+s$QcxP`EmE9
    zl$79Wg~WR{)#eBwL(SFR_h*+i1IQ9+Qb&r+!hIlZ8_jgE4?##@bIBKem#`Ty+R?c8R@)K97F#=
    zU||{XTv?+gcrfs`C$Ar;11CM5`2M|P8C=OnW-5Q(qw3|aM?R_9&=A#JQ|cVv(DdbU
    zxAuTa%AW=S?cKM^ssxNOC@$;UGOP#QlMEFTaX6mHd?`Fr0`VQn)vAMdxUDsadq-jE
    z(L*|Oj=_6Cj;d~rAg%=m%OSeRAJ@RXb6f!fQ~spm1jUuVxb{u*R9)Qg7}jn=)ZO_Hd%NUPoY%Aei_xRgUUEiy??bEU#9H
    zh3?Ov|By{jpTx)qEZhRq5hGC1(1CiXsWmhr4vTUT{g^)NprWF>8ajK{cpr24Hz4}|
    znbr6&aVo!j@fAS&57lmGG2%VLqI15V&
    z6frAqCrrpc-@VHd0zQ2FaZ4cO)ga_0shrZx(z&nutn(U-X)L
    zb+DThptFZj_tq3GOd1?^GjH?-N#LR2?o)Ysx`@QlSl`ow)0a55`6f169g@^Oy|&aE
    zNUibnq>9S*3J^=<*%N1`rd)4;r-9WmeCxe>c=jC0JWf{%RNVUa<4;e|OP8JqB9+~`
    zQ~+8Gm=v#$3f%hCoG#Z@>%4Q}tk-L#a0?Pdz;2P>HvesORJe#jY1_;$c`r1#7uW{}
    zlsGWNKqa(q39J}jWJ(?*a#T>+R)@=pSB2#2Yh&Z(KaZ8
    z8}J$d2oL$%?95CtJ3EGN-RO@>F+f^}@LJT1qrIH*^yl~P+xM9~z`_!KK2HWlMix*_
    z$k3p)EM^rqU6B$HXb3?W4OyMR#*l0w9v&VJ;@i&Zizk;0V`5`t19tD;y|y@kc&D@i
    zCx&uCllz_a7cH3zP75*L)}x4Q+OXm0<&k%sx`-1`N%Pql@`z`Gz`0zt`!Q?XL=dRI
    z#XuOCn)>~lu@k-}Xw5G7eEu8(?g0nKOYjdkffp%%<3tH`zJbad=-jjzMR}v&?_YZh
    z@7;Tkjwmlac54C2<$FRREX@+xtxY60iF$tRhh10Zq1$Q+$5sEr;o%d%sS5rGQQKXJ
    zzEA&y2n6EP0Mcbh
    z7>yp}D211Cz?epLnM2n*HzcK$-1hFmGvaCBiq50;Ovs%(SMZ??75f|BIGcu(JJ)I-
    zJecAZ%r`u9rU^j56i#W{E~vNi_wTbSKr+*l$AB~`IlU+y!NCbO&k-1e5zEJFqrU-<
    z7OAIFS(85c8$}@q9iZjBAX|B0^-swDn4FX(BgDSI^sFfq_7yUxl>ooepihx|5dDba
    zo4P_mf*Pkz)w&3ojblQ(R(zTE;d!yZDaiw160w4Q-7>SVFgc7R=!AC770sL(W>p*1#_5T>`oc|;?AklR>bC+=k_u})hy8)@Fojy@#+}=vgM)mjUekOx1n8%LY>08%7hx1CHG5XTuhWn;sKQ{f=mS!fiOU0Z4kDC3uapF8kT5@i-_-UK@b@@f&|f6LueSuWNB@;o$VN($k&)-u5wY>{Gfmhp
    zOekGg!s@T0aC1^FT&IPTMWc}FM@$3aGo;j9c1Ck4EWdyL+#&$w%`14FAG>hjLIe50
    zGRZa_xles@dD%NMCZ_KY+GrIb2uXVhl(8bMpIm!;Ekd(h*?4BoNA1@1mEQ1RC}^KX
    zDjj0lO)Et}ai4BsCiZ<~#6}`Y7Yvcmn3z+S&CL23!59%Z+(f1nOin@CyI6zD(}N&j
    zTwF{|+=}9O{JGlbC6rAGTg(q!!
    z?AVK4fZvJ($e||;D|!WpAVyAE>1Sy5beQuj$(RPDd?tIe;*~RP2S*~+V@bYfIV#>8
    zOC%T?xVD;u8hR1+p9nX`!?r{a(hY~rgbX-pKXIr#DFgt#qZJ(i*uf5Mm2ePq5|3emD+=KqEwXaW!S5R;SJ11%1!Gi}M0duXi
    zxp?u4A9|!(c?E@ZNIO0NV>-#+KHUVXaEagOT@Iqs5(DCN@I_N@
    zB1bO2aUF;KFqiH`)udbSfdWLMFEwRnw|@VDBQNi4W@I_G>E>^q;mJN40tyTdWq9~w
    zy1_1`%Cp6YOWM_Z3}Ikn6fZ5R?7!roE>F<{i`W4Qh6A{?hh%9k#jf3g#h7@oi&fOv
    z(-KAcE}W)t2e8_LE^6$5#XKfWg#;}(
    zeDHeCryyClAg1<`KOnB4z~&FyXl_;!BiEvng2A0S0lV6MW{r9K3uvd7ndIs1mG%B5
    zEd<)mD6aH3JNH$29P}-YlO$Sj`t@s|Ps=nQBg}#`EDrjH`w*aog}sn5+`W7EE4J64
    zH@Gok7%pTq4Q&)Sd1q08Y~Fl_vK4;lBp(jV3#0?Qtd$bKZO4wGmD;txCHE$*+^pXn
    z!4^?d_Ap{Zjpd1xgM3~`?Hb94_6}(M+mg$z$r6^U9MCIe;^Z=~>Pyq#a-daNwR~u0
    zZr%@TQO7}CTsrWor3VxI6Ij42<_-?e2GOXwc7r{C)y=OGyogPNcV|)!MxeRmFTsRO
    znXTMr3(v^a7pF02s;_^07PHYI5{AUy7d+V2RbrM|9`JX0VLc;*jHsx{FNZPi+H#hc1CT;t;2QBNOomCdi)Sdn<#I>p_
    zdg3Itu
    zST;(XdjE}=e&$8G0*BuIQ!FAz@AFG{aYwNo9B%c2)ktgT$NU)3JI1kG2Q~901O!5V
    z9FP$g7q|F_TD-M-Cv=3aZu9QyKL67e6Bg|P-5y+`uA3+h9Mt%HS&DBJ$@
    z$&*X5S~;jEV{_#ZeWJAl`204P0Qdvj`ykNQez&^edy(LHlN9Z`O*g2MuQ0v*a#?d{
    zgDT0-ChV@7?_@nXvK9IhTkrlYK}%OX2$DGtQKA6d0@Z%yi!Wt}ACbKO2;NcDGo!z|
    z|49}JTFSaG;{88$A(6Z8_eMARyF(pP@1PI##Pc|6OuJK+)U=qUm~La2bga_xF->1!
    z!y7(d&%)5N{bAHQp=TF#!`G5;r6E4q8PpDOLWhy?EMCg_;*>C-o=e2|2qPZYX^@R631qg*@K
    z*?Y!NM6luju>^iAY62>>N4o)abZ_Y9Ev{laC~P$_nBE)S&@^}{xHn$_Hk_feSGNVz2VtVY%2pUo5Yim;<3&N
    z`ycln^zEFmonlhlRtHjOz{T0Y$~qGJaEEBigycF9h#YNfMpBZJd}u~Cr!e7jU~JQ#
    zit%@$3U8ZAyM^}mB_Uy9<3n!WUY4VM4ZM+Op-7H+1OTk_3kVGJCx$LPK-c^=BO^m>
    z>(;H8A4oQbb$1(0;%x6Z&QqsaFB-8A2Lp~&+PBYv?_5b&Fmyl_6ahA#)?Znq@`?4t
    zioZj7YJ7ZsVftYIj326AI@|=4BfRK7zho=>3h|8hlwo8*v!Y
    zuFpFUj=wkG`cDx-GKBF5=+LUPToYyfX(WzpqD^Wczxw}IP5k?7Re1lR(#K73IllEA
    zg4nNBzDIMJYK~;!2u^m<%+`vXvYxhMzz}3F+)So}ged*ao%49d%Ya^XORLvw$)l5n
    zl4wwjB|GnRfa?R(=IfX~JlQ}?-A0a#fkV-qcqzYyo>rb9KB62yC4-z}9+%9D
    z`f+nu>|Ke2B@LkmBv*DS94ztk^{-5^_`vqkUn>Pd)kj7mjw>yZC~Ci~(FEs7S6n+5
    SEeuNtLR0mmO7SuCYyS^i59aLv
    
    literal 30647
    zcmY(r2Rv1e8$W(YWo3u#m6eRL%3cu}(LgBomc3eN*YRecnS5WXcu5uboB|2)6%
    zz?TzfEp=7YA@ZNJ+T2(06A~v4BUcoPj28JH0V?qgBmD5>!>ihAC#Oy_5}zVIUQ8E(
    zLUEw3sw&;^99pQgwlnGXIy|f$7JB3N`OMN3S4a&1S?h%X=b62QW*tItt=_X&$4j+`
    zIDS{tt1*-a3}uJ}WT+XW2@DY`8K)_RW*mRhoHWI2{YD#$>8BUa&#KX+6VRWMZ>ybq
    zb4a$?`B>HU)mE5X=Jq3$lBAma+M6|@IiF5bE5FU^afyI{0FI6zebO@frjJ58S7_5q
    zPoM_MI=)zOg^7u_iVABpG89O#;3n^jpIKX5`*7>_?T*yL?Nk+kmNV8gzT>#%!-M5%
    z+q=6=aq;n&IJLjG%m0mzyk~EJN>^cDoG&Y+Vsp&L`R8Zqz2W+OW;MlE_l)SheSB86
    z>i=nN&DS5;Rur~fwWg8nYGSD0Qmcr2VD92VQ@8ATD^UX#qj|Qyvvcb2Oxv3%-l6@}
    zORgv2C3vp3Fu;kLS5`#fS)n3z8U)|&*V?(coffIv;ySKK8?C_uCovQVq*7+`rcq3!
    zB}FL@d?V(hL%~mo(@`AAm*Xf-_`;7OM!pazB46NPOL}2qZ-|sN+NO=;f^14(a1cu~
    zYb%!;!~65YCRW7ChO4AcPm`0F@W%U_@)QHAPun;B__HT=J|uGKl2G0a
    z(z@l)^Cp@CZ)Ck_7=%rz3^oSs=#!F?(ABotbUQmcA3l8G$VpT<-0qt0R%dUSoy{>+
    z-uU+~c$;yk+I#(-VTQ-UhYe-+c6N5bEf0KsSuS3@$W@66`_x3K5Z2P7_D12cnX0(f
    z90e*YJbZF*du>Hlcj0GdeLXvT3g`_ufz!;_)7V6Lza8T2hW1*2$R@z#5ABm)Bj
    zbp2=ESmdpOPm$V3iRzw{lb(<;e7+AK(iImMbGcf=%q^n^zkObC%e`@R8;WC$!H45u75W`6W+Ujp9s0~
    z_EvE%^i3flB-gaHLrC&Ch*!3zBe`ipg3psrPfx>1lzV4&#F<+9`shGk{V)I5+i?@xsfMFR%EV|9lsa-59{2
    z*Q6ySc?H{Fe&P8zm~U8gN`y8=&gWJ;&R3qYYpq$5G(?p!b$4WLqK~1HM}I@8t?GAV
    ziKD(|`B=Zjnqzh3OECvklSGNmbu^ZGy{8@PurU(7VNjLB%xd{%5Bb%o`WNoqEB}_S
    z%up=HSvSsVFxo0F#hBQ+vkzCM2tZBPMV%*I&UY*vRXC0!i>8{Dg<$rc+59wbY^0VUa@O>DhHi%Jpv{LjIRkV8mSnOKU=exTZm1Wt%ATFp23
    zdL2AzHqIS5LgJUpD&<&n(qH@mnnsz}hX`NL!hNb#V?-K;BY{zICN;#?Oau)dvMd_|HaF&}{ctdD+KENI9cLvjC>dgG
    zD;*IK5kc`ftwI6MZohY9mEk?o>ob)_OItr#dfFBusz9lXt-EQ7K+
    zxH?;Le<@I2v$e(fO`>*)MP6y)O!*DRuog7xThti8m_^&i326#6mN7=3YeTjx;DOPv
    zU%#XurV=~$xtiY2Up9;jg3}Lq#ORlr#QX{Gwz^TWteV*^HLEWW+R*%4_2Z|%gj8s?A>22dik;-eUgdr
    z^Miv&Vgym}fYH(Oea$KaA1W&a;$(0UtnWikhe&U(uXD0RPPf0lh^~CqHP_;D5_Lsg
    z-5JUfxv;kfe*6epT3X8KNS>TDf9UQ$g**^n5I<~g<}dTSx*F5=fJliWQD$^>lnW!-
    zdhg-G8$!IOZ}7Zpya5+2J6XKhnrzo!UpoN}jO!5-X2OdNjZH8t`e8ghfW=5~a+F%T
    zv7F4-zj{rS88dM?B4GK^W;L43JzY#___L*@gJ)(Q*xB2MoEOj4
    zO>O=8GfRVW2a4M7kHPmpU+nrnwZeFv0}(m
    zy#EMbEu0qy#M-;Mrja9eOnuKYNJvbiv8`%ld;^ts4Fw+uEuRkj`N1P0xM}|QHqXCb
    z_|(v>lFNLlzWNJ9X@)`>`g!-<+~7T^nPy#X*j}4!X~s|ydG7qqZBhM-RCB+nM@Z-N
    z)|NCcAD>!~kz~^8`O?YXAwLsjWMn42*b2>R1<@kW8WUu9w+KgexZ+j6npxpK=I3mP
    z-N@BfcMgA}{v{}9aGJ~dV$HL2aLPQmO};dp`f^p->H8v
    zt5{7#O_0C@h7JK!mw1x*349fFRwh0pY4VKXQsH?HdNc4%BP|-HLda245*GO`#thc|ZDCm&&AoyVO0P2jmcFBFPe|;Nxz%(bZfZZ
    z+G1MeMRD=K&6+v6lV6kf?U*s&`uk6|eRS%Q+gvbW*j*fGTqbknOV+8+Q)0V6GFx
    zKA)B@AHldYtxZgD^!yfIs+>!a!_@Yh*d3Oqr*mfY{`On^v0E3vZz%Na;bqhJi%rcu
    zBMmHulcD-Y(v#FaH#DFChF%wpZT>z8LpUclSBPVRi148X38%HH8&GH5VfvuCgd6`h?rdq4RU
    z2Ct(=mRd7SgfaH^+WE^j1WzO2VMd25x`9ljMsJvtEpC0??G0etxz1$TBph5YCKXOf
    zLLM|BL>FF@7n)RZ(Z^roIZ&zM`zjAMwpyZD$ezso>zGTrkxhP~-aIctk
    zx-l0ZLLk|x7AxjX&hcZ40}n|!7pP?k(oh^XHUp_Ph>rj@9}3?wh;>o_2W`J?CP9F<
    zf0=(jw4y8tDMw%OD46cnO&!ea$9SIKipFDeMdo)J_^zObf?rOy2I!%X{sMh19hFAI
    z7x+Q_Kg^ynRq#BLfF_!UE3i^qIug!@fCZ)@Zhpc0Q;nM_DtazXx^h4&RFYTmJ99cx)jTN@r9k94Ud?{{;w4yzv2&FytEW%&Kj
    zSk*VE()Hi3ZsR2u3%1g}UkeMWc{dud7@R{}{o-L!jPm2h3QswxXj9?3XJlnn6QUnp
    z9jClJwzDh~7Fj)`ldbP%U_#OJ*nOv5Ioj=;*|($doRl;voJ1({gQ2RN~DofHpucl5n`6AkMhQ#+tuo6*>$olPabk@FrW
    zPoBJ9aLZ#+raV!Fm6cT^TR*r3&9%+C5hCirv!g1R6eM;c?}lcU)DX
    z7;9^i`T2Rznb_N^Xx7(W>B@EWGItr_k!7JCHkg-=9#V4
    zY0t&d{^p%d=ojyP2=?{$HSa)iQZZjdk&}~8K=GHJnluklC%LWO-x5BS@@8mOHqLQhlQnp_
    zhHU3;jRifF53_v3RJl+vsyWNu5*t@(Tts<-tg3t7?zS_&=B2#M*{-7O+&6IPXA+7e
    zG>J;txph~zdif!`}+*eT2I>QQ~(I*qBNL=FPK;%-228ivs?kh~K=G^&oTD}l;T?EF!6F;S6&TeQpi4l7)H{`ftg$1-
    zHXZVUCoTxXt0lS0XxkbjqwSJA=W*^I763mJ20-z#?3mzBc9NkS#NWERIng4-gixse
    z*4IBdvrs3Qnwmn@0hDb{(I>4mq44wb<9hPfkG*g{*HvqL2(omWPuneu(HVR~P|*B6P6Qlmkxgr|Vg!1rGWvw@5AN63GW7U^
    zXLNX9QMV<32w@0SQ{|%WHq?5d{(Iyq?<-(Q{Pdw*q3L~9aWeYavai0RnFwd<=fN{1
    zlYsKZz4a8LBSp_EK^vI!%Gj4_SIZj-fzo5{TbzILJ%Kiahy_R8*78BB0tP!wT|0Ar
    zI#Q^2n8xT(b721|j}N@CUsxC!8~*7kS>Ie
    zgNA_Cu7L-DFp<&N?q&*Atu5qnSw?0R@K?yz+tOB_@m(_n<8RNUGPJRdZ;DAsob79_
    zuBuu=9+84VX0fiWl>HK5uEsKZ+07AGIF;ybGV~41~BxcdcJwGG5B5+h;)>;G&GKo+QPz;>DzTr>|
    zR~qp(${83X8Q@50*W}-;3nj5gE<*4PzLC0UFJl4B8Oo;P8gsgdCJLnkEFy*II)%Q6H$_
    zMK~3!5h(Ht{#S{5vB0>~Q4?rw=mqP#NNb^eD_~EfT(Np@e6Y-FuxtxK?DXi~L8#&l
    z))Z7p7-~ZK^Q_8JeuZINi=k_IXJX~Ceb;cYUyF-D`kKx6+=KM;^4sDA_z%b^|FxT&8~lJFWiDjk
    z8d4t&(Hg2k9v5DR87^5BMC;^Y>*^wa0Aoi4=tvFk+@X~jf`|QDURH-u5MKH{VSe%I
    zkB1jt=ZD`xv&HI5_yEG%XuTnLcE|(tt)nWhuD+8$M@M?RqVU}1NH{NYRcwGB_3}&B
    zKtim3sf5;7+{4SK`rSaf;)<3M_7f5oPBam|EpuY5v{j75OokK65b!_b(>EXEZo1qh~^^ZOp94yhzFRp*QE6-XYwfKFW(%Yr@
    z_Nk?KKA#TTtwVDY3Vp|u<{i4GMcr3KJp+QzD`Y3L@yqa-ns?HFcK-^uyI~M_(0j4`H(!e)smx
    zgUx#ONQHxn0TL971&+ZWVn)}NjKzu_VQl%G?#K-2;&->d-fq@Gu|3){NBF;8|4n{6
    zJ;B%ySS)9p44-aF^S^&3hD$Zee#=dp^@saX_7)EDa1h@m*VLxvz0T#}j%ff@VizxR
    z$H@RN`fO2L;WG2W(1gwV7pL$1opBmwSK)ZTis)J~Kst91*Y8de`olj7JXmte3|<;n
    zfFfi+jFvR*4}za=goz$4L*f2GDm#ytp$G#XOJIoninpWVn>lWE6YY0p$$3f%ak%YN2HEMFn7Li~b9
    z))d$4(DX?2)TkF^4GaV!-4Rk-D>=NauWM>oX0P>4q^k<##sz6WgBhvbX5_$6>1x|L
    z;$W7`Ap7WvbN;*@WCv^%+@2F1zrY~1fD|@P~{5iS!>O`+P%;Qr5FyF~;%WMQ+
    z*()T2F%Qo!Nf$+=04il;i#aA?GiY)nPe1{zEOt)}T+<+E$U>%n2R#BqoF=Ex8vLlw
    zho20ND;suYtu}_i=zr$}Y(d4m6;DA*z9YhcKks#*4S#QK5Z96DT}fD_L8VvmLED=z
    z1xp+$uO5whujaNE@l|AiP8STWfxd`i_x`uYk;(JAef2V<8?=h%p(2#
    zl_A?(Nd;wW;%il;sKP!bPhcb5Fzo4gSj=`ujn^6(LFqC%tLkBYrYa~S94SJVN
    zsh~?BJrTjP=g*NIRtp9KP~7hB>j+*X`U6czh@KXkUD_JWt$r`;2`G`0ufFiWz{eWG
    zMY*)(5IsT?T2x)ltVA^diWHaH+n7dhIqZf36LM`E2+@m)iE*;o=IxLz5>OQn_Vxw<
    zHZjQ6`BY|)t-B~DMgUibZ~KhP+Z%!dLqjxe%Yfbu4M22tcf=X5S7pFllybNjNeXSx|aW=oUSJpeIiVkm)P2T39Fy13kZ}D0IL;lK^F5VWH}R
    zV?^Gf!9gSJsdRl(7~06oeyxrn2O@_rX?Qz0UVRB(H_pCMa3b3m5E4vnFkxt!iys*M
    zjYl<_hW)&*X85hcJL}9)Oc|f`#SwE&oZ4SQ&C8wZ&e4}2kXTwd50PMhMcqvc|BZ
    z?2wr7f5ovN!*cOBDji|f%TDe1%C2DM1rd%3xO1T1KRgBJbR_7BM{OZsJ1Wk&g6To=
    z=!5}Yr?cKtRym^gFG!UMocJ1`SU=wx`gGc3=AFP6E9GXpcJ5t8WMiXkQY{QfQ4VLstY5&nVM!X!CUvKNO
    z+XY3mRTt?gd%(*)yj({9xNNL%dSqa+S*zt7E9dn==tdK$BtVbAP&t}fmH4j
    z;g!3(ByN#S2izUzxr64nPhsUONYH57I{wzJJs;cS(P~rJE?DvJ|
    z>*Mfp`mwGx3USNb4lR(y=Bu1cHx(<3#}ChC1O0>ncKcjf@~oo|W-w)VvOio5$S~jw
    z!0v}BYp&m_x{bM0yE4nm%geqk-C&TBXc%?j1jYbR1#7sVjTyMwzA)>vwcnbMTd=U1eEQL04D)KG5dSr{E!2T_S~HjXBNx;I@hQ==9ik;6JBN=V;cvh@g2!~KqEz4
    z8&0wI8ISiU7Y|PaaJ-cm51^kk>pY>Px3-*?alfAMl>IaLrT)*GcX+_UsNcL3L-}%{
    zZW$N`$e)ReqjetjC+g)ScU!#bE6EP~jQbCWls?RlEb|7tC(d&aPe^xaqaT*Pk>5kCN9&CC9OJPAmO!Eb^xC%fDMe@sitT2ID8Ud{&h*aXGzU
    zF?7DbE}{%~IsbPu5zgVT_BJb6xNTqdT~;`r{^jsny(Oi-zU}vZ-zWDm*&O`T(Wa;crg>v#{|6;nik%<
    zNQVA~g|4Wd7YN?0YUXupW}}I4|M$w`%E}OM7K4J0M^Ec#->_%%xJ*znx|erD5W%}`
    z$$+Hc3DF~Bt@m7AX~7jOB4H1yS@mNFa>BJ{6{ZAyuOr!MQzUj=Qvf;ftWJeM%wI9t
    zAe#CQ4a}y}@qu0{jJBPawOt$z6*R#uA4t{bYAbvIEehW@)RF0mcmdFwfU8T9c-%MH
    z8b*CdpIBw%43x`|m~Qkz5&w|;KiJ_<3RPoW6DFZ1GVm
    zfCqrZhji>!8sz~GehmSXF)dpX_>;R*(CK>c?Y1GP)a!;otT0}Vqe{l
    z7@Y(<+!;iiZip^=uyBQmJ-wpm{N60jZsWf>8#03BVAc7sMu)L1Um|CFu&hUnd
    zwt}}#L;%9hS~nx;4pt^5eHoRs5qAX6&T{)d`x$Xx`=pW{b9tX}R?fND7r$6eC~;hl
    zG+zS$tW5OQ`ya*~8tcXdrN3k~=OW;|&LpWg8cma;H4=xp9L}OGf7cVf3O81W+29{n|wKlj6pb<
    z=K<6lPT|1W7a}e#Gi~u|NacY$fyn-MO!#U+5iNj{{OHKY$j}|nsb&%+DUp@a#88PShVyq1
    zt2BB~>C)A0cQ`h#$wprSSp*;%I>SsL2;Qw}5G)X*bmG%V_GVxce%Ov~2p>4Xdn)_)
    zGdEI_pN>q!mc9U0t|xYq=lW~|I2xYI|KpV1{`m?mKfC7TnerS89xHY@RJ6}D;^e`f
    zouho`&*}wvKkHMaQ=g#2+R3tcVb*tmkDY1Nd=>CZCS-e1G%e-60iXulJyk
    zyjD55e2-qcH-r>X<#%O;X;Qcyi~Qe;EGLBRS`g8K(}
    z^n4*miXdBq-q&n_M<0mQ1#^X_gS9{c@>AwU^hA%O$JJ`j09oO9~X
    zmS(gZSc9ruoQX2vIppW;?(TLYb{@PEkSk|8tH<{pJX83lFv9UFP$!TJ3YG(POj7ch
    zp%Cu{NGc#ECX&BtnfV;e4Zx;izOcG^r}^7As;SkAgjygq_b-G1Jjd=)>_)^yf>j59
    z;rf++H}kM3w!@N-c6w8}hzYSRKQ{f4!$^Dv0Y`SN{bEK3+RN+AjEj+7`ryb&Gq8R`
    z^P>Fxdp0(t@gjHWvA?A$97Zah#G83vy`~399%Er~C2Jd{$ouLE3|Bno2W$f#;H_rB
    z@W>k=BqTHeb{w@Wl6$J~T+K*8K^ybj7__7gzoNEa6c-d8e#)fA^ZG&z+Ru*_kq{)n
    z+9WHGNb%b+iuSspnGX1!A((o+K4k7dQpA)j}stSjrMxy^-w>_T{Iw?B@&96Dz74iTuHXc6G
    zY8_(aW}CnABkjhLIJ$jZEc%5Ow~P8^Ve@NDSPIR-Vxdr70*z11ro^sEpph>V+*9EW
    zjCv*_p0zz24gVsfhESWm?2z+CZaIq-6ymB`|G#sB1oA4E?ezkX2=+J?X`C`lnRjn_5@}A
    zZS(sd?+jnw7CenW1#MnLH}6&kPJq68rqdko?wt=!X<8w47;*?lg=>j3fi{H>Vw|0X
    zE;vB(Wpm$H9{et^6f2c;@ofVG1O9VfXl#TkS4QP@RiU8?D$TgyMc+L?crD!YKm}B#
    z)Bz13emYrMmR#3bS}FZJZTM%_emC1S{?lNE_kOSKc4je*JKpbRy0rLOSCiYfZU0Keu%-_q)IIt21?h9rk;}uaL=m1zrd;R69+bAuXgk&k2fSrk{F
    zah=I}vV-YOS(R3EZHA6*H;rugVi*{D~JKBqKr@h(U_T8u?y`RDzci!3ZIqf6a#~cxr1z55wRo
    z2bGv%-VF%)3dX(&uKjikNB|<=war|Bk1`<=c%XhB{5P0KAHu?YOh0T+!<1{zu^%q=
    ziusURTG)|!-^8T;u!BpD3&Ks`9W+)&zy0)qy$VD_4Eru)
    zNgr2$E)PwqzZO{TfGepYacVDFP(TG>gf**1_A!Bl0eU9aCz7Wqi1Dy*HJn6Ly}r*7
    zOG6TkAC2gp;Euv`01BW_xj=vyq6cWP=uTD?i1+Fck-SsUXo#TU
    z!EvD
    zyFkpLZUQHl_$dnU5^XdEg7{KAm9MRyA}7BGfCI9H?KmX03-zCj+M5zN;Ho>^U!I2Y
    z=K^&-00hPMj*gJC+s(~Hov8{Uw+0{%lrlFtRB8*;yKZ?pFE@oohJXw3Bol7$UK@N<#+fqy5A<~ondEZSYK@pp_P7oP%}URtq!~<941nZ*PZZQ*r&`$
    zc|qPRXaH~-4Jc>EH0BxPBB^6uCwAWzCE5Ldy>LQ4oi`2e^Jc_bgoc8sn*}E!@;hjE
    zE5HAQl0U?bf4i<_6<%)>gcyuC#Ie?UfPfUd^?Ed@-A!r?fkm
    zQm@EA>0KzbGK$-hEo-FJez}oln{(P5qoGCYf6IvKs?RslP(pJP2TPC9!fA)w?08=t
    zqXLnesMbzy2>T?`u+4ymh}q==O%f7+|0U{s_L7LqZbgNVi4Z`^S~HsD
    zC%LlWi(W%($?TE^M1nvf0NoRc^IZ(^e-(-g&w(-=73z=^g%{<2A|$|qzZ>VALFh-0
    zPSEg73CRbFr*-|sr-qRX0$7!RM7|~S0?N=%+k(_D)y4%l!`V*AGdf`bLO|6gKA!&c
    zN*!?jb9yOG2p=<${;qP<-gP%vxB%xiS~J^e+;8)f+o($F(?CDIqz<;PIPZ*Z{{QTSew&qBE|x_H
    z(bss*hb19uqw0-eBoA@6v1i1!obzG$8!@AydknxtEtw
    zzLGnPMi2zBpEIB>B3#X+*88*q*ng_%f|y2#CK)aDu
    zHk-+^J%3>2XhH}d$^ub?GfOwaIlwzO=X=9t3EE&ZyR%JB{qbUh%M)uoT(*?}BSfHK
    z5fk?hfYEA2;0BLf-&}@j(o9aK+&t!Nw&_u$7A
    z*B~=I-;@%LR&eIGm{f+8W`9~qI6X`Q28_4sh4^htB9WXVUrb
    zHB<$)?)o+W15mplWrSqX)OtrpPZg9JUU2vDz}VW7fx0$ujrSEOQlJszGpXF%++3BJ
    zrB@)dH#^G<$4x>asPS#)o}P?Y!)y81=|a_DoI<*RJ`}hsu$B8G5EBJU1fKa}v1!>B
    z#Q2d_SzY{(0nS635UfV!bf?|~N*|Rwl1XY1Hu}s$sCf4-J8V<^`t?>Lm3UVNK>hqt
    z{D>5i6+biZkso=WDkw#+=qRHtQ-)T?BSD-5(zuL;x88p>lRz@Vm3z?kTMP{ell0g)UlRVyhs-&XmX0LT-ZH%UaThhKzYyT&O-D&{8}OB3ZmDu_`JIFLi-hA5)mBk~fl<
    ziX0<`X4LwY;OUsQnVdx#bM5%H2f%S{^=#N67zS#~hH-YxuY{)KcWlqkVhp*@J5!>T
    zAKL2-t)JkT<+hIVwhqEYW-pfZ)GBu@ai;=vkD@;vvB=-Z%&E79X
    zh_{%iGrIQn^^v-U*VGn$&VX31*qdj;dlp=Ki^Cig$2HBPAl`)<^GK@3@RP6z1j{}e
    zS{tkmvXHjLp@0I2lOJ53n0=!TSR5+79BalE5*=BsO{;T7xdBQoM~y0)7oK~2WAfdYi(X@c2*oD
    z0p3}BsX($lAbi~$mV{5-Zg<7_wyi0%E%y{xtc
    zLR+}>^XT6Fco!^}O?TqN3Ajw~OKPzE2d*|^@$kxvigHw~F)}h9iTx1$?pg!;v3aTo
    z@&>TG28%s9xz~l`#V%anN|ZrzjOwUT@93|)_eW
    zmQaS4#3fiRKs^4IObE_kD0ERgw%&GjQMHb@4`7dErrh7L$XI$5U-2^RizAApTBYIbk?VJLv^K92Anb
    zqUC3j@M%Ce1H=aq#SXwa!x>=6OVTas#OAen8q`N20pg$=Nv%QgsEP_2SnH5TE}V$kP3W$Wg;HTNwS*X8P#`xh=nxVF8z`8U!9;}6luU$qE_N}?s%hDo
    z9RBvhut*30U%gW@
    zoKSw~kpMOMW7fHRdDL+sy-SJe`xM%!FWu%t&L4Yb;ImjmJKTu0Rq>!eU-itej(z_9(5%Y3s(G4|m$MbM=Ifb&VXWbd(ppH*{SQtQb9|KgHo
    z&+f_4hXS-5yU|;{-aSe`v-7y$xc^VfT8(LcT!4x9+q<{tcRLFTvxjjv?;L`jqYa3{
    zfU&3Fz};53V>UDEaVsOpbp@qwJCd2ElYM>rf=6I27?zrxm%ryH*^h*OcKrQj@cnfu
    zEfc2b@t(Rf7@JyUL0GvB+4y)RtoC%@`QL2Vva%CW^r4fzsR!a>WKgYt1QrO#dfzhotrp*}ifuozjmO`svng{h?2EiQPNR&)>l=>ir3azXHZP
    ze4WkIhh1z02+(CL?|w7R1Vs}`_(NxHUIut{EPshNkZweFgzdZWVc`$MlMrXJc*1d(E3oEAZ5g#6vMB=_cAmq?
    zVhoJ(5mN({o`gXNt$w`jr#nlNA7x)UuE;EsFFVE!idz!Vh8ih~
    zmkJ(yBL5^bG9+Gn{s$z6C@7D_gQG|hvXTI=rGj<>@IfGRWZ%N*|78m1%itntv!z*>
    z-D8ebq&wYl>2b%K$i>8qi+jPzS*ZmVBEvly6lG>BY{NbQ`%NpA|927==zu?G(@9E6
    zsj)zC2<83%0|AB-B
    zB9Yef^KHOa3%$X;B#?2`YjNr;eWV+g0w&BP^0Dzh(Jll4u
    zd=nh`zmZ+D_qPp%hb$@x!~d^pjw|^?-$H~iiBPz#fkY!0^fqJo6)`C(K7DDtrg^5Q
    zc7qA*E?A?pV{Tg@X>8Vz{aEK78u`R-+fx#o*
    z`|5RpAg~XS-z0M(@|=SM1rQm#T&fV&=2p8fCD@`t#CR7=nB`zz3W0PU77;PkldZ)?
    z$gvO(HG$*=wpk;L1U-;B{`FiDY;zVjJv2q22519_U=DCk{QudXyVVPOj?~Pq1I)1a
    zjTg#Wrk#OLOI^$2AZ~uC4_Z(cqrvVSLB*GtTbfF?=X084^+j91+iAjzos~(Ln1s(u
    zXA+MkeV}5`mUp>2lxc4pq7Es6}Md%4~dJVG*!Y~-MbcO&RvOSiR-)@-gLz2Okmy`u@%WA7P|JEvA7157=F1kza72KIph&^;v3m+++gDv@c&frdZ@zctul6
    z?JWuhlx)}gqf22U%Mo9!#7Wye!bk32TDfm4b4qlN4d)E&@qoQqaD!{b9)Im~
    zyZ*{jrpze`lv*~j{VRUT0g1j+tm7s|!DqOB{G*Uz+|6RIb8Gc?$XRiaq=ihlRQOt2
    z$j!UWaSxPiYb{MSKGxpRQRRwGjP#iqQ`#@HX9gev@WuJf@YmNnMQ4ybbkxGH>5WT6
    zMJZuo-;DcP?S?m=OjX}5L{_O_aYbepUF*26P~UGdQ08Z_w7`&5`{#20C($vVAAgkQ
    zxJqdICSWVhegrVatXSCgBkVxYqsEkCgHua!=X_>V20Z?_uHHgdy@G`m@OuZ!Y5^TUv-Y^qCpenFgG_BJ0@iJ=hUBI-B)KPWo43>G0h5
    zF3dn1#qM`yKXzns0#br~#;Vt0#xi06{HxtaU)}{^&&a|uQW`QY2
    zpaftblq@4_`^ct{ny#*{Hi{(<;z)3p1z41jr(n2
    zGYa?-*e36-yVf8u4PK1dqwNP`xl(92C{<{E5iTEU*5CyfqydPfgBBHi&Y8T7fqv
    z=Rbj3D{NZDGYdJ50LVW;U=Z8i-M!lSg^kJo-};Nuh<}M+>aPsg&f1#xh*<|omp@Gn
    zud2_c#>9Q>kHAo1?W@o37!~ghkk(0L=&qi>^Mz~<4JBdP9apY}gQaMYuE=vKrO@$9>it=kyBhqiR;F0$Kq&-tiDc@QFl
    zKI&**i;t3^bWbA+J~L$xzljb(Xj0T7f3{6MjpSZz;Y)Co*$j!E*A0}N!a2L|{V0bl
    z7F^yX8=&s0*?!Q^1GIIWT}o0?b=qZrXMH#Yw}^EyWdPYZ;q`0kWE@;G*O3$^SZ}Q}
    zFk{Yl*9)L3?(qH02|7I3UB)3dSb=egY{l%*lT8P=
    zf>}>`Bvtjm2zE9|k-Quu#_4y@*_wl$dB+{20txwl>6DXoyGu>*c_91V7cT~=vw0CA
    zpPzOeShfmqTahLeN;#qe30M?^tyCho9#ZAQ@po%kfrS@a^}ElXJrfldCqiw(?UEc}
    zrPUIa#B;h3B>nnx!p$aMT}
    z6R9^@<1G1ICA?IBcwow~0&dXXHj(L4*tG3k&U?J_=_KTj;BX(OyKD=b;cfuP{Xk%4
    zY3sM{WA9f~uBHs^hg-PSC69XKjzW-FZC&e9h&jvpaTxEc>ng{D{YrBh~nTgUhBtJLPl
    zd$`9FZFdh`Iv9q_qT6OxT0|aEqgE^*Z&+@PZPuO4rh7DV`_XaB&Nl>z_C5jk9jg3Y
    z946*DzB~53%Xju7${%j;@mHJgl(FsJUy;KL^83GakD=h6L8%iSk2gIgy~dIx3Bguj
    zmxr4w!7rBP9u=0Qu|0eu);a+dv-LN=KJ12|TW`iA
    z_!uYpAMf|C`qXdwY$5LfEADQ=tyQ?E)^I{X!ob>Ed8q$RLJhc@IK(wURvAlu2L$5J
    z#gZP(Iv$)D-)fZ_OW?ct)fd7Q&WJcOksgi4|-~YZ~%6@Hq-N;49uQn#{b)Ug&w+n4;-QsdyTG>6#
    zy8_Gi&BUT_T&p>kk-??m&&74IcynQYH(=bXi@d#ogSlziTaL1;sePe#V`zS3JHKlu
    zzl*CMDKbVW2a`(|%VaDTCnoMS<8ZGY9QO%*y_5W++o~DNosbm~e*T7ZmFGxg)1@Pd
    zQ$^H$!SK1|aPDux{p*=mF0n4$s-eX}Pnpp9+_05wWJ;aohIJl{V
    z`qu{2bN7=Z?L{9S_y%VfX3xFr!iwldOn?XSN40JnL1UPg}cu4Mz$mcaYpFC`h-Qj{J)9g0eg
    z`mXQlabG__0u)S_mUlPbr}2~CH+oi7Br@CnI^f$kJ#sc#MxTv^PnE87U$b9DMfu-+
    zf9n$QK$YA46gX`vs;We?va;Wb?kX&d)mAswTzh@tIGhM2>ak=3m|f%^;|a;6Q@pQ8
    zke8{ULHvyT%+rn}DeiOU+7><5S5_L}erSfBQ+j70l>YX15gX6dryUjnW<@vyUAdrR
    zCn>&r?vt}fe^U}=4!;t{uE1tm?XDJZ;?%cXUG|0H@~+7g$GYtk)$WTfTd?ai*{iFd
    zwl>?*opqH~_F!vT+I6D3`(~=6S5YKkC9c&1=`1
    z;TCg5z`x*r3*K@|qt(wL*+i?JCx&U2PR{=Fk-T_tVI}_6tLb}j6qJ-jPH}_pZl04}
    zZKhRCA1JY=B~n6lt$mwc=NA-gggc#R6+WH}Vt6MD>l7VQ@4w$Yt;Sh=2=B7_;lV!2
    zf2`KW_Rse@()U09=;a(getZjF{}&&1HMQ8{i~&2zn3x#p5f8u~z6aZLKNE3mKKaI&
    z^bQM^`Mv@I)ZpOYK)EA3yhv-Q-OXin8yi7X8tiT9#MajN?GmCM9c){El>J_8sayWH
    zMU>oOtR^HZjQqZ(Wq>)OVZkjV7J%|kmhqVS)tw1OYfDQ@g2H2YRGL!2iMMykg%`IX
    z-x2E3HYPe8%aRR)&jNlP-4#lIUa-8dfMTO9UW8BiG6zS)qUA`PUlOsVrS-4^vJ=f`
    zuOR>b753eMSp9F<#~uk4g=CaUdMYw93q>i(UJcvV%!sUth8-bPBqL-Nj};*^NhMh!
    zD=WJwD$;wM-~0am{?Q-tJkB|v&poc|y6?|@4i9868e3Qhvu~3>Mj!rS*9h7vRAWTG
    zddN34HkRkhqS-r*;FonJkZcr)q2}Gmt9|@cTxzvwvnGh^*RPLp5k@$99kFVrbN+cE
    z6TlteDxZi$aDK~kjbPsV50wq@;5th&ckhN;cEYgBru5@&NH;*FWg+q0m-L*3HI8la
    zqu(_Ba_j14EON7r85HZq#S`BZ^f)Cb)>sxQbE&;6HONZHYqV_3E6V08>~!cHO>&mm
    zid{F#D9H_j=fN)wa}A2zM`hV$v6Mdz|lmw5HCh;0_4uOr9ZVv?N#m#ww*w$i15OCOufW?W{k*@r+THU3*
    zLABTEn8ba5*d3hyTLX#Lip=g(#HE>!hS%6pq6NZHf+`}-3$wKtjER+eTz_H1g|E+)nr
    z%ptgTmWwEPx1?c6O|@Kwpg)CVz0T?R4!iJ9}^Sv`oe5f6ep2!VT{|kyTWaR
    z5x2|6NT+)AsBnwBoXa2TV2_gw8;^KE>_3$L&Q<@HB=0#iF>%~>n!6--MWPC=wu1hv
    z8(Z#9V;ILL=IwROjiAKxJ-S6=6y%Q8He
    zrL#lIvFrBRsh0RSN{cpEql)o~m|HN41-U(YvQ2-(xzCneY^=FXM?<)|x$zvdX1+W>
    z5K-l~Oin1<
    zbRJV3p`yu+&TUVkI5St;}~GU
    zL)~jJ^QlnQ<0~f|mL~R(Ro2NRTwmOxjvj5)8;FpPdJm-En2QJ4n7sSq_D#_&hj8T&
    zUcXhwt~B1}-~*ws?X2t8No$;m5&1FQeaGot+l`bIq0zB1OyF?bWoKIA5cuPV$2$sf
    z_0JC#OH0c*{)b}izf@IKqoSizdxSc$V(10YD)RmQ{rh#TCp#Rkn-(X1{c1M@KbK^v
    z>F(ZwU}|lsci!4Wy(NC1^!oUX+qOl__LaTxaJS?MiVdzc7oz)N=Kt5gLO}o2J@{V^
    z;R!oce;j?KNt_KAdi~ZB5uI!mKV^}fa5rfT-Kxiq9g9u;E6#SaqoYIY!J<)~@%2Lk
    z-gJe9g~lZg27$F69o1RmW}Ni+M0&bNv0WQ&{|m2YZkHC&W~wr9)}V6GcX}Vwb)(X&
    z%Tt}};C5bL|D~p;0(~$Md1uSmkuW;vUQA9-{#xpDk+6NsqZ^D|+<<+ykV$m=Hl#vs
    z#UYmg4^d1NIi9D4*vZhkh?th0o&EK5x#XXv*>IH7D?Zx~@1N~0sYl;u^Ta=#Z8t7;
    zdeH7MdrzmrYd#WKIYu$}eV9YCLDLq$b-KpJJg*|Rku=xhW4Z=x3(xcQLn__o{pUhp
    zqM8X(EEvz%fCQY*rqgXQ)PVuXMJf6J#IX^h)
    z9KNH4dg8>18j^kB7YIjh6Qo7n{K*c$oi&5Ltux1s>z5N#pu+
    zfN;f`9L(wz9JB4&rf~Tf<}@V@6yYfI+vFpIvl(~ayo$qWlG!vQB0}wsfKtfz|HOGX
    zVMwsF+glI4w`jU9wtD^Jm_SM7@?3v7V*6u6#cw}<=9fxq$JGpLqpgfIbzo-EySg|*gbk;#ZtXzM}Zn2ifyG*?!KI2X6Q^U^#8YJ-BbI3`dI$o
    zp0FFh0QD^`>n>cl@VcdCN6V$Qwzi83!}ftaPOs3Ubem<k
    zZ99y&{T&hf=c?^}Oy}6OYllTN+~szBF0i00bs3zCGJ7;p=rO5_2+syj`vv)-MNXw+
    zMhPb*+PV+$@aTh$|QbAb6Xnlwp~Zo21HIyLBV!wNbY(er1VQ7et;
    zY(t2QPD(nx)*;C*mb15neP;TG1rNo9dh1+!vAv$<1|5WdH~Dx8wxgOWfV+rY2&X52
    z_@BL83u_Ye|EyE6>d(}UNRNDW-3dSdYEcnvoFgPH1gK&dzWp4VR_<6;S;@)G9ffT$XI%Z>Oc^*UF$G
    zWNUil$PuQdTxt=g4djg
    z)&s$-KcbKR&c1Q2J@Ygfm6iwn?*NVzJ9bBk5nQZe^SgKM4D{Y|=ArC{2tH}|kRF#3
    zQ;93TTyF&i(hx8+#%#rG1;%?b3R9V(yfd7She@ckdcuNHnn*X?`p$h#46zwaD=D^0H6=
    zYi@1^)jVMI6f*42zJ8?7X~R55KgZDCYmfZqA^;Qo
    z`Y~f2Egju9e&yN19;f?RV>vRUflXP%o(9J~4w0KBY$8r3Q67g<`zg7rHM@0Vg->kdPW{;H8+!DsUH>6+<
    zY25AW2jPeEo~)QvNam+dZc;2y&&d1a
    zq0gNWBu3m3^)R`roQ`Vkw{zl7vPBI%08b$avQ7P{3?&6omHLv^{4kiL1
    z)29Gujpgy$+)*AVzqEV%_U)XTHqjxygGa{ujRP-A_$*jstdH$KDrE$xt+n=P@Hdg-
    zLIdq0+WEf>)!w{VwY!CCTCz60{0Inp%@{r%Q
    z;Y!mT!QeE}F|BJOvg{;Z!R%u)C;&xAJvfUr1Fc+v)C?dGut&FvXw+dEvT)169$SbT
    zm}g20k>FhQl^&bX=KyeDTr)*k`3_yY4G33jSxEMX?vH!)a_!GLpy0qecQ~M?;mf%F
    zZxhM>hN2B7@%iFnPZAISF7BnnS^{iD+4R`lHw&^TxHO?frn_Btn}x?;leq#-;TDk2
    zfy8&${f1Z|>-|j#_}BQNS+x-JeF-Z@P8|dTX857?HfjWxp+$m?U8F4)fw=CC@Pif(i_YftaHZWj~os=q<8aa_ae9M7X
    z=u}HG73h$+ug&eMF55m
    zqi!MO`~za1vWm*}UA4_Q`^x4%mp^WJ^~%9E2JARWq}d`rTOOVU%*lsf-XXQo9EikZ
    zZ_a)YY3JTzQel)@$5082&`dsDWq6#{sWXTB{X@COCRM-w{DE)1+d(teQ*^vYS^2Sg
    zgsPTSq-``&a#2Bfa*wCN<(cR6BU)-Yw*Y6c2b9h0<_4;!Ih8*2{G1B-n)49Si;h4C
    zZ@O4oSs#>1@qRyZ$pPo>v({_e4UOMN~LnPlV06
    zc|N1GT=elV`Z2I%uir>Nr}ZHIor{{yevOTIM!m_WbR{!Nii%+J3`ISfU)VnlPH3Io
    zz0e0y`b@3n)0`E5yuRDDlJhYI$qUzaT!R+?n#NQ|!#0
    z9IERz=ElYXNl}ZdQEb`M%*+g-?#m^e^NLDJh3;b~9Wa-s;=-BV;2!j%VXCp-7%nKz
    zb1vxp<>Sh65t>=u)BW-h}q}+*|!lx=ay`?eXhN`tNJ*
    zf^zn5m{ZKFFI5sEnZ&S>e%aJ=4&fE|C`VCNq2Xy->y&$#WJW`K`zA~{7WOH=BdL>^
    z*LY`iKOvU4CpIT_j<-AKU-opXDG6yv>xj>!w16@eWv-O`U0vmWukc~}9dMUUCq+H3
    zMvFVuhO@S-zrH8LJx&v4W@qmzo<)Dh7tU;hs$=TvT?tid`ZTU-`MRR9IqMxtOjx|S
    zXV$#Nzgv9TqrYjgbE#Bs)6WI*hFjj&+(eE(6mR*t4~kv-YXX~nl(_>;^VD=jxy14W
    zt=Hh?X_GV32M%z+l0Lj0NwVM>otSVmjo%#JxX*W%Y#{)gaTE6C*Obf
    zA@umq@@s$PSsqMJPKG8Paxt9d_3`o1qFY0TL)#Z!nakki7196VR{B+&78MaDf)OC(
    z9=_Tgw%z;Dfh}w&b+|^y$1BG2ro?k?O{{Hgbr}LwQ?-SU`gp&Iw%~y-N@`+AOpT;Q
    zKD)2qCh!SdSH_9wg7+}Pl^<=#McCX<)kap}+e$p-s6b+*3{%JkR3Z#5;??tPV
    zj8$*3{ja~vgMzHrwYUlSQy7`)@j2TtA?K*gX}O~8$s^2XeAIME0Uw5zcdmRR^5P{v
    zNrE67Vje!^-{>c_YZoI~B_O5yx;oVyvU0#W)>bHnJCygF0#giO)`I(p^Bb_}Sn+@I
    zH|q4#xhDvf>X?oPX9v6+jrw*{Y0sbErzgbDZbmw1_lXk;Z5SE^6A23oJNJIP%&F%F
    z6o#lDIL>wl1&}SsKTYxb&I)O441ND~c$g;f9be5PXR(E!5vH
    zWs`5q%e%WVXWXhjXuUVI@LWZKH`Lo%S)xv#3TUvY>Xw$TpIeHmxIQ0CW*iV!{{0B&DX*`T|U
    zTLUgq4{^5;QYs>1%<^1E_NK09W_O^KS|SUQVoh^lqdrXvcIeGu-958wp{SQwfEcFdL1PUk(GJ#KGRz>Gfua8Bxs|fqlo~Ll)%24kW@*Y2%{k7gKK~P$oef&(2z+(
    zkU~eOwwH9YUL4&|e%h3d@7f0RX=!QS{QoWc<0T}tuz{iZd9%6LQ+QC+Q&hOr-XzM;$GZp=6r1;L9D6Q^uYUpDw*oCu6ZYyFP#-fy2lS^c?vkDopDThNrlGE
    zhf9IBk*{e0AQAWeW==U@!G&~%kr<8b(T~1OY*)2?5o-
    z(3q^%kvwPc%Hn#t1fMstdmxCRG*t)ir1SFH)%)*k$wfoMKs^#UB&_KB_e0FC{bqpa
    z)dPA%vkk`Ln)gs`_H&%14S4IE5UOvzj?ZXZ6*qt&#*-i)aRQWygM$M)!43t5t&+BHYe*;)ww+JLDFm#YI;x}R%tUyb#FKi~
    zyN60p$IXj+UaN3+s&T22b_u)gbUL4o?L%gyc!eDGT;SQfnNfvPN}?H41EK0Nf!V^G
    z+Vkpe-ncO|Je;cGOQk%9Y;6!Ze|`h}l9N95^W}#tYB3_eR#wpLUYf9tQ6J9_8VH`I
    zRiqdc6y(r#nMkpy4+72!zVNJDMoDhFrKCFYFVTHA_yK0TP0JIrZbUpihH7YFrz`25&*)EN~l9_xSpO=8WQZ`bfbek>t3378aII&)s&c
    ztuCG+B}bh7X>H{=ogh_v;N|KKR7Azu=)xOs0Ek
    zM;|gzZ^eg+fP4407{_-~tP6bym|Ce6F#m}kl_M*uAn2TGIz=_CF(Sg^;+*IXXmct+
    zQ9=3IwQF(9$rY+fTx9=Yg69lw#2O-*ROJ-TR28f``q87OCNr)sE-uVB
    zwCB7(O%*4BhzvoeO<;?{b^?ZyY!#xPlwP`shN`-HkS%M#pZCjtd?`g0@ggi}N6ss8
    z-YqN3!{20Y*o`2+wzdY3&$AtgY_@in-FYTlQ7r5Rsh#&~^-znBNW|HgAf?u0Eh#OQ
    zaDuNrMZ3vYo4CJ2)#6PCilX2kQHhDuKcsYT))GM$t}9E_QBzYxkp{=06({Stw_Wx;;_dAnJLjRT
    zkZ!n{=qyedOXX*)?>M1pzi;t1m4afIt)$3GQGUt6ksQA(B{~0<=-EmCvitkqVW7Cz
    zAwBbnja<%!cGLLB2}b)?TIxSsE`IxXYWS&*Tvx-V*Hov7*vD0_hBwEvUeI(19fCdz
    zMKa{}ZTfm~$bSDTw(rqAeE4v~y`kJ^)$X#Ue-QTzj42?ZVAbiL4Nk?&3tRFqqNH&2
    z$`wV^Qm0T7SCi8^4zGSYdIj@jh=5zS=&Ag*;%tTA4AnMNdrWR*7d(6}Raf7{r0?HT
    zQC2FIDsI`pj2I$(@Zd%=j^R>aq~wV;B3~aP;WD7;)I((;>$fm%T*Tz*tbI>)^NZ`i
    zDWV)ZrK{En$;&4g^C73+1QZ~TpQ0;rb2tO}ZVqc}5g4>9Nc@xEI?Jti-xUvjgAk6w
    zI-z`nEeUD^)NYX{J5BQqQGDT0&K?;UZN6K0v0omsf~?*kfGHaqZbq3b3W_zX8I+Aw
    zrXke;pWavS+COl0xt}jWx`s+2lgQYZTHaR`6&3cb=SXB<;*2h+=;&-%f+P-JO%TvZ
    z$kqVF2_(R~fAr`5ITc#$`0-*hRi6{t8p2>uK^%fw@nh<@d1weS2z*x{^)qq%k0L~k
    z%gevx9a7ik2b9PupZm_1HXs@zw#s|W-CkY2vWk|y50f6Gj4I)`EugD6?_To;_bXbGIOx
    z2g9iCd@J2d=v9ua^w|V|Y6#gN!l>-zL?MPiFYUUYZiA|W
    zwTzCB4+6;(5)vY?_Mps|0IMG%=JP*{;6F{dtGhC#9^rrF{|N)5D`YzboO6e&G-4>w
    zei(xSXbBQpAQj6Y=#r95
    zL4i0~O+<~Dh>D510hJ^A>N3IvkF2YZ@6rs5noa?9S-eN3q4t(p|3ZvSkILj1S#~r6
    zs0Ood6SscNNz|Z8={r_*)4JMQEStowUa{R4P~ub0-ZY%9aTgWfkYB&NJ?1|1Lb8G!
    zr~YA!CWl#~=g
    zJ}GkbFqRaho^?439;M|A
    z{;Kuyh(^m=(rxi^;EU1GQA}`}`CPsUb!Ta@)ofa2CJoj=u`;eFbz0m{fo4EM*lCk3
    zc%)ng__1h206b8?;_n%yooBeezVGF&7aujr`L??*Y(iLLqsHSIJV~%<*w{i&rh!Zq
    zb-ALb?`VJg#LODf-@<2V_vBAsY8BO~+Ead8&-WC0N{)S})c)u&yu1Cb_Xs`aBkG&Vt@$P<>8oIYpx7v
    zFU`H?Ac}IYBCYAA(-|76+F1qHqDIm%_M=a-o2qj2p@kG*Tb_yJ>89iPbq{h~`Jb3{
    zXs)pPA-8Y+GBf>$r3}b_YkSz=ff?
    zJ=$@pnHe^|$h=gFW7j9g?RTblm5H&DC
    zQ9#&_6bqnA1m%#ltk75Jz#t3Y4<2Nj1p4{<)}d1Ft7?FPl#a1+mIir8SpB0XPqK!E
    zhf8m50Co%SD|2y77(h&x%H5Kr#S}TBt*e`6vcu(LIt#LYriK8wS}leRMIyvQX)`G*
    z9hsiawXY%rq@?5~{-(}eMTh>&q&EVxp!Q_H^d5*z#nv{{wyA4D5NI^b*es!hPb`pbRlC+-m80S7R_Qn`z?0hJH$MjDu
    zQc+>_gbxYgHLqQ(c1Nwt)H&0qW68ePNd|hu7Z495;f^(f@Lmg4ARxL$-@SWuUHaxW
    zbV>lT0c_dHOQu(IbUsG4)2=Iz7ohU#gY9bBhbb*-fWqdaC`R@YrHOvj&EAPjgp$w~
    zI50xc=6zA7=|ttvuCsWF9Al|dk1GDK1WJ-v1q&2Y^zFJg@&u_M<&)wi*$k)H+OC?t
    z6W2u)&g@`97td6dP7D2dyt>cl(d7iwTa*@18vdp>-)tM4c|lvh8-J30*LIx5`gwFk
    za~2s!Pc(Mreh$&wDffenGb<^i*6`Vv%1`+@0LY
    zoR??w4ZVGRw<{|010Ren%ugxpZ3%zjlO_7EvvI9wX?`r{T))qe|6UqQ^ov!L;iY2W
    z{~)03&S})zr)i}|otG!3%9q=_J~z7<{88zt>~~2jUwo+~EG!Joi`O}
    z-@H$96!V=3n|hY!*l+Ln{!ct=g710ewR*AtIst%wG)>7k7M>9ac4!K0?d&hG}N??JykWo@qYlcJQutG
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index 5a6fe65ced..b354e29792 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -1411,22 +1411,13 @@ reinterpret_cast(it->second)->set_angle_z(angle_z);
     #endif // ENABLE_GIZMOS_3D
     }
     
    -void GLCanvas3D::Gizmos::render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const
    +void GLCanvas3D::Gizmos::render_current_gizmo(const BoundingBoxf3& box) const
     {
         if (!m_enabled)
             return;
     
    -    ::glDisable(GL_DEPTH_TEST);
    -
         if (box.radius() > 0.0)
             _render_current_gizmo(box);
    -
    -    ::glPushMatrix();
    -    ::glLoadIdentity();
    -
    -    _render_overlay(canvas);
    -
    -    ::glPopMatrix();
     }
     
     void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const
    @@ -1434,13 +1425,26 @@ void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const BoundingBox
         if (!m_enabled)
             return;
     
    -    ::glDisable(GL_DEPTH_TEST);
    -
         GLGizmoBase* curr = _get_current();
         if (curr != nullptr)
             curr->render_for_picking(box);
     }
     
    +void GLCanvas3D::Gizmos::render_overlay(const GLCanvas3D& canvas) const
    +{
    +    if (!m_enabled)
    +        return;
    +
    +    ::glDisable(GL_DEPTH_TEST);
    +
    +    ::glPushMatrix();
    +    ::glLoadIdentity();
    +
    +    _render_overlay(canvas);
    +
    +    ::glPopMatrix();
    +}
    +
     void GLCanvas3D::Gizmos::_reset()
     {
         for (GizmosMap::value_type& gizmo : m_gizmos)
    @@ -2349,6 +2353,9 @@ void GLCanvas3D::render()
     
         _picking_pass();
         _render_background();
    +
    +    _render_current_gizmo();
    +
         // untextured bed needs to be rendered before objects
         if (is_custom_bed)
         {
    @@ -2357,6 +2364,7 @@ void GLCanvas3D::render()
             _render_axes(false);
         }
         _render_objects();
    +
         // textured bed needs to be rendered after objects
         if (!is_custom_bed)
         {
    @@ -2364,9 +2372,9 @@ void GLCanvas3D::render()
             _render_bed(theta);
         }
         _render_cutting_plane();
    +    _render_gizmos_overlay();
         _render_warning_texture();
         _render_legend_texture();
    -    _render_gizmo();
         _render_toolbar();
         _render_layer_editing_overlay();
     
    @@ -3816,8 +3824,8 @@ void GLCanvas3D::_picking_pass() const
     
             ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     
    -        _render_volumes(true);
             m_gizmos.render_current_gizmo_for_picking_pass(_selected_volumes_bounding_box());
    +        _render_volumes(true);
     
             if (m_multisample_allowed)
                 ::glEnable(GL_MULTISAMPLE);
    @@ -3918,6 +3926,7 @@ void GLCanvas3D::_render_objects() const
             return;
     
         ::glEnable(GL_LIGHTING);
    +    ::glEnable(GL_DEPTH_TEST);
     
         if (!m_shader_enabled)
             _render_volumes(false);
    @@ -4059,9 +4068,14 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const
             ::glDisable(GL_LIGHTING);
     }
     
    -void GLCanvas3D::_render_gizmo() const
    +void GLCanvas3D::_render_current_gizmo() const
     {
    -    m_gizmos.render(*this, _selected_volumes_bounding_box());
    +    m_gizmos.render_current_gizmo(_selected_volumes_bounding_box());
    +}
    +
    +void GLCanvas3D::_render_gizmos_overlay() const
    +{
    +    m_gizmos.render_overlay(*this);
     }
     
     void GLCanvas3D::_render_toolbar() const
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    index 585c2526e0..b3606d014b 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    @@ -382,8 +382,9 @@ public:
             float get_angle_z() const;
             void set_angle_z(float angle_z);
     
    -        void render(const GLCanvas3D& canvas, const BoundingBoxf3& box) const;
    +        void render_current_gizmo(const BoundingBoxf3& box) const;
             void render_current_gizmo_for_picking_pass(const BoundingBoxf3& box) const;
    +        void render_overlay(const GLCanvas3D& canvas) const;
     
         private:
             void _reset();
    @@ -681,7 +682,8 @@ private:
         void _render_legend_texture() const;
         void _render_layer_editing_overlay() const;
         void _render_volumes(bool fake_colors) const;
    -    void _render_gizmo() const;
    +    void _render_current_gizmo() const;
    +    void _render_gizmos_overlay() const;
         void _render_toolbar() const;
     
         float _get_layers_editing_cursor_z_relative() const;
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index ca4c4a2034..3af5dfc9e5 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -46,16 +46,29 @@ void GLGizmoBase::Grabber::render(bool hover) const
         else
             ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float));
     
    +#if ENABLE_GIZMOS_3D
    +    render(render_color, true);
    +#else
         render(render_color);
    +#endif // ENABLE_GIZMOS_3D
     }
     
    +#if ENABLE_GIZMOS_3D
    +void GLGizmoBase::Grabber::render(const float* render_color, bool use_lighting) const
    +#else
     void GLGizmoBase::Grabber::render(const float* render_color) const
    +#endif // ENABLE_GIZMOS_3D
     {
         float half_size = dragging ? HalfSize * DraggingScaleFactor : HalfSize;
    +#if ENABLE_GIZMOS_3D
    +    if (use_lighting)
    +        ::glEnable(GL_LIGHTING);
    +#else
         float min_x = -half_size;
         float max_x = +half_size;
         float min_y = -half_size;
         float max_y = +half_size;
    +#endif // !ENABLE_GIZMOS_3D
     
         ::glColor3f((GLfloat)render_color[0], (GLfloat)render_color[1], (GLfloat)render_color[2]);
     
    @@ -67,6 +80,48 @@ void GLGizmoBase::Grabber::render(const float* render_color) const
         ::glRotatef((GLfloat)angle_y * rad_to_deg, 0.0f, 1.0f, 0.0f);
         ::glRotatef((GLfloat)angle_z * rad_to_deg, 0.0f, 0.0f, 1.0f);
     
    +#if ENABLE_GIZMOS_3D
    +    // face min x
    +    ::glPushMatrix();
    +    ::glTranslatef(-(GLfloat)half_size, 0.0f, 0.0f);
    +    ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face max x
    +    ::glPushMatrix();
    +    ::glTranslatef((GLfloat)half_size, 0.0f, 0.0f);
    +    ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face min y
    +    ::glPushMatrix();
    +    ::glTranslatef(0.0f, -(GLfloat)half_size, 0.0f);
    +    ::glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face max y
    +    ::glPushMatrix();
    +    ::glTranslatef(0.0f, (GLfloat)half_size, 0.0f);
    +    ::glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face min z
    +    ::glPushMatrix();
    +    ::glTranslatef(0.0f, 0.0f, -(GLfloat)half_size);
    +    ::glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +
    +    // face max z
    +    ::glPushMatrix();
    +    ::glTranslatef(0.0f, 0.0f, (GLfloat)half_size);
    +    render_face(half_size);
    +    ::glPopMatrix();
    +#else
         ::glDisable(GL_CULL_FACE);
         ::glBegin(GL_TRIANGLES);
         ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
    @@ -77,10 +132,31 @@ void GLGizmoBase::Grabber::render(const float* render_color) const
         ::glVertex3f((GLfloat)min_x, (GLfloat)min_y, 0.0f);
         ::glEnd();
         ::glEnable(GL_CULL_FACE);
    +#endif // ENABLE_GIZMOS_3D
     
         ::glPopMatrix();
    +
    +#if ENABLE_GIZMOS_3D
    +    if (use_lighting)
    +        ::glDisable(GL_LIGHTING);
    +#endif // ENABLE_GIZMOS_3D
     }
     
    +#if ENABLE_GIZMOS_3D
    +void GLGizmoBase::Grabber::render_face(float half_size) const
    +{
    +    ::glBegin(GL_TRIANGLES);
    +    ::glNormal3f(0.0f, 0.0f, 1.0f);
    +    ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f);
    +    ::glVertex3f((GLfloat)half_size, -(GLfloat)half_size, 0.0f);
    +    ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f);
    +    ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f);
    +    ::glVertex3f(-(GLfloat)half_size, (GLfloat)half_size, 0.0f);
    +    ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f);
    +    ::glEnd();
    +}
    +#endif // ENABLE_GIZMOS_3D
    +
     GLGizmoBase::GLGizmoBase()
         : m_group_id(-1)
         , m_state(Off)
    @@ -239,7 +315,11 @@ void GLGizmoRotate::on_update(const Linef3& mouse_ray)
     
     void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
     {
    +#if ENABLE_GIZMOS_3D
    +    ::glEnable(GL_DEPTH_TEST);
    +#else
         ::glDisable(GL_DEPTH_TEST);
    +#endif // ENABLE_GIZMOS_3D
     
         if (!m_keep_initial_values)
         {
    @@ -293,7 +373,11 @@ void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
     
     void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const
     {
    +#if ENABLE_GIZMOS_3D
    +    ::glEnable(GL_DEPTH_TEST);
    +#else
         ::glDisable(GL_DEPTH_TEST);
    +#endif // ENABLE_GIZMOS_3D
     
         ::glPushMatrix();
         transform_to_local();
    @@ -397,9 +481,7 @@ void GLGizmoRotate::render_angle() const
     void GLGizmoRotate::render_grabber() const
     {
         float grabber_radius = m_radius + GrabberOffset;
    -    m_grabbers[0].center.x = ::cos(m_angle) * grabber_radius;
    -    m_grabbers[0].center.y = ::sin(m_angle) * grabber_radius;
    -    m_grabbers[0].center.z = 0.0f;
    +    m_grabbers[0].center = Pointf3(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0f);
         m_grabbers[0].angle_z = m_angle;
     
     #if ENABLE_GIZMOS_3D
    @@ -650,14 +732,10 @@ void GLGizmoScale::on_render(const BoundingBoxf3& box) const
         coordf_t min_y = box.min.y - (coordf_t)Offset;
         coordf_t max_y = box.max.y + (coordf_t)Offset;
     
    -    m_grabbers[0].center.x = min_x;
    -    m_grabbers[0].center.y = min_y;
    -    m_grabbers[1].center.x = max_x;
    -    m_grabbers[1].center.y = min_y;
    -    m_grabbers[2].center.x = max_x;
    -    m_grabbers[2].center.y = max_y;
    -    m_grabbers[3].center.x = min_x;
    -    m_grabbers[3].center.y = max_y;
    +    m_grabbers[0].center = Pointf3(min_x, min_y, 0.0f);
    +    m_grabbers[1].center = Pointf3(max_x, min_y, 0.0f);
    +    m_grabbers[2].center = Pointf3(max_x, max_y, 0.0f);
    +    m_grabbers[3].center = Pointf3(min_x, max_y, 0.0f);
     
         ::glLineWidth(2.0f);
         ::glColor3fv(m_drag_color);
    @@ -720,7 +798,7 @@ bool GLGizmoScale3D::on_init()
         if (!m_textures[On].load_from_file(filename, false))
             return false;
     
    -    for (unsigned int i = 0; i < 7; ++i)
    +    for (int i = 0; i < 10; ++i)
         {
             m_grabbers.push_back(Grabber());
         }
    @@ -752,13 +830,13 @@ void GLGizmoScale3D::on_update(const Linef3& mouse_ray)
             do_scale_y(mouse_ray);
         else if ((m_hover_id == 4) || (m_hover_id == 5))
             do_scale_z(mouse_ray);
    -    else if (m_hover_id == 6)
    +    else if (m_hover_id >= 6)
             do_scale_uniform(mouse_ray);
     }
     
     void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
     {
    -    ::glDisable(GL_DEPTH_TEST);
    +    ::glEnable(GL_DEPTH_TEST);
     
         Vectorf3 offset_vec((coordf_t)Offset, (coordf_t)Offset, (coordf_t)Offset);
     
    @@ -766,89 +844,88 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
         const Pointf3& center = m_box.center();
     
         // x axis
    -    m_grabbers[0].center.x = m_box.min.x;
    -    m_grabbers[0].center.y = center.y;
    -    m_grabbers[0].center.z = center.z;
    -    m_grabbers[1].center.x = m_box.max.x;
    -    m_grabbers[1].center.y = center.y;
    -    m_grabbers[1].center.z = center.z;
    -
    -    // y axis
    -    m_grabbers[2].center.x = center.x;
    -    m_grabbers[2].center.y = m_box.min.y;
    -    m_grabbers[2].center.z = center.z;
    -    m_grabbers[3].center.x = center.x;
    -    m_grabbers[3].center.y = m_box.max.y;
    -    m_grabbers[3].center.z = center.z;
    -
    -    // z axis
    -    m_grabbers[4].center.x = center.x;
    -    m_grabbers[4].center.y = center.y;
    -    m_grabbers[4].center.z = m_box.min.z;
    -    m_grabbers[5].center.x = center.x;
    -    m_grabbers[5].center.y = center.y;
    -    m_grabbers[5].center.z = m_box.max.z;
    -
    -    // uniform
    -    m_grabbers[6].center = m_box.min;
    -
    +    m_grabbers[0].center = Pointf3(m_box.min.x, center.y, center.z);
    +    m_grabbers[1].center = Pointf3(m_box.max.x, center.y, center.z);
         ::memcpy((void*)m_grabbers[0].color, (const void*)RED, 3 * sizeof(float));
         ::memcpy((void*)m_grabbers[1].color, (const void*)RED, 3 * sizeof(float));
    +
    +    // y axis
    +    m_grabbers[2].center = Pointf3(center.x, m_box.min.y, center.z);
    +    m_grabbers[3].center = Pointf3(center.x, m_box.max.y, center.z);
         ::memcpy((void*)m_grabbers[2].color, (const void*)GREEN, 3 * sizeof(float));
         ::memcpy((void*)m_grabbers[3].color, (const void*)GREEN, 3 * sizeof(float));
    +
    +    // z axis
    +    m_grabbers[4].center = Pointf3(center.x, center.y, m_box.min.z);
    +    m_grabbers[5].center = Pointf3(center.x, center.y, m_box.max.z);
         ::memcpy((void*)m_grabbers[4].color, (const void*)BLUE, 3 * sizeof(float));
         ::memcpy((void*)m_grabbers[5].color, (const void*)BLUE, 3 * sizeof(float));
    -    ::memcpy((void*)m_grabbers[6].color, (const void*)m_highlight_color, 3 * sizeof(float));
     
    -    ::glLineWidth(2.0f);
    +    // uniform
    +    m_grabbers[6].center = Pointf3(m_box.min.x, m_box.min.y, m_box.min.z);
    +    m_grabbers[7].center = Pointf3(m_box.max.x, m_box.min.y, m_box.min.z);
    +    m_grabbers[8].center = Pointf3(m_box.max.x, m_box.max.y, m_box.min.z);
    +    m_grabbers[9].center = Pointf3(m_box.min.x, m_box.max.y, m_box.min.z);
    +    for (int i = 6; i < 10; ++i)
    +    {
    +        ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float));
    +    }
    +
    +    ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f);
     
         if (m_hover_id == -1)
         {
    -        // draw box
             ::glColor3fv(m_base_color);
    -        render_box_x_faces();
    -        render_box_y_faces();
    -        render_box_z_faces();
    -
    +        // draw box
    +        render_box();
             // draw grabbers
             render_grabbers();
         }
         else if ((m_hover_id == 0) || (m_hover_id == 1))
         {
    -        ::glColor3fv(m_drag_color);
    -        render_box_x_faces();
    +        ::glColor3fv(m_grabbers[0].color);
    +        // draw connection
    +        render_grabbers_connection(0, 1);
    +        // draw grabbers
             m_grabbers[0].render(true);
             m_grabbers[1].render(true);
         }
         else if ((m_hover_id == 2) || (m_hover_id == 3))
         {
    -        ::glColor3fv(m_drag_color);
    -        render_box_y_faces();
    +        ::glColor3fv(m_grabbers[2].color);
    +        // draw connection
    +        render_grabbers_connection(2, 3);
    +        // draw grabbers
             m_grabbers[2].render(true);
             m_grabbers[3].render(true);
         }
         else if ((m_hover_id == 4) || (m_hover_id == 5))
         {
    -        ::glColor3fv(m_drag_color);
    -        render_box_z_faces();
    +        ::glColor3fv(m_grabbers[4].color);
    +        // draw connection
    +        render_grabbers_connection(4, 5);
    +        // draw grabbers
             m_grabbers[4].render(true);
             m_grabbers[5].render(true);
         }
    -    else if (m_hover_id == 6)
    +    else if (m_hover_id >= 6)
         {
             ::glColor3fv(m_drag_color);
    -        render_box_x_faces();
    -        render_box_y_faces();
    -        render_box_z_faces();
    -        m_grabbers[6].render(true);
    +        // draw box
    +        render_box();
    +        // draw grabbers
    +        for (int i = 6; i < 10; ++i)
    +        {
    +            m_grabbers[i].render(true);
    +        }
         }
     }
     
     void GLGizmoScale3D::on_render_for_picking(const BoundingBoxf3& box) const
     {
    -    ::glDisable(GL_DEPTH_TEST);
    +    ::glEnable(GL_DEPTH_TEST);
     
    -    for (unsigned int i = 0; i < 7; ++i)
    +    for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i)
         {
             m_grabbers[i].color[0] = 1.0f;
             m_grabbers[i].color[1] = 1.0f;
    @@ -858,52 +935,43 @@ void GLGizmoScale3D::on_render_for_picking(const BoundingBoxf3& box) const
         render_grabbers_for_picking();
     }
     
    -void GLGizmoScale3D::render_box_x_faces() const
    +void GLGizmoScale3D::render_box() const
     {
    +    // bottom face
         ::glBegin(GL_LINE_LOOP);
         ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    +    ::glEnd();
    +
    +    // top face
    +    ::glBegin(GL_LINE_LOOP);
         ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
         ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    -    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    -    ::glEnd();
    -    ::glBegin(GL_LINE_LOOP);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
         ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    +    ::glEnd();
    +
    +    // vertical edges
    +    ::glBegin(GL_LINES);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z); ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z); ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z); ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    +    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z); ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
         ::glEnd();
     }
     
    -void GLGizmoScale3D::render_box_y_faces() const
    +void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const
     {
    -    ::glBegin(GL_LINE_LOOP);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    -    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    -    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    -    ::glEnd();
    -    ::glBegin(GL_LINE_LOOP);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    -    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    -    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    -    ::glEnd();
    -}
    -
    -void GLGizmoScale3D::render_box_z_faces() const
    -{
    -    ::glBegin(GL_LINE_LOOP);
    -    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    -    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.min.z);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.min.z);
    -    ::glEnd();
    -    ::glBegin(GL_LINE_LOOP);
    -    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    -    ::glVertex3f((GLfloat)m_box.min.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.min.y, (GLfloat)m_box.max.z);
    -    ::glVertex3f((GLfloat)m_box.max.x, (GLfloat)m_box.max.y, (GLfloat)m_box.max.z);
    -    ::glEnd();
    +    unsigned int grabbers_count = (unsigned int)m_grabbers.size();
    +    if ((id_1 < grabbers_count) && (id_2 < grabbers_count))
    +    {
    +        ::glBegin(GL_LINES);
    +        ::glVertex3f((GLfloat)m_grabbers[id_1].center.x, (GLfloat)m_grabbers[id_1].center.y, (GLfloat)m_grabbers[id_1].center.z);
    +        ::glVertex3f((GLfloat)m_grabbers[id_2].center.x, (GLfloat)m_grabbers[id_2].center.y, (GLfloat)m_grabbers[id_2].center.z);
    +        ::glEnd();
    +    }
     }
     
     Linef3 transform(const Linef3& line, const Eigen::Transform& t)
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 736d05c275..77b03fa863 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -35,10 +35,21 @@ protected:
             Grabber();
     
             void render(bool hover) const;
    +#if ENABLE_GIZMOS_3D
    +        void render_for_picking() const { render(color, false); }
    +#else
             void render_for_picking() const { render(color); }
    +#endif // ENABLE_GIZMOS_3D
     
         private:
    +#if ENABLE_GIZMOS_3D
    +        void render(const float* render_color, bool use_lighting) const;
    +#else
             void render(const float* render_color) const;
    +#endif // ENABLE_GIZMOS_3D
    +#if ENABLE_GIZMOS_3D
    +        void render_face(float half_size) const;
    +#endif // ENABLE_GIZMOS_3D
         };
     
     public:
    @@ -281,9 +292,9 @@ protected:
         virtual void on_render(const BoundingBoxf3& box) const;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const;
     
    -    void render_box_x_faces() const;
    -    void render_box_y_faces() const;
    -    void render_box_z_faces() const;
    +private:
    +    void render_box() const;
    +    void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const;
     
         void do_scale_x(const Linef3& mouse_ray);
         void do_scale_y(const Linef3& mouse_ray);
    
    From 0c984c75841f2f691af5a73c070ba9d2378bb634 Mon Sep 17 00:00:00 2001
    From: Vojtech Kral 
    Date: Tue, 21 Aug 2018 11:10:32 +0200
    Subject: [PATCH 154/185] Print host bugfixes / refactoring
    
    ---
     lib/Slic3r.pm                                 |  1 -
     lib/Slic3r/GUI/Plater.pm                      |  2 +-
     xs/CMakeLists.txt                             |  5 ++-
     xs/src/libslic3r/PrintConfig.cpp              |  4 ---
     xs/src/perlglue.cpp                           |  1 -
     xs/src/slic3r/GUI/Tab.cpp                     | 18 +++++-----
     xs/src/slic3r/Utils/Duet.cpp                  | 35 +++++++++----------
     xs/src/slic3r/Utils/Duet.hpp                  |  5 +--
     xs/src/slic3r/Utils/Http.cpp                  | 17 ++++-----
     xs/src/slic3r/Utils/Http.hpp                  |  6 ++--
     xs/src/slic3r/Utils/OctoPrint.cpp             |  4 ++-
     xs/src/slic3r/Utils/OctoPrint.hpp             |  3 +-
     .../{PrintHostFactory.cpp => PrintHost.cpp}   |  8 +++--
     xs/src/slic3r/Utils/PrintHost.hpp             |  6 +++-
     xs/src/slic3r/Utils/PrintHostFactory.hpp      | 24 -------------
     xs/src/slic3r/Utils/PrintHostSendDialog.cpp   |  4 +--
     xs/src/slic3r/Utils/PrintHostSendDialog.hpp   |  2 --
     xs/xsp/Utils_PrintHost.xsp                    |  2 ++
     xs/xsp/Utils_PrintHostFactory.xsp             | 13 -------
     xs/xsp/my.map                                 |  6 +---
     20 files changed, 61 insertions(+), 105 deletions(-)
     rename xs/src/slic3r/Utils/{PrintHostFactory.cpp => PrintHost.cpp} (73%)
     delete mode 100644 xs/src/slic3r/Utils/PrintHostFactory.hpp
     delete mode 100644 xs/xsp/Utils_PrintHostFactory.xsp
    
    diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
    index 7aacd1fd90..46627311ff 100644
    --- a/lib/Slic3r.pm
    +++ b/lib/Slic3r.pm
    @@ -168,7 +168,6 @@ sub thread_cleanup {
         *Slic3r::GUI::TabIface::DESTROY         = sub {};
         *Slic3r::OctoPrint::DESTROY             = sub {};
         *Slic3r::Duet::DESTROY                  = sub {};
    -    *Slic3r::PrintHostFactory::DESTROY      = sub {};
         *Slic3r::PresetUpdater::DESTROY         = sub {};
         return undef;  # this prevents a "Scalars leaked" warning
     }
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index 89f803228e..dbdf0be274 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -1585,7 +1585,7 @@ sub on_export_completed {
     
         # Send $self->{send_gcode_file} to OctoPrint.
         if ($send_gcode) {
    -        my $host = Slic3r::PrintHostFactory::get_print_host($self->{config});
    +        my $host = Slic3r::PrintHost::get_print_host($self->{config});
     
             if ($host->send_gcode($self->{send_gcode_file})) {
                 $self->statusbar->SetStatusText(L("Upload to host finished."));
    diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt
    index 3558b6d3c3..be7b57b72b 100644
    --- a/xs/CMakeLists.txt
    +++ b/xs/CMakeLists.txt
    @@ -257,8 +257,8 @@ add_library(libslic3r_gui STATIC
         ${LIBDIR}/slic3r/Utils/OctoPrint.hpp
         ${LIBDIR}/slic3r/Utils/Duet.cpp
         ${LIBDIR}/slic3r/Utils/Duet.hpp
    -    ${LIBDIR}/slic3r/Utils/PrintHostFactory.cpp
    -    ${LIBDIR}/slic3r/Utils/PrintHostFactory.hpp
    +    ${LIBDIR}/slic3r/Utils/PrintHost.cpp
    +    ${LIBDIR}/slic3r/Utils/PrintHost.hpp
         ${LIBDIR}/slic3r/Utils/Bonjour.cpp
         ${LIBDIR}/slic3r/Utils/Bonjour.hpp
         ${LIBDIR}/slic3r/Utils/PresetUpdater.cpp
    @@ -417,7 +417,6 @@ set(XS_XSP_FILES
         ${XSP_DIR}/Surface.xsp
         ${XSP_DIR}/SurfaceCollection.xsp
         ${XSP_DIR}/TriangleMesh.xsp
    -    ${XSP_DIR}/Utils_PrintHostFactory.xsp
         ${XSP_DIR}/Utils_PrintHost.xsp
         ${XSP_DIR}/Utils_PresetUpdater.xsp
         ${XSP_DIR}/AppController.xsp
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index 943db2a302..bf5f734ac4 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -2144,9 +2144,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
             "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid",
             "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", 
             "seal_position", "vibration_limit", "bed_size", 
    -        // Maybe one day we will rename octoprint_host to print_host as it has been done in the upstream Slic3r.
    -        // Commenting this out fixes github issue #869 for now.
    -        // "octoprint_host",
             "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe"
         };
     
    @@ -2156,7 +2153,6 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
         }
         
         if (! print_config_def.has(opt_key)) {
    -        //printf("Unknown option %s\n", opt_key.c_str());
             opt_key = "";
             return;
         }
    diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
    index 997938a910..d6bd0e94c3 100644
    --- a/xs/src/perlglue.cpp
    +++ b/xs/src/perlglue.cpp
    @@ -67,7 +67,6 @@ REGISTER_CLASS(PresetUpdater, "PresetUpdater");
     REGISTER_CLASS(AppController, "AppController");
     REGISTER_CLASS(PrintController, "PrintController");
     REGISTER_CLASS(PrintHost, "PrintHost");
    -REGISTER_CLASS(PrintHostFactory, "PrintHostFactory");
     
     SV* ConfigBase__as_hash(ConfigBase* THIS)
     {
    diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp
    index 13b0ece5f5..bde4fdc348 100644
    --- a/xs/src/slic3r/GUI/Tab.cpp
    +++ b/xs/src/slic3r/GUI/Tab.cpp
    @@ -5,7 +5,6 @@
     #include "../../libslic3r/Utils.hpp"
     
     #include "slic3r/Utils/Http.hpp"
    -#include "slic3r/Utils/PrintHostFactory.hpp"
     #include "slic3r/Utils/PrintHost.hpp"
     #include "slic3r/Utils/Serial.hpp"
     #include "BonjourDialog.hpp"
    @@ -1550,8 +1549,8 @@ void TabPrinter::build()
     			sizer->Add(btn);
     
     			btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {
    -				PrintHost *host = PrintHostFactory::get_print_host(m_config);
    -				if (host == NULL) {
    +				std::unique_ptr host(PrintHost::get_print_host(m_config));
    +				if (! host) {
     					const auto text = wxString::Format("%s",
     						_(L("Could not get a valid Printer Host reference")));
     					show_error(this, text);
    @@ -1563,8 +1562,6 @@ void TabPrinter::build()
     				} else {
     					show_error(this, host->get_test_failed_msg(msg));
     				}
    -
    -				delete (host);
     			});
     
     			return sizer;
    @@ -1905,11 +1902,12 @@ void TabPrinter::update(){
     			m_serial_test_btn->Disable();
     	}
     
    -	PrintHost *host = PrintHostFactory::get_print_host(m_config);
    -	m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test());
    -	m_printhost_browse_btn->Enable(host->have_auto_discovery());
    -	delete (host);
    -	
    +	{
    +		std::unique_ptr host(PrintHost::get_print_host(m_config));
    +		m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test());
    +		m_printhost_browse_btn->Enable(host->has_auto_discovery());
    +	}
    +
     	bool have_multiple_extruders = m_extruders_count > 1;
     	get_field("toolchange_gcode")->toggle(have_multiple_extruders);
     	get_field("single_extruder_multi_material")->toggle(have_multiple_extruders);
    diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
    index 86573ff300..517f024864 100644
    --- a/xs/src/slic3r/Utils/Duet.cpp
    +++ b/xs/src/slic3r/Utils/Duet.cpp
    @@ -2,6 +2,7 @@
     #include "PrintHostSendDialog.hpp"
     
     #include 
    +#include 
     #include 
     #include 
     #include 
    @@ -31,6 +32,8 @@ Duet::Duet(DynamicPrintConfig *config) :
     	password(config->opt_string("printhost_apikey"))
     {}
     
    +Duet::~Duet() {}
    +
     bool Duet::test(wxString &msg) const
     {
     	bool connected = connect(msg);
    @@ -48,8 +51,7 @@ wxString Duet::get_test_ok_msg () const
     
     wxString Duet::get_test_failed_msg (wxString &msg) const
     {
    -	return wxString::Format("%s: %s",
    -						_(L("Could not connect to Duet")), msg);
    +	return wxString::Format("%s: %s", _(L("Could not connect to Duet")), msg);
     }
     
     bool Duet::send_gcode(const std::string &filename) const
    @@ -91,23 +93,17 @@ bool Duet::send_gcode(const std::string &filename) const
     		% upload_cmd;
     
     	auto http = Http::post(std::move(upload_cmd));
    -	http.postfield_add_file(filename)
    +	http.set_post_body(filename)
     		.on_complete([&](std::string body, unsigned status) {
     			BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
     			progress_dialog.Update(PROGRESS_RANGE);
     
     			int err_code = get_err_code_from_body(body);
    -			switch (err_code) {
    -				case 0:
    -					break;
    -				default:
    -					auto msg = format_error(body, L("Unknown error occured"), 0);
    -					GUI::show_error(&progress_dialog, std::move(msg));
    -					res = false;
    -					break;
    -			}
    -
    -			if (err_code == 0 && print) {
    +			if (err_code != 0) {
    +				auto msg = format_error(body, L("Unknown error occured"), 0);
    +				GUI::show_error(&progress_dialog, std::move(msg));
    +				res = false;
    +			} else if (print) {
     				wxString errormsg;
     				res = start_print(errormsg, upload_filepath.string());
     				if (!res) {
    @@ -139,7 +135,7 @@ bool Duet::send_gcode(const std::string &filename) const
     	return res;
     }
     
    -bool Duet::have_auto_discovery() const
    +bool Duet::has_auto_discovery() const
     {
     	return false;
     }
    @@ -228,12 +224,15 @@ std::string Duet::get_base_url() const
     
     std::string Duet::timestamp_str() const
     {
    +	enum { BUFFER_SIZE = 32 };
    +
     	auto t = std::time(nullptr);
     	auto tm = *std::localtime(&t);
    -	std::stringstream ss;
    -	ss << "time=" << std::put_time(&tm, "%Y-%d-%mT%H:%M:%S");
     
    -	return ss.str();
    +	char buffer[BUFFER_SIZE];
    +	std::strftime(buffer, BUFFER_SIZE, "%Y-%d-%mT%H:%M:%S", &tm);
    +
    +	return std::string(buffer);
     }
     
     wxString Duet::format_error(const std::string &body, const std::string &error, unsigned status)
    diff --git a/xs/src/slic3r/Utils/Duet.hpp b/xs/src/slic3r/Utils/Duet.hpp
    index 83ba0cbbbc..bc210d7a45 100644
    --- a/xs/src/slic3r/Utils/Duet.hpp
    +++ b/xs/src/slic3r/Utils/Duet.hpp
    @@ -17,18 +17,19 @@ class Duet : public PrintHost
     {
     public:
     	Duet(DynamicPrintConfig *config);
    +	virtual ~Duet();
     
     	bool test(wxString &curl_msg) const;
     	wxString get_test_ok_msg () const;
     	wxString get_test_failed_msg (wxString &msg) const;
     	// Send gcode file to duet, filename is expected to be in UTF-8
     	bool send_gcode(const std::string &filename) const;
    -	bool have_auto_discovery() const;
    +	bool has_auto_discovery() const;
     	bool can_test() const;
     private:
     	std::string host;
     	std::string password;
    -	
    +
     	std::string get_upload_url(const std::string &filename) const;
     	std::string get_connect_url() const;
     	std::string get_base_url() const;
    diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp
    index f5407b9fb6..a92e399a08 100644
    --- a/xs/src/slic3r/Utils/Http.cpp
    +++ b/xs/src/slic3r/Utils/Http.cpp
    @@ -62,7 +62,7 @@ struct Http::priv
     	static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
     
     	void form_add_file(const char *name, const fs::path &path, const char* filename);
    -	void postfield_add_file(const fs::path &path);
    +	void set_post_body(const fs::path &path);
     
     	std::string curl_error(CURLcode curlcode);
     	std::string body_size_error();
    @@ -190,14 +190,11 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha
     	}
     }
     
    -void Http::priv::postfield_add_file(const fs::path &path)
    +void Http::priv::set_post_body(const fs::path &path)
     {
    -	std::ifstream f (path.string());
    -	std::string file_content { std::istreambuf_iterator(f), std::istreambuf_iterator() };
    -	if (!postfields.empty()) {
    -		postfields += "&";
    -	}
    -	postfields += file_content;
    +	std::ifstream file(path.string());
    +	std::string file_content { std::istreambuf_iterator(file), std::istreambuf_iterator() };
    +	postfields = file_content;
     }
     
     std::string Http::priv::curl_error(CURLcode curlcode)
    @@ -356,9 +353,9 @@ Http& Http::form_add_file(const std::string &name, const fs::path &path, const s
     	return *this;
     }
     
    -Http& Http::postfield_add_file(const fs::path &path)
    +Http& Http::set_post_body(const fs::path &path)
     {
    -	if (p) { p->postfield_add_file(path);}
    +	if (p) { p->set_post_body(path);}
     	return *this;
     }
     
    diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp
    index cf5712d966..f1302b0ed9 100644
    --- a/xs/src/slic3r/Utils/Http.hpp
    +++ b/xs/src/slic3r/Utils/Http.hpp
    @@ -73,8 +73,10 @@ public:
     	// Same as above except also override the file's filename with a custom one
     	Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
     
    -	// Add the file as POSTFIELD to the request, this can be used for hosts which do not support multipart requests
    -	Http& postfield_add_file(const boost::filesystem::path &path);
    +	// Set the file contents as a POST request body.
    +	// The data is used verbatim, it is not additionally encoded in any way.
    +	// This can be used for hosts which do not support multipart requests.
    +	Http& set_post_body(const boost::filesystem::path &path);
     
     	// Callback called on HTTP request complete
     	Http& on_complete(CompleteFn fn);
    diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp
    index c62f9b55c7..db86d76974 100644
    --- a/xs/src/slic3r/Utils/OctoPrint.cpp
    +++ b/xs/src/slic3r/Utils/OctoPrint.cpp
    @@ -19,6 +19,8 @@ OctoPrint::OctoPrint(DynamicPrintConfig *config) :
     	cafile(config->opt_string("printhost_cafile"))
     {}
     
    +OctoPrint::~OctoPrint() {}
    +
     bool OctoPrint::test(wxString &msg) const
     {
     	// Since the request is performed synchronously here,
    @@ -125,7 +127,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const
     	return res;
     }
     
    -bool OctoPrint::have_auto_discovery() const
    +bool OctoPrint::has_auto_discovery() const
     {
     	return true;
     }
    diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp
    index aea2ba58f9..f6c4d58c87 100644
    --- a/xs/src/slic3r/Utils/OctoPrint.hpp
    +++ b/xs/src/slic3r/Utils/OctoPrint.hpp
    @@ -17,13 +17,14 @@ class OctoPrint : public PrintHost
     {
     public:
     	OctoPrint(DynamicPrintConfig *config);
    +	virtual ~OctoPrint();
     
     	bool test(wxString &curl_msg) const;
     	wxString get_test_ok_msg () const;
     	wxString get_test_failed_msg (wxString &msg) const;
     	// Send gcode file to octoprint, filename is expected to be in UTF-8
     	bool send_gcode(const std::string &filename) const;
    -	bool have_auto_discovery() const;
    +	bool has_auto_discovery() const;
     	bool can_test() const;
     private:
     	std::string host;
    diff --git a/xs/src/slic3r/Utils/PrintHostFactory.cpp b/xs/src/slic3r/Utils/PrintHost.cpp
    similarity index 73%
    rename from xs/src/slic3r/Utils/PrintHostFactory.cpp
    rename to xs/src/slic3r/Utils/PrintHost.cpp
    index 173c5d743c..dd72bae40f 100644
    --- a/xs/src/slic3r/Utils/PrintHostFactory.cpp
    +++ b/xs/src/slic3r/Utils/PrintHost.cpp
    @@ -1,4 +1,3 @@
    -#include "PrintHostFactory.hpp"
     #include "OctoPrint.hpp"
     #include "Duet.hpp"
     
    @@ -7,7 +6,9 @@
     namespace Slic3r {
     
     
    -PrintHost * PrintHostFactory::get_print_host(DynamicPrintConfig *config)
    +PrintHost::~PrintHost() {}
    +
    +PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
     {
     	PrintHostType kind = config->option>("host_type")->value;
     	if (kind == htOctoPrint) {
    @@ -15,7 +16,8 @@ PrintHost * PrintHostFactory::get_print_host(DynamicPrintConfig *config)
     	} else if (kind == htDuet) {
     		return new Duet(config);
     	}
    -	return NULL;
    +	return nullptr;
     }
     
    +
     }
    diff --git a/xs/src/slic3r/Utils/PrintHost.hpp b/xs/src/slic3r/Utils/PrintHost.hpp
    index 2047406356..bc828ea469 100644
    --- a/xs/src/slic3r/Utils/PrintHost.hpp
    +++ b/xs/src/slic3r/Utils/PrintHost.hpp
    @@ -1,6 +1,7 @@
     #ifndef slic3r_PrintHost_hpp_
     #define slic3r_PrintHost_hpp_
     
    +#include 
     #include 
     #include 
     
    @@ -13,14 +14,17 @@ class DynamicPrintConfig;
     class PrintHost
     {
     public:
    +	virtual ~PrintHost();
     
     	virtual bool test(wxString &curl_msg) const = 0;
     	virtual wxString get_test_ok_msg () const = 0;
     	virtual wxString get_test_failed_msg (wxString &msg) const = 0;
     	// Send gcode file to print host, filename is expected to be in UTF-8
     	virtual bool send_gcode(const std::string &filename) const = 0;
    -	virtual bool have_auto_discovery() const = 0;
    +	virtual bool has_auto_discovery() const = 0;
     	virtual bool can_test() const = 0;
    +
    +	static PrintHost* get_print_host(DynamicPrintConfig *config);
     };
     
     
    diff --git a/xs/src/slic3r/Utils/PrintHostFactory.hpp b/xs/src/slic3r/Utils/PrintHostFactory.hpp
    deleted file mode 100644
    index 4c9ff2bf24..0000000000
    --- a/xs/src/slic3r/Utils/PrintHostFactory.hpp
    +++ /dev/null
    @@ -1,24 +0,0 @@
    -#ifndef slic3r_PrintHostFactory_hpp_
    -#define slic3r_PrintHostFactory_hpp_
    -
    -#include 
    -#include 
    -
    -
    -namespace Slic3r {
    -
    -class DynamicPrintConfig;
    -class PrintHost;
    -
    -class PrintHostFactory
    -{
    -public:
    -	PrintHostFactory() {};
    -	~PrintHostFactory() {};
    -	static PrintHost * get_print_host(DynamicPrintConfig *config);
    -};
    -
    -
    -}
    -
    -#endif
    diff --git a/xs/src/slic3r/Utils/PrintHostSendDialog.cpp b/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    index b1dd86961f..c5d441f876 100644
    --- a/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.cpp
    @@ -36,9 +36,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
     	wxString stem(path.stem().wstring());
     	txt_filename->SetSelection(0, stem.Length());
     
    -	if (!can_start_print) {
    -		box_print->Disable();
    -	}
    +	box_print->Enable(can_start_print);
     
     	Fit();
     }
    diff --git a/xs/src/slic3r/Utils/PrintHostSendDialog.hpp b/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    index 7d2040d976..dc4a8d6f7c 100644
    --- a/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    +++ b/xs/src/slic3r/Utils/PrintHostSendDialog.hpp
    @@ -22,14 +22,12 @@ namespace Slic3r {
     
     class PrintHostSendDialog : public GUI::MsgDialog
     {
    -
     private:
     	wxTextCtrl *txt_filename;
     	wxCheckBox *box_print;
     	bool can_start_print;
     
     public:
    -
     	PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print);
     	boost::filesystem::path filename() const;
     	bool print() const;
    diff --git a/xs/xsp/Utils_PrintHost.xsp b/xs/xsp/Utils_PrintHost.xsp
    index 0c3fea137c..59c09c4317 100644
    --- a/xs/xsp/Utils_PrintHost.xsp
    +++ b/xs/xsp/Utils_PrintHost.xsp
    @@ -7,4 +7,6 @@
     
     %name{Slic3r::PrintHost} class PrintHost {
     	bool send_gcode(std::string filename) const;
    +
    +	static PrintHost* get_print_host(DynamicPrintConfig *config);
     };
    diff --git a/xs/xsp/Utils_PrintHostFactory.xsp b/xs/xsp/Utils_PrintHostFactory.xsp
    deleted file mode 100644
    index 2b083c957d..0000000000
    --- a/xs/xsp/Utils_PrintHostFactory.xsp
    +++ /dev/null
    @@ -1,13 +0,0 @@
    -%module{Slic3r::XS};
    -
    -%{
    -#include 
    -#include "slic3r/Utils/PrintHostFactory.hpp"
    -%}
    -
    -%name{Slic3r::PrintHostFactory} class PrintHostFactory {
    -    PrintHostFactory();
    -    ~PrintHostFactory();
    -
    -   static PrintHost * get_print_host(DynamicPrintConfig *config);
    -};
    diff --git a/xs/xsp/my.map b/xs/xsp/my.map
    index aefe7b3454..ba20ee2362 100644
    --- a/xs/xsp/my.map
    +++ b/xs/xsp/my.map
    @@ -239,11 +239,7 @@ Ref 				O_OBJECT_SLIC3R_T
     PresetUpdater*              O_OBJECT_SLIC3R
     Ref          O_OBJECT_SLIC3R_T
     
    -PrintHostFactory*            O_OBJECT_SLIC3R
    -Ref        O_OBJECT_SLIC3R_T
    -Clone      O_OBJECT_SLIC3R_T
    -
    -PrintHost*                   O_OBJECT_SLIC3R
    +PrintHost*                  O_OBJECT_SLIC3R
     
     Axis                  T_UV
     ExtrusionLoopRole     T_UV
    
    From cb138a20b861a288ba448666049e0df45fb71c5d Mon Sep 17 00:00:00 2001
    From: bubnikv 
    Date: Tue, 21 Aug 2018 17:43:05 +0200
    Subject: [PATCH 155/185] Completely replaced the homebrew Pointf3 class with
     Eigen Vec3d. Replaced the unscale macro with a template, implemented
     templates for unscaling Eigen vectors.
    
    ---
     xs/src/libslic3r/BoundingBox.cpp           |  72 +++++++-------
     xs/src/libslic3r/BoundingBox.hpp           |  44 ++++-----
     xs/src/libslic3r/ExtrusionEntity.hpp       |   2 +-
     xs/src/libslic3r/Fill/FillConcentric.cpp   |   2 +-
     xs/src/libslic3r/Fill/FillGyroid.cpp       |   4 +-
     xs/src/libslic3r/Fill/FillRectilinear.cpp  |   2 +-
     xs/src/libslic3r/Fill/FillRectilinear2.cpp |   2 +-
     xs/src/libslic3r/Fill/FillRectilinear3.cpp |   2 +-
     xs/src/libslic3r/GCode.cpp                 |  13 +--
     xs/src/libslic3r/GCode/Analyzer.cpp        |  18 ++--
     xs/src/libslic3r/GCode/Analyzer.hpp        |  16 ++--
     xs/src/libslic3r/GCode/CoolingBuffer.cpp   |   2 +-
     xs/src/libslic3r/GCode/PrintExtents.cpp    |  14 +--
     xs/src/libslic3r/GCodeWriter.cpp           |   6 +-
     xs/src/libslic3r/GCodeWriter.hpp           |   8 +-
     xs/src/libslic3r/Line.cpp                  |   4 +-
     xs/src/libslic3r/Line.hpp                  |  10 +-
     xs/src/libslic3r/Model.cpp                 |  54 +++++------
     xs/src/libslic3r/Model.hpp                 |   8 +-
     xs/src/libslic3r/PerimeterGenerator.cpp    |   4 +-
     xs/src/libslic3r/Point.hpp                 |  55 ++++-------
     xs/src/libslic3r/Print.cpp                 |   6 +-
     xs/src/libslic3r/Print.hpp                 |   2 +-
     xs/src/libslic3r/PrintObject.cpp           |   6 +-
     xs/src/libslic3r/SVG.cpp                   |   2 +-
     xs/src/libslic3r/Slicing.cpp               |   4 +-
     xs/src/libslic3r/TriangleMesh.cpp          |  44 ++++-----
     xs/src/libslic3r/TriangleMesh.hpp          |   2 +-
     xs/src/libslic3r/libslic3r.h               |   4 +-
     xs/src/perlglue.cpp                        |   2 +-
     xs/src/slic3r/GUI/2DBed.cpp                |   4 +-
     xs/src/slic3r/GUI/3DScene.cpp              |  81 ++++++++--------
     xs/src/slic3r/GUI/3DScene.hpp              |   8 +-
     xs/src/slic3r/GUI/BedShapeDialog.cpp       |   4 +-
     xs/src/slic3r/GUI/GLCanvas3D.cpp           | 105 +++++++++++----------
     xs/src/slic3r/GUI/GLCanvas3D.hpp           |  14 +--
     xs/src/slic3r/GUI/GLGizmo.cpp              |   5 +-
     xs/src/slic3r/GUI/GLGizmo.hpp              |   1 -
     xs/xsp/BoundingBox.xsp                     |  12 +--
     xs/xsp/GUI_3DScene.xsp                     |   4 +-
     xs/xsp/Line.xsp                            |   8 +-
     xs/xsp/Model.xsp                           |   6 +-
     xs/xsp/Point.xsp                           |  20 ++--
     xs/xsp/TriangleMesh.xsp                    |   4 +-
     xs/xsp/my.map                              |   6 +-
     xs/xsp/typemap.xspt                        |   6 +-
     46 files changed, 329 insertions(+), 373 deletions(-)
    
    diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp
    index 68136b9160..81a748f8fc 100644
    --- a/xs/src/libslic3r/BoundingBox.cpp
    +++ b/xs/src/libslic3r/BoundingBox.cpp
    @@ -9,7 +9,7 @@ namespace Slic3r {
     template BoundingBoxBase::BoundingBoxBase(const std::vector &points);
     template BoundingBoxBase::BoundingBoxBase(const std::vector &points);
     
    -template BoundingBox3Base::BoundingBox3Base(const std::vector &points);
    +template BoundingBox3Base::BoundingBox3Base(const std::vector &points);
     
     BoundingBox::BoundingBox(const Lines &lines)
     {
    @@ -22,8 +22,7 @@ BoundingBox::BoundingBox(const Lines &lines)
         *this = BoundingBox(points);
     }
     
    -void
    -BoundingBox::polygon(Polygon* polygon) const
    +void BoundingBox::polygon(Polygon* polygon) const
     {
         polygon->points.clear();
         polygon->points.resize(4);
    @@ -37,8 +36,7 @@ BoundingBox::polygon(Polygon* polygon) const
         polygon->points[3](1) = this->max(1);
     }
     
    -Polygon
    -BoundingBox::polygon() const
    +Polygon BoundingBox::polygon() const
     {
         Polygon p;
         this->polygon(&p);
    @@ -73,18 +71,17 @@ BoundingBoxBase::scale(double factor)
     }
     template void BoundingBoxBase::scale(double factor);
     template void BoundingBoxBase::scale(double factor);
    -template void BoundingBoxBase::scale(double factor);
    +template void BoundingBoxBase::scale(double factor);
     
     template  void
     BoundingBoxBase::merge(const PointClass &point)
     {
         if (this->defined) {
    -        this->min(0) = std::min(point(0), this->min(0));
    -        this->min(1) = std::min(point(1), this->min(1));
    -        this->max(0) = std::max(point(0), this->max(0));
    -        this->max(1) = std::max(point(1), this->max(1));
    +        this->min = this->min.cwiseMin(point);
    +        this->max = this->max.cwiseMax(point);
         } else {
    -        this->min = this->max = point;
    +        this->min = point;
    +        this->max = point;
             this->defined = true;
         }
     }
    @@ -105,10 +102,8 @@ BoundingBoxBase::merge(const BoundingBoxBase &bb)
         assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1));
         if (bb.defined) {
             if (this->defined) {
    -            this->min(0) = std::min(bb.min(0), this->min(0));
    -            this->min(1) = std::min(bb.min(1), this->min(1));
    -            this->max(0) = std::max(bb.max(0), this->max(0));
    -            this->max(1) = std::max(bb.max(1), this->max(1));
    +            this->min = this->min.cwiseMin(bb.min);
    +            this->max = this->max.cwiseMax(bb.max);
             } else {
                 this->min = bb.min;
                 this->max = bb.max;
    @@ -123,19 +118,22 @@ template  void
     BoundingBox3Base::merge(const PointClass &point)
     {
         if (this->defined) {
    -        this->min(2) = std::min(point(2), this->min(2));
    -        this->max(2) = std::max(point(2), this->max(2));
    +        this->min = this->min.cwiseMin(point);
    +        this->max = this->max.cwiseMax(point);
    +    } else {
    +        this->min = point;
    +        this->max = point;
    +        this->defined = true;
         }
    -    BoundingBoxBase::merge(point);
     }
    -template void BoundingBox3Base::merge(const Pointf3 &point);
    +template void BoundingBox3Base::merge(const Vec3d &point);
     
     template  void
     BoundingBox3Base::merge(const std::vector &points)
     {
         this->merge(BoundingBox3Base(points));
     }
    -template void BoundingBox3Base::merge(const Pointf3s &points);
    +template void BoundingBox3Base::merge(const Pointf3s &points);
     
     template  void
     BoundingBox3Base::merge(const BoundingBox3Base &bb)
    @@ -143,13 +141,16 @@ BoundingBox3Base::merge(const BoundingBox3Base &bb)
         assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2));
         if (bb.defined) {
             if (this->defined) {
    -            this->min(2) = std::min(bb.min(2), this->min(2));
    -            this->max(2) = std::max(bb.max(2), this->max(2));
    +            this->min = this->min.cwiseMin(bb.min);
    +            this->max = this->max.cwiseMax(bb.max);
    +        } else {
    +            this->min = bb.min;
    +            this->max = bb.max;
    +            this->defined = true;
             }
    -        BoundingBoxBase::merge(bb);
         }
     }
    -template void BoundingBox3Base::merge(const BoundingBox3Base &bb);
    +template void BoundingBox3Base::merge(const BoundingBox3Base &bb);
     
     template  PointClass
     BoundingBoxBase::size() const
    @@ -164,7 +165,7 @@ BoundingBox3Base::size() const
     {
         return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2));
     }
    -template Pointf3 BoundingBox3Base::size() const;
    +template Vec3d BoundingBox3Base::size() const;
     
     template  double BoundingBoxBase::radius() const
     {
    @@ -183,7 +184,7 @@ template  double BoundingBox3Base::radius() const
         double z = this->max(2) - this->min(2);
         return 0.5 * sqrt(x*x+y*y+z*z);
     }
    -template double BoundingBox3Base::radius() const;
    +template double BoundingBox3Base::radius() const;
     
     template  void
     BoundingBoxBase::offset(coordf_t delta)
    @@ -202,15 +203,12 @@ BoundingBox3Base::offset(coordf_t delta)
         this->min -= v;
         this->max += v;
     }
    -template void BoundingBox3Base::offset(coordf_t delta);
    +template void BoundingBox3Base::offset(coordf_t delta);
     
     template  PointClass
     BoundingBoxBase::center() const
     {
    -    return PointClass(
    -        (this->max(0) + this->min(0))/2,
    -        (this->max(1) + this->min(1))/2
    -    );
    +    return (this->min + this->max) / 2;
     }
     template Point BoundingBoxBase::center() const;
     template Pointf BoundingBoxBase::center() const;
    @@ -218,13 +216,9 @@ template Pointf BoundingBoxBase::center() const;
     template  PointClass
     BoundingBox3Base::center() const
     {
    -    return PointClass(
    -        (this->max(0) + this->min(0))/2,
    -        (this->max(1) + this->min(1))/2,
    -        (this->max(2) + this->min(2))/2
    -    );
    +    return (this->min + this->max) / 2;
     }
    -template Pointf3 BoundingBox3Base::center() const;
    +template Vec3d BoundingBox3Base::center() const;
     
     template  coordf_t
     BoundingBox3Base::max_size() const
    @@ -232,7 +226,7 @@ BoundingBox3Base::max_size() const
         PointClass s = size();
         return std::max(s(0), std::max(s(1), s(2)));
     }
    -template coordf_t BoundingBox3Base::max_size() const;
    +template coordf_t BoundingBox3Base::max_size() const;
     
     // Align a coordinate to a grid. The coordinate may be negative,
     // the aligned value will never be bigger than the original one.
    @@ -287,7 +281,7 @@ BoundingBoxf3 BoundingBoxf3::transformed(const Transform3f& matrix) const
             max_z = std::max(max_z, transf_vertices(2, i));
         }
     
    -    return BoundingBoxf3(Pointf3((coordf_t)min_x, (coordf_t)min_y, (coordf_t)min_z), Pointf3((coordf_t)max_x, (coordf_t)max_y, (coordf_t)max_z));
    +    return BoundingBoxf3(Vec3d((coordf_t)min_x, (coordf_t)min_y, (coordf_t)min_z), Vec3d((coordf_t)max_x, (coordf_t)max_y, (coordf_t)max_z));
     }
     
     }
    diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp
    index d09658774f..7a02878607 100644
    --- a/xs/src/libslic3r/BoundingBox.hpp
    +++ b/xs/src/libslic3r/BoundingBox.hpp
    @@ -7,11 +7,6 @@
     
     namespace Slic3r {
     
    -typedef Point   Size;
    -typedef Point3  Size3;
    -typedef Pointf  Sizef;
    -typedef Pointf3 Sizef3;
    -
     template 
     class BoundingBoxBase
     {
    @@ -20,7 +15,7 @@ public:
         PointClass max;
         bool defined;
         
    -    BoundingBoxBase() : defined(false) {};
    +    BoundingBoxBase() : defined(false), min(PointClass::Zero()), max(PointClass::Zero()) {}
         BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : 
             min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
         BoundingBoxBase(const std::vector& points)
    @@ -29,14 +24,11 @@ public:
                 CONFESS("Empty point set supplied to BoundingBoxBase constructor");
     
             typename std::vector::const_iterator it = points.begin();
    -        this->min(0) = this->max(0) = (*it)(0);
    -        this->min(1) = this->max(1) = (*it)(1);
    -        for (++it; it != points.end(); ++it)
    -        {
    -            this->min(0) = std::min((*it)(0), this->min(0));
    -            this->min(1) = std::min((*it)(1), this->min(1));
    -            this->max(0) = std::max((*it)(0), this->max(0));
    -            this->max(1) = std::max((*it)(1), this->max(1));
    +        this->min = *it;
    +        this->max = *it;
    +        for (++ it; it != points.end(); ++ it) {
    +            this->min = this->min.cwiseMin(*it);
    +            this->max = this->max.cwiseMax(*it);
             }
             this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1));
         }
    @@ -71,19 +63,17 @@ public:
             BoundingBoxBase(pmin, pmax) 
             { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; }
         BoundingBox3Base(const std::vector& points)
    -        : BoundingBoxBase(points)
         {
             if (points.empty())
                 CONFESS("Empty point set supplied to BoundingBox3Base constructor");
    -
             typename std::vector::const_iterator it = points.begin();
    -        this->min(2) = this->max(2) = (*it)(2);
    -        for (++it; it != points.end(); ++it)
    -        {
    -            this->min(2) = std::min((*it)(2), this->min(2));
    -            this->max(2) = std::max((*it)(2), this->max(2));
    +        this->min = *it;
    +        this->max = *it;
    +        for (++ it; it != points.end(); ++ it) {
    +            this->min = this->min.cwiseMin(*it);
    +            this->max = this->max.cwiseMax(*it);
             }
    -        this->defined &= (this->min(2) < this->max(2));
    +        this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2));
         }
         void merge(const PointClass &point);
         void merge(const std::vector &points);
    @@ -91,7 +81,7 @@ public:
         PointClass size() const;
         double radius() const;
         void translate(coordf_t x, coordf_t y, coordf_t z) { assert(this->defined); PointClass v(x, y, z); this->min += v; this->max += v; }
    -    void translate(const Pointf3 &v) { this->min += v; this->max += v; }
    +    void translate(const Vec3d &v) { this->min += v; this->max += v; }
         void offset(coordf_t delta);
         PointClass center() const;
         coordf_t max_size() const;
    @@ -146,12 +136,12 @@ public:
         BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {};
     };
     
    -class BoundingBoxf3 : public BoundingBox3Base 
    +class BoundingBoxf3 : public BoundingBox3Base 
     {
     public:
    -    BoundingBoxf3() : BoundingBox3Base() {};
    -    BoundingBoxf3(const Pointf3 &pmin, const Pointf3 &pmax) : BoundingBox3Base(pmin, pmax) {};
    -    BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {};
    +    BoundingBoxf3() : BoundingBox3Base() {};
    +    BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base(pmin, pmax) {};
    +    BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {};
     
         BoundingBoxf3 transformed(const Transform3f& matrix) const;
     };
    diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp
    index 15363e8eda..90675c04a7 100644
    --- a/xs/src/libslic3r/ExtrusionEntity.hpp
    +++ b/xs/src/libslic3r/ExtrusionEntity.hpp
    @@ -149,7 +149,7 @@ public:
         // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
         double min_mm3_per_mm() const { return this->mm3_per_mm; }
         Polyline as_polyline() const { return this->polyline; }
    -    virtual double total_volume() const { return mm3_per_mm * unscale(length()); }
    +    virtual double total_volume() const { return mm3_per_mm * unscale(length()); }
     
     private:
         void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
    diff --git a/xs/src/libslic3r/Fill/FillConcentric.cpp b/xs/src/libslic3r/Fill/FillConcentric.cpp
    index d0f9510ccc..8a3a7ea89d 100644
    --- a/xs/src/libslic3r/Fill/FillConcentric.cpp
    +++ b/xs/src/libslic3r/Fill/FillConcentric.cpp
    @@ -21,7 +21,7 @@ void FillConcentric::_fill_surface_single(
         
         if (params.density > 0.9999f && !params.dont_adjust) {
             distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance);
    -        this->spacing = unscale(distance);
    +        this->spacing = unscale(distance);
         }
     
         Polygons loops = (Polygons)expolygon;
    diff --git a/xs/src/libslic3r/Fill/FillGyroid.cpp b/xs/src/libslic3r/Fill/FillGyroid.cpp
    index 003893649b..bbaebc5f9d 100644
    --- a/xs/src/libslic3r/Fill/FillGyroid.cpp
    +++ b/xs/src/libslic3r/Fill/FillGyroid.cpp
    @@ -71,10 +71,10 @@ static std::vector make_one_period(double width, double scaleFactor, dou
         for (unsigned int i=1;i(scaleFactor) * std::abs(cross2(rp, lp) - cross2(rp - lp, tp)) / lrv.norm();
             if (dist_mm > tolerance) {                               // if the difference from straight line is more than this
                 double x = 0.5f * (points[i-1](0) + points[i](0));
                 points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip)));
    diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp
    index 2209d219e4..205eb1b669 100644
    --- a/xs/src/libslic3r/Fill/FillRectilinear.cpp
    +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp
    @@ -27,7 +27,7 @@ void FillRectilinear::_fill_surface_single(
         // define flow spacing according to requested density
         if (params.density > 0.9999f && !params.dont_adjust) {
             this->_line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), this->_line_spacing);
    -        this->spacing = unscale(this->_line_spacing);
    +        this->spacing = unscale(this->_line_spacing);
         } else {
             // extend bounding box so that our pattern will be aligned with other layers
             // Transform the reference point to the rotated coordinate system.
    diff --git a/xs/src/libslic3r/Fill/FillRectilinear2.cpp b/xs/src/libslic3r/Fill/FillRectilinear2.cpp
    index f18ab0f62f..65440d0efe 100644
    --- a/xs/src/libslic3r/Fill/FillRectilinear2.cpp
    +++ b/xs/src/libslic3r/Fill/FillRectilinear2.cpp
    @@ -792,7 +792,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP
         // define flow spacing according to requested density
         if (params.full_infill() && !params.dont_adjust) {
             line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), line_spacing);
    -        this->spacing = unscale(line_spacing);
    +        this->spacing = unscale(line_spacing);
         } else {
             // extend bounding box so that our pattern will be aligned with other layers
             // Transform the reference point to the rotated coordinate system.
    diff --git a/xs/src/libslic3r/Fill/FillRectilinear3.cpp b/xs/src/libslic3r/Fill/FillRectilinear3.cpp
    index d80bbfe6e1..72c8b1ec5d 100644
    --- a/xs/src/libslic3r/Fill/FillRectilinear3.cpp
    +++ b/xs/src/libslic3r/Fill/FillRectilinear3.cpp
    @@ -391,7 +391,7 @@ static bool prepare_infill_hatching_segments(
             // Full infill, adjust the line spacing to fit an integer number of lines.
             out.line_spacing = Fill::_adjust_solid_spacing(bounding_box.size()(0), line_spacing);
             // Report back the adjusted line spacing.
    -        fill_dir_params.spacing = float(unscale(line_spacing));
    +        fill_dir_params.spacing = unscale(line_spacing);
         } else {
             // Extend bounding box so that our pattern will be aligned with the other layers.
             // Transform the reference point to the rotated coordinate system.
    diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp
    index 419299135f..2f911440ad 100644
    --- a/xs/src/libslic3r/GCode.cpp
    +++ b/xs/src/libslic3r/GCode.cpp
    @@ -64,7 +64,7 @@ std::string OozePrevention::pre_toolchange(GCode &gcodegen)
         // move to the nearest standby point
         if (!this->standby_points.empty()) {
             // get current position in print coordinates
    -        Pointf3 writer_pos = gcodegen.writer().get_position();
    +        Vec3d writer_pos = gcodegen.writer().get_position();
             Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
             
             // find standby point
    @@ -74,7 +74,7 @@ std::string OozePrevention::pre_toolchange(GCode &gcodegen)
             /*  We don't call gcodegen.travel_to() because we don't need retraction (it was already
                 triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
                 of the destination point must not be transformed by origin nor current extruder offset.  */
    -        gcode += gcodegen.writer().travel_to_xy(Pointf::new_unscale(standby_point), 
    +        gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), 
                 "move to standby position");
         }
         
    @@ -831,7 +831,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
                         final_extruder_id   = tool_ordering.last_extruder();
                         assert(final_extruder_id != (unsigned int)-1);
                     }
    -                this->set_origin(unscale(copy(0)), unscale(copy(1)));
    +                this->set_origin(unscale(copy));
                     if (finished_objects > 0) {
                         // Move to the origin position for the copy we're going to print.
                         // This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
    @@ -1547,7 +1547,7 @@ void GCode::process_layer(
                         if (m_last_obj_copy != this_object_copy)
                             m_avoid_crossing_perimeters.use_external_mp_once = true;
                         m_last_obj_copy = this_object_copy;
    -                    this->set_origin(unscale(copy(0)), unscale(copy(1)));
    +                    this->set_origin(unscale(copy));
                         if (object_by_extruder.support != nullptr && !print_wipe_extrusions) {
                             m_layer = layers[layer_id].support_layer;
                             gcode += this->extrude_support(
    @@ -2621,9 +2621,7 @@ std::string GCode::set_extruder(unsigned int extruder_id)
     Pointf GCode::point_to_gcode(const Point &point) const
     {
         Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset);
    -    return Pointf(
    -        unscale(point(0)) + m_origin(0) - extruder_offset(0),
    -        unscale(point(1)) + m_origin(1) - extruder_offset(1));
    +    return unscale(point) + m_origin - extruder_offset;
     }
     
     // convert a model-space scaled point into G-code coordinates
    @@ -2635,7 +2633,6 @@ Point GCode::gcode_to_point(const Pointf &point) const
             scale_(point(1) - m_origin(1) + extruder_offset(1)));
     }
     
    -
     // Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
     // during infill/perimeter wiping, or normally (depends on wiping_entities parameter)
     // Returns a reference to member to avoid copying.
    diff --git a/xs/src/libslic3r/GCode/Analyzer.cpp b/xs/src/libslic3r/GCode/Analyzer.cpp
    index 44e6e9acb9..ce9a9ac8e3 100644
    --- a/xs/src/libslic3r/GCode/Analyzer.cpp
    +++ b/xs/src/libslic3r/GCode/Analyzer.cpp
    @@ -14,7 +14,7 @@ static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
     static const float INCHES_TO_MM = 25.4f;
     static const float DEFAULT_FEEDRATE = 0.0f;
     static const unsigned int DEFAULT_EXTRUDER_ID = 0;
    -static const Slic3r::Pointf3 DEFAULT_START_POSITION = Slic3r::Pointf3(0.0f, 0.0f, 0.0f);
    +static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f);
     static const float DEFAULT_START_EXTRUSION = 0.0f;
     
     namespace Slic3r {
    @@ -71,7 +71,7 @@ bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other)
         return false;
     }
     
    -GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder)
    +GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder)
         : type(type)
         , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate)
         , start_position(start_position)
    @@ -80,7 +80,7 @@ GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusi
     {
     }
     
    -GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, const GCodeAnalyzer::Metadata& data, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder)
    +GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, const GCodeAnalyzer::Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder)
         : type(type)
         , data(data)
         , start_position(start_position)
    @@ -587,12 +587,12 @@ void GCodeAnalyzer::_reset_axes_position()
         ::memset((void*)m_state.position, 0, Num_Axis * sizeof(float));
     }
     
    -void GCodeAnalyzer::_set_start_position(const Pointf3& position)
    +void GCodeAnalyzer::_set_start_position(const Vec3d& position)
     {
         m_state.start_position = position;
     }
     
    -const Pointf3& GCodeAnalyzer::_get_start_position() const
    +const Vec3d& GCodeAnalyzer::_get_start_position() const
     {
         return m_state.start_position;
     }
    @@ -612,9 +612,9 @@ float GCodeAnalyzer::_get_delta_extrusion() const
         return _get_axis_position(E) - m_state.start_extrusion;
     }
     
    -Pointf3 GCodeAnalyzer::_get_end_position() const
    +Vec3d GCodeAnalyzer::_get_end_position() const
     {
    -    return Pointf3(m_state.position[X], m_state.position[Y], m_state.position[Z]);
    +    return Vec3d(m_state.position[X], m_state.position[Y], m_state.position[Z]);
     }
     
     void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type)
    @@ -673,7 +673,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
         Metadata data;
         float z = FLT_MAX;
         Polyline polyline;
    -    Pointf3 position(FLT_MAX, FLT_MAX, FLT_MAX);
    +    Vec3d position(FLT_MAX, FLT_MAX, FLT_MAX);
         float volumetric_rate = FLT_MAX;
         GCodePreviewData::Range height_range;
         GCodePreviewData::Range width_range;
    @@ -742,7 +742,7 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
             return;
     
         Polyline3 polyline;
    -    Pointf3 position(FLT_MAX, FLT_MAX, FLT_MAX);
    +    Vec3d position(FLT_MAX, FLT_MAX, FLT_MAX);
         GCodePreviewData::Travel::EType type = GCodePreviewData::Travel::Num_Types;
         GCodePreviewData::Travel::Polyline::EDirection direction = GCodePreviewData::Travel::Polyline::Num_Directions;
         float feedrate = FLT_MAX;
    diff --git a/xs/src/libslic3r/GCode/Analyzer.hpp b/xs/src/libslic3r/GCode/Analyzer.hpp
    index 03dbab3383..27a49b8690 100644
    --- a/xs/src/libslic3r/GCode/Analyzer.hpp
    +++ b/xs/src/libslic3r/GCode/Analyzer.hpp
    @@ -75,12 +75,12 @@ public:
     
             EType type;
             Metadata data;
    -        Pointf3 start_position;
    -        Pointf3 end_position;
    +        Vec3d start_position;
    +        Vec3d end_position;
             float delta_extruder;
     
    -        GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder);
    -        GCodeMove(EType type, const Metadata& data, const Pointf3& start_position, const Pointf3& end_position, float delta_extruder);
    +        GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder);
    +        GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder);
         };
     
         typedef std::vector GCodeMovesList;
    @@ -93,7 +93,7 @@ private:
             EPositioningType global_positioning_type;
             EPositioningType e_local_positioning_type;
             Metadata data;
    -        Pointf3 start_position;
    +        Vec3d start_position = Vec3d::Zero();
             float start_extrusion;
             float position[Num_Axis];
         };
    @@ -206,15 +206,15 @@ private:
         // Sets axes position to zero
         void _reset_axes_position();
     
    -    void _set_start_position(const Pointf3& position);
    -    const Pointf3& _get_start_position() const;
    +    void _set_start_position(const Vec3d& position);
    +    const Vec3d& _get_start_position() const;
     
         void _set_start_extrusion(float extrusion);
         float _get_start_extrusion() const;
         float _get_delta_extrusion() const;
     
         // Returns current xyz position (from m_state.position[])
    -    Pointf3 _get_end_position() const;
    +    Vec3d _get_end_position() const;
     
         // Adds a new move with the given data
         void _store_move(GCodeMove::EType type);
    diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
    index 0f361b250d..40ccc7b09f 100644
    --- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp
    +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
    @@ -23,7 +23,7 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_
     void CoolingBuffer::reset()
     {
         m_current_pos.assign(5, 0.f);
    -    Pointf3 pos = m_gcodegen.writer().get_position();
    +    Vec3d pos = m_gcodegen.writer().get_position();
         m_current_pos[0] = float(pos(0));
         m_current_pos[1] = float(pos(1));
         m_current_pos[2] = float(pos(2));
    diff --git a/xs/src/libslic3r/GCode/PrintExtents.cpp b/xs/src/libslic3r/GCode/PrintExtents.cpp
    index e1d4e257f6..a1cd0cde9f 100644
    --- a/xs/src/libslic3r/GCode/PrintExtents.cpp
    +++ b/xs/src/libslic3r/GCode/PrintExtents.cpp
    @@ -32,8 +32,8 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusio
         BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width));
         BoundingBoxf bboxf;
         if (! empty(bbox)) {
    -        bboxf.min = Pointf::new_unscale(bbox.min);
    -        bboxf.max = Pointf::new_unscale(bbox.max);
    +        bboxf.min = unscale(bbox.min);
    +        bboxf.max = unscale(bbox.max);
     		bboxf.defined = true;
         }
         return bboxf;
    @@ -46,8 +46,8 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio
             bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
         BoundingBoxf bboxf;
         if (! empty(bbox)) {
    -        bboxf.min = Pointf::new_unscale(bbox.min);
    -        bboxf.max = Pointf::new_unscale(bbox.max);
    +        bboxf.min = unscale(bbox.min);
    +        bboxf.max = unscale(bbox.max);
     		bboxf.defined = true;
     	}
         return bboxf;
    @@ -60,8 +60,8 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext
             bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
         BoundingBoxf bboxf;
         if (! empty(bbox)) {
    -        bboxf.min = Pointf::new_unscale(bbox.min);
    -        bboxf.max = Pointf::new_unscale(bbox.max);
    +        bboxf.min = unscale(bbox.min);
    +        bboxf.max = unscale(bbox.max);
     		bboxf.defined = true;
     	}
         return bboxf;
    @@ -123,7 +123,7 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object
                     bbox_this.merge(extrusionentity_extents(extrusion_entity));
             for (const Point &offset : print_object._shifted_copies) {
                 BoundingBoxf bbox_translated(bbox_this);
    -            bbox_translated.translate(Pointf::new_unscale(offset));
    +            bbox_translated.translate(unscale(offset));
                 bbox.merge(bbox_translated);
             }
         }
    diff --git a/xs/src/libslic3r/GCodeWriter.cpp b/xs/src/libslic3r/GCodeWriter.cpp
    index cefbdad2e2..5352e95022 100644
    --- a/xs/src/libslic3r/GCodeWriter.cpp
    +++ b/xs/src/libslic3r/GCodeWriter.cpp
    @@ -290,7 +290,7 @@ std::string GCodeWriter::travel_to_xy(const Pointf &point, const std::string &co
         return gcode.str();
     }
     
    -std::string GCodeWriter::travel_to_xyz(const Pointf3 &point, const std::string &comment)
    +std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment)
     {
         /*  If target Z is lower than current Z but higher than nominal Z we
             don't perform the Z move but we only move in the XY plane and
    @@ -299,7 +299,7 @@ std::string GCodeWriter::travel_to_xyz(const Pointf3 &point, const std::string &
         if (!this->will_move_z(point(2))) {
             double nominal_z = m_pos(2) - m_lifted;
             m_lifted = m_lifted - (point(2) - nominal_z);
    -        return this->travel_to_xy(point.xy());
    +        return this->travel_to_xy(to_2d(point));
         }
         
         /*  In all the other cases, we perform an actual XYZ move and cancel
    @@ -373,7 +373,7 @@ std::string GCodeWriter::extrude_to_xy(const Pointf &point, double dE, const std
         return gcode.str();
     }
     
    -std::string GCodeWriter::extrude_to_xyz(const Pointf3 &point, double dE, const std::string &comment)
    +std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
     {
         m_pos = point;
         m_lifted = 0;
    diff --git a/xs/src/libslic3r/GCodeWriter.hpp b/xs/src/libslic3r/GCodeWriter.hpp
    index f706b87689..d02bbfe609 100644
    --- a/xs/src/libslic3r/GCodeWriter.hpp
    +++ b/xs/src/libslic3r/GCodeWriter.hpp
    @@ -56,17 +56,17 @@ public:
         std::string toolchange(unsigned int extruder_id);
         std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
         std::string travel_to_xy(const Pointf &point, const std::string &comment = std::string());
    -    std::string travel_to_xyz(const Pointf3 &point, const std::string &comment = std::string());
    +    std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string());
         std::string travel_to_z(double z, const std::string &comment = std::string());
         bool        will_move_z(double z) const;
         std::string extrude_to_xy(const Pointf &point, double dE, const std::string &comment = std::string());
    -    std::string extrude_to_xyz(const Pointf3 &point, double dE, const std::string &comment = std::string());
    +    std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
         std::string retract(bool before_wipe = false);
         std::string retract_for_toolchange(bool before_wipe = false);
         std::string unretract();
         std::string lift();
         std::string unlift();
    -    Pointf3     get_position() const { return m_pos; }
    +    Vec3d       get_position() const { return m_pos; }
     
     private:
         std::vector    m_extruders;
    @@ -81,7 +81,7 @@ private:
         unsigned int    m_last_bed_temperature;
         bool            m_last_bed_temperature_reached;
         double          m_lifted;
    -    Pointf3         m_pos;
    +    Vec3d           m_pos = Vec3d::Zero();
     
         std::string _travel_to_z(double z, const std::string &comment);
         std::string _retract(double length, double restart_extra, const std::string &comment);
    diff --git a/xs/src/libslic3r/Line.cpp b/xs/src/libslic3r/Line.cpp
    index 93e888efa9..a29e4ce4ee 100644
    --- a/xs/src/libslic3r/Line.cpp
    +++ b/xs/src/libslic3r/Line.cpp
    @@ -97,11 +97,11 @@ bool Line::intersection(const Line &l2, Point *intersection) const
         return false;  // not intersecting
     }
     
    -Pointf3 Linef3::intersect_plane(double z) const
    +Vec3d Linef3::intersect_plane(double z) const
     {
         auto   v = (this->b - this->a).cast();
         double t = (z - this->a(2)) / v(2);
    -    return Pointf3(this->a(0) + v(0) * t, this->a(1) + v(1) * t, z);
    +    return Vec3d(this->a(0) + v(0) * t, this->a(1) + v(1) * t, z);
     }
     
     }
    diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp
    index 39c2e1ff1d..39a7c2223b 100644
    --- a/xs/src/libslic3r/Line.hpp
    +++ b/xs/src/libslic3r/Line.hpp
    @@ -81,13 +81,13 @@ public:
     class Linef3
     {
     public:
    -    Linef3() {}
    -    explicit Linef3(Pointf3 _a, Pointf3 _b): a(_a), b(_b) {}
    -    Pointf3 intersect_plane(double z) const;
    +    Linef3() : a(0., 0., 0.), b(0., 0., 0.) {}
    +    explicit Linef3(Vec3d _a, Vec3d _b): a(_a), b(_b) {}
    +    Vec3d   intersect_plane(double z) const;
         void    scale(double factor) { this->a *= factor; this->b *= factor; }
     
    -    Pointf3 a;
    -    Pointf3 b;
    +    Vec3d a;
    +    Vec3d b;
     };
     
     } // namespace Slic3r
    diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
    index c5b8ad8dfe..cc59b65588 100644
    --- a/xs/src/libslic3r/Model.cpp
    +++ b/xs/src/libslic3r/Model.cpp
    @@ -251,7 +251,7 @@ void Model::center_instances_around_point(const Pointf &point)
             for (size_t i = 0; i < o->instances.size(); ++ i)
                 bb.merge(o->instance_bounding_box(i, false));
     
    -    Pointf shift = point - 0.5 * bb.size().xy() - bb.min.xy();
    +    Pointf shift = point - 0.5 * to_2d(bb.size()) - to_2d(bb.min);
         for (ModelObject *o : this->objects) {
             for (ModelInstance *i : o->instances)
                 i->offset += shift;
    @@ -309,8 +309,8 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
             for (size_t i = 0; i < o->instances.size(); ++ i) {
                 // an accurate snug bounding box around the transformed mesh.
                 BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
    -            instance_sizes.push_back(bbox.size().xy());
    -            instance_centers.push_back(bbox.center().xy());
    +            instance_sizes.emplace_back(to_2d(bbox.size()));
    +            instance_centers.emplace_back(to_2d(bbox.center()));
             }
     
         Pointfs positions;
    @@ -332,7 +332,7 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
     // Duplicate the entire model preserving instance relative positions.
     void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb)
     {
    -    Pointfs model_sizes(copies_num-1, this->bounding_box().size().xy());
    +    Pointfs model_sizes(copies_num-1, to_2d(this->bounding_box().size()));
         Pointfs positions;
         if (! _arrange(model_sizes, dist, bb, positions))
             CONFESS("Cannot duplicate part as the resulting objects would not fit on the print bed.\n");
    @@ -375,7 +375,7 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist)
         ModelObject* object = this->objects.front();
         object->clear_instances();
     
    -    Sizef3 size = object->bounding_box().size();
    +    Vec3d size = object->bounding_box().size();
     
         for (size_t x_copy = 1; x_copy <= x; ++x_copy) {
             for (size_t y_copy = 1; y_copy <= y; ++y_copy) {
    @@ -642,7 +642,7 @@ BoundingBoxf3 ModelObject::tight_bounding_box(bool include_modifiers) const
                         {
                             // original point
                             const stl_vertex& v = facet.vertex[i];
    -                        Pointf3 p((double)v.x, (double)v.y, (double)v.z);
    +                        Vec3d p((double)v.x, (double)v.y, (double)v.z);
     
                             // scale
                             p(0) *= inst->scaling_factor;
    @@ -727,10 +727,10 @@ void ModelObject::center_around_origin()
     			bb.merge(v->mesh.bounding_box());
         
         // first align to origin on XYZ
    -    Vectorf3 vector(-bb.min(0), -bb.min(1), -bb.min(2));
    +    Vec3d vector(-bb.min(0), -bb.min(1), -bb.min(2));
         
         // then center it on XY
    -    Sizef3 size = bb.size();
    +    Vec3d size = bb.size();
         vector(0) -= size(0)/2;
         vector(1) -= size(1)/2;
         
    @@ -741,7 +741,7 @@ void ModelObject::center_around_origin()
             for (ModelInstance *i : this->instances) {
                 // apply rotation and scaling to vector as well before translating instance,
                 // in order to leave final position unaltered
    -            Vectorf v = - vector.xy();
    +            Vectorf v = - to_2d(vector);
                 v.rotate(i->rotation);
                 i->offset += v * i->scaling_factor;
             }
    @@ -757,12 +757,12 @@ void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z)
             m_bounding_box.translate(x, y, z);
     }
     
    -void ModelObject::scale(const Pointf3 &versor)
    +void ModelObject::scale(const Vec3d &versor)
     {
         for (ModelVolume *v : this->volumes)
             v->mesh.scale(versor);
         // reset origin translation since it doesn't make sense anymore
    -    this->origin_translation = Pointf3(0,0,0);
    +    this->origin_translation = Vec3d(0,0,0);
         this->invalidate_bounding_box();
     }
     
    @@ -784,7 +784,7 @@ void ModelObject::rotate(float angle, const Axis &axis)
             }
         }
     
    -    this->origin_translation = Pointf3(0, 0, 0);
    +    this->origin_translation = Vec3d(0, 0, 0);
         this->invalidate_bounding_box();
     }
     
    @@ -798,7 +798,7 @@ void ModelObject::transform(const float* matrix3x4)
             v->mesh.transform(matrix3x4);
         }
     
    -    origin_translation = Pointf3(0.0, 0.0, 0.0);
    +    origin_translation = Vec3d(0.0, 0.0, 0.0);
         invalidate_bounding_box();
     }
     
    @@ -806,7 +806,7 @@ void ModelObject::mirror(const Axis &axis)
     {
         for (ModelVolume *v : this->volumes)
             v->mesh.mirror(axis);
    -    this->origin_translation = Pointf3(0,0,0);
    +    this->origin_translation = Vec3d(0,0,0);
         this->invalidate_bounding_box();
     }
     
    @@ -929,7 +929,7 @@ void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_
                         {
                             // original point
                             const stl_vertex& v = facet.vertex[i];
    -                        Pointf3 p((double)v.x, (double)v.y, (double)v.z);
    +                        Vec3d p((double)v.x, (double)v.y, (double)v.z);
     
                             // scale
                             p(0) *= inst->scaling_factor;
    @@ -970,7 +970,7 @@ void ModelObject::print_info() const
         TriangleMesh mesh = this->raw_mesh();
         mesh.check_topology();
         BoundingBoxf3 bb = mesh.bounding_box();
    -    Sizef3 size = bb.size();
    +    Vec3d size = bb.size();
         cout << "size_x = " << size(0) << endl;
         cout << "size_y = " << size(1) << endl;
         cout << "size_z = " << size(2) << endl;
    @@ -1082,30 +1082,20 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes
         for (int i = 0; i < mesh->stl.stats.number_of_facets; ++ i) {
             const stl_facet &facet = mesh->stl.facet_start[i];
             for (int j = 0; j < 3; ++ j) {
    -            stl_vertex v = facet.vertex[j];
    -            double xold = v.x;
    -            double yold = v.y;
    -            v.x = float(c * xold - s * yold);
    -            v.y = float(s * xold + c * yold);
    -            bbox.merge(Pointf3(v.x, v.y, v.z));
    +            const stl_vertex &v = facet.vertex[j];
    +			bbox.merge(Vec3d(c * v.x - s * v.y, s * v.x + c * v.y, v.z));
             }
         }
         if (! empty(bbox)) {
             // Scale the bounding box uniformly.
             if (std::abs(this->scaling_factor - 1.) > EPSILON) {
    -            bbox.min(0) *= float(this->scaling_factor);
    -            bbox.min(1) *= float(this->scaling_factor);
    -            bbox.min(2) *= float(this->scaling_factor);
    -            bbox.max(0) *= float(this->scaling_factor);
    -            bbox.max(1) *= float(this->scaling_factor);
    -            bbox.max(2) *= float(this->scaling_factor);
    +            bbox.min *= this->scaling_factor;
    +			bbox.max *= this->scaling_factor;
             }
             // Translate the bounding box.
             if (! dont_translate) {
    -            bbox.min(0) += float(this->offset(0));
    -            bbox.min(1) += float(this->offset(1));
    -            bbox.max(0) += float(this->offset(0));
    -            bbox.max(1) += float(this->offset(1));
    +            Eigen::Map(bbox.min.data()) += this->offset;
    +            Eigen::Map(bbox.max.data()) += this->offset;
             }
         }
         return bbox;
    diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
    index 8dcfff024e..dfe391b0e8 100644
    --- a/xs/src/libslic3r/Model.hpp
    +++ b/xs/src/libslic3r/Model.hpp
    @@ -84,7 +84,7 @@ public:
             center_around_origin() method. Callers might want to apply the same translation
             to new volumes before adding them to this object in order to preserve alignment
             when user expects that. */
    -    Pointf3                 origin_translation;
    +    Vec3d                   origin_translation;
         
         Model* get_model() const { return m_model; };
         
    @@ -120,9 +120,9 @@ public:
         // A snug bounding box around the transformed non-modifier object volumes.
         BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const;
         void center_around_origin();
    -    void translate(const Vectorf3 &vector) { this->translate(vector(0), vector(1), vector(2)); }
    +    void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); }
         void translate(coordf_t x, coordf_t y, coordf_t z);
    -    void scale(const Pointf3 &versor);
    +    void scale(const Vec3d &versor);
         void rotate(float angle, const Axis &axis);
         void transform(const float* matrix3x4);
         void mirror(const Axis &axis);
    @@ -138,7 +138,7 @@ public:
         void print_info() const;
         
     private:        
    -    ModelObject(Model *model) : layer_height_profile_valid(false), m_model(model), m_bounding_box_valid(false) {}
    +    ModelObject(Model *model) : layer_height_profile_valid(false), m_model(model), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false) {}
         ModelObject(Model *model, const ModelObject &other, bool copy_volumes = true);
         ModelObject& operator= (ModelObject other);
         void swap(ModelObject &other);
    diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp
    index 8e7b33cc8b..de8aeeb2ab 100644
    --- a/xs/src/libslic3r/PerimeterGenerator.cpp
    +++ b/xs/src/libslic3r/PerimeterGenerator.cpp
    @@ -243,7 +243,7 @@ void PerimeterGenerator::process()
                     perimeter_spacing / 2;
             // only apply infill overlap if we actually have one perimeter
             if (inset > 0)
    -            inset -= scale_(this->config->get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)));
    +            inset -= scale_(this->config->get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)));
             // simplify infill contours according to resolution
             Polygons pp;
             for (ExPolygon &ex : last)
    @@ -420,7 +420,7 @@ static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyli
                 path.polyline.append(line.b);
                 // Convert from spacing to extrusion width based on the extrusion model
                 // of a square extrusion ended with semi circles.
    -            flow.width = unscale(w) + flow.height * (1. - 0.25 * PI);
    +            flow.width = unscale(w) + flow.height * (1. - 0.25 * PI);
                 #ifdef SLIC3R_DEBUG
                 printf("  filling %f gap\n", flow.width);
                 #endif
    diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
    index 3119511750..e189e22eec 100644
    --- a/xs/src/libslic3r/Point.hpp
    +++ b/xs/src/libslic3r/Point.hpp
    @@ -18,17 +18,9 @@ class MultiPoint;
     class Point;
     class Point3;
     class Pointf;
    -class Pointf3;
     typedef Point                       Vector;
     typedef Point3                      Vector3;
     typedef Pointf                      Vectorf;
    -typedef Pointf3                     Vectorf3;
    -typedef std::vector          Points;
    -typedef std::vector         PointPtrs;
    -typedef std::vector   PointConstPtrs;
    -typedef std::vector         Points3;
    -typedef std::vector         Pointfs;
    -typedef std::vector        Pointf3s;
     
     // Eigen types, to replace the Slic3r's own types in the future.
     // Vector types with a fixed point coordinate base type.
    @@ -43,6 +35,13 @@ typedef Eigen::Matrix Vec3f;
     typedef Eigen::Matrix Vec2d;
     typedef Eigen::Matrix Vec3d;
     
    +typedef std::vector                              Points;
    +typedef std::vector                             PointPtrs;
    +typedef std::vector                       PointConstPtrs;
    +typedef std::vector                             Points3;
    +typedef std::vector                             Pointfs;
    +typedef std::vector                              Pointf3s;
    +
     typedef Eigen::Transform Transform2f;
     typedef Eigen::Transform Transform2d;
     typedef Eigen::Transform Transform3f;
    @@ -53,6 +52,18 @@ inline coord_t cross2(const Vec2crd &v1, const Vec2crd &v2) { return v1(0) * v2(
     inline float   cross2(const Vec2f   &v1, const Vec2f   &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
     inline double  cross2(const Vec2d   &v1, const Vec2d   &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
     
    +inline Vec2crd to_2d(const Vec3crd &pt3) { return Vec2crd(pt3(0), pt3(1)); }
    +inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
    +inline Vec2f   to_2d(const Vec3f   &pt3) { return Vec2f  (pt3(0), pt3(1)); }
    +inline Vec2d   to_2d(const Vec3d   &pt3) { return Vec2d  (pt3(0), pt3(1)); }
    +
    +inline Vec2d   unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); }
    +inline Vec2d   unscale(const Vec2crd &pt) { return Vec2d(unscale(pt(0)), unscale(pt(1))); }
    +inline Vec2d   unscale(const Vec2d   &pt) { return Vec2d(unscale(pt(0)), unscale(pt(1))); }
    +inline Vec3d   unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale(x), unscale(y), unscale(z)); }
    +inline Vec3d   unscale(const Vec3crd &pt) { return Vec3d(unscale(pt(0)), unscale(pt(1)), unscale(pt(2))); }
    +inline Vec3d   unscale(const Vec3d   &pt) { return Vec3d(unscale(pt(0)), unscale(pt(1)), unscale(pt(2))); }
    +
     inline std::string to_string(const Vec2crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; }
     inline std::string to_string(const Vec2d   &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; }
     inline std::string to_string(const Vec3crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + ", " + std::to_string(pt(2)) + "]"; }
    @@ -229,8 +240,6 @@ public:
             this->Vec3crd::operator=(other);
             return *this;
         }
    -
    -    Point xy() const { return Point((*this)(0), (*this)(1)); }
     };
     
     std::ostream& operator<<(std::ostream &stm, const Pointf &pointf);
    @@ -245,8 +254,6 @@ public:
         // This constructor allows you to construct Pointf from Eigen expressions
         template
         Pointf(const Eigen::MatrixBase &other) : Vec2d(other) {}
    -    static Pointf new_unscale(coord_t x, coord_t y) { return Pointf(unscale(x), unscale(y)); }
    -    static Pointf new_unscale(const Point &p) { return Pointf(unscale(p(0)), unscale(p(1))); }
     
         // This method allows you to assign Eigen expressions to MyVectorType
         template
    @@ -262,30 +269,6 @@ public:
         bool operator< (const Pointf& rhs) const { return (*this)(0) < rhs(0) || ((*this)(0) == rhs(0) && (*this)(1) < rhs(1)); }
     };
     
    -class Pointf3 : public Vec3d
    -{
    -public:
    -    typedef coordf_t coord_type;
    -
    -    explicit Pointf3() { (*this)(0) = (*this)(1) = (*this)(2) = 0.; }
    -    explicit Pointf3(coordf_t x, coordf_t y, coordf_t z) { (*this)(0) = x; (*this)(1) = y; (*this)(2) = z; }
    -    // This constructor allows you to construct Pointf from Eigen expressions
    -    template
    -    Pointf3(const Eigen::MatrixBase &other) : Vec3d(other) {}
    -    static Pointf3 new_unscale(coord_t x, coord_t y, coord_t z) { return Pointf3(unscale(x), unscale(y), unscale(z)); }
    -    static Pointf3 new_unscale(const Point3& p) { return Pointf3(unscale(p(0)), unscale(p(1)), unscale(p(2))); }
    -
    -    // This method allows you to assign Eigen expressions to MyVectorType
    -    template
    -    Pointf3& operator=(const Eigen::MatrixBase &other)
    -    {
    -        this->Vec3d::operator=(other);
    -        return *this;
    -    }
    -
    -    Pointf xy() const { return Pointf((*this)(0), (*this)(1)); }
    -};
    -
     } // namespace Slic3r
     
     // start Boost
    diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
    index 82dee90493..230201cc1f 100644
    --- a/xs/src/libslic3r/Print.cpp
    +++ b/xs/src/libslic3r/Print.cpp
    @@ -536,7 +536,7 @@ bool Print::has_skirt() const
     std::string Print::validate() const
     {
         BoundingBox bed_box_2D = get_extents(Polygon::new_scale(config.bed_shape.values));
    -    BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Pointf3(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config.max_print_height));
    +	BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(config.max_print_height)));
         // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced.
         print_volume.min(2) = -1e10;
         unsigned int printable_count = 0;
    @@ -724,7 +724,7 @@ BoundingBox Print::bounding_box() const
         for (const PrintObject *object : this->objects)
             for (Point copy : object->_shifted_copies) {
                 bb.merge(copy);
    -            copy += object->size.xy();
    +            copy += to_2d(object->size);
                 bb.merge(copy);
             }
         return bb;
    @@ -967,7 +967,7 @@ void Print::_make_skirt()
             this->skirt.append(eloop);
             if (this->config.min_skirt_length.value > 0) {
                 // The skirt length is limited. Sum the total amount of filament length extruded, in mm.
    -            extruded_length[extruder_idx] += unscale(loop.length()) * extruders_e_per_mm[extruder_idx];
    +            extruded_length[extruder_idx] += unscale(loop.length()) * extruders_e_per_mm[extruder_idx];
                 if (extruded_length[extruder_idx] < this->config.min_skirt_length.value) {
                     // Not extruded enough yet with the current extruder. Add another loop.
                     if (i == 1)
    diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp
    index 36e6784cd4..734b04e3f2 100644
    --- a/xs/src/libslic3r/Print.hpp
    +++ b/xs/src/libslic3r/Print.hpp
    @@ -144,7 +144,7 @@ public:
         bool set_copies(const Points &points);
         bool reload_model_instances();
         // since the object is aligned to origin, bounding box coincides with size
    -    BoundingBox bounding_box() const { return BoundingBox(Point(0,0), this->size.xy()); }
    +    BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); }
     
         // adds region_id, too, if necessary
         void add_region_volume(unsigned int region_id, int volume_id) {
    diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp
    index 8df7111963..10ebcb18e5 100644
    --- a/xs/src/libslic3r/PrintObject.cpp
    +++ b/xs/src/libslic3r/PrintObject.cpp
    @@ -50,7 +50,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding
             // (copies are expressed in G-code coordinates and this translation is not publicly exposed).
             this->_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1));
             // Scale the object size and store it
    -        Pointf3 size = modobj_bbox.size();
    +        Vec3d size = modobj_bbox.size();
             this->size = Point3::new_scale(size(0), size(1), size(2));
         }
         
    @@ -1121,7 +1121,7 @@ SlicingParameters PrintObject::slicing_parameters() const
     {
         return SlicingParameters::create_from_config(
             this->print()->config, this->config, 
    -        unscale(this->size(2)), this->print()->object_extruders());
    +        unscale(this->size(2)), this->print()->object_extruders());
     }
     
     bool PrintObject::update_layer_height_profile(std::vector &layer_height_profile) const
    @@ -1335,7 +1335,7 @@ std::vector PrintObject::_slice_region(size_t region_id, const std::
                     // consider the first one
                     this->model_object()->instances.front()->transform_mesh(&mesh, true);
                     // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
    -                mesh.translate(- float(unscale(this->_copies_shift(0))), - float(unscale(this->_copies_shift(1))), -float(this->model_object()->bounding_box().min(2)));
    +                mesh.translate(- unscale(this->_copies_shift(0)), - unscale(this->_copies_shift(1)), - float(this->model_object()->bounding_box().min(2)));
                     // perform actual slicing
                     TriangleMeshSlicer mslicer(&mesh);
                     mslicer.slice(z, &layers);
    diff --git a/xs/src/libslic3r/SVG.cpp b/xs/src/libslic3r/SVG.cpp
    index d3a0eed36b..2113acc433 100644
    --- a/xs/src/libslic3r/SVG.cpp
    +++ b/xs/src/libslic3r/SVG.cpp
    @@ -3,7 +3,7 @@
     
     #include 
     
    -#define COORD(x) ((float)unscale((x))*10)
    +#define COORD(x) (unscale((x))*10)
     
     namespace Slic3r {
     
    diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp
    index d745a803c0..bebff6c432 100644
    --- a/xs/src/libslic3r/Slicing.cpp
    +++ b/xs/src/libslic3r/Slicing.cpp
    @@ -607,7 +607,7 @@ int generate_layer_height_texture(
                 // Intensity profile to visualize the layers.
                 coordf_t intensity = cos(M_PI * 0.7 * (mid - z) / h);
                 // Color mapping from layer height to RGB.
    -            Pointf3 color(
    +            Vec3d color(
                     intensity * lerp(coordf_t(color1(0)), coordf_t(color2(0)), t), 
                     intensity * lerp(coordf_t(color1(1)), coordf_t(color2(1)), t),
                     intensity * lerp(coordf_t(color1(2)), coordf_t(color2(2)), t));
    @@ -639,7 +639,7 @@ int generate_layer_height_texture(
                     const Point3 &color1 = palette_raw[idx1];
                     const Point3 &color2 = palette_raw[idx2];
                     // Color mapping from layer height to RGB.
    -                Pointf3 color(
    +                Vec3d color(
                         lerp(coordf_t(color1(0)), coordf_t(color2(0)), t), 
                         lerp(coordf_t(color1(1)), coordf_t(color2(1)), t),
                         lerp(coordf_t(color1(2)), coordf_t(color2(2)), t));
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index 30ef637e51..ff226ad9ee 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -52,17 +52,17 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& fa
         for (int i = 0; i < stl.stats.number_of_facets; i++) {
             stl_facet facet;
     
    -        const Pointf3& ref_f1 = points[facets[i](0)];
    +        const Vec3d& ref_f1 = points[facets[i](0)];
             facet.vertex[0].x = ref_f1(0);
             facet.vertex[0].y = ref_f1(1);
             facet.vertex[0].z = ref_f1(2);
     
    -        const Pointf3& ref_f2 = points[facets[i](1)];
    +        const Vec3d& ref_f2 = points[facets[i](1)];
             facet.vertex[1].x = ref_f2(0);
             facet.vertex[1].y = ref_f2(1);
             facet.vertex[1].z = ref_f2(2);
     
    -        const Pointf3& ref_f3 = points[facets[i](2)];
    +        const Vec3d& ref_f3 = points[facets[i](2)];
             facet.vertex[2].x = ref_f3(0);
             facet.vertex[2].y = ref_f3(1);
             facet.vertex[2].z = ref_f3(2);
    @@ -300,7 +300,7 @@ void TriangleMesh::scale(float factor)
         stl_invalidate_shared_vertices(&this->stl);
     }
     
    -void TriangleMesh::scale(const Pointf3 &versor)
    +void TriangleMesh::scale(const Vec3d &versor)
     {
         float fversor[3];
         fversor[0] = versor(0);
    @@ -1493,8 +1493,8 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
                 facet.normal.y = 0;
                 facet.normal.z = -1;
                 for (size_t i = 0; i <= 2; ++i) {
    -                facet.vertex[i].x = unscale(p.points[i](0));
    -                facet.vertex[i].y = unscale(p.points[i](1));
    +                facet.vertex[i].x = unscale(p.points[i](0));
    +                facet.vertex[i].y = unscale(p.points[i](1));
                     facet.vertex[i].z = z;
                 }
                 stl_add_facet(&upper->stl, &facet);
    @@ -1519,8 +1519,8 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
                 facet.normal.y = 0;
                 facet.normal.z = 1;
                 for (size_t i = 0; i <= 2; ++i) {
    -                facet.vertex[i].x = unscale(polygon->points[i](0));
    -                facet.vertex[i].y = unscale(polygon->points[i](1));
    +                facet.vertex[i].x = unscale(polygon->points[i](0));
    +                facet.vertex[i].y = unscale(polygon->points[i](1));
                     facet.vertex[i].z = z;
                 }
                 stl_add_facet(&lower->stl, &facet);
    @@ -1534,10 +1534,10 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
     
     // Generate the vertex list for a cube solid of arbitrary size in X/Y/Z.
     TriangleMesh make_cube(double x, double y, double z) {
    -    Pointf3 pv[8] = { 
    -        Pointf3(x, y, 0), Pointf3(x, 0, 0), Pointf3(0, 0, 0), 
    -        Pointf3(0, y, 0), Pointf3(x, y, z), Pointf3(0, y, z), 
    -        Pointf3(0, 0, z), Pointf3(x, 0, z) 
    +    Vec3d pv[8] = { 
    +        Vec3d(x, y, 0), Vec3d(x, 0, 0), Vec3d(0, 0, 0), 
    +        Vec3d(0, y, 0), Vec3d(x, y, z), Vec3d(0, y, z), 
    +        Vec3d(0, 0, z), Vec3d(x, 0, z) 
         };
         Point3 fv[12] = { 
             Point3(0, 1, 2), Point3(0, 2, 3), Point3(4, 5, 6), 
    @@ -1561,8 +1561,8 @@ TriangleMesh make_cylinder(double r, double h, double fa) {
         std::vector facets;
     
         // 2 special vertices, top and bottom center, rest are relative to this
    -    vertices.emplace_back(Pointf3(0.0, 0.0, 0.0));
    -    vertices.emplace_back(Pointf3(0.0, 0.0, h));
    +    vertices.emplace_back(Vec3d(0.0, 0.0, 0.0));
    +    vertices.emplace_back(Vec3d(0.0, 0.0, h));
     
         // adjust via rounding to get an even multiple for any provided angle.
         double angle = (2*PI / floor(2*PI / fa));
    @@ -1572,13 +1572,13 @@ TriangleMesh make_cylinder(double r, double h, double fa) {
         // top and bottom.
         // Special case: Last line shares 2 vertices with the first line.
         unsigned id = vertices.size() - 1;
    -    vertices.emplace_back(Pointf3(sin(0) * r , cos(0) * r, 0));
    -    vertices.emplace_back(Pointf3(sin(0) * r , cos(0) * r, h));
    +    vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, 0));
    +    vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, h));
         for (double i = 0; i < 2*PI; i+=angle) {
             Pointf p(0, r);
             p.rotate(i);
    -        vertices.emplace_back(Pointf3(p(0), p(1), 0.));
    -        vertices.emplace_back(Pointf3(p(0), p(1), h));
    +        vertices.emplace_back(Vec3d(p(0), p(1), 0.));
    +        vertices.emplace_back(Vec3d(p(0), p(1), h));
             id = vertices.size() - 1;
             facets.emplace_back(Point3( 0, id - 1, id - 3)); // top
             facets.emplace_back(Point3(id,      1, id - 2)); // bottom
    @@ -1619,7 +1619,7 @@ TriangleMesh make_sphere(double rho, double fa) {
     
         // special case: first ring connects to 0,0,0
         // insert and form facets.
    -    vertices.emplace_back(Pointf3(0.0, 0.0, -rho));
    +    vertices.emplace_back(Vec3d(0.0, 0.0, -rho));
         size_t id = vertices.size();
         for (size_t i = 0; i < ring.size(); i++) {
             // Fixed scaling 
    @@ -1628,7 +1628,7 @@ TriangleMesh make_sphere(double rho, double fa) {
             const double r = sqrt(abs(rho*rho - z*z));
             Pointf b(0, r);
             b.rotate(ring[i]);
    -        vertices.emplace_back(Pointf3(b(0), b(1), z));
    +        vertices.emplace_back(Vec3d(b(0), b(1), z));
             facets.emplace_back((i == 0) ? Point3(1, 0, ring.size()) : Point3(id, 0, id - 1));
             ++ id;
         }
    @@ -1641,7 +1641,7 @@ TriangleMesh make_sphere(double rho, double fa) {
             for (size_t i = 0; i < ring.size(); i++) {
                 Pointf b(0, r);
                 b.rotate(ring[i]); 
    -            vertices.emplace_back(Pointf3(b(0), b(1), z));
    +            vertices.emplace_back(Vec3d(b(0), b(1), z));
                 if (i == 0) {
                     // wrap around
                     facets.emplace_back(Point3(id + ring.size() - 1 , id, id - 1)); 
    @@ -1657,7 +1657,7 @@ TriangleMesh make_sphere(double rho, double fa) {
     
         // special case: last ring connects to 0,0,rho*2.0
         // only form facets.
    -    vertices.emplace_back(Pointf3(0.0, 0.0, rho));
    +    vertices.emplace_back(Vec3d(0.0, 0.0, rho));
         for (size_t i = 0; i < ring.size(); i++) {
             if (i == 0) {
                 // third vertex is on the other side of the ring.
    diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
    index c700784a51..b04d09f32f 100644
    --- a/xs/src/libslic3r/TriangleMesh.hpp
    +++ b/xs/src/libslic3r/TriangleMesh.hpp
    @@ -37,7 +37,7 @@ public:
         bool is_manifold() const;
         void WriteOBJFile(char* output_file);
         void scale(float factor);
    -    void scale(const Pointf3 &versor);
    +    void scale(const Vec3d &versor);
         void translate(float x, float y, float z);
         void rotate(float angle, const Axis &axis);
         void rotate_x(float angle);
    diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h
    index 862ce2e516..6fea64cd12 100644
    --- a/xs/src/libslic3r/libslic3r.h
    +++ b/xs/src/libslic3r/libslic3r.h
    @@ -45,7 +45,6 @@ typedef double  coordf_t;
     //FIXME Better to use an inline function with an explicit return type.
     //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); }
     #define scale_(val) ((val) / SCALING_FACTOR)
    -#define unscale(val) ((val) * SCALING_FACTOR)
     #define SCALED_EPSILON scale_(EPSILON)
     /* Implementation of CONFESS("foo"): */
     #ifdef _MSC_VER
    @@ -102,6 +101,9 @@ inline std::string debug_out_path(const char *name, ...)
     
     namespace Slic3r {
     
    +template
    +inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); }
    +
     enum Axis { X=0, Y, Z, E, F, NUM_AXES };
     
     template 
    diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
    index dbaf35ae47..46d8d1618a 100644
    --- a/xs/src/perlglue.cpp
    +++ b/xs/src/perlglue.cpp
    @@ -43,7 +43,7 @@ REGISTER_CLASS(BridgeDetector, "BridgeDetector");
     REGISTER_CLASS(Point, "Point");
     REGISTER_CLASS(Point3, "Point3");
     REGISTER_CLASS(Pointf, "Pointf");
    -REGISTER_CLASS(Pointf3, "Pointf3");
    +__REGISTER_CLASS(Vec3d, "Pointf3");
     REGISTER_CLASS(DynamicPrintConfig, "Config");
     REGISTER_CLASS(StaticPrintConfig, "Config::Static");
     REGISTER_CLASS(PrintObjectConfig, "Config::PrintObject");
    diff --git a/xs/src/slic3r/GUI/2DBed.cpp b/xs/src/slic3r/GUI/2DBed.cpp
    index 5f087a8816..35c23272e6 100644
    --- a/xs/src/slic3r/GUI/2DBed.cpp
    +++ b/xs/src/slic3r/GUI/2DBed.cpp
    @@ -91,8 +91,8 @@ void Bed_2D::repaint()
     	for (auto pl : polylines)
     	{
     		for (size_t i = 0; i < pl.points.size()-1; i++){
    -			Point pt1 = to_pixels(Pointf::new_unscale(pl.points[i]));
    -			Point pt2 = to_pixels(Pointf::new_unscale(pl.points[i+1]));
    +			Point pt1 = to_pixels(unscale(pl.points[i]));
    +			Point pt2 = to_pixels(unscale(pl.points[i+1]));
     			dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1));
     		}
     	}
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 36952d230e..86cbe1b6fb 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -195,7 +195,8 @@ const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f };
     const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f };
     
     GLVolume::GLVolume(float r, float g, float b, float a)
    -    : m_angle_z(0.0f)
    +    : m_origin(0, 0, 0)
    +    , m_angle_z(0.0f)
         , m_scale_factor(1.0f)
         , m_dirty(true)
         , composite_id(-1)
    @@ -252,12 +253,12 @@ void GLVolume::set_render_color()
             set_render_color(color, 4);
     }
     
    -const Pointf3& GLVolume::get_origin() const
    +const Vec3d& GLVolume::get_origin() const
     {
         return m_origin;
     }
     
    -void GLVolume::set_origin(const Pointf3& origin)
    +void GLVolume::set_origin(const Vec3d& origin)
     {
         m_origin = origin;
         m_dirty = true;
    @@ -629,7 +630,7 @@ std::vector GLVolumeCollection::load_object(
                 }
                 v.is_modifier = model_volume->modifier;
                 v.shader_outside_printer_detection_enabled = !model_volume->modifier;
    -            v.set_origin(Pointf3(instance->offset(0), instance->offset(1), 0.0));
    +            v.set_origin(Vec3d(instance->offset(0), instance->offset(1), 0.0));
                 v.set_angle_z(instance->rotation);
                 v.set_scale_factor(instance->scaling_factor);
             }
    @@ -667,7 +668,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
                                        {8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8},
                                        {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}};
             for (int i=0;i<16;++i)
    -            points.push_back(Pointf3(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2]));
    +            points.push_back(Vec3d(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2]));
             for (int i=0;i<28;++i)
                 facets.push_back(Point3(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2]));
             TriangleMesh tooth_mesh(points, facets);
    @@ -680,7 +681,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
                 tooth_mesh.translate(min_width, 0.f, 0.f);
             }
     
    -        mesh.scale(Pointf3(width/(n*min_width), 1.f, height)); // Scaling to proper width
    +        mesh.scale(Vec3d(width/(n*min_width), 1.f, height)); // Scaling to proper width
         }
         else
             mesh = make_cube(width, depth, height);
    @@ -700,7 +701,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
         else
             v.indexed_vertex_array.load_mesh_flat_shading(mesh);
     
    -    v.set_origin(Pointf3(pos_x, pos_y, 0.));
    +    v.set_origin(Vec3d(pos_x, pos_y, 0.));
     
         // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
         v.bounding_box = v.indexed_vertex_array.bounding_box();
    @@ -786,7 +787,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
             return false;
     
         BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
    -    BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Pointf3(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height")));
    +    BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), unscale(config->opt_float("max_print_height"))));
         // Allow the objects to protrude below the print bed
         print_volume.min(2) = -1e10;
     
    @@ -962,7 +963,7 @@ static void thick_lines_to_indexed_vertex_array(
         for (size_t ii = 0; ii < lines_end; ++ ii) {
             size_t i = (ii == lines.size()) ? 0 : ii;
             const Line &line = lines[i];
    -        double len = unscale(line.length());
    +        double len = unscale(line.length());
             double inv_len = 1.0 / len;
             double bottom_z = top_z - heights[i];
             double middle_z = 0.5 * (top_z + bottom_z);
    @@ -972,11 +973,11 @@ static void thick_lines_to_indexed_vertex_array(
             bool is_last = (ii == lines_end - 1);
             bool is_closing = closed && is_last;
     
    -        Vectorf v = Vectorf::new_unscale(line.vector());
    +        Vectorf v = unscale(line.vector());
             v *= inv_len;
     
    -        Pointf a = Pointf::new_unscale(line.a);
    -        Pointf b = Pointf::new_unscale(line.b);
    +        Pointf a = unscale(line.a);
    +        Pointf b = unscale(line.b);
             Pointf a1 = a;
             Pointf a2 = a;
             Pointf b1 = b;
    @@ -993,7 +994,7 @@ static void thick_lines_to_indexed_vertex_array(
     
             // calculate new XY normals
             Vector n = line.normal();
    -        Vectorf3 xy_right_normal = Vectorf3::new_unscale(n(0), n(1), 0);
    +        Vec3d xy_right_normal = unscale(n(0), n(1), 0);
             xy_right_normal *= inv_len;
     
             int idx_a[4];
    @@ -1196,15 +1197,15 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
         int      idx_initial[4] = { -1, -1, -1, -1 };
         int      idx_prev[4] = { -1, -1, -1, -1 };
         double   z_prev = 0.0;
    -    Vectorf3 n_right_prev;
    -    Vectorf3 n_top_prev;
    -    Vectorf3 unit_v_prev;
    +    Vec3d n_right_prev = Vec3d::Zero();
    +    Vec3d n_top_prev = Vec3d::Zero();
    +    Vec3d unit_v_prev = Vec3d::Zero();
         double   width_initial = 0.0;
     
         // new vertices around the line endpoints
         // left, right, top, bottom
    -    Pointf3 a[4];
    -    Pointf3 b[4];
    +    Vec3d a[4] = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
    +    Vec3d b[4] = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
     
         // loop once more in case of closed loops
         size_t lines_end = closed ? (lines.size() + 1) : lines.size();
    @@ -1216,17 +1217,17 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
             double height = heights[i];
             double width = widths[i];
     
    -        Vectorf3 unit_v = Vectorf3::new_unscale(line.vector()).normalized();
    +        Vec3d unit_v = unscale(line.vector()).normalized();
     
    -        Vectorf3 n_top;
    -        Vectorf3 n_right;
    -        Vectorf3 unit_positive_z(0.0, 0.0, 1.0);
    +        Vec3d n_top = Vec3d::Zero();
    +        Vec3d n_right = Vec3d::Zero();
    +        Vec3d unit_positive_z(0.0, 0.0, 1.0);
     
             if ((line.a(0) == line.b(0)) && (line.a(1) == line.b(1)))
             {
                 // vertical segment
    -            n_right = (line.a(2) < line.b(2)) ? Vectorf3(-1.0, 0.0, 0.0) : Vectorf3(1.0, 0.0, 0.0);
    -            n_top = Vectorf3(0.0, 1.0, 0.0);
    +            n_right = (line.a(2) < line.b(2)) ? Vec3d(-1.0, 0.0, 0.0) : Vec3d(1.0, 0.0, 0.0);
    +            n_top = Vec3d(0.0, 1.0, 0.0);
             }
             else
             {
    @@ -1235,10 +1236,10 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
                 n_top = n_right.cross(unit_v).normalized();
             }
     
    -        Vectorf3 rl_displacement = 0.5 * width * n_right;
    -        Vectorf3 tb_displacement = 0.5 * height * n_top;
    -        Pointf3 l_a = Pointf3::new_unscale(line.a);
    -        Pointf3 l_b = Pointf3::new_unscale(line.b);
    +        Vec3d rl_displacement = 0.5 * width * n_right;
    +        Vec3d tb_displacement = 0.5 * height * n_top;
    +        Vec3d l_a = unscale(line.a);
    +        Vec3d l_b = unscale(line.b);
     
             a[RIGHT] = l_a + rl_displacement;
             a[LEFT] = l_a - rl_displacement;
    @@ -1249,8 +1250,8 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
             b[TOP] = l_b + tb_displacement;
             b[BOTTOM] = l_b - tb_displacement;
     
    -        Vectorf3 n_bottom = -n_top;
    -        Vectorf3 n_left = -n_right;
    +        Vec3d n_bottom = -n_top;
    +        Vec3d n_left = -n_right;
     
             int idx_a[4];
             int idx_b[4];
    @@ -1316,9 +1317,9 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
                     // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc.
     
                     // averages normals
    -                Vectorf3 average_n_right = 0.5 * (n_right + n_right_prev).normalized();
    -                Vectorf3 average_n_left = -average_n_right;
    -                Vectorf3 average_rl_displacement = 0.5 * width * average_n_right;
    +                Vec3d average_n_right = 0.5 * (n_right + n_right_prev).normalized();
    +                Vec3d average_n_left = -average_n_right;
    +                Vec3d average_rl_displacement = 0.5 * width * average_n_right;
     
                     // updates vertices around a
                     a[RIGHT] = l_a + average_rl_displacement;
    @@ -1448,7 +1449,7 @@ static void point_to_indexed_vertex_array(const Point3& point,
     {
         // builds a double piramid, with vertices on the local axes, around the point
     
    -    Pointf3 center = Pointf3::new_unscale(point);
    +    Vec3d center = unscale(point);
     
         double scale_factor = 1.0;
         double w = scale_factor * width;
    @@ -1462,13 +1463,13 @@ static void point_to_indexed_vertex_array(const Point3& point,
             idxs[i] = idx_last + i;
         }
     
    -    Vectorf3 displacement_x(w, 0.0, 0.0);
    -    Vectorf3 displacement_y(0.0, w, 0.0);
    -    Vectorf3 displacement_z(0.0, 0.0, h);
    +    Vec3d displacement_x(w, 0.0, 0.0);
    +    Vec3d displacement_y(0.0, w, 0.0);
    +    Vec3d displacement_z(0.0, 0.0, h);
     
    -    Vectorf3 unit_x(1.0, 0.0, 0.0);
    -    Vectorf3 unit_y(0.0, 1.0, 0.0);
    -    Vectorf3 unit_z(0.0, 0.0, 1.0);
    +    Vec3d unit_x(1.0, 0.0, 0.0);
    +    Vec3d unit_y(0.0, 1.0, 0.0);
    +    Vec3d unit_z(0.0, 0.0, 1.0);
     
         // vertices
         volume.push_geometry(center - displacement_x, -unit_x); // idxs[0]
    diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
    index 41839a3404..3fbfae1697 100644
    --- a/xs/src/slic3r/GUI/3DScene.hpp
    +++ b/xs/src/slic3r/GUI/3DScene.hpp
    @@ -119,7 +119,7 @@ public:
             push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
         }
     
    -    inline void push_geometry(const Pointf3& p, const Vectorf3& n) {
    +    inline void push_geometry(const Vec3d& p, const Vec3d& n) {
             push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
         }
     
    @@ -255,7 +255,7 @@ public:
     
     private:
         // Offset of the volume to be rendered.
    -    Pointf3               m_origin;
    +    Vec3d                 m_origin;
         // Rotation around Z axis of the volume to be rendered.
         float                 m_angle_z;
         // Scale factor of the volume to be rendered.
    @@ -319,8 +319,8 @@ public:
         // Sets render color in dependence of current state
         void set_render_color();
     
    -    const Pointf3& get_origin() const;
    -    void set_origin(const Pointf3& origin);
    +    const Vec3d& get_origin() const;
    +    void set_origin(const Vec3d& origin);
         void set_angle_z(float angle_z);
         void set_scale_factor(float scale_factor);
     
    diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp
    index f021e1b96a..b8a57a0c55 100644
    --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp
    +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp
    @@ -195,7 +195,7 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
     			// all vertices are equidistant to center
     			m_shape_options_book->SetSelection(SHAPE_CIRCULAR);
     			auto optgroup = m_optgroups[SHAPE_CIRCULAR];
    -			boost::any ret = wxNumberFormatter::ToString(unscale(avg_dist * 2), 0);
    +			boost::any ret = wxNumberFormatter::ToString(unscale(avg_dist * 2), 0);
      			optgroup->set_value("diameter", ret);
     			update_shape();
     			return;
    @@ -332,7 +332,7 @@ void BedShapePanel::load_stl()
     	auto polygon = expolygons[0].contour;
     	std::vector points;
     	for (auto pt : polygon.points)
    -		points.push_back(Pointf::new_unscale(pt));
    +		points.push_back(unscale(pt));
     	m_canvas->m_bed_shape = points;
     	update_preview();
     }
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index d380c4bfc0..a9ef69357e 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -71,8 +71,8 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
         if (generate_tex_coords)
             m_tex_coords = std::vector(t_size, 0.0f);
     
    -    float min_x = (float)unscale(triangles[0].points[0](0));
    -    float min_y = (float)unscale(triangles[0].points[0](1));
    +    float min_x = unscale(triangles[0].points[0](0));
    +    float min_y = unscale(triangles[0].points[0](1));
         float max_x = min_x;
         float max_y = min_y;
     
    @@ -83,8 +83,8 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
             for (unsigned int v = 0; v < 3; ++v)
             {
                 const Point& p = t.points[v];
    -            float x = (float)unscale(p(0));
    -            float y = (float)unscale(p(1));
    +            float x = unscale(p(0));
    +            float y = unscale(p(1));
     
                 m_vertices[v_coord++] = x;
                 m_vertices[v_coord++] = y;
    @@ -137,11 +137,11 @@ bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
         unsigned int coord = 0;
         for (const Line& l : lines)
         {
    -        m_vertices[coord++] = (float)unscale(l.a(0));
    -        m_vertices[coord++] = (float)unscale(l.a(1));
    +        m_vertices[coord++] = unscale(l.a(0));
    +        m_vertices[coord++] = unscale(l.a(1));
             m_vertices[coord++] = z;
    -        m_vertices[coord++] = (float)unscale(l.b(0));
    -        m_vertices[coord++] = (float)unscale(l.b(1));
    +        m_vertices[coord++] = unscale(l.b(0));
    +        m_vertices[coord++] = unscale(l.b(1));
             m_vertices[coord++] = z;
         }
     
    @@ -375,7 +375,7 @@ void GLCanvas3D::Bed::_calc_bounding_box()
         m_bounding_box = BoundingBoxf3();
         for (const Pointf& p : m_shape)
         {
    -        m_bounding_box.merge(Pointf3(p(0), p(1), 0.0));
    +        m_bounding_box.merge(Vec3d(p(0), p(1), 0.0));
         }
     }
     
    @@ -591,7 +591,7 @@ bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2)
     }
     
     GLCanvas3D::Axes::Axes()
    -    : length(0.0f)
    +    : origin(0, 0, 0), length(0.0f)
     {
     }
     
    @@ -1040,7 +1040,7 @@ void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object,
         // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
         layer_height_max *= 1.12;
     
    -    coordf_t max_z = unscale(print_object.size(2));
    +    coordf_t max_z = unscale(print_object.size(2));
         double layer_height = dynamic_cast(print_object.config.option("layer_height"))->value;
         float l = bar_rect.get_left();
         float w = bar_rect.get_right() - l;
    @@ -1075,11 +1075,12 @@ void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object,
     }
     
     const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX);
    -const Pointf3 GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX);
    +const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX);
     
     GLCanvas3D::Mouse::Drag::Drag()
         : start_position_2D(Invalid_2D_Point)
         , start_position_3D(Invalid_3D_Point)
    +    , volume_center_offset(0, 0, 0)
         , move_with_shift(false)
         , move_volume_idx(-1)
         , gizmo_volume_idx(-1)
    @@ -1950,7 +1951,7 @@ void GLCanvas3D::set_bed_shape(const Pointfs& shape)
         bool new_shape = m_bed.set_shape(shape);
     
         // Set the origin and size for painting of the coordinate system axes.
    -    m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z);
    +    m_axes.origin = Vec3d(0.0, 0.0, (coordf_t)GROUND_Z);
         set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size());
     
         if (new_shape)
    @@ -1970,7 +1971,7 @@ void GLCanvas3D::set_auto_bed_shape()
         // draw a default square bed around object center
         const BoundingBoxf3& bbox = volumes_bounding_box();
         coordf_t max_size = bbox.max_size();
    -    const Pointf3 center = bbox.center();
    +    const Vec3d center = bbox.center();
     
         Pointfs bed_shape;
         bed_shape.reserve(4);
    @@ -1982,7 +1983,7 @@ void GLCanvas3D::set_auto_bed_shape()
         set_bed_shape(bed_shape);
     
         // Set the origin for painting of the coordinate system axes.
    -    m_axes.origin = Pointf3(center(0), center(1), (coordf_t)GROUND_Z);
    +    m_axes.origin = Vec3d(center(0), center(1), (coordf_t)GROUND_Z);
     }
     
     void GLCanvas3D::set_axes_length(float length)
    @@ -2799,7 +2800,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                     {
                         // The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate pos x, y,
                         // an converts the screen space coordinate to unscaled object space.
    -                    Pointf3 pos3d = (volume_idx == -1) ? Pointf3(DBL_MAX, DBL_MAX, DBL_MAX) : _mouse_to_3d(pos);
    +                    Vec3d pos3d = (volume_idx == -1) ? Vec3d(DBL_MAX, DBL_MAX, DBL_MAX) : _mouse_to_3d(pos);
     
                         // Only accept the initial position, if it is inside the volume bounding box.
                         BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
    @@ -2831,7 +2832,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             // Get new position at the same Z of the initial click point.
             float z0 = 0.0f;
             float z1 = 1.0f;
    -        Pointf3 cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2));
    +        Vec3d cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2));
     
             // Clip the new position, so the object center remains close to the bed.
             cur_pos += m_mouse.drag.volume_center_offset;
    @@ -2839,13 +2840,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             if (!m_bed.contains(cur_pos2))
             {
                 Point ip = m_bed.point_projection(cur_pos2);
    -            cur_pos(0) = unscale(ip(0));
    -            cur_pos(1) = unscale(ip(1));
    +            cur_pos(0) = unscale(ip(0));
    +            cur_pos(1) = unscale(ip(1));
             }
             cur_pos -= m_mouse.drag.volume_center_offset;
     
             // Calculate the translation vector.
    -        Vectorf3 vector = cur_pos - m_mouse.drag.start_position_3D;
    +        Vec3d vector = cur_pos - m_mouse.drag.start_position_3D;
             // Get the volume being dragged.
             GLVolume* volume = m_volumes.volumes[m_mouse.drag.move_volume_idx];
             // Get all volumes belonging to the same group, if any.
    @@ -2867,7 +2868,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
     
             // Apply new temporary volume origin and ignore Z.
             for (GLVolume* v : volumes)
    -            v->set_origin(v->get_origin() + Vectorf3(vector(0), vector(1), 0.0));
    +            v->set_origin(v->get_origin() + Vec3d(vector(0), vector(1), 0.0));
     
             m_mouse.drag.start_position_3D = cur_pos;
             m_gizmos.refresh();
    @@ -2878,7 +2879,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         {
             m_mouse.dragging = true;
     
    -        const Pointf3& cur_pos = _mouse_to_bed_3d(pos);
    +        const Vec3d& cur_pos = _mouse_to_bed_3d(pos);
             m_gizmos.update(Pointf(cur_pos(0), cur_pos(1)));
     
             std::vector volumes;
    @@ -2931,7 +2932,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 {
                     bb.merge(volume->transformed_bounding_box());
                 }
    -            const Pointf3& size = bb.size();
    +            const Vec3d& size = bb.size();
                 m_on_update_geometry_info_callback.call(size(0), size(1), size(2), m_gizmos.get_scale());
             }
     
    @@ -2954,7 +2955,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 // if dragging over blank area with left button, rotate
                 if (m_mouse.is_start_position_3D_defined())
                 {
    -                const Pointf3& orig = m_mouse.drag.start_position_3D;
    +                const Vec3d& orig = m_mouse.drag.start_position_3D;
                     m_camera.phi += (((float)pos(0) - (float)orig(0)) * TRACKBALLSIZE);
                     m_camera.set_theta(m_camera.get_theta() - ((float)pos(1) - (float)orig(1)) * TRACKBALLSIZE);
     
    @@ -2962,7 +2963,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
     
                     m_dirty = true;
                 }
    -            m_mouse.drag.start_position_3D = Pointf3((coordf_t)pos(0), (coordf_t)pos(1), 0.0);
    +            m_mouse.drag.start_position_3D = Vec3d((coordf_t)pos(0), (coordf_t)pos(1), 0.0);
             }
             else if (evt.MiddleIsDown() || evt.RightIsDown())
             {
    @@ -2971,8 +2972,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
                 {
                     // get point in model space at Z = 0
                     float z = 0.0f;
    -                const Pointf3& cur_pos = _mouse_to_3d(pos, &z);
    -                Pointf3 orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
    +                const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
    +                Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
                     m_camera.target += orig - cur_pos;
     
                     m_on_viewport_changed_callback.call();
    @@ -3277,16 +3278,16 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co
         ::glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
     
         // camera axes
    -    Pointf3 right((coordf_t)matrix[0], (coordf_t)matrix[4], (coordf_t)matrix[8]);
    -    Pointf3 up((coordf_t)matrix[1], (coordf_t)matrix[5], (coordf_t)matrix[9]);
    -    Pointf3 forward((coordf_t)matrix[2], (coordf_t)matrix[6], (coordf_t)matrix[10]);
    +    Vec3d right((coordf_t)matrix[0], (coordf_t)matrix[4], (coordf_t)matrix[8]);
    +    Vec3d up((coordf_t)matrix[1], (coordf_t)matrix[5], (coordf_t)matrix[9]);
    +    Vec3d forward((coordf_t)matrix[2], (coordf_t)matrix[6], (coordf_t)matrix[10]);
     
    -    Pointf3 bb_min = bbox.min;
    -    Pointf3 bb_max = bbox.max;
    -    Pointf3 bb_center = bbox.center();
    +    Vec3d bb_min = bbox.min;
    +    Vec3d bb_max = bbox.max;
    +    Vec3d bb_center = bbox.center();
     
         // bbox vertices in world space
    -    std::vector vertices;
    +    std::vector vertices;
         vertices.reserve(8);
         vertices.push_back(bb_min);
         vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2));
    @@ -3303,11 +3304,11 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co
         // margin factor to give some empty space around the bbox
         coordf_t margin_factor = 1.25;
     
    -    for (const Pointf3 v : vertices)
    +    for (const Vec3d v : vertices)
         {
             // project vertex on the plane perpendicular to camera forward axis
    -        Pointf3 pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2));
    -        Pointf3 proj_on_plane = pos - pos.dot(forward) * forward;
    +        Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2));
    +        Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
     
             // calculates vertex coordinate along camera xy axes
             coordf_t x_on_plane = proj_on_plane.dot(right);
    @@ -3389,7 +3390,7 @@ void GLCanvas3D::_camera_tranform() const
         ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch
         ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f);          // yaw
     
    -    Pointf3 neg_target = - m_camera.target;
    +    Vec3d neg_target = - m_camera.target;
         ::glTranslatef((GLfloat)neg_target(0), (GLfloat)neg_target(1), (GLfloat)neg_target(2));
     }
     
    @@ -3730,7 +3731,7 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
         {
             const Rect& rect = LayersEditing::get_bar_rect_screen(*this);
             float b = rect.get_bottom();
    -        m_layers_editing.last_z = unscale(selected_obj->size(2)) * (b - evt->GetY() - 1.0f) / (b - rect.get_top());
    +        m_layers_editing.last_z = unscale(selected_obj->size(2)) * (b - evt->GetY() - 1.0f) / (b - rect.get_top());
             m_layers_editing.last_action = evt->ShiftDown() ? (evt->RightIsDown() ? 3 : 2) : (evt->RightIsDown() ? 0 : 1);
         }
     
    @@ -3760,10 +3761,10 @@ void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
         _start_timer();
     }
     
    -Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
    +Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
     {
         if (m_canvas == nullptr)
    -        return Pointf3(DBL_MAX, DBL_MAX, DBL_MAX);
    +        return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
     
         _camera_tranform();
     
    @@ -3783,10 +3784,10 @@ Pointf3 GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
     
         GLdouble out_x, out_y, out_z;
         ::gluUnProject((GLdouble)mouse_pos(0), (GLdouble)y, (GLdouble)mouse_z, modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z);
    -    return Pointf3((coordf_t)out_x, (coordf_t)out_y, (coordf_t)out_z);
    +    return Vec3d((coordf_t)out_x, (coordf_t)out_y, (coordf_t)out_z);
     }
     
    -Pointf3 GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
    +Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
     {
         float z0 = 0.0f;
         float z1 = 1.0f;
    @@ -4501,7 +4502,7 @@ bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data)
             TypesList::iterator type = std::find(types.begin(), types.end(), Type(polyline.type));
             if (type != types.end())
             {
    -            type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
    +            type->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
                 type->volume->offsets.push_back(type->volume->indexed_vertex_array.quad_indices.size());
                 type->volume->offsets.push_back(type->volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4567,7 +4568,7 @@ bool GLCanvas3D::_travel_paths_by_feedrate(const GCodePreviewData& preview_data)
             FeedratesList::iterator feedrate = std::find(feedrates.begin(), feedrates.end(), Feedrate(polyline.feedrate));
             if (feedrate != feedrates.end())
             {
    -            feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
    +            feedrate->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
                 feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.quad_indices.size());
                 feedrate->volume->offsets.push_back(feedrate->volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4633,7 +4634,7 @@ bool GLCanvas3D::_travel_paths_by_tool(const GCodePreviewData& preview_data, con
             ToolsList::iterator tool = std::find(tools.begin(), tools.end(), Tool(polyline.extruder_id));
             if (tool != tools.end())
             {
    -            tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
    +            tool->volume->print_zs.push_back(unscale(polyline.polyline.bounding_box().min(2)));
                 tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.quad_indices.size());
                 tool->volume->offsets.push_back(tool->volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4662,7 +4663,7 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data)
     
             for (const GCodePreviewData::Retraction::Position& position : copy)
             {
    -            volume->print_zs.push_back(unscale(position.position(2)));
    +            volume->print_zs.push_back(unscale(position.position(2)));
                 volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
                 volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4693,7 +4694,7 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data)
     
             for (const GCodePreviewData::Retraction::Position& position : copy)
             {
    -            volume->print_zs.push_back(unscale(position.position(2)));
    +            volume->print_zs.push_back(unscale(position.position(2)));
                 volume->offsets.push_back(volume->indexed_vertex_array.quad_indices.size());
                 volume->offsets.push_back(volume->indexed_vertex_array.triangle_indices.size());
     
    @@ -4816,7 +4817,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state()
             if (opt != nullptr)
             {
                 BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
    -            print_volume = BoundingBoxf3(Pointf3(unscale(bed_box_2D.min(0)) - tolerance_x, unscale(bed_box_2D.min(1)) - tolerance_y, 0.0), Pointf3(unscale(bed_box_2D.max(0)) + tolerance_x, unscale(bed_box_2D.max(1)) + tolerance_y, m_config->opt_float("max_print_height")));
    +            print_volume = BoundingBoxf3(Vec3d(unscale(bed_box_2D.min(0)) - tolerance_x, unscale(bed_box_2D.min(1)) - tolerance_y, 0.0), Vec3d(unscale(bed_box_2D.max(0)) + tolerance_x, unscale(bed_box_2D.max(1)) + tolerance_y, m_config->opt_float("max_print_height")));
                 // Allow the objects to protrude below the print bed
                 print_volume.min(2) = -1e10;
             }
    @@ -4849,7 +4850,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs)
     
         std::set done;  // prevent moving instances twice
         bool object_moved = false;
    -    Pointf3 wipe_tower_origin(0.0, 0.0, 0.0);
    +    Vec3d wipe_tower_origin(0.0, 0.0, 0.0);
         for (int volume_idx : volume_idxs)
         {
             GLVolume* volume = m_volumes.volumes[volume_idx];
    @@ -4868,7 +4869,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs)
             {
                 // Move a regular object.
                 ModelObject* model_object = m_model->objects[obj_idx];
    -            const Pointf3& origin = volume->get_origin();
    +            const Vec3d& origin = volume->get_origin();
                 model_object->instances[instance_idx]->offset = Pointf(origin(0), origin(1));
                 model_object->invalidate_bounding_box();
                 object_moved = true;
    @@ -4881,7 +4882,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs)
         if (object_moved)
             m_on_instance_moved_callback.call();
     
    -    if (wipe_tower_origin != Pointf3(0.0, 0.0, 0.0))
    +    if (wipe_tower_origin != Vec3d(0.0, 0.0, 0.0))
             m_on_wipe_tower_moved_callback.call(wipe_tower_origin(0), wipe_tower_origin(1));
     }
     
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    index ae20882fc6..3d35495b42 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    @@ -120,7 +120,7 @@ public:
             float zoom;
             float phi;
     //        float distance;
    -        Pointf3 target;
    +        Vec3d target;
     
         private:
             float m_theta;
    @@ -185,7 +185,7 @@ public:
     
         struct Axes
         {
    -        Pointf3 origin;
    +        Vec3d origin;
             float length;
     
             Axes();
    @@ -299,11 +299,11 @@ public:
             struct Drag
             {
                 static const Point Invalid_2D_Point;
    -            static const Pointf3 Invalid_3D_Point;
    +            static const Vec3d Invalid_3D_Point;
     
                 Point start_position_2D;
    -            Pointf3 start_position_3D;
    -            Vectorf3 volume_center_offset;
    +            Vec3d start_position_3D;
    +            Vec3d volume_center_offset;
     
                 bool move_with_shift;
                 int move_volume_idx;
    @@ -636,10 +636,10 @@ private:
     
         // Convert the screen space coordinate to an object space coordinate.
         // If the Z screen space coordinate is not provided, a depth buffer value is substituted.
    -    Pointf3 _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
    +    Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
     
         // Convert the screen space coordinate to world coordinate on the bed.
    -    Pointf3 _mouse_to_bed_3d(const Point& mouse_pos);
    +    Vec3d _mouse_to_bed_3d(const Point& mouse_pos);
     
         void _start_timer();
         void _stop_timer();
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index f4f947d9c2..eaf24c8bc7 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -262,11 +262,10 @@ void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
     {
         ::glDisable(GL_DEPTH_TEST);
     
    -    const Pointf size = box.size().xy();
    -    m_center = box.center().xy();
    +    m_center = to_2d(box.center());
         if (!m_keep_radius)
         {
    -        m_radius = Offset + ::sqrt(sqr(0.5f * size(0)) + sqr(0.5f * size(1)));
    +        m_radius = Offset + 0.5 * to_2d(box.size()).norm();
             m_keep_radius = true;
         }
     
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 506b3972e7..d6223a14d6 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -9,7 +9,6 @@
     namespace Slic3r {
     
     class BoundingBoxf3;
    -class Pointf3;
     
     namespace GUI {
     
    diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp
    index d65f8a5237..46a6da98cb 100644
    --- a/xs/xsp/BoundingBox.xsp
    +++ b/xs/xsp/BoundingBox.xsp
    @@ -96,17 +96,17 @@ new_from_points(CLASS, points)
         Clone clone()
             %code{% RETVAL = THIS; %};
         void merge(BoundingBoxf3* bb) %code{% THIS->merge(*bb); %};
    -    void merge_point(Pointf3* point) %code{% THIS->merge(*point); %};
    +    void merge_point(Vec3d* point) %code{% THIS->merge(*point); %};
         void scale(double factor);
         void translate(double x, double y, double z);
         void offset(double delta);
    -    bool contains_point(Pointf3* point) %code{% RETVAL = THIS->contains(*point); %};
    -    Clone size();
    -    Clone center();
    +    bool contains_point(Vec3d* point) %code{% RETVAL = THIS->contains(*point); %};
    +    Clone size();
    +    Clone center();
         double radius();
         bool empty() %code{% RETVAL = empty(*THIS); %};
    -    Clone min_point() %code{% RETVAL = THIS->min; %};
    -    Clone max_point() %code{% RETVAL = THIS->max; %};
    +    Clone min_point() %code{% RETVAL = THIS->min; %};
    +    Clone max_point() %code{% RETVAL = THIS->max; %};
         double x_min() %code{% RETVAL = THIS->min(0); %};
         double x_max() %code{% RETVAL = THIS->max(0); %};
         double y_min() %code{% RETVAL = THIS->min(1); %};
    diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp
    index 6349a7c766..d71e4011c0 100644
    --- a/xs/xsp/GUI_3DScene.xsp
    +++ b/xs/xsp/GUI_3DScene.xsp
    @@ -55,10 +55,10 @@
         int                 object_idx() const;
         int                 volume_idx() const;
         int                 instance_idx() const;
    -    Clone      origin() const
    +    Clone        origin() const
             %code%{ RETVAL = THIS->get_origin(); %};
         void                translate(double x, double y, double z)
    -        %code%{ THIS->set_origin(THIS->get_origin() + Pointf3(x, y, z)); %};
    +        %code%{ THIS->set_origin(THIS->get_origin() + Vec3d(x, y, z)); %};
         Clone bounding_box() const
             %code%{ RETVAL = THIS->bounding_box; %};
         Clone transformed_bounding_box() const;
    diff --git a/xs/xsp/Line.xsp b/xs/xsp/Line.xsp
    index f7437de01c..777dc41fa7 100644
    --- a/xs/xsp/Line.xsp
    +++ b/xs/xsp/Line.xsp
    @@ -78,15 +78,15 @@ Line::coincides_with(line_sv)
     
     
     %name{Slic3r::Linef3} class Linef3 {
    -    Linef3(Pointf3* a, Pointf3* b)
    +    Linef3(Vec3d* a, Vec3d* b)
             %code{% RETVAL = new Linef3(*a, *b); %};
         ~Linef3();
         Clone clone()
             %code{% RETVAL = THIS; %};
    -    Ref a()
    +    Ref a()
             %code{% RETVAL = &THIS->a; %};
    -    Ref b()
    +    Ref b()
             %code{% RETVAL = &THIS->b; %};
    -    Clone intersect_plane(double z);
    +    Clone intersect_plane(double z);
         void scale(double factor);
     };
    diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp
    index 25c26c3804..d8e5095270 100644
    --- a/xs/xsp/Model.xsp
    +++ b/xs/xsp/Model.xsp
    @@ -289,9 +289,9 @@ ModelMaterial::attributes()
         void set_layer_height_profile(std::vector profile)
             %code%{ THIS->layer_height_profile = profile; THIS->layer_height_profile_valid = true; %};
     
    -    Ref origin_translation()
    +    Ref origin_translation()
             %code%{ RETVAL = &THIS->origin_translation; %};
    -    void set_origin_translation(Pointf3* point)
    +    void set_origin_translation(Vec3d* point)
             %code%{ THIS->origin_translation = *point; %};
         
         bool needed_repair() const;
    @@ -299,7 +299,7 @@ ModelMaterial::attributes()
         int facets_count();
         void center_around_origin();
         void translate(double x, double y, double z);
    -    void scale_xyz(Pointf3* versor)
    +    void scale_xyz(Vec3d* versor)
             %code{% THIS->scale(*versor); %};
         void rotate(float angle, Axis axis);
         void mirror(Axis axis);
    diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp
    index 2a98f9c396..dc4f592cd2 100644
    --- a/xs/xsp/Point.xsp
    +++ b/xs/xsp/Point.xsp
    @@ -121,10 +121,10 @@ Point::coincides_with(point_sv)
         std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf", (*THIS)(0), (*THIS)(1)); RETVAL = buf; %};
     };
     
    -%name{Slic3r::Pointf3} class Pointf3 {
    -    Pointf3(double _x = 0, double _y = 0, double _z = 0);
    -    ~Pointf3();
    -    Clone clone()
    +%name{Slic3r::Pointf3} class Vec3d {
    +    Vec3d(double _x = 0, double _y = 0, double _z = 0);
    +    ~Vec3d();
    +    Clone clone()
             %code{% RETVAL = THIS; %};
         double x()
             %code{% RETVAL = (*THIS)(0); %};
    @@ -139,14 +139,14 @@ Point::coincides_with(point_sv)
         void set_z(double val)
             %code{% (*THIS)(2) = val; %};
         void translate(double x, double y, double z)
    -        %code{% *THIS += Pointf3(x, y, z); %};
    +        %code{% *THIS += Vec3d(x, y, z); %};
         void scale(double factor)
             %code{% *THIS *= factor; %};
    -    double distance_to(Pointf3* point)
    +    double distance_to(Vec3d* point)
             %code{% RETVAL = (*point - *THIS).norm(); %};
    -    Pointf3* negative()
    -        %code{% RETVAL = new Pointf3(- *THIS); %};
    -    Pointf3* vector_to(Pointf3* point)
    -        %code{% RETVAL = new Pointf3(*point - *THIS); %};
    +    Vec3d* negative()
    +        %code{% RETVAL = new Vec3d(- *THIS); %};
    +    Vec3d* vector_to(Vec3d* point)
    +        %code{% RETVAL = new Vec3d(*point - *THIS); %};
         std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf,%lf", (*THIS)(0), (*THIS)(1), (*THIS)(2)); RETVAL = buf; %};
     };
    diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp
    index 1691066a50..4a269bd847 100644
    --- a/xs/xsp/TriangleMesh.xsp
    +++ b/xs/xsp/TriangleMesh.xsp
    @@ -16,7 +16,7 @@
         void repair();
         void WriteOBJFile(char* output_file);
         void scale(float factor);
    -    void scale_xyz(Pointf3* versor)
    +    void scale_xyz(Vec3d* versor)
             %code{% THIS->scale(*versor); %};
         void translate(float x, float y, float z);
         void rotate_x(float angle);
    @@ -33,7 +33,7 @@
         ExPolygons horizontal_projection();
         Clone convex_hull();
         Clone bounding_box();
    -    Clone center()
    +    Clone center()
             %code{% RETVAL = THIS->bounding_box().center(); %};
         int facets_count();
         void reset_repair_stats();
    diff --git a/xs/xsp/my.map b/xs/xsp/my.map
    index 4a14f483fc..4c3cc35222 100644
    --- a/xs/xsp/my.map
    +++ b/xs/xsp/my.map
    @@ -70,9 +70,9 @@ Pointf*                    O_OBJECT_SLIC3R
     Ref                O_OBJECT_SLIC3R_T
     Clone              O_OBJECT_SLIC3R_T
     
    -Pointf3*                   O_OBJECT_SLIC3R
    -Ref               O_OBJECT_SLIC3R_T
    -Clone             O_OBJECT_SLIC3R_T
    +Vec3d*                     O_OBJECT_SLIC3R
    +Ref                 O_OBJECT_SLIC3R_T
    +Clone               O_OBJECT_SLIC3R_T
     
     Line*                      O_OBJECT_SLIC3R
     Ref                  O_OBJECT_SLIC3R_T
    diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt
    index b576b1373e..7141487946 100644
    --- a/xs/xsp/typemap.xspt
    +++ b/xs/xsp/typemap.xspt
    @@ -25,9 +25,9 @@
     %typemap{Pointf*};
     %typemap{Ref}{simple};
     %typemap{Clone}{simple};
    -%typemap{Pointf3*};
    -%typemap{Ref}{simple};
    -%typemap{Clone}{simple};
    +%typemap{Vec3d*};
    +%typemap{Ref}{simple};
    +%typemap{Clone}{simple};
     %typemap{BoundingBox*};
     %typemap{Ref}{simple};
     %typemap{Clone}{simple};
    
    From bc64154f2111465b13b84c38f378d9a1138516b2 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Tue, 21 Aug 2018 17:47:05 +0200
    Subject: [PATCH 156/185] PrusaDoubleSlider improvement
    
    + OnKeyDown
    + marked selected thumb
    + marked selected control
    ...
    ---
     resources/icons/down_half_circle.png  | Bin 0 -> 506 bytes
     resources/icons/left_half_circle.png  | Bin 0 -> 518 bytes
     resources/icons/right_half_circle.png | Bin 0 -> 521 bytes
     resources/icons/up_half_circle.png    | Bin 0 -> 517 bytes
     xs/src/slic3r/GUI/GUI.cpp             |   3 +-
     xs/src/slic3r/GUI/wxExtensions.cpp    | 119 ++++++++++++++++++++++----
     xs/src/slic3r/GUI/wxExtensions.hpp    |  16 +++-
     7 files changed, 118 insertions(+), 20 deletions(-)
     create mode 100644 resources/icons/down_half_circle.png
     create mode 100644 resources/icons/left_half_circle.png
     create mode 100644 resources/icons/right_half_circle.png
     create mode 100644 resources/icons/up_half_circle.png
    
    diff --git a/resources/icons/down_half_circle.png b/resources/icons/down_half_circle.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..84aba5c3cdd7b54590874c1999593bb3da8b50fd
    GIT binary patch
    literal 506
    zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c!3HEhl+{lMQjEnx?oJHr&dIz4a#+$GeH|GX
    zHuiJ>Nn{1`ISV`@iy0V%N?Oj+ueoXKL{?^yL>W8Sy_Q&I?yN`F#c^(
    zlnE46Epd$~Nl7e8wMs5Z1yT$~28Kqu29~-;#vz6lR)z*v2Bx|O=2iv<8LQ0dQ8eV{
    zr(~v8;?^Mfz3(tkgCxj?;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U#L5e~$
    zOL9^f+;j4aiz^lUixTtFQx%*ui;{Cv6+Dyk^N!!=nE+I}&C|s(L?d|WguR?hjv_7l
    zZFin=VX+Qq(RMr-A+V8++u-UEhc^e9)*NDM*~}um*3pNDrT&`v_C-x6-?B6RPP>2L
    zjn#e++kR<_M<14Xc{wRJr+-)!aBLIjTfvCNj42z}d}b4V9dEq&jd95ToT$odg*M|I
    zBFl?1!X=eW1YYaCDG!($ZSg-N_~9Dn$9pz7F)J>%=e@da*=|pH)t5T{<+m!^k8VGx
    re_K_?tiY;m=e?F|3{{^E>KnvU@0-4U)LZln=mQ2%S3j3^P6Cc6VX;4}uH!E}sliR#xDc4m3&!jDK4c
    zWda3NOI#yLQW8s2t&)pUffR$0fuWJEfu*jIafqRXm7#%^fvK*6xs`!I#wxRV6b-rg
    zDVb@NxHU+A?>h|CAPKS|I6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD>GkfM;x
    zlAKfq_niFV;z|YoqQt!PR0ZeEqU4-Z1<&OCyyN$ICIFQl@^o#Zf-s&7JG2Zu@#nxR&$u?=W7l;>ts@a<(-Op0Jm6KWLe2
    zHd*5h)0L=#&bcD>cBLNayjR)YxW+Gi7bNue<6Sxbi5DF|wF&+`2J{Vsr>mdKI;Vst
    E09RGD!TCc6VX;4}uH!E}sliR#xDc4m3&!jDK4c
    zWda3NOI#yLQW8s2t&)pUffR$0fuWJEfu*jIafqRXm7#%^fvK*6xs`!I#wxRV6b-rg
    zDVb@NxHU+A?>h|CAPKS|I6tkVJh3R1p}f3YFEcN@I61K(RWH9NefB#WDWD>GkfM;x
    zlAKfq_niFV;z|YoqQt!PR0ZeEqU4-Z1<&OCyyN$ICIFQl^>lFz(Fjid6Mz20e8+&!
    zM#rPgjRzObYix|}VU#rZx1L$+2Ybq2#z*=NInsv?2yBy5VQ2H<>2a7O*>STmf}_KK
    zePY541`|Q984FBU8bpejKVJUL!y`TUfYS=Dga+=8?`PPfelxc-&G2l$RiMGZD5kJ)
    zvur=pgfqLXD?}U^^H>*b5DYrFm_ejKYo3M3ANEJ<9dg23oe!ETlzFoJYGL+e^jW?|
    ziMjc|%?1JCh!6S#|5+ZDJLGWntZAxtU}F*J$YWz;csx%>XqWDSRX`6hc)I$ztaD0e
    F0sxp@rON;S
    
    literal 0
    HcmV?d00001
    
    diff --git a/resources/icons/up_half_circle.png b/resources/icons/up_half_circle.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..9eea07fda7f4e2b7aa63a2370c2899b44106791a
    GIT binary patch
    literal 517
    zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c!3HEhl+{lMQjEnx?oJHr&dIz4a#+$GeH|GX
    zHuiJ>Nn{1`ISV`@iy0V%N?Oj+ueoXKL{?^yL>W8Sy_Q&I?yN`F#c^(
    zlnE46Epd$~Nl7e8wMs5Z1yT$~28Kqu29~-;#vz6lR)z*v2Bx|O=2iv<8LQ0dQ8eV{
    zr(~v8;?^Mfz3(tkgCxj?;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U#L5e~$
    zOL9^f+;j4aiz^lUixTtFQx%*ui;{Cv6+Dyk^N!!=nE+IJ(9^{+L?d|WgpGWRjv}u7
    z7X?I*icQ(q9H;%K!)@`xi$4TbFTeMrx3{b5NSKrIwcW;(RF&R%<{1?~ziV#1euC7V
    zB1=b;)(Y0;H>$siZP|4}(s}8-z3T6I4>9IU3)otEGNeTBj<5OUn^)dB+fR9YLh8)y
    zf*a0}>x%6HpI#L)N!U?5ZGZB`&kwuS?r&{97qU1iHD&2@=TjODv&zo>?v0x?Bh9_K
    zk!_j%e`&E*p=z@tnk(M2PZLkoiB`FpcKKYh(aCA-KHBD=R?CNp06oLt>FVdQ&MBb@
    E0N(4k1^@s6
    
    literal 0
    HcmV?d00001
    
    diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
    index e50c7220a0..67be479879 100644
    --- a/xs/src/slic3r/GUI/GUI.cpp
    +++ b/xs/src/slic3r/GUI/GUI.cpp
    @@ -1057,7 +1057,8 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
         sizer->AddSpacer(5);
         sizer->Add(slider_h, 0, wxEXPAND | wxLEFT, 20);
         sizer->AddSpacer(5);
    -    PrusaDoubleSlider* slider_v = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 100, wxDefaultPosition, wxSize(wxDefaultSize.x ,150), wxSL_VERTICAL);
    +    PrusaDoubleSlider* slider_v = new PrusaDoubleSlider(parent, wxID_ANY, 5, 7, 0, 10, wxDefaultPosition, wxSize(wxDefaultSize.x ,150), wxSL_VERTICAL);
    +    slider_v->SetKoefForLabels(0.15);
         sizer->AddSpacer(5);
         sizer->Add(slider_v, 0, wxLEFT, 20);
         sizer->AddSpacer(5);
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index 710d91b496..3ba0d38863 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -6,6 +6,7 @@
     #include 
     #include 
     #include 
    +#include 
     
     const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200;
     const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200;
    @@ -761,7 +762,7 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
                                             long style,
                                             const wxValidator& val,
                                             const wxString& name) : 
    -    wxControl(parent, id, pos, size, wxBORDER_NONE),
    +    wxControl(parent, id, pos, size, wxWANTS_CHARS | wxBORDER_NONE),
         m_lower_value(lowerValue), m_higher_value (higherValue), 
         m_min_value(minValue), m_max_value(maxValue),
         m_style(style)
    @@ -784,8 +785,10 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
         Bind(wxEVT_LEFT_DOWN,   &PrusaDoubleSlider::OnLeftDown, this);
         Bind(wxEVT_MOTION,      &PrusaDoubleSlider::OnMotion,   this);
         Bind(wxEVT_LEFT_UP,     &PrusaDoubleSlider::OnLeftUp,   this);
    -    Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeftUp,   this);
         Bind(wxEVT_MOUSEWHEEL,  &PrusaDoubleSlider::OnWheel,    this);
    +    Bind(wxEVT_ENTER_WINDOW,&PrusaDoubleSlider::OnEnterWin, this);
    +    Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeaveWin,   this);
    +    Bind(wxEVT_KEY_DOWN,    &PrusaDoubleSlider::OnKeyDown,  this);
     
         // control's view variables
         SLIDER_MARGIN     = 2 + (style == wxSL_HORIZONTAL ? m_thumb_higher.GetWidth() : m_thumb_higher.GetHeight());
    @@ -865,9 +868,22 @@ void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& highe
         }
     }
     
    -void PrusaDoubleSlider::OnPaint(wxPaintEvent& event)
    +void PrusaDoubleSlider::draw_focus_rect()
    +{
    +    if (!m_is_focused) 
    +        return;
    +    const wxSize sz = GetSize();
    +    wxPaintDC dc(this);
    +    const wxPen pen = wxPen(wxColour(128, 128, 10), 1, wxPENSTYLE_DOT);
    +    dc.SetPen(pen);
    +    dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT));
    +    dc.DrawRectangle(2, 2, sz.x - 4, sz.y - 4);
    +}
    +
    +void PrusaDoubleSlider::render()
     {
         SetBackgroundColour(GetParent()->GetBackgroundColour());
    +    draw_focus_rect();
     
         wxPaintDC dc(this);    
         int width, height;
    @@ -888,6 +904,21 @@ void PrusaDoubleSlider::OnPaint(wxPaintEvent& event)
         draw_higher_thumb(dc, pos);
     }
     
    +void PrusaDoubleSlider::draw_info_line(wxDC& dc, const wxPoint& pos, const wxSize& thumb_size, const SelectedSlider selection)
    +{
    +    if (m_selection == selection) {
    +        dc.SetPen(DARK_ORANGE_PEN);
    +        is_horizontal() ? dc.DrawLine(pos.x, pos.y - thumb_size.y, pos.x, pos.y + thumb_size.y):
    +                          dc.DrawLine(pos.x - thumb_size.x, pos.y-1, pos.x + thumb_size.x, pos.y-1);
    +    }
    +}
    +
    +wxString PrusaDoubleSlider::get_label(const int value)
    +{
    +    return m_label_koef == 1.0 ? wxString::Format("%d", value) :
    +                                 wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None);
    +}
    +
     void PrusaDoubleSlider::draw_lower_thumb(wxDC& dc, const wxPoint& pos)
     {
         // Draw thumb
    @@ -902,13 +933,16 @@ void PrusaDoubleSlider::draw_lower_thumb(wxDC& dc, const wxPoint& pos)
             y_draw = pos.y;
         }
         dc.DrawBitmap(m_thumb_lower, x_draw, y_draw);
    +    // Draw info_line
    +    draw_info_line(dc, pos, thumb_size, ssLower);
     
         // Draw thumb text
         wxCoord text_width, text_height;
    -    dc.GetTextExtent(wxString::Format("%d", m_lower_value), &text_width, &text_height);
    +    wxString label = get_label(m_lower_value);
    +    dc.GetTextExtent(label, &text_width, &text_height);
         wxPoint text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + thumb_size.x) :
    -                                         wxPoint(pos.x + thumb_size.y, pos.y - 1 - text_height);
    -    dc.DrawText(wxString::Format("%d", m_lower_value), text_pos);
    +                                         wxPoint(pos.x + thumb_size.x+1, pos.y - 0.5*text_height - 1);
    +    dc.DrawText(label, text_pos);
     
         // Update thumb rect
         m_rect_lower_thumb = wxRect(x_draw, y_draw, thumb_size.x, thumb_size.y);
    @@ -928,13 +962,16 @@ void PrusaDoubleSlider::draw_higher_thumb(wxDC& dc, const wxPoint& pos)
             y_draw = pos.y - thumb_size.y;
         }
         dc.DrawBitmap(m_thumb_higher, x_draw, y_draw);
    +    // Draw info_line
    +    draw_info_line(dc, pos, thumb_size, ssHigher);
     
         // Draw thumb text
         wxCoord text_width, text_height;
    -    dc.GetTextExtent(wxString::Format("%d", m_higher_value), &text_width, &text_height);
    -    wxPoint text_pos = is_horizontal() ? wxPoint(pos.x - text_width-1,                pos.y - thumb_size.x - text_height) :
    -                                         wxPoint(pos.x - text_width-1 - thumb_size.y, pos.y + 1);
    -    dc.DrawText(wxString::Format("%d", m_higher_value), text_pos);
    +    wxString label = get_label(m_higher_value);
    +    dc.GetTextExtent(label, &text_width, &text_height);
    +    wxPoint text_pos = is_horizontal() ? wxPoint(pos.x - text_width-1, pos.y - thumb_size.x - text_height) :
    +                                         wxPoint(pos.x - text_width - 1 - thumb_size.x, pos.y - 0.5*text_height + 1);
    +    dc.DrawText(label, text_pos);
     
         // Update thumb rect
         m_rect_higher_thumb = wxRect(x_draw, y_draw, thumb_size.x, thumb_size.y);
    @@ -1033,6 +1070,9 @@ void PrusaDoubleSlider::OnMotion(wxMouseEvent& event)
     void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event)
     {
         m_is_left_down = false;
    +    m_selection = ssUndef;
    +    Refresh();
    +    Update();
         event.Skip();
     
         wxCommandEvent e(wxEVT_SCROLL_CHANGED);
    @@ -1040,18 +1080,29 @@ void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event)
         ProcessWindowEvent(e);
     }
     
    -void PrusaDoubleSlider::OnWheel(wxMouseEvent& event)
    +void PrusaDoubleSlider::OnEnterWin(wxMouseEvent& event)
     {
    -    wxClientDC dc(this);
    -    wxPoint pos = event.GetLogicalPosition(dc);
    -    detect_selected_slider(pos, true);
    +    m_is_focused = true;
    +    Refresh();
    +    Update();
    +    event.Skip();
    +}
     
    -    if (m_selection == ssUndef)
    -        return;
    +void PrusaDoubleSlider::OnLeaveWin(wxMouseEvent& event)
    +{
    +    m_is_focused = false;
    +    OnLeftUp(event);
    +}
     
    -    int delta = event.GetWheelRotation() > 0 ? -1 : 1;
    +// "condition" have to be true for:
    +//    -  value increase (if wxSL_VERTICAL)
    +//    -  value decrease (if wxSL_HORIZONTAL) 
    +void PrusaDoubleSlider::move_current_thumb(const bool condition)
    +{
    +    int delta = condition ? -1 : 1;
         if (is_horizontal())
             delta *= -1;
    +
         if (m_selection == ssLower) {
             m_lower_value -= delta;
             correct_lower_value();
    @@ -1067,4 +1118,38 @@ void PrusaDoubleSlider::OnWheel(wxMouseEvent& event)
         e.SetEventObject(this);
         ProcessWindowEvent(e);
     }
    +
    +void PrusaDoubleSlider::OnWheel(wxMouseEvent& event)
    +{
    +    wxClientDC dc(this);
    +    wxPoint pos = event.GetLogicalPosition(dc);
    +    detect_selected_slider(pos, true);
    +
    +    if (m_selection == ssUndef)
    +        return;
    +
    +    move_current_thumb(event.GetWheelRotation() > 0);
    +}
    +
    +void PrusaDoubleSlider::OnKeyDown(wxKeyEvent &event)
    +{
    +    if (is_horizontal())
    +    {
    +        if (event.GetKeyCode() == WXK_LEFT || event.GetKeyCode() == WXK_RIGHT)
    +            move_current_thumb(event.GetKeyCode() == WXK_LEFT); 
    +        else if (event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_DOWN){
    +            m_selection = event.GetKeyCode() == WXK_UP ? ssHigher : ssLower;
    +            Refresh();
    +        }
    +    }
    +    else {
    +        if (event.GetKeyCode() == WXK_LEFT || event.GetKeyCode() == WXK_RIGHT) {
    +            m_selection = event.GetKeyCode() == WXK_LEFT ? ssHigher : ssLower;
    +            Refresh();
    +        }
    +        else if (event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_DOWN)
    +            move_current_thumb(event.GetKeyCode() == WXK_UP);
    +    }
    +}
    +
     // *****************************************************************************
    diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
    index 584882d96d..59d27e448b 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.hpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.hpp
    @@ -525,27 +525,37 @@ public:
         }
         void SetLowerValue(int lower_val);
         void SetHigherValue(int higher_val);
    +    void SetKoefForLabels(float koef){ m_label_koef = koef;}
     
         wxSize DoGetBestSize(){ return wxDefaultSize; }
     
    -    void OnPaint(wxPaintEvent& event);
    +    void OnPaint(wxPaintEvent& ){ render();}
         void OnLeftDown(wxMouseEvent& event);
         void OnMotion(wxMouseEvent& event);
         void OnLeftUp(wxMouseEvent& event);
    +    void OnEnterWin(wxMouseEvent& event);
    +    void OnLeaveWin(wxMouseEvent& event);
         void OnWheel(wxMouseEvent& event);
    +    void OnKeyDown(wxKeyEvent &event);
     
     protected:
    + 
    +    void    render();
    +    void    draw_info_line(wxDC& dc, const wxPoint& pos, const wxSize& thumb_size, SelectedSlider selection);
    +    wxString    get_label(const int value);
         void    correct_lower_value();
         void    correct_higher_value();
         void    draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
         double  get_scroll_step();
         void    get_lower_and_higher_position(int& lower_pos, int& higher_pos);
    -    void    draw_lower_thumb (wxDC& dc, const wxPoint& pos);
    +    void    draw_focus_rect();
    +    void    draw_lower_thumb(wxDC& dc, const wxPoint& pos);
         void    draw_higher_thumb(wxDC& dc, const wxPoint& pos);
         int     position_to_value(wxDC& dc, const wxCoord x, const wxCoord y);
         void    detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false);
         bool    is_point_in_rect(const wxPoint& pt, const wxRect& rect);
         bool    is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
    +    void    move_current_thumb(const bool condition);
     
     private:
         int         m_min_value;
    @@ -556,10 +566,12 @@ private:
         wxBitmap    m_thumb_lower;
         SelectedSlider  m_selection;
         bool        m_is_left_down = false;
    +    bool        m_is_focused = false;
     
         wxRect      m_rect_lower_thumb;
         wxRect      m_rect_higher_thumb;
         long        m_style;
    +    float       m_label_koef = 1.0;
     
     // control's view variables
         wxCoord SLIDER_MARGIN; // margin around slider
    
    From cae08061129066bf4a225f8f4af4920cf49af7eb Mon Sep 17 00:00:00 2001
    From: bubnikv 
    Date: Tue, 21 Aug 2018 20:34:45 +0200
    Subject: [PATCH 157/185] Eradicated most of Pointf extras compared to pure
     Eigen::Vector2d.
    
    ---
     xs/src/libslic3r/BoundingBox.hpp        |  2 +-
     xs/src/libslic3r/Config.hpp             |  2 +-
     xs/src/libslic3r/GCode.hpp              |  1 +
     xs/src/libslic3r/GCode/PrintExtents.cpp | 23 ++++++++---------------
     xs/src/libslic3r/Geometry.cpp           |  8 ++++----
     xs/src/libslic3r/Line.hpp               |  2 +-
     xs/src/libslic3r/Model.cpp              | 24 +++++++++++-------------
     xs/src/libslic3r/Model.hpp              |  2 +-
     xs/src/libslic3r/Point.cpp              | 22 ----------------------
     xs/src/libslic3r/Point.hpp              | 11 +++++++----
     xs/src/libslic3r/Polygon.hpp            | 11 ++++++-----
     xs/src/libslic3r/Polyline.hpp           |  9 ++++-----
     xs/src/libslic3r/TriangleMesh.cpp       |  9 +++------
     xs/src/perlglue.cpp                     |  2 +-
     xs/src/slic3r/GUI/2DBed.cpp             | 11 +++--------
     xs/src/slic3r/GUI/2DBed.hpp             |  4 ++--
     xs/src/slic3r/GUI/3DScene.cpp           |  6 +++---
     xs/src/slic3r/GUI/BedShapeDialog.cpp    |  6 ++++--
     xs/src/slic3r/GUI/Field.cpp             | 13 +++++--------
     xs/src/slic3r/GUI/GLGizmo.cpp           |  1 +
     xs/xsp/Point.xsp                        |  2 +-
     21 files changed, 68 insertions(+), 103 deletions(-)
    
    diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp
    index 7a02878607..cb8bc3ad96 100644
    --- a/xs/src/libslic3r/BoundingBox.hpp
    +++ b/xs/src/libslic3r/BoundingBox.hpp
    @@ -18,7 +18,7 @@ public:
         BoundingBoxBase() : defined(false), min(PointClass::Zero()), max(PointClass::Zero()) {}
         BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : 
             min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
    -    BoundingBoxBase(const std::vector& points)
    +    BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero())
         {
             if (points.empty())
                 CONFESS("Empty point set supplied to BoundingBoxBase constructor");
    diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp
    index ef54b12648..a1b0f624ea 100644
    --- a/xs/src/libslic3r/Config.hpp
    +++ b/xs/src/libslic3r/Config.hpp
    @@ -696,7 +696,7 @@ public:
             std::istringstream is(str);
             std::string point_str;
             while (std::getline(is, point_str, ',')) {
    -            Pointf point;
    +            Pointf point(Vec2d::Zero());
                 std::istringstream iss(point_str);
                 std::string coord_str;
                 if (std::getline(iss, coord_str, 'x')) {
    diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp
    index 4953c39fef..235ca62f0b 100644
    --- a/xs/src/libslic3r/GCode.hpp
    +++ b/xs/src/libslic3r/GCode.hpp
    @@ -125,6 +125,7 @@ private:
     class GCode {
     public:        
         GCode() : 
    +    	m_origin(Vec2d::Zero()),
             m_enable_loop_clipping(true), 
             m_enable_cooling_markers(false), 
             m_enable_extrusion_role_markers(false), 
    diff --git a/xs/src/libslic3r/GCode/PrintExtents.cpp b/xs/src/libslic3r/GCode/PrintExtents.cpp
    index a1cd0cde9f..dd0b18db84 100644
    --- a/xs/src/libslic3r/GCode/PrintExtents.cpp
    +++ b/xs/src/libslic3r/GCode/PrintExtents.cpp
    @@ -136,8 +136,9 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
     {
         // Wipe tower extrusions are saved as if the tower was at the origin with no rotation
         // We need to get position and angle of the wipe tower to transform them to actual position.
    -    Pointf wipe_tower_pos(print.config.wipe_tower_x.value, print.config.wipe_tower_y.value);
    -    float wipe_tower_angle = print.config.wipe_tower_rotation_angle.value;
    +    Transform2d trafo =
    +        Eigen::Translation2d(print.config.wipe_tower_x.value, print.config.wipe_tower_y.value) *
    +        Eigen::Rotation2Dd(print.config.wipe_tower_rotation_angle.value);
     
         BoundingBoxf bbox;
         for (const std::vector &tool_changes : print.m_wipe_tower_tool_changes) {
    @@ -147,19 +148,11 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
                 for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
                     const WipeTower::Extrusion &e = tcr.extrusions[i];
                     if (e.width > 0) {
    -                    Pointf  p1((&e - 1)->pos.x, (&e - 1)->pos.y);
    -                    Pointf  p2(e.pos.x, e.pos.y);
    -                    p1.rotate(wipe_tower_angle);
    -                    p1 += wipe_tower_pos;
    -                    p2.rotate(wipe_tower_angle);
    -                    p2 += wipe_tower_pos;
    -
    -                    bbox.merge(p1);
    -                    coordf_t radius = 0.5 * e.width;
    -                    bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius);
    -                    bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius);
    -                    bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius);
    -                    bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius);
    +                    Pointf delta = 0.5 * Vec2d(e.width, e.width);
    +                    Pointf p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y);
    +                    Pointf p2 = trafo * Vec2d(e.pos.x, e.pos.y);
    +                    bbox.merge(p1.cwiseMin(p2) - delta);
    +                    bbox.merge(p1.cwiseMax(p2) + delta);
                     }
                 }
             }
    diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp
    index b4813be14a..f513341551 100644
    --- a/xs/src/libslic3r/Geometry.cpp
    +++ b/xs/src/libslic3r/Geometry.cpp
    @@ -410,13 +410,13 @@ Pointfs arrange(size_t num_parts, const Pointf &part_size, coordf_t gap, const B
     }
     #else
     class ArrangeItem {
    -    public:
    -    Pointf pos;
    +public:
    +    Pointf pos = Vec2d::Zero();
         size_t index_x, index_y;
         coordf_t dist;
     };
     class ArrangeItemIndex {
    -    public:
    +public:
         coordf_t index;
         ArrangeItem item;
         ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {};
    @@ -433,7 +433,7 @@ arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const Boundi
         part(0) += dist;
         part(1) += dist;
         
    -    Pointf area;
    +    Pointf area(Vec2d::Zero());
         if (bb != NULL && bb->defined) {
             area = bb->size();
         } else {
    diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp
    index 39a7c2223b..65e04dc930 100644
    --- a/xs/src/libslic3r/Line.hpp
    +++ b/xs/src/libslic3r/Line.hpp
    @@ -71,7 +71,7 @@ public:
     class Linef
     {
     public:
    -    Linef() {}
    +    Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {}
         explicit Linef(Pointf _a, Pointf _b): a(_a), b(_b) {}
     
         Pointf a;
    diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
    index cc59b65588..047751ecd6 100644
    --- a/xs/src/libslic3r/Model.cpp
    +++ b/xs/src/libslic3r/Model.cpp
    @@ -726,24 +726,22 @@ void ModelObject::center_around_origin()
             if (! v->modifier)
     			bb.merge(v->mesh.bounding_box());
         
    -    // first align to origin on XYZ
    -    Vec3d vector(-bb.min(0), -bb.min(1), -bb.min(2));
    -    
    -    // then center it on XY
    +    // First align to origin on XYZ, then center it on XY.
         Vec3d size = bb.size();
    -    vector(0) -= size(0)/2;
    -    vector(1) -= size(1)/2;
    +    size(2) = 0.;
    +    Vec3d shift3 = - bb.min - 0.5 * size;
    +    // Unaligned vector, for the Rotation2D to work on Visual Studio 2013.
    +    Eigen::Vector2d shift2 = to_2d(shift3);
         
    -    this->translate(vector);
    -    this->origin_translation += vector;
    +    this->translate(shift3);
    +    this->origin_translation += shift3;
         
         if (!this->instances.empty()) {
             for (ModelInstance *i : this->instances) {
                 // apply rotation and scaling to vector as well before translating instance,
                 // in order to leave final position unaltered
    -            Vectorf v = - to_2d(vector);
    -            v.rotate(i->rotation);
    -            i->offset += v * i->scaling_factor;
    +            Eigen::Rotation2Dd rot(i->rotation);
    +            i->offset -= rot * shift2 * i->scaling_factor;
             }
             this->invalidate_bounding_box();
         }
    @@ -762,7 +760,7 @@ void ModelObject::scale(const Vec3d &versor)
         for (ModelVolume *v : this->volumes)
             v->mesh.scale(versor);
         // reset origin translation since it doesn't make sense anymore
    -    this->origin_translation = Vec3d(0,0,0);
    +    this->origin_translation = Vec3d::Zero();
         this->invalidate_bounding_box();
     }
     
    @@ -784,7 +782,7 @@ void ModelObject::rotate(float angle, const Axis &axis)
             }
         }
     
    -    this->origin_translation = Vec3d(0, 0, 0);
    +    this->origin_translation = Vec3d::Zero();
         this->invalidate_bounding_box();
     }
     
    diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
    index dfe391b0e8..a7597ada51 100644
    --- a/xs/src/libslic3r/Model.hpp
    +++ b/xs/src/libslic3r/Model.hpp
    @@ -235,7 +235,7 @@ private:
         // Parent object, owning this instance.
         ModelObject* object;
     
    -    ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), object(object), print_volume_state(PVS_Inside) {}
    +    ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), offset(Vec2d::Zero()), object(object), print_volume_state(PVS_Inside) {}
         ModelInstance(ModelObject *object, const ModelInstance &other) :
             rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset), object(object), print_volume_state(PVS_Inside) {}
     };
    diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp
    index 10578641e0..0fe76c6113 100644
    --- a/xs/src/libslic3r/Point.cpp
    +++ b/xs/src/libslic3r/Point.cpp
    @@ -153,28 +153,6 @@ std::ostream& operator<<(std::ostream &stm, const Pointf &pointf)
         return stm << pointf(0) << "," << pointf(1);
     }
     
    -void Pointf::rotate(double angle)
    -{
    -    double cur_x = (*this)(0);
    -    double cur_y = (*this)(1);
    -    double s     = ::sin(angle);
    -    double c     = ::cos(angle);
    -    (*this)(0) = c * cur_x - s * cur_y;
    -    (*this)(1) = c * cur_y + s * cur_x;
    -}
    -
    -void Pointf::rotate(double angle, const Pointf ¢er)
    -{
    -    double cur_x = (*this)(0);
    -    double cur_y = (*this)(1);
    -    double s     = ::sin(angle);
    -    double c     = ::cos(angle);
    -    double dx    = cur_x - center(0);
    -    double dy    = cur_y - center(1);
    -    (*this)(0) = center(0) + c * dx - s * dy;
    -    (*this)(1) = center(1) + c * dy + s * dx;
    -}
    -
     namespace int128 {
     
     int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3)
    diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
    index e189e22eec..b5af3b1def 100644
    --- a/xs/src/libslic3r/Point.hpp
    +++ b/xs/src/libslic3r/Point.hpp
    @@ -47,6 +47,8 @@ typedef Eigen::Transform Transform2d
     typedef Eigen::Transform Transform3f;
     typedef Eigen::Transform Transform3d;
     
    +inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0) || (lhs(0) == rhs(0) && lhs(1) < rhs(1)); }
    +
     inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
     inline coord_t cross2(const Vec2crd &v1, const Vec2crd &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
     inline float   cross2(const Vec2f   &v1, const Vec2f   &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
    @@ -249,7 +251,8 @@ class Pointf : public Vec2d
     public:
         typedef coordf_t coord_type;
     
    -    explicit Pointf() { (*this)(0) = (*this)(1) = 0.; }
    +//    explicit Pointf() { (*this)(0) = (*this)(1) = 0.; }
    +    explicit Pointf() {  }
         explicit Pointf(coordf_t x, coordf_t y) { (*this)(0) = x; (*this)(1) = y; }
         // This constructor allows you to construct Pointf from Eigen expressions
         template
    @@ -263,10 +266,10 @@ public:
             return *this;
         }
     
    -    void    rotate(double angle);
    -    void    rotate(double angle, const Pointf ¢er);
    +//    void    rotate(double angle);
    +//    void    rotate(double angle, const Pointf ¢er);
     
    -    bool operator< (const Pointf& rhs) const { return (*this)(0) < rhs(0) || ((*this)(0) == rhs(0) && (*this)(1) < rhs(1)); }
    +private:
     };
     
     } // namespace Slic3r
    diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp
    index 0091846206..18a2d2b950 100644
    --- a/xs/src/libslic3r/Polygon.hpp
    +++ b/xs/src/libslic3r/Polygon.hpp
    @@ -24,11 +24,12 @@ public:
         explicit Polygon(const Points &points): MultiPoint(points) {}
         Polygon(const Polygon &other) : MultiPoint(other.points) {}
         Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {}
    -	static Polygon new_scale(std::vector points) { 
    -		Points int_points;
    -		for (auto pt : points)
    -			int_points.push_back(Point::new_scale(pt(0), pt(1)));
    -		return Polygon(int_points);
    +	static Polygon new_scale(const std::vector &points) { 
    +        Polygon pgn;
    +        pgn.points.reserve(points.size());
    +        for (const Pointf &pt : points)
    +            pgn.points.emplace_back(Point::new_scale(pt(0), pt(1)));
    +		return pgn;
     	}
         Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
         Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; }
    diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp
    index edd27aaf68..f61e467178 100644
    --- a/xs/src/libslic3r/Polyline.hpp
    +++ b/xs/src/libslic3r/Polyline.hpp
    @@ -23,12 +23,11 @@ public:
         explicit Polyline(const Point &p1, const Point &p2) { points.reserve(2); points.emplace_back(p1); points.emplace_back(p2); }
         Polyline& operator=(const Polyline &other) { points = other.points; return *this; }
         Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; }
    -	static Polyline new_scale(std::vector points) {
    +	static Polyline new_scale(const std::vector &points) {
     		Polyline pl;
    -		Points int_points;
    -		for (auto pt : points)
    -			int_points.push_back(Point::new_scale(pt(0), pt(1)));
    -		pl.append(int_points);
    +		pl.points.reserve(points.size());
    +		for (const Pointf &pt : points)
    +			pl.points.emplace_back(Point::new_scale(pt(0), pt(1)));
     		return pl;
         }
         
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index ff226ad9ee..b1d88f2c61 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -1575,8 +1575,7 @@ TriangleMesh make_cylinder(double r, double h, double fa) {
         vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, 0));
         vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, h));
         for (double i = 0; i < 2*PI; i+=angle) {
    -        Pointf p(0, r);
    -        p.rotate(i);
    +        Vec2d p = Eigen::Rotation2Dd(i) * Eigen::Vector2d(0, r);
             vertices.emplace_back(Vec3d(p(0), p(1), 0.));
             vertices.emplace_back(Vec3d(p(0), p(1), h));
             id = vertices.size() - 1;
    @@ -1626,8 +1625,7 @@ TriangleMesh make_sphere(double rho, double fa) {
             const double z = -rho + increment*rho*2.0;
             // radius of the circle for this step.
             const double r = sqrt(abs(rho*rho - z*z));
    -        Pointf b(0, r);
    -        b.rotate(ring[i]);
    +        Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
             vertices.emplace_back(Vec3d(b(0), b(1), z));
             facets.emplace_back((i == 0) ? Point3(1, 0, ring.size()) : Point3(id, 0, id - 1));
             ++ id;
    @@ -1639,8 +1637,7 @@ TriangleMesh make_sphere(double rho, double fa) {
             const double r = sqrt(abs(rho*rho - z*z));
     
             for (size_t i = 0; i < ring.size(); i++) {
    -            Pointf b(0, r);
    -            b.rotate(ring[i]); 
    +            Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
                 vertices.emplace_back(Vec3d(b(0), b(1), z));
                 if (i == 0) {
                     // wrap around
    diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
    index 46d8d1618a..693221f84f 100644
    --- a/xs/src/perlglue.cpp
    +++ b/xs/src/perlglue.cpp
    @@ -270,7 +270,7 @@ bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* v
             values.reserve(len);
             for (size_t i = 0; i < len; i++) {
                 SV** elem = av_fetch(av, i, 0);
    -            Pointf point;
    +            Pointf point(Vec2d::Zero());
                 if (elem == NULL || !from_SV_check(*elem, &point)) return false;
                 values.emplace_back(point);
             }
    diff --git a/xs/src/slic3r/GUI/2DBed.cpp b/xs/src/slic3r/GUI/2DBed.cpp
    index 35c23272e6..ccca4320ca 100644
    --- a/xs/src/slic3r/GUI/2DBed.cpp
    +++ b/xs/src/slic3r/GUI/2DBed.cpp
    @@ -79,8 +79,7 @@ void Bed_2D::repaint()
     	auto step = 10;  // 1cm grid
     	Polylines polylines;
     	for (auto x = bb.min(0) - fmod(bb.min(0), step) + step; x < bb.max(0); x += step) {
    -		Polyline pl = Polyline::new_scale({ Pointf(x, bb.min(1)), Pointf(x, bb.max(1)) });
    -		polylines.push_back(pl);
    +		polylines.push_back(Polyline::new_scale({ Pointf(x, bb.min(1)), Pointf(x, bb.max(1)) }));
     	}
     	for (auto y = bb.min(1) - fmod(bb.min(1), step) + step; y < bb.max(1); y += step) {
     		polylines.push_back(Polyline::new_scale({ Pointf(bb.min(0), y), Pointf(bb.max(0), y) }));
    @@ -112,9 +111,7 @@ void Bed_2D::repaint()
     	auto x_end = Pointf(origin_px(0) + axes_len, origin_px(1));
     	dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(x_end(0), x_end(1)));
     	for (auto angle : { -arrow_angle, arrow_angle }){
    -		auto end = x_end;
    -		end(0) -= arrow_len;
    -		end.rotate(angle, x_end);
    +		auto end = Eigen::Translation2d(x_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- x_end) * Eigen::Vector2d(x_end(0) - arrow_len, x_end(1));
     		dc.DrawLine(wxPoint(x_end(0), x_end(1)), wxPoint(end(0), end(1)));
     	}
     
    @@ -122,9 +119,7 @@ void Bed_2D::repaint()
     	auto y_end = Pointf(origin_px(0), origin_px(1) - axes_len);
     	dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(y_end(0), y_end(1)));
     	for (auto angle : { -arrow_angle, arrow_angle }) {
    -		auto end = y_end;
    -		end(1) += arrow_len;
    -		end.rotate(angle, y_end);
    +		auto end = Eigen::Translation2d(y_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- y_end) * Eigen::Vector2d(y_end(0), y_end(1) + arrow_len);
     		dc.DrawLine(wxPoint(y_end(0), y_end(1)), wxPoint(end(0), end(1)));
     	}
     
    diff --git a/xs/src/slic3r/GUI/2DBed.hpp b/xs/src/slic3r/GUI/2DBed.hpp
    index 4b14986a2b..399be6f120 100644
    --- a/xs/src/slic3r/GUI/2DBed.hpp
    +++ b/xs/src/slic3r/GUI/2DBed.hpp
    @@ -14,8 +14,8 @@ class Bed_2D : public wxPanel
     	bool		m_painted = false;
     	bool		m_interactive = false;
     	double		m_scale_factor;
    -	Pointf		m_shift;
    -	Pointf		m_pos;
    +	Pointf		m_shift = Vec2d::Zero();
    +	Pointf		m_pos = Vec2d::Zero();
     	std::function	m_on_move = nullptr;
     
     	Point		to_pixels(Pointf point);
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 86cbe1b6fb..43d79a7a9c 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -952,8 +952,8 @@ static void thick_lines_to_indexed_vertex_array(
         // right, left, top, bottom
         int     idx_prev[4]      = { -1, -1, -1, -1 };
         double  bottom_z_prev    = 0.;
    -    Pointf  b1_prev;
    -    Vectorf v_prev;
    +    Pointf  b1_prev(Vec2d::Zero());
    +    Vectorf v_prev(Vec2d::Zero());
         int     idx_initial[4]   = { -1, -1, -1, -1 };
         double  width_initial    = 0.;
         double  bottom_z_initial = 0.0;
    @@ -1064,7 +1064,7 @@ static void thick_lines_to_indexed_vertex_array(
                     {
                         // Create a sharp corner with an overshot and average the left / right normals.
                         // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc.
    -                    Pointf intersection;
    +                    Pointf intersection(Vec2d::Zero());
                         Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection);
                         a1 = intersection;
                         a2 = 2. * a - intersection;
    diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp
    index b8a57a0c55..c47de94aa6 100644
    --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp
    +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp
    @@ -230,11 +230,13 @@ void BedShapePanel::update_shape()
     {
     	auto page_idx = m_shape_options_book->GetSelection();
     	if (page_idx == SHAPE_RECTANGULAR) {
    -		Pointf rect_size, rect_origin;
    +		Pointf rect_size(Vec2d::Zero());
    +		Pointf rect_origin(Vec2d::Zero());
     		try{
     			rect_size = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
     		catch (const std::exception &e){
    -			return;}
    +			return;
    +		}
     		try{
     			rect_origin = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
     		}
    diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp
    index 942b134400..32688ec3c3 100644
    --- a/xs/src/slic3r/GUI/Field.cpp
    +++ b/xs/src/slic3r/GUI/Field.cpp
    @@ -667,7 +667,7 @@ void PointCtrl::set_value(const Pointf& value, bool change_event)
     
     void PointCtrl::set_value(const boost::any& value, bool change_event)
     {
    -	Pointf pt;
    +	Pointf pt(Vec2d::Zero());
     	const Pointf *ptf = boost::any_cast(&value);
     	if (!ptf)
     	{
    @@ -681,13 +681,10 @@ void PointCtrl::set_value(const boost::any& value, bool change_event)
     
     boost::any& PointCtrl::get_value()
     {
    -	Pointf ret_point;
    -	double val;
    -	x_textctrl->GetValue().ToDouble(&val);
    -	ret_point(0) = val;
    -	y_textctrl->GetValue().ToDouble(&val);
    -	ret_point(1) = val;
    -	return m_value = ret_point;
    +	double x, y;
    +	x_textctrl->GetValue().ToDouble(&x);
    +	y_textctrl->GetValue().ToDouble(&y);
    +	return m_value = Pointf(x, y);
     }
     
     void StaticText::BUILD()
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index eaf24c8bc7..62242ea8a0 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -396,6 +396,7 @@ GLGizmoScale::GLGizmoScale()
         : GLGizmoBase()
         , m_scale(1.0f)
         , m_starting_scale(1.0f)
    +    , m_starting_drag_position(Vec2d::Zero())
     {
     }
     
    diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp
    index dc4f592cd2..7671b7d033 100644
    --- a/xs/xsp/Point.xsp
    +++ b/xs/xsp/Point.xsp
    @@ -113,7 +113,7 @@ Point::coincides_with(point_sv)
         void scale(double factor)
             %code{% *THIS *= factor; %};
         void rotate(double angle, Pointf* center)
    -        %code{% THIS->rotate(angle, *center); %};
    +        %code{% *THIS = Eigen::Translation2d(*center) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- *center) * Eigen::Vector2d((*THIS)(0), (*THIS)(1)); %};
         Pointf* negative()
             %code{% RETVAL = new Pointf(- *THIS); %};
         Pointf* vector_to(Pointf* point)
    
    From 0b5b02e0027b9994e26f5b719355d6e08bb4b2db Mon Sep 17 00:00:00 2001
    From: bubnikv 
    Date: Tue, 21 Aug 2018 21:05:24 +0200
    Subject: [PATCH 158/185] Eradicated the Pointf class, replaced with Eigen
     Vector3d
    
    ---
     xs/src/libslic3r/BoundingBox.cpp           | 18 +++++-----
     xs/src/libslic3r/BoundingBox.hpp           | 10 +++---
     xs/src/libslic3r/Config.hpp                | 18 +++++-----
     xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp  |  4 +--
     xs/src/libslic3r/Fill/FillGyroid.cpp       | 24 +++++++------
     xs/src/libslic3r/Fill/FillPlanePath.cpp    | 42 +++++++++++-----------
     xs/src/libslic3r/Fill/FillRectilinear3.cpp |  8 ++---
     xs/src/libslic3r/Format/PRUS.cpp           |  2 +-
     xs/src/libslic3r/GCode.cpp                 | 16 ++++-----
     xs/src/libslic3r/GCode.hpp                 | 12 +++----
     xs/src/libslic3r/GCode/PrintExtents.cpp    | 10 +++---
     xs/src/libslic3r/GCodeWriter.cpp           |  4 +--
     xs/src/libslic3r/GCodeWriter.hpp           |  4 +--
     xs/src/libslic3r/Geometry.cpp              | 30 ++++++++--------
     xs/src/libslic3r/Geometry.hpp              |  6 ++--
     xs/src/libslic3r/Line.hpp                  |  6 ++--
     xs/src/libslic3r/Model.cpp                 |  6 ++--
     xs/src/libslic3r/Model.hpp                 |  4 +--
     xs/src/libslic3r/ModelArrange.hpp          |  2 +-
     xs/src/libslic3r/Point.cpp                 |  2 +-
     xs/src/libslic3r/Point.hpp                 | 32 ++---------------
     xs/src/libslic3r/Polygon.cpp               |  4 +--
     xs/src/libslic3r/Polygon.hpp               |  4 +--
     xs/src/libslic3r/Polyline.hpp              |  4 +--
     xs/src/libslic3r/Print.hpp                 |  2 +-
     xs/src/libslic3r/PrintConfig.cpp           |  4 +--
     xs/src/libslic3r/PrintObject.cpp           |  2 +-
     xs/src/libslic3r/SVG.cpp                   |  4 +--
     xs/src/libslic3r/SupportMaterial.cpp       |  4 +--
     xs/src/perlglue.cpp                        | 18 +++++-----
     xs/src/slic3r/GUI/2DBed.cpp                | 28 +++++++--------
     xs/src/slic3r/GUI/2DBed.hpp                | 14 ++++----
     xs/src/slic3r/GUI/3DScene.cpp              | 28 +++++++--------
     xs/src/slic3r/GUI/BedShapeDialog.cpp       | 34 +++++++++---------
     xs/src/slic3r/GUI/BedShapeDialog.hpp       |  4 +--
     xs/src/slic3r/GUI/Field.cpp                |  8 ++---
     xs/src/slic3r/GUI/Field.hpp                |  2 +-
     xs/src/slic3r/GUI/GLCanvas3D.cpp           | 30 ++++++++--------
     xs/src/slic3r/GUI/GLCanvas3D.hpp           | 10 +++---
     xs/src/slic3r/GUI/GLGizmo.cpp              | 16 ++++-----
     xs/src/slic3r/GUI/GLGizmo.hpp              | 14 ++++----
     xs/src/slic3r/GUI/GUI.cpp                  |  4 +--
     xs/src/xsinit.h                            |  6 ++--
     xs/xsp/BoundingBox.xsp                     | 10 +++---
     xs/xsp/GCode.xsp                           |  4 +--
     xs/xsp/Geometry.xsp                        |  2 +-
     xs/xsp/Model.xsp                           |  6 ++--
     xs/xsp/Point.xsp                           | 20 +++++------
     xs/xsp/Print.xsp                           |  2 +-
     xs/xsp/my.map                              |  6 ++--
     xs/xsp/typemap.xspt                        |  6 ++--
     51 files changed, 267 insertions(+), 293 deletions(-)
    
    diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp
    index 81a748f8fc..9bd2327ad5 100644
    --- a/xs/src/libslic3r/BoundingBox.cpp
    +++ b/xs/src/libslic3r/BoundingBox.cpp
    @@ -7,7 +7,7 @@
     namespace Slic3r {
     
     template BoundingBoxBase::BoundingBoxBase(const std::vector &points);
    -template BoundingBoxBase::BoundingBoxBase(const std::vector &points);
    +template BoundingBoxBase::BoundingBoxBase(const std::vector &points);
     
     template BoundingBox3Base::BoundingBox3Base(const std::vector &points);
     
    @@ -70,7 +70,7 @@ BoundingBoxBase::scale(double factor)
         this->max *= factor;
     }
     template void BoundingBoxBase::scale(double factor);
    -template void BoundingBoxBase::scale(double factor);
    +template void BoundingBoxBase::scale(double factor);
     template void BoundingBoxBase::scale(double factor);
     
     template  void
    @@ -86,7 +86,7 @@ BoundingBoxBase::merge(const PointClass &point)
         }
     }
     template void BoundingBoxBase::merge(const Point &point);
    -template void BoundingBoxBase::merge(const Pointf &point);
    +template void BoundingBoxBase::merge(const Vec2d &point);
     
     template  void
     BoundingBoxBase::merge(const std::vector &points)
    @@ -94,7 +94,7 @@ BoundingBoxBase::merge(const std::vector &points)
         this->merge(BoundingBoxBase(points));
     }
     template void BoundingBoxBase::merge(const Points &points);
    -template void BoundingBoxBase::merge(const Pointfs &points);
    +template void BoundingBoxBase::merge(const Pointfs &points);
     
     template  void
     BoundingBoxBase::merge(const BoundingBoxBase &bb)
    @@ -112,7 +112,7 @@ BoundingBoxBase::merge(const BoundingBoxBase &bb)
         }
     }
     template void BoundingBoxBase::merge(const BoundingBoxBase &bb);
    -template void BoundingBoxBase::merge(const BoundingBoxBase &bb);
    +template void BoundingBoxBase::merge(const BoundingBoxBase &bb);
     
     template  void
     BoundingBox3Base::merge(const PointClass &point)
    @@ -158,7 +158,7 @@ BoundingBoxBase::size() const
         return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1));
     }
     template Point BoundingBoxBase::size() const;
    -template Pointf BoundingBoxBase::size() const;
    +template Vec2d BoundingBoxBase::size() const;
     
     template  PointClass
     BoundingBox3Base::size() const
    @@ -175,7 +175,7 @@ template  double BoundingBoxBase::radius() const
         return 0.5 * sqrt(x*x+y*y);
     }
     template double BoundingBoxBase::radius() const;
    -template double BoundingBoxBase::radius() const;
    +template double BoundingBoxBase::radius() const;
     
     template  double BoundingBox3Base::radius() const
     {
    @@ -194,7 +194,7 @@ BoundingBoxBase::offset(coordf_t delta)
         this->max += v;
     }
     template void BoundingBoxBase::offset(coordf_t delta);
    -template void BoundingBoxBase::offset(coordf_t delta);
    +template void BoundingBoxBase::offset(coordf_t delta);
     
     template  void
     BoundingBox3Base::offset(coordf_t delta)
    @@ -211,7 +211,7 @@ BoundingBoxBase::center() const
         return (this->min + this->max) / 2;
     }
     template Point BoundingBoxBase::center() const;
    -template Pointf BoundingBoxBase::center() const;
    +template Vec2d BoundingBoxBase::center() const;
     
     template  PointClass
     BoundingBox3Base::center() const
    diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp
    index cb8bc3ad96..6190d4b939 100644
    --- a/xs/src/libslic3r/BoundingBox.hpp
    +++ b/xs/src/libslic3r/BoundingBox.hpp
    @@ -39,7 +39,7 @@ public:
         PointClass size() const;
         double radius() const;
         void translate(coordf_t x, coordf_t y) { assert(this->defined); PointClass v(x, y); this->min += v; this->max += v; }
    -    void translate(const Pointf &v) { this->min += v; this->max += v; }
    +    void translate(const Vec2d &v) { this->min += v; this->max += v; }
         void offset(coordf_t delta);
         PointClass center() const;
         bool contains(const PointClass &point) const {
    @@ -128,12 +128,12 @@ public:
         BoundingBox3(const Points3& points) : BoundingBox3Base(points) {};
     };
     
    -class BoundingBoxf : public BoundingBoxBase 
    +class BoundingBoxf : public BoundingBoxBase 
     {
     public:
    -    BoundingBoxf() : BoundingBoxBase() {};
    -    BoundingBoxf(const Pointf &pmin, const Pointf &pmax) : BoundingBoxBase(pmin, pmax) {};
    -    BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {};
    +    BoundingBoxf() : BoundingBoxBase() {};
    +    BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase(pmin, pmax) {};
    +    BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {};
     };
     
     class BoundingBoxf3 : public BoundingBox3Base 
    diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp
    index a1b0f624ea..7f75f815d0 100644
    --- a/xs/src/libslic3r/Config.hpp
    +++ b/xs/src/libslic3r/Config.hpp
    @@ -622,11 +622,11 @@ public:
         }
     };
     
    -class ConfigOptionPoint : public ConfigOptionSingle
    +class ConfigOptionPoint : public ConfigOptionSingle
     {
     public:
    -    ConfigOptionPoint() : ConfigOptionSingle(Pointf(0,0)) {}
    -    explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle(value) {}
    +    ConfigOptionPoint() : ConfigOptionSingle(Vec2d(0,0)) {}
    +    explicit ConfigOptionPoint(const Vec2d &value) : ConfigOptionSingle(value) {}
         
         static ConfigOptionType static_type() { return coPoint; }
         ConfigOptionType        type()  const override { return static_type(); }
    @@ -652,13 +652,13 @@ public:
         }
     };
     
    -class ConfigOptionPoints : public ConfigOptionVector
    +class ConfigOptionPoints : public ConfigOptionVector
     {
     public:
    -    ConfigOptionPoints() : ConfigOptionVector() {}
    -    explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector(n, value) {}
    -    explicit ConfigOptionPoints(std::initializer_list il) : ConfigOptionVector(std::move(il)) {}
    -    explicit ConfigOptionPoints(const std::vector &values) : ConfigOptionVector(values) {}
    +    ConfigOptionPoints() : ConfigOptionVector() {}
    +    explicit ConfigOptionPoints(size_t n, const Vec2d &value) : ConfigOptionVector(n, value) {}
    +    explicit ConfigOptionPoints(std::initializer_list il) : ConfigOptionVector(std::move(il)) {}
    +    explicit ConfigOptionPoints(const std::vector &values) : ConfigOptionVector(values) {}
     
         static ConfigOptionType static_type() { return coPoints; }
         ConfigOptionType        type()  const override { return static_type(); }
    @@ -696,7 +696,7 @@ public:
             std::istringstream is(str);
             std::string point_str;
             while (std::getline(is, point_str, ',')) {
    -            Pointf point(Vec2d::Zero());
    +            Vec2d point(Vec2d::Zero());
                 std::istringstream iss(point_str);
                 std::string coord_str;
                 if (std::getline(iss, coord_str, 'x')) {
    diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp
    index 16ed12fb95..6a37e4369f 100644
    --- a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp
    +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp
    @@ -54,7 +54,7 @@ static std::vector perpendPoints(const coordf_t offset, const size_t b
     // components that are outside these limits are set to the limits.
     static inline void trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY)
     {
    -    for (Pointf &pt : pts) {
    +    for (Vec2d &pt : pts) {
             pt(0) = clamp(minX, maxX, pt(0));
             pt(1) = clamp(minY, maxY, pt(1));
         }
    @@ -66,7 +66,7 @@ static inline Pointfs zip(const std::vector &x, const std::vector& one_period, double width, double height, double offset, double scaleFactor,
    +    const std::vector& one_period, double width, double height, double offset, double scaleFactor,
         double z_cos, double z_sin, bool vertical)
     {
    -    std::vector points = one_period;
    +    std::vector points = one_period;
         double period = points.back()(0);
         points.pop_back();
         int n = points.size();
         do {
    -        points.emplace_back(Pointf(points[points.size()-n](0) + period, points[points.size()-n](1)));
    +        points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1)));
         } while (points.back()(0) < width);
         points.back()(0) = width;
     
    @@ -55,14 +55,14 @@ static inline Polyline make_wave(
         return polyline;
     }
     
    -static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip)
    +static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip)
     {
    -    std::vector points;
    +    std::vector points;
         double dx = M_PI_4; // very coarse spacing to begin with
         double limit = std::min(2*M_PI, width);
         for (double x = 0.; x < limit + EPSILON; x += dx) {  // so the last point is there too
             x = std::min(x, limit);
    -        points.emplace_back(Pointf(x,f(x, z_sin,z_cos, vertical, flip)));
    +        points.emplace_back(Vec2d(x,f(x, z_sin,z_cos, vertical, flip)));
         }
     
         // now we will check all internal points and in case some are too far from the line connecting its neighbours,
    @@ -77,11 +77,13 @@ static std::vector make_one_period(double width, double scaleFactor, dou
             double dist_mm = unscale(scaleFactor) * std::abs(cross2(rp, lp) - cross2(rp - lp, tp)) / lrv.norm();
             if (dist_mm > tolerance) {                               // if the difference from straight line is more than this
                 double x = 0.5f * (points[i-1](0) + points[i](0));
    -            points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip)));
    +            points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
                 x = 0.5f * (points[i+1](0) + points[i](0));
    -            points.emplace_back(Pointf(x, f(x, z_sin, z_cos, vertical, flip)));
    -            std::sort(points.begin(), points.end());            // we added the points to the end, but need them all in order
    -            --i;                                                // decrement i so we also check the first newly added point
    +            points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
    +            // we added the points to the end, but need them all in order
    +            std::sort(points.begin(), points.end(), [](const Vec2d &lhs, const Vec2d &rhs){ return lhs < rhs; });
    +            // decrement i so we also check the first newly added point
    +            --i;
             }
         }
         return points;
    @@ -107,7 +109,7 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double
             std::swap(width,height);
         }
     
    -    std::vector one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time
    +    std::vector one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time
         Polylines result;
     
         for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI)           // creates odd polylines
    diff --git a/xs/src/libslic3r/Fill/FillPlanePath.cpp b/xs/src/libslic3r/Fill/FillPlanePath.cpp
    index bb7d5ac801..615cc6efed 100644
    --- a/xs/src/libslic3r/Fill/FillPlanePath.cpp
    +++ b/xs/src/libslic3r/Fill/FillPlanePath.cpp
    @@ -86,12 +86,12 @@ Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t m
         coordf_t r = 1;
         Pointfs out;
         //FIXME Vojtech: If used as a solid infill, there is a gap left at the center.
    -    out.push_back(Pointf(0, 0));
    -    out.push_back(Pointf(1, 0));
    +    out.push_back(Vec2d(0, 0));
    +    out.push_back(Vec2d(1, 0));
         while (r < rmax) {
             theta += 1. / r;
             r = a + b * theta;
    -        out.push_back(Pointf(r * cos(theta), r * sin(theta)));
    +        out.push_back(Vec2d(r * cos(theta), r * sin(theta)));
         }
         return out;
     }
    @@ -162,7 +162,7 @@ Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x,
         line.reserve(sz2);
         for (size_t i = 0; i < sz2; ++ i) {
             Point p = hilbert_n_to_xy(i);
    -        line.push_back(Pointf(p(0) + min_x, p(1) + min_y));
    +        line.push_back(Vec2d(p(0) + min_x, p(1) + min_y));
         }
         return line;
     }
    @@ -175,27 +175,27 @@ Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_
         coordf_t r = 0;
         coordf_t r_inc = sqrt(2.);
         Pointfs out;
    -    out.push_back(Pointf(0, 0));
    +    out.push_back(Vec2d(0, 0));
         while (r < rmax) {
             r += r_inc;
             coordf_t rx = r / sqrt(2.);
             coordf_t r2 = r + rx;
    -        out.push_back(Pointf( r,  0.));
    -        out.push_back(Pointf( r2, rx));
    -        out.push_back(Pointf( rx, rx));
    -        out.push_back(Pointf( rx, r2));
    -        out.push_back(Pointf(0.,  r));
    -        out.push_back(Pointf(-rx, r2));
    -        out.push_back(Pointf(-rx, rx));
    -        out.push_back(Pointf(-r2, rx));
    -        out.push_back(Pointf(-r,  0.));
    -        out.push_back(Pointf(-r2, -rx));
    -        out.push_back(Pointf(-rx, -rx));
    -        out.push_back(Pointf(-rx, -r2));
    -        out.push_back(Pointf(0., -r));
    -        out.push_back(Pointf( rx, -r2));
    -        out.push_back(Pointf( rx, -rx));
    -        out.push_back(Pointf( r2+r_inc, -rx));
    +        out.push_back(Vec2d( r,  0.));
    +        out.push_back(Vec2d( r2, rx));
    +        out.push_back(Vec2d( rx, rx));
    +        out.push_back(Vec2d( rx, r2));
    +        out.push_back(Vec2d(0.,  r));
    +        out.push_back(Vec2d(-rx, r2));
    +        out.push_back(Vec2d(-rx, rx));
    +        out.push_back(Vec2d(-r2, rx));
    +        out.push_back(Vec2d(-r,  0.));
    +        out.push_back(Vec2d(-r2, -rx));
    +        out.push_back(Vec2d(-rx, -rx));
    +        out.push_back(Vec2d(-rx, -r2));
    +        out.push_back(Vec2d(0., -r));
    +        out.push_back(Vec2d( rx, -r2));
    +        out.push_back(Vec2d( rx, -rx));
    +        out.push_back(Vec2d( r2+r_inc, -rx));
         }
         return out;
     }
    diff --git a/xs/src/libslic3r/Fill/FillRectilinear3.cpp b/xs/src/libslic3r/Fill/FillRectilinear3.cpp
    index 72c8b1ec5d..8fc129eacb 100644
    --- a/xs/src/libslic3r/Fill/FillRectilinear3.cpp
    +++ b/xs/src/libslic3r/Fill/FillRectilinear3.cpp
    @@ -217,11 +217,11 @@ Point SegmentIntersection::pos() const
         const Point   &seg_start = poly.points[(this->iSegment == 0) ? poly.points.size() - 1 : this->iSegment - 1];
         const Point   &seg_end   = poly.points[this->iSegment];
         // Point, vector of the segment.
    -    const Pointf   p1(seg_start.cast());
    -    const Pointf   v1((seg_end - seg_start).cast());
    +    const Vec2d   p1(seg_start.cast());
    +    const Vec2d   v1((seg_end - seg_start).cast());
         // Point, vector of this hatching line.
    -    const Pointf   p2(line->pos.cast());
    -    const Pointf   v2(line->dir.cast());
    +    const Vec2d   p2(line->pos.cast());
    +    const Vec2d   v2(line->dir.cast());
         // Intersect the two rays.
         double denom = v1(0) * v2(1) - v2(0) * v1(1);
         Point out;
    diff --git a/xs/src/libslic3r/Format/PRUS.cpp b/xs/src/libslic3r/Format/PRUS.cpp
    index b817f8305e..9f42fc151b 100644
    --- a/xs/src/libslic3r/Format/PRUS.cpp
    +++ b/xs/src/libslic3r/Format/PRUS.cpp
    @@ -166,7 +166,7 @@ bool load_prus(const char *path, Model *model)
                 float  trafo[3][4] = { 0 };
                 double instance_rotation = 0.;
                 double instance_scaling_factor = 1.f;
    -            Pointf instance_offset(0., 0.); 
    +            Vec2d instance_offset(0., 0.); 
                 bool   trafo_set = false;
                 unsigned int group_id     = (unsigned int)-1;
                 unsigned int extruder_id  = (unsigned int)-1;
    diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp
    index 2f911440ad..fa18ddf3fc 100644
    --- a/xs/src/libslic3r/GCode.cpp
    +++ b/xs/src/libslic3r/GCode.cpp
    @@ -207,7 +207,7 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
             check_add_eol(gcode);
         }
         // A phony move to the end position at the wipe tower.
    -    gcodegen.writer().travel_to_xy(Pointf(end_pos.x, end_pos.y));
    +    gcodegen.writer().travel_to_xy(Vec2d(end_pos.x, end_pos.y));
         gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
     
         // Prepare a future wipe.
    @@ -293,7 +293,7 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen)
             gcodegen.writer().toolchange(current_extruder_id);
             gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
             // A phony move to the end position at the wipe tower.
    -        gcodegen.writer().travel_to_xy(Pointf(m_priming.end_pos.x, m_priming.end_pos.y));
    +        gcodegen.writer().travel_to_xy(Vec2d(m_priming.end_pos.x, m_priming.end_pos.y));
             gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos));
             // Prepare a future wipe.
             gcodegen.m_wipe.path.points.clear();
    @@ -783,7 +783,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
                 Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
                 Polygons skirts;
                 for (unsigned int extruder_id : print.extruders()) {
    -                const Pointf &extruder_offset = print.config.extruder_offset.get_at(extruder_id);
    +                const Vec2d &extruder_offset = print.config.extruder_offset.get_at(extruder_id);
                     Polygon s(outer_skirt);
                     s.translate(Point::new_scale(- extruder_offset(0), - extruder_offset(1)));
                     skirts.emplace_back(std::move(s));
    @@ -1632,7 +1632,7 @@ void GCode::set_extruders(const std::vector &extruder_ids)
             }
     }
     
    -void GCode::set_origin(const Pointf &pointf)
    +void GCode::set_origin(const Vec2d &pointf)
     {    
         // if origin increases (goes towards right), last_pos decreases because it goes towards left
         const Point translate(
    @@ -2618,16 +2618,16 @@ std::string GCode::set_extruder(unsigned int extruder_id)
     }
     
     // convert a model-space scaled point into G-code coordinates
    -Pointf GCode::point_to_gcode(const Point &point) const
    +Vec2d GCode::point_to_gcode(const Point &point) const
     {
    -    Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset);
    +    Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
         return unscale(point) + m_origin - extruder_offset;
     }
     
     // convert a model-space scaled point into G-code coordinates
    -Point GCode::gcode_to_point(const Pointf &point) const
    +Point GCode::gcode_to_point(const Vec2d &point) const
     {
    -    Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset);
    +    Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
         return Point(
             scale_(point(0) - m_origin(0) + extruder_offset(0)),
             scale_(point(1) - m_origin(1) + extruder_offset(1)));
    diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp
    index 235ca62f0b..dc8a321354 100644
    --- a/xs/src/libslic3r/GCode.hpp
    +++ b/xs/src/libslic3r/GCode.hpp
    @@ -153,12 +153,12 @@ public:
         void            do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr);
     
         // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
    -    const Pointf&   origin() const { return m_origin; }
    -    void            set_origin(const Pointf &pointf);
    -    void            set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Pointf(x, y)); }
    +    const Vec2d&   origin() const { return m_origin; }
    +    void            set_origin(const Vec2d &pointf);
    +    void            set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); }
         const Point&    last_pos() const { return m_last_pos; }
    -    Pointf          point_to_gcode(const Point &point) const;
    -    Point           gcode_to_point(const Pointf &point) const;
    +    Vec2d          point_to_gcode(const Point &point) const;
    +    Point           gcode_to_point(const Vec2d &point) const;
         const FullPrintConfig &config() const { return m_config; }
         const Layer*    layer() const { return m_layer; }
         GCodeWriter&    writer() { return m_writer; }
    @@ -259,7 +259,7 @@ protected:
         /* Origin of print coordinates expressed in unscaled G-code coordinates.
            This affects the input arguments supplied to the extrude*() and travel_to()
            methods. */
    -    Pointf                              m_origin;
    +    Vec2d                              m_origin;
         FullPrintConfig                     m_config;
         GCodeWriter                         m_writer;
         PlaceholderParser                   m_placeholder_parser;
    diff --git a/xs/src/libslic3r/GCode/PrintExtents.cpp b/xs/src/libslic3r/GCode/PrintExtents.cpp
    index dd0b18db84..2ed6077696 100644
    --- a/xs/src/libslic3r/GCode/PrintExtents.cpp
    +++ b/xs/src/libslic3r/GCode/PrintExtents.cpp
    @@ -148,9 +148,9 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
                 for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
                     const WipeTower::Extrusion &e = tcr.extrusions[i];
                     if (e.width > 0) {
    -                    Pointf delta = 0.5 * Vec2d(e.width, e.width);
    -                    Pointf p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y);
    -                    Pointf p2 = trafo * Vec2d(e.pos.x, e.pos.y);
    +                    Vec2d delta = 0.5 * Vec2d(e.width, e.width);
    +                    Vec2d p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y);
    +                    Vec2d p2 = trafo * Vec2d(e.pos.x, e.pos.y);
                         bbox.merge(p1.cwiseMin(p2) - delta);
                         bbox.merge(p1.cwiseMax(p2) + delta);
                     }
    @@ -169,8 +169,8 @@ BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print)
             for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
                 const WipeTower::Extrusion &e = tcr.extrusions[i];
                 if (e.width > 0) {
    -                Pointf  p1((&e - 1)->pos.x, (&e - 1)->pos.y);
    -                Pointf  p2(e.pos.x, e.pos.y);
    +                Vec2d  p1((&e - 1)->pos.x, (&e - 1)->pos.y);
    +                Vec2d  p2(e.pos.x, e.pos.y);
                     bbox.merge(p1);
                     coordf_t radius = 0.5 * e.width;
                     bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius);
    diff --git a/xs/src/libslic3r/GCodeWriter.cpp b/xs/src/libslic3r/GCodeWriter.cpp
    index 5352e95022..6ef17f4f41 100644
    --- a/xs/src/libslic3r/GCodeWriter.cpp
    +++ b/xs/src/libslic3r/GCodeWriter.cpp
    @@ -276,7 +276,7 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
         return gcode.str();
     }
     
    -std::string GCodeWriter::travel_to_xy(const Pointf &point, const std::string &comment)
    +std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
     {
         m_pos(0) = point(0);
         m_pos(1) = point(1);
    @@ -358,7 +358,7 @@ bool GCodeWriter::will_move_z(double z) const
         return true;
     }
     
    -std::string GCodeWriter::extrude_to_xy(const Pointf &point, double dE, const std::string &comment)
    +std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
     {
         m_pos(0) = point(0);
         m_pos(1) = point(1);
    diff --git a/xs/src/libslic3r/GCodeWriter.hpp b/xs/src/libslic3r/GCodeWriter.hpp
    index d02bbfe609..664b0e3a1d 100644
    --- a/xs/src/libslic3r/GCodeWriter.hpp
    +++ b/xs/src/libslic3r/GCodeWriter.hpp
    @@ -55,11 +55,11 @@ public:
         std::string toolchange_prefix() const;
         std::string toolchange(unsigned int extruder_id);
         std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
    -    std::string travel_to_xy(const Pointf &point, const std::string &comment = std::string());
    +    std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
         std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string());
         std::string travel_to_z(double z, const std::string &comment = std::string());
         bool        will_move_z(double z) const;
    -    std::string extrude_to_xy(const Pointf &point, double dE, const std::string &comment = std::string());
    +    std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string());
         std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
         std::string retract(bool before_wipe = false);
         std::string retract_for_toolchange(bool before_wipe = false);
    diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp
    index f513341551..c978d46b61 100644
    --- a/xs/src/libslic3r/Geometry.cpp
    +++ b/xs/src/libslic3r/Geometry.cpp
    @@ -345,7 +345,7 @@ linint(double value, double oldmin, double oldmax, double newmin, double newmax)
     // If the points have the same weight, sort them lexicographically by their positions.
     struct ArrangeItem {
         ArrangeItem() {}
    -    Pointf    pos;
    +    Vec2d    pos;
         coordf_t  weight;
         bool operator<(const ArrangeItem &other) const {
             return weight < other.weight ||
    @@ -353,17 +353,17 @@ struct ArrangeItem {
         }
     };
     
    -Pointfs arrange(size_t num_parts, const Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box)
    +Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box)
     {
         // Use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm.
    -    const Pointf       cell_size(part_size(0) + gap, part_size(1) + gap);
    +    const Vec2d       cell_size(part_size(0) + gap, part_size(1) + gap);
     
         const BoundingBoxf bed_bbox = (bed_bounding_box != NULL && bed_bounding_box->defined) ? 
             *bed_bounding_box :
             // Bogus bed size, large enough not to trigger the unsufficient bed size error.
             BoundingBoxf(
    -            Pointf(0, 0),
    -            Pointf(cell_size(0) * num_parts, cell_size(1) * num_parts));
    +            Vec2d(0, 0),
    +            Vec2d(cell_size(0) * num_parts, cell_size(1) * num_parts));
     
         // This is how many cells we have available into which to put parts.
         size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0)));
    @@ -372,8 +372,8 @@ Pointfs arrange(size_t num_parts, const Pointf &part_size, coordf_t gap, const B
             CONFESS(PRINTF_ZU " parts won't fit in your print area!\n", num_parts);
         
         // Get a bounding box of cellw x cellh cells, centered at the center of the bed.
    -    Pointf       cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap);
    -    Pointf       cells_offset(bed_bbox.center() - 0.5 * cells_size);
    +    Vec2d       cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap);
    +    Vec2d       cells_offset(bed_bbox.center() - 0.5 * cells_size);
         BoundingBoxf cells_bb(cells_offset, cells_size + cells_offset);
         
         // List of cells, sorted by distance from center.
    @@ -405,13 +405,13 @@ Pointfs arrange(size_t num_parts, const Pointf &part_size, coordf_t gap, const B
         Pointfs positions;
         positions.reserve(num_parts);
         for (std::vector::const_iterator it = cellsorder.begin(); it != cellsorder.end(); ++ it)
    -        positions.push_back(Pointf(it->pos(0) - 0.5 * part_size(0), it->pos(1) - 0.5 * part_size(1)));
    +        positions.push_back(Vec2d(it->pos(0) - 0.5 * part_size(0), it->pos(1) - 0.5 * part_size(1)));
         return positions;
     }
     #else
     class ArrangeItem {
     public:
    -    Pointf pos = Vec2d::Zero();
    +    Vec2d pos = Vec2d::Zero();
         size_t index_x, index_y;
         coordf_t dist;
     };
    @@ -423,17 +423,17 @@ public:
     };
     
     bool
    -arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions)
    +arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions)
     {
         positions.clear();
     
    -    Pointf part = part_size;
    +    Vec2d part = part_size;
     
         // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm
         part(0) += dist;
         part(1) += dist;
         
    -    Pointf area(Vec2d::Zero());
    +    Vec2d area(Vec2d::Zero());
         if (bb != NULL && bb->defined) {
             area = bb->size();
         } else {
    @@ -449,11 +449,11 @@ arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const Boundi
             return false;
         
         // total space used by cells
    -    Pointf cells(cellw * part(0), cellh * part(1));
    +    Vec2d cells(cellw * part(0), cellh * part(1));
         
         // bounding box of total space used by cells
         BoundingBoxf cells_bb;
    -    cells_bb.merge(Pointf(0,0)); // min
    +    cells_bb.merge(Vec2d(0,0)); // min
         cells_bb.merge(cells);  // max
         
         // center bounding box to area
    @@ -533,7 +533,7 @@ arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const Boundi
             coordf_t cx = c.item.index_x - lx;
             coordf_t cy = c.item.index_y - ty;
             
    -        positions.push_back(Pointf(cx * part(0), cy * part(1)));
    +        positions.push_back(Vec2d(cx * part(0), cy * part(1)));
         }
         
         if (bb != NULL && bb->defined) {
    diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp
    index 95c13fcdb0..1945340077 100644
    --- a/xs/src/libslic3r/Geometry.hpp
    +++ b/xs/src/libslic3r/Geometry.hpp
    @@ -66,7 +66,7 @@ static inline bool is_ccw(const Polygon &poly)
         return o == ORIENTATION_CCW;
     }
     
    -inline bool ray_ray_intersection(const Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res)
    +inline bool ray_ray_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res)
     {
         double denom = v1(0) * v2(1) - v2(0) * v1(1);
         if (std::abs(denom) < EPSILON)
    @@ -77,7 +77,7 @@ inline bool ray_ray_intersection(const Pointf &p1, const Vectorf &v1, const Poin
         return true;
     }
     
    -inline bool segment_segment_intersection(const Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res)
    +inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res)
     {
         double denom = v1(0) * v2(1) - v2(0) * v1(1);
         if (std::abs(denom) < EPSILON)
    @@ -123,7 +123,7 @@ void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* ret
     double linint(double value, double oldmin, double oldmax, double newmin, double newmax);
     bool arrange(
         // input
    -    size_t num_parts, const Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, 
    +    size_t num_parts, const Vec2d &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, 
         // output
         Pointfs &positions);
     
    diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp
    index 65e04dc930..b1294f5223 100644
    --- a/xs/src/libslic3r/Line.hpp
    +++ b/xs/src/libslic3r/Line.hpp
    @@ -72,10 +72,10 @@ class Linef
     {
     public:
         Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {}
    -    explicit Linef(Pointf _a, Pointf _b): a(_a), b(_b) {}
    +    explicit Linef(Vec2d _a, Vec2d _b): a(_a), b(_b) {}
     
    -    Pointf a;
    -    Pointf b;
    +    Vec2d a;
    +    Vec2d b;
     };
     
     class Linef3
    diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
    index 047751ecd6..e0f2264763 100644
    --- a/xs/src/libslic3r/Model.cpp
    +++ b/xs/src/libslic3r/Model.cpp
    @@ -243,7 +243,7 @@ BoundingBoxf3 Model::transformed_bounding_box() const
         return bb;
     }
     
    -void Model::center_instances_around_point(const Pointf &point)
    +void Model::center_instances_around_point(const Vec2d &point)
     {
     //    BoundingBoxf3 bb = this->bounding_box();
         BoundingBoxf3 bb;
    @@ -251,7 +251,7 @@ void Model::center_instances_around_point(const Pointf &point)
             for (size_t i = 0; i < o->instances.size(); ++ i)
                 bb.merge(o->instance_bounding_box(i, false));
     
    -    Pointf shift = point - 0.5 * to_2d(bb.size()) - to_2d(bb.min);
    +    Vec2d shift = point - 0.5 * to_2d(bb.size()) - to_2d(bb.min);
         for (ModelObject *o : this->objects) {
             for (ModelInstance *i : o->instances)
                 i->offset += shift;
    @@ -343,7 +343,7 @@ void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb)
             // make a copy of the pointers in order to avoid recursion when appending their copies
             ModelInstancePtrs instances = o->instances;
             for (const ModelInstance *i : instances) {
    -            for (const Pointf &pos : positions) {
    +            for (const Vec2d &pos : positions) {
                     ModelInstance *instance = o->add_instance(*i);
                     instance->offset += pos;
                 }
    diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
    index a7597ada51..6b6db7696d 100644
    --- a/xs/src/libslic3r/Model.hpp
    +++ b/xs/src/libslic3r/Model.hpp
    @@ -213,7 +213,7 @@ public:
     //    Transform3d     transform;
         double rotation;            // Rotation around the Z axis, in radians around mesh center point
         double scaling_factor;
    -    Pointf offset;              // in unscaled coordinates
    +    Vec2d offset;              // in unscaled coordinates
         
         // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state())
         EPrintVolumeState print_volume_state;
    @@ -288,7 +288,7 @@ public:
         BoundingBoxf3 bounding_box() const;
         // Returns tight axis aligned bounding box of this model
         BoundingBoxf3 transformed_bounding_box() const;
    -    void center_instances_around_point(const Pointf &point);
    +    void center_instances_around_point(const Vec2d &point);
         void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
         TriangleMesh mesh() const;
         bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
    diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp
    index 67825248a8..db18e67318 100644
    --- a/xs/src/libslic3r/ModelArrange.hpp
    +++ b/xs/src/libslic3r/ModelArrange.hpp
    @@ -468,7 +468,7 @@ void applyResult(
             // appropriately
             auto off = item.translation();
             Radians rot = item.rotation();
    -        Pointf foff(off.X*SCALING_FACTOR + batch_offset,
    +        Vec2d foff(off.X*SCALING_FACTOR + batch_offset,
                         off.Y*SCALING_FACTOR);
     
             // write the tranformation data into the model instance
    diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp
    index 0fe76c6113..ec6c28fe86 100644
    --- a/xs/src/libslic3r/Point.cpp
    +++ b/xs/src/libslic3r/Point.cpp
    @@ -148,7 +148,7 @@ Point Point::projection_onto(const Line &line) const
         return ((line.a - *this).cast().squaredNorm() < (line.b - *this).cast().squaredNorm()) ? line.a : line.b;
     }
     
    -std::ostream& operator<<(std::ostream &stm, const Pointf &pointf)
    +std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf)
     {
         return stm << pointf(0) << "," << pointf(1);
     }
    diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
    index b5af3b1def..2d0304f4ea 100644
    --- a/xs/src/libslic3r/Point.hpp
    +++ b/xs/src/libslic3r/Point.hpp
    @@ -17,10 +17,8 @@ class Line;
     class MultiPoint;
     class Point;
     class Point3;
    -class Pointf;
     typedef Point                       Vector;
     typedef Point3                      Vector3;
    -typedef Pointf                      Vectorf;
     
     // Eigen types, to replace the Slic3r's own types in the future.
     // Vector types with a fixed point coordinate base type.
    @@ -39,7 +37,7 @@ typedef std::vector                              Points;
     typedef std::vector                             PointPtrs;
     typedef std::vector                       PointConstPtrs;
     typedef std::vector                             Points3;
    -typedef std::vector                             Pointfs;
    +typedef std::vector                              Pointfs;
     typedef std::vector                              Pointf3s;
     
     typedef Eigen::Transform Transform2f;
    @@ -244,33 +242,7 @@ public:
         }
     };
     
    -std::ostream& operator<<(std::ostream &stm, const Pointf &pointf);
    -
    -class Pointf : public Vec2d
    -{
    -public:
    -    typedef coordf_t coord_type;
    -
    -//    explicit Pointf() { (*this)(0) = (*this)(1) = 0.; }
    -    explicit Pointf() {  }
    -    explicit Pointf(coordf_t x, coordf_t y) { (*this)(0) = x; (*this)(1) = y; }
    -    // This constructor allows you to construct Pointf from Eigen expressions
    -    template
    -    Pointf(const Eigen::MatrixBase &other) : Vec2d(other) {}
    -
    -    // This method allows you to assign Eigen expressions to MyVectorType
    -    template
    -    Pointf& operator=(const Eigen::MatrixBase &other)
    -    {
    -        this->Vec2d::operator=(other);
    -        return *this;
    -    }
    -
    -//    void    rotate(double angle);
    -//    void    rotate(double angle, const Pointf ¢er);
    -
    -private:
    -};
    +std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf);
     
     } // namespace Slic3r
     
    diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp
    index f9c82d198a..14248d84ff 100644
    --- a/xs/src/libslic3r/Polygon.cpp
    +++ b/xs/src/libslic3r/Polygon.cpp
    @@ -297,10 +297,10 @@ Point Polygon::point_projection(const Point &point) const
                     dmin = d;
                     proj = pt1;
                 }
    -            Pointf v1(coordf_t(pt1(0) - pt0(0)), coordf_t(pt1(1) - pt0(1)));
    +            Vec2d v1(coordf_t(pt1(0) - pt0(0)), coordf_t(pt1(1) - pt0(1)));
                 coordf_t div = v1.squaredNorm();
                 if (div > 0.) {
    -                Pointf v2(coordf_t(point(0) - pt0(0)), coordf_t(point(1) - pt0(1)));
    +                Vec2d v2(coordf_t(point(0) - pt0(0)), coordf_t(point(1) - pt0(1)));
                     coordf_t t = v1.dot(v2) / div;
                     if (t > 0. && t < 1.) {
                         Point foot(coord_t(floor(coordf_t(pt0(0)) + t * v1(0) + 0.5)), coord_t(floor(coordf_t(pt0(1)) + t * v1(1) + 0.5)));
    diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp
    index 18a2d2b950..54909352ce 100644
    --- a/xs/src/libslic3r/Polygon.hpp
    +++ b/xs/src/libslic3r/Polygon.hpp
    @@ -24,10 +24,10 @@ public:
         explicit Polygon(const Points &points): MultiPoint(points) {}
         Polygon(const Polygon &other) : MultiPoint(other.points) {}
         Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {}
    -	static Polygon new_scale(const std::vector &points) { 
    +	static Polygon new_scale(const std::vector &points) { 
             Polygon pgn;
             pgn.points.reserve(points.size());
    -        for (const Pointf &pt : points)
    +        for (const Vec2d &pt : points)
                 pgn.points.emplace_back(Point::new_scale(pt(0), pt(1)));
     		return pgn;
     	}
    diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp
    index f61e467178..0c934e0748 100644
    --- a/xs/src/libslic3r/Polyline.hpp
    +++ b/xs/src/libslic3r/Polyline.hpp
    @@ -23,10 +23,10 @@ public:
         explicit Polyline(const Point &p1, const Point &p2) { points.reserve(2); points.emplace_back(p1); points.emplace_back(p2); }
         Polyline& operator=(const Polyline &other) { points = other.points; return *this; }
         Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; }
    -	static Polyline new_scale(const std::vector &points) {
    +	static Polyline new_scale(const std::vector &points) {
     		Polyline pl;
     		pl.points.reserve(points.size());
    -		for (const Pointf &pt : points)
    +		for (const Vec2d &pt : points)
     			pl.points.emplace_back(Point::new_scale(pt(0), pt(1)));
     		return pl;
         }
    diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp
    index 734b04e3f2..ef43033dab 100644
    --- a/xs/src/libslic3r/Print.hpp
    +++ b/xs/src/libslic3r/Print.hpp
    @@ -138,7 +138,7 @@ public:
         const ModelObject*  model_object() const    { return this->_model_object; }
     
         const Points& copies() const { return this->_copies; }
    -    bool add_copy(const Pointf &point);
    +    bool add_copy(const Vec2d &point);
         bool delete_last_copy();
         bool delete_all_copies() { return this->set_copies(Points()); }
         bool set_copies(const Points &points);
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index 840096c740..56a24a6f7a 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -35,7 +35,7 @@ PrintConfigDef::PrintConfigDef()
     
         def = this->add("bed_shape", coPoints);
     	def->label = L("Bed shape");
    -    def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) };
    +    def->default_value = new ConfigOptionPoints { Vec2d(0,0), Vec2d(200,0), Vec2d(200,200), Vec2d(0,200) };
         
         def = this->add("bed_temperature", coInts);
         def->label = L("Other layers");
    @@ -392,7 +392,7 @@ PrintConfigDef::PrintConfigDef()
                        "from the XY coordinate).");
         def->sidetext = L("mm");
         def->cli = "extruder-offset=s@";
    -    def->default_value = new ConfigOptionPoints { Pointf(0,0) };
    +    def->default_value = new ConfigOptionPoints { Vec2d(0,0) };
     
         def = this->add("extrusion_axis", coString);
         def->label = L("Extrusion axis");
    diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp
    index 10ebcb18e5..54f7cb30a6 100644
    --- a/xs/src/libslic3r/PrintObject.cpp
    +++ b/xs/src/libslic3r/PrintObject.cpp
    @@ -59,7 +59,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding
         this->layer_height_profile = model_object->layer_height_profile;
     }
     
    -bool PrintObject::add_copy(const Pointf &point)
    +bool PrintObject::add_copy(const Vec2d &point)
     {
         Points points = this->_copies;
         points.push_back(Point::new_scale(point(0), point(1)));
    diff --git a/xs/src/libslic3r/SVG.cpp b/xs/src/libslic3r/SVG.cpp
    index 2113acc433..03f55802ef 100644
    --- a/xs/src/libslic3r/SVG.cpp
    +++ b/xs/src/libslic3r/SVG.cpp
    @@ -58,8 +58,8 @@ SVG::draw(const Line &line, std::string stroke, coordf_t stroke_width)
     
     void SVG::draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coordf_t stroke_width)
     {
    -    Pointf dir(line.b(0)-line.a(0), line.b(1)-line.a(1));
    -    Pointf perp(-dir(1), dir(0));
    +    Vec2d dir(line.b(0)-line.a(0), line.b(1)-line.a(1));
    +    Vec2d perp(-dir(1), dir(0));
         coordf_t len = sqrt(perp(0)*perp(0) + perp(1)*perp(1));
         coordf_t da  = coordf_t(0.5)*line.a_width/len;
         coordf_t db  = coordf_t(0.5)*line.b_width/len;
    diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp
    index 98f41a6639..4b7e789764 100644
    --- a/xs/src/libslic3r/SupportMaterial.cpp
    +++ b/xs/src/libslic3r/SupportMaterial.cpp
    @@ -2057,8 +2057,8 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
                             const Point &p1 = *(it-1);
                             const Point &p2 = *it;
                             // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance.
    -                        const Pointf v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1)));
    -                        const Pointf v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1)));
    +                        const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1)));
    +                        const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1)));
                             coordf_t a = v_seg.squaredNorm();
                             coordf_t b = 2. * v_seg.dot(v_cntr);
                             coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance;
    diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
    index 693221f84f..1ad31c8f82 100644
    --- a/xs/src/perlglue.cpp
    +++ b/xs/src/perlglue.cpp
    @@ -42,7 +42,7 @@ REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3");
     REGISTER_CLASS(BridgeDetector, "BridgeDetector");
     REGISTER_CLASS(Point, "Point");
     REGISTER_CLASS(Point3, "Point3");
    -REGISTER_CLASS(Pointf, "Pointf");
    +__REGISTER_CLASS(Vec2d, "Pointf");
     __REGISTER_CLASS(Vec3d, "Pointf3");
     REGISTER_CLASS(DynamicPrintConfig, "Config");
     REGISTER_CLASS(StaticPrintConfig, "Config::Static");
    @@ -133,7 +133,7 @@ SV* ConfigOption_to_SV(const ConfigOption &opt, const ConfigOptionDef &def)
             auto optv = static_cast(&opt);
             AV* av = newAV();
             av_fill(av, optv->values.size()-1);
    -        for (const Pointf &v : optv->values)
    +        for (const Vec2d &v : optv->values)
                 av_store(av, &v - optv->values.data(), perl_to_SV_clone_ref(v));
             return newRV_noinc((SV*)av);
         }
    @@ -263,14 +263,14 @@ bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* v
             return from_SV_check(value, &static_cast(opt)->value);
         case coPoints:
         {
    -        std::vector &values = static_cast(opt)->values;
    +        std::vector &values = static_cast(opt)->values;
             AV* av = (AV*)SvRV(value);
             const size_t len = av_len(av)+1;
             values.clear();
             values.reserve(len);
             for (size_t i = 0; i < len; i++) {
                 SV** elem = av_fetch(av, i, 0);
    -            Pointf point(Vec2d::Zero());
    +            Vec2d point(Vec2d::Zero());
                 if (elem == NULL || !from_SV_check(*elem, &point)) return false;
                 values.emplace_back(point);
             }
    @@ -509,7 +509,7 @@ void from_SV_check(SV* point_sv, Point* point)
         }
     }
     
    -SV* to_SV_pureperl(const Pointf* point)
    +SV* to_SV_pureperl(const Vec2d* point)
     {
         AV* av = newAV();
         av_fill(av, 1);
    @@ -518,23 +518,23 @@ SV* to_SV_pureperl(const Pointf* point)
         return newRV_noinc((SV*)av);
     }
     
    -bool from_SV(SV* point_sv, Pointf* point)
    +bool from_SV(SV* point_sv, Vec2d* point)
     {
         AV* point_av = (AV*)SvRV(point_sv);
         SV* sv_x = *av_fetch(point_av, 0, 0);
         SV* sv_y = *av_fetch(point_av, 1, 0);
         if (!looks_like_number(sv_x) || !looks_like_number(sv_y)) return false;
         
    -    *point = Pointf(SvNV(sv_x), SvNV(sv_y));
    +    *point = Vec2d(SvNV(sv_x), SvNV(sv_y));
         return true;
     }
     
    -bool from_SV_check(SV* point_sv, Pointf* point)
    +bool from_SV_check(SV* point_sv, Vec2d* point)
     {
         if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) {
             if (!sv_isa(point_sv, perl_class_name(point)) && !sv_isa(point_sv, perl_class_name_ref(point)))
                 CONFESS("Not a valid %s object (got %s)", perl_class_name(point), HvNAME(SvSTASH(SvRV(point_sv))));
    -        *point = *(Pointf*)SvIV((SV*)SvRV( point_sv ));
    +        *point = *(Vec2d*)SvIV((SV*)SvRV( point_sv ));
             return true;
         } else {
             return from_SV(point_sv, point);
    diff --git a/xs/src/slic3r/GUI/2DBed.cpp b/xs/src/slic3r/GUI/2DBed.cpp
    index ccca4320ca..e19f839cdd 100644
    --- a/xs/src/slic3r/GUI/2DBed.cpp
    +++ b/xs/src/slic3r/GUI/2DBed.cpp
    @@ -32,7 +32,7 @@ void Bed_2D::repaint()
     	cw--;
     	ch--;
     
    -	auto cbb = BoundingBoxf(Pointf(0, 0),Pointf(cw, ch));
    +	auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch));
     	// leave space for origin point
     	cbb.min(0) += 4;
     	cbb.max -= Vec2d(4., 4.);
    @@ -50,19 +50,19 @@ void Bed_2D::repaint()
     	auto bed_shape = m_bed_shape;
     	auto bed_polygon = Polygon::new_scale(m_bed_shape);
     	auto bb = BoundingBoxf(m_bed_shape);
    -	bb.merge(Pointf(0, 0));  // origin needs to be in the visible area
    +	bb.merge(Vec2d(0, 0));  // origin needs to be in the visible area
     	auto bw = bb.size()(0);
     	auto bh = bb.size()(1);
     	auto bcenter = bb.center();
     
     	// calculate the scaling factor for fitting bed shape in canvas area
     	auto sfactor = std::min(cw/bw, ch/bh);
    -	auto shift = Pointf(
    +	auto shift = Vec2d(
     		ccenter(0) - bcenter(0) * sfactor,
     		ccenter(1) - bcenter(1) * sfactor
     		);
     	m_scale_factor = sfactor;
    -	m_shift = Pointf(shift(0) + cbb.min(0),
    +	m_shift = Vec2d(shift(0) + cbb.min(0),
     					shift(1) - (cbb.max(1) - GetSize().GetHeight()));
     
     	// draw bed fill
    @@ -79,10 +79,10 @@ void Bed_2D::repaint()
     	auto step = 10;  // 1cm grid
     	Polylines polylines;
     	for (auto x = bb.min(0) - fmod(bb.min(0), step) + step; x < bb.max(0); x += step) {
    -		polylines.push_back(Polyline::new_scale({ Pointf(x, bb.min(1)), Pointf(x, bb.max(1)) }));
    +		polylines.push_back(Polyline::new_scale({ Vec2d(x, bb.min(1)), Vec2d(x, bb.max(1)) }));
     	}
     	for (auto y = bb.min(1) - fmod(bb.min(1), step) + step; y < bb.max(1); y += step) {
    -		polylines.push_back(Polyline::new_scale({ Pointf(bb.min(0), y), Pointf(bb.max(0), y) }));
    +		polylines.push_back(Polyline::new_scale({ Vec2d(bb.min(0), y), Vec2d(bb.max(0), y) }));
     	}
     	polylines = intersection_pl(polylines, bed_polygon);
     
    @@ -101,14 +101,14 @@ void Bed_2D::repaint()
     	dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxTRANSPARENT));
     	dc.DrawPolygon(&pt_list, 0, 0);
     
    -	auto origin_px = to_pixels(Pointf(0, 0));
    +	auto origin_px = to_pixels(Vec2d(0, 0));
     
     	// draw axes
     	auto axes_len = 50;
     	auto arrow_len = 6;
     	auto arrow_angle = Geometry::deg2rad(45.0);
     	dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxSOLID));  // red
    -	auto x_end = Pointf(origin_px(0) + axes_len, origin_px(1));
    +	auto x_end = Vec2d(origin_px(0) + axes_len, origin_px(1));
     	dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(x_end(0), x_end(1)));
     	for (auto angle : { -arrow_angle, arrow_angle }){
     		auto end = Eigen::Translation2d(x_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- x_end) * Eigen::Vector2d(x_end(0) - arrow_len, x_end(1));
    @@ -116,7 +116,7 @@ void Bed_2D::repaint()
     	}
     
     	dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxSOLID));  // green
    -	auto y_end = Pointf(origin_px(0), origin_px(1) - axes_len);
    +	auto y_end = Vec2d(origin_px(0), origin_px(1) - axes_len);
     	dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(y_end(0), y_end(1)));
     	for (auto angle : { -arrow_angle, arrow_angle }) {
     		auto end = Eigen::Translation2d(y_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- y_end) * Eigen::Vector2d(y_end(0), y_end(1) + arrow_len);
    @@ -137,7 +137,7 @@ void Bed_2D::repaint()
     	dc.DrawText(origin_label, origin_label_x, origin_label_y);
     
     	// draw current position
    -	if (m_pos!= Pointf(0, 0)) {
    +	if (m_pos!= Vec2d(0, 0)) {
     		auto pos_px = to_pixels(m_pos);
     		dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxSOLID));
     		dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxTRANSPARENT));
    @@ -151,7 +151,7 @@ void Bed_2D::repaint()
     }
     
     // convert G - code coordinates into pixels
    -Point Bed_2D::to_pixels(Pointf point){
    +Point Bed_2D::to_pixels(Vec2d point){
     	auto p = point * m_scale_factor + m_shift;
     	return Point(p(0), GetSize().GetHeight() - p(1)); 
     }
    @@ -170,11 +170,11 @@ void Bed_2D::mouse_event(wxMouseEvent event){
     }
     
     // convert pixels into G - code coordinates
    -Pointf Bed_2D::to_units(Point point){
    -	return (Pointf(point(0), GetSize().GetHeight() - point(1)) - m_shift) * (1. / m_scale_factor);
    +Vec2d Bed_2D::to_units(Point point){
    +	return (Vec2d(point(0), GetSize().GetHeight() - point(1)) - m_shift) * (1. / m_scale_factor);
     }
     
    -void Bed_2D::set_pos(Pointf pos){
    +void Bed_2D::set_pos(Vec2d pos){
     	m_pos = pos;
     	Refresh();
     }
    diff --git a/xs/src/slic3r/GUI/2DBed.hpp b/xs/src/slic3r/GUI/2DBed.hpp
    index 399be6f120..d7a7f4260e 100644
    --- a/xs/src/slic3r/GUI/2DBed.hpp
    +++ b/xs/src/slic3r/GUI/2DBed.hpp
    @@ -14,15 +14,15 @@ class Bed_2D : public wxPanel
     	bool		m_painted = false;
     	bool		m_interactive = false;
     	double		m_scale_factor;
    -	Pointf		m_shift = Vec2d::Zero();
    -	Pointf		m_pos = Vec2d::Zero();
    -	std::function	m_on_move = nullptr;
    +	Vec2d		m_shift = Vec2d::Zero();
    +	Vec2d		m_pos = Vec2d::Zero();
    +	std::function	m_on_move = nullptr;
     
    -	Point		to_pixels(Pointf point);
    -	Pointf		to_units(Point point);
    +	Point		to_pixels(Vec2d point);
    +	Vec2d		to_units(Point point);
     	void		repaint();
     	void		mouse_event(wxMouseEvent event);
    -	void		set_pos(Pointf pos);
    +	void		set_pos(Vec2d pos);
     
     public:
     	Bed_2D(wxWindow* parent) 
    @@ -41,7 +41,7 @@ public:
     	}
     	~Bed_2D(){}
     
    -	std::vector		m_bed_shape;
    +	std::vector		m_bed_shape;
     		
     };
     
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 43d79a7a9c..e516ffbb5b 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -952,8 +952,8 @@ static void thick_lines_to_indexed_vertex_array(
         // right, left, top, bottom
         int     idx_prev[4]      = { -1, -1, -1, -1 };
         double  bottom_z_prev    = 0.;
    -    Pointf  b1_prev(Vec2d::Zero());
    -    Vectorf v_prev(Vec2d::Zero());
    +    Vec2d  b1_prev(Vec2d::Zero());
    +    Vec2d v_prev(Vec2d::Zero());
         int     idx_initial[4]   = { -1, -1, -1, -1 };
         double  width_initial    = 0.;
         double  bottom_z_initial = 0.0;
    @@ -973,23 +973,23 @@ static void thick_lines_to_indexed_vertex_array(
             bool is_last = (ii == lines_end - 1);
             bool is_closing = closed && is_last;
     
    -        Vectorf v = unscale(line.vector());
    +        Vec2d v = unscale(line.vector());
             v *= inv_len;
     
    -        Pointf a = unscale(line.a);
    -        Pointf b = unscale(line.b);
    -        Pointf a1 = a;
    -        Pointf a2 = a;
    -        Pointf b1 = b;
    -        Pointf b2 = b;
    +        Vec2d a = unscale(line.a);
    +        Vec2d b = unscale(line.b);
    +        Vec2d a1 = a;
    +        Vec2d a2 = a;
    +        Vec2d b1 = b;
    +        Vec2d b2 = b;
             {
                 double dist = 0.5 * width;  // scaled
                 double dx = dist * v(0);
                 double dy = dist * v(1);
    -            a1 += Vectorf(+dy, -dx);
    -            a2 += Vectorf(-dy, +dx);
    -            b1 += Vectorf(+dy, -dx);
    -            b2 += Vectorf(-dy, +dx);
    +            a1 += Vec2d(+dy, -dx);
    +            a2 += Vec2d(-dy, +dx);
    +            b1 += Vec2d(+dy, -dx);
    +            b2 += Vec2d(-dy, +dx);
             }
     
             // calculate new XY normals
    @@ -1064,7 +1064,7 @@ static void thick_lines_to_indexed_vertex_array(
                     {
                         // Create a sharp corner with an overshot and average the left / right normals.
                         // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc.
    -                    Pointf intersection(Vec2d::Zero());
    +                    Vec2d intersection(Vec2d::Zero());
                         Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection);
                         a1 = intersection;
                         a2 = 2. * a - intersection;
    diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp
    index c47de94aa6..e04f2b3700 100644
    --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp
    +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp
    @@ -48,14 +48,14 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
     	auto optgroup = init_shape_options_page(_(L("Rectangular")));
     		ConfigOptionDef def;
     		def.type = coPoints;
    -		def.default_value = new ConfigOptionPoints{ Pointf(200, 200) };
    +		def.default_value = new ConfigOptionPoints{ Vec2d(200, 200) };
     		def.label = L("Size");
     		def.tooltip = L("Size in X and Y of the rectangular plate.");
     		Option option(def, "rect_size");
     		optgroup->append_single_option_line(option);
     
     		def.type = coPoints;
    -		def.default_value = new ConfigOptionPoints{ Pointf(0, 0) };
    +		def.default_value = new ConfigOptionPoints{ Vec2d(0, 0) };
     		def.label = L("Origin");
     		def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
     		option = Option(def, "rect_origin");
    @@ -159,11 +159,11 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
                     y_max = std::max(y_max, pt(1));
                 }
     
    -            auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) };
    +            auto origin = new ConfigOptionPoints{ Vec2d(-x_min, -y_min) };
     
     			m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
     			auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
    -			optgroup->set_value("rect_size", new ConfigOptionPoints{ Pointf(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]);
    +			optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]);
     			optgroup->set_value("rect_origin", origin);
     			update_shape();
     			return;
    @@ -206,8 +206,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
     		// Invalid polygon.Revert to default bed dimensions.
     		m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
     		auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
    -		optgroup->set_value("rect_size", new ConfigOptionPoints{ Pointf(200, 200) });
    -		optgroup->set_value("rect_origin", new ConfigOptionPoints{ Pointf(0, 0) });
    +		optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) });
    +		optgroup->set_value("rect_origin", new ConfigOptionPoints{ Vec2d(0, 0) });
     		update_shape();
     		return;
     	}
    @@ -230,15 +230,15 @@ void BedShapePanel::update_shape()
     {
     	auto page_idx = m_shape_options_book->GetSelection();
     	if (page_idx == SHAPE_RECTANGULAR) {
    -		Pointf rect_size(Vec2d::Zero());
    -		Pointf rect_origin(Vec2d::Zero());
    +		Vec2d rect_size(Vec2d::Zero());
    +		Vec2d rect_origin(Vec2d::Zero());
     		try{
    -			rect_size = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
    +			rect_size = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
     		catch (const std::exception &e){
     			return;
     		}
     		try{
    -			rect_origin = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
    +			rect_origin = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin"));
     		}
     		catch (const std::exception &e){
     			return;}
    @@ -259,10 +259,10 @@ void BedShapePanel::update_shape()
     		x1 -= dx;
     		y0 -= dy;
     		y1 -= dy;
    -		m_canvas->m_bed_shape = {	Pointf(x0, y0),
    -									Pointf(x1, y0),
    -									Pointf(x1, y1),
    -									Pointf(x0, y1)};
    +		m_canvas->m_bed_shape = {	Vec2d(x0, y0),
    +									Vec2d(x1, y0),
    +									Vec2d(x1, y1),
    +									Vec2d(x0, y1)};
     	} 
     	else if(page_idx == SHAPE_CIRCULAR) {
     		double diameter;
    @@ -276,10 +276,10 @@ void BedShapePanel::update_shape()
     		auto r = diameter / 2;
     		auto twopi = 2 * PI;
     		auto edges = 60;
    -		std::vector points;
    +		std::vector points;
     		for (size_t i = 1; i <= 60; ++i){
     			auto angle = i * twopi / edges;
    -			points.push_back(Pointf(r*cos(angle), r*sin(angle)));
    +			points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
     		}
     		m_canvas->m_bed_shape = points;
     	}
    @@ -332,7 +332,7 @@ void BedShapePanel::load_stl()
     	}
     
     	auto polygon = expolygons[0].contour;
    -	std::vector points;
    +	std::vector points;
     	for (auto pt : polygon.points)
     		points.push_back(unscale(pt));
     	m_canvas->m_bed_shape = points;
    diff --git a/xs/src/slic3r/GUI/BedShapeDialog.hpp b/xs/src/slic3r/GUI/BedShapeDialog.hpp
    index 5ff4880637..d8ba5a9128 100644
    --- a/xs/src/slic3r/GUI/BedShapeDialog.hpp
    +++ b/xs/src/slic3r/GUI/BedShapeDialog.hpp
    @@ -34,7 +34,7 @@ public:
     	void		load_stl();
     	
     	// Returns the resulting bed shape polygon. This value will be stored to the ini file.
    -	std::vector	GetValue() { return m_canvas->m_bed_shape; }
    +	std::vector	GetValue() { return m_canvas->m_bed_shape; }
     };
     
     class BedShapeDialog : public wxDialog
    @@ -46,7 +46,7 @@ public:
     	~BedShapeDialog(){  }
     
     	void		build_dialog(ConfigOptionPoints* default_pt);
    -	std::vector	GetValue() { return m_panel->GetValue(); }
    +	std::vector	GetValue() { return m_panel->GetValue(); }
     };
     
     } // GUI
    diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp
    index 32688ec3c3..b36afda4f5 100644
    --- a/xs/src/slic3r/GUI/Field.cpp
    +++ b/xs/src/slic3r/GUI/Field.cpp
    @@ -653,7 +653,7 @@ void PointCtrl::BUILD()
     	y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
     }
     
    -void PointCtrl::set_value(const Pointf& value, bool change_event)
    +void PointCtrl::set_value(const Vec2d& value, bool change_event)
     {
     	m_disable_change_event = !change_event;
     
    @@ -667,8 +667,8 @@ void PointCtrl::set_value(const Pointf& value, bool change_event)
     
     void PointCtrl::set_value(const boost::any& value, bool change_event)
     {
    -	Pointf pt(Vec2d::Zero());
    -	const Pointf *ptf = boost::any_cast(&value);
    +	Vec2d pt(Vec2d::Zero());
    +	const Vec2d *ptf = boost::any_cast(&value);
     	if (!ptf)
     	{
     		ConfigOptionPoints* pts = boost::any_cast(value);
    @@ -684,7 +684,7 @@ boost::any& PointCtrl::get_value()
     	double x, y;
     	x_textctrl->GetValue().ToDouble(&x);
     	y_textctrl->GetValue().ToDouble(&y);
    -	return m_value = Pointf(x, y);
    +	return m_value = Vec2d(x, y);
     }
     
     void StaticText::BUILD()
    diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp
    index db8d2a4085..e19c47d689 100644
    --- a/xs/src/slic3r/GUI/Field.hpp
    +++ b/xs/src/slic3r/GUI/Field.hpp
    @@ -372,7 +372,7 @@ public:
     
     	void			BUILD()  override;
     
    -	void			set_value(const Pointf& value, bool change_event = false);
    +	void			set_value(const Vec2d& value, bool change_event = false);
     	void			set_value(const boost::any& value, bool change_event = false);
     	boost::any&		get_value() override;
     
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index a9ef69357e..015f7e6cc3 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -317,7 +317,7 @@ bool GLCanvas3D::Bed::set_shape(const Pointfs& shape)
         _calc_bounding_box();
     
         ExPolygon poly;
    -    for (const Pointf& p : m_shape)
    +    for (const Vec2d& p : m_shape)
         {
             poly.contour.append(Point(scale_(p(0)), scale_(p(1))));
         }
    @@ -373,7 +373,7 @@ void GLCanvas3D::Bed::render(float theta) const
     void GLCanvas3D::Bed::_calc_bounding_box()
     {
         m_bounding_box = BoundingBoxf3();
    -    for (const Pointf& p : m_shape)
    +    for (const Vec2d& p : m_shape)
         {
             m_bounding_box.merge(Vec3d(p(0), p(1), 0.0));
         }
    @@ -1168,7 +1168,7 @@ void GLCanvas3D::Gizmos::set_enabled(bool enable)
         m_enabled = enable;
     }
     
    -void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos)
    +void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos)
     {
         if (!m_enabled)
             return;
    @@ -1187,14 +1187,14 @@ void GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Poin
             // we currently use circular icons for gizmo, so we check the radius
             if (it->second->get_state() != GLGizmoBase::On)
             {
    -            bool inside = (mouse_pos - Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size;
    +            bool inside = (mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size;
                 it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off);
             }
             top_y += (tex_size + OverlayGapY);
         }
     }
     
    -void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos)
    +void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos)
     {
         if (!m_enabled)
             return;
    @@ -1211,7 +1211,7 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Poi
             float half_tex_size = 0.5f * tex_size;
     
             // we currently use circular icons for gizmo, so we check the radius
    -        if ((mouse_pos - Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
    +        if ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
             {
                 if ((it->second->get_state() == GLGizmoBase::On))
                 {
    @@ -1260,7 +1260,7 @@ void GLCanvas3D::Gizmos::set_hover_id(int id)
         }
     }
     
    -bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const
    +bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const
     {
         if (!m_enabled)
             return false;
    @@ -1277,7 +1277,7 @@ bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const
             float half_tex_size = 0.5f * tex_size;
     
             // we currently use circular icons for gizmo, so we check the radius
    -        if ((mouse_pos - Pointf(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
    +        if ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
                 return true;
     
             top_y += (tex_size + OverlayGapY);
    @@ -1295,7 +1295,7 @@ bool GLCanvas3D::Gizmos::grabber_contains_mouse() const
         return (curr != nullptr) ? (curr->get_hover_id() != -1) : false;
     }
     
    -void GLCanvas3D::Gizmos::update(const Pointf& mouse_pos)
    +void GLCanvas3D::Gizmos::update(const Vec2d& mouse_pos)
     {
         if (!m_enabled)
             return;
    @@ -2719,7 +2719,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         else if (evt.Leaving())
         {
             // to remove hover on objects when the mouse goes out of this canvas
    -        m_mouse.position = Pointf(-1.0, -1.0);
    +        m_mouse.position = Vec2d(-1.0, -1.0);
             m_dirty = true;
         }
         else if (evt.LeftDClick() && (m_hover_volume_id != -1))
    @@ -2880,7 +2880,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
             m_mouse.dragging = true;
     
             const Vec3d& cur_pos = _mouse_to_bed_3d(pos);
    -        m_gizmos.update(Pointf(cur_pos(0), cur_pos(1)));
    +        m_gizmos.update(Vec2d(cur_pos(0), cur_pos(1)));
     
             std::vector volumes;
             if (m_mouse.drag.gizmo_volume_idx != -1)
    @@ -3052,7 +3052,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         }
         else if (evt.Moving())
         {
    -        m_mouse.position = Pointf((coordf_t)pos(0), (coordf_t)pos(1));
    +        m_mouse.position = Vec2d((coordf_t)pos(0), (coordf_t)pos(1));
             // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
             if (m_picking_enabled)
                 m_dirty = true;
    @@ -3396,9 +3396,9 @@ void GLCanvas3D::_camera_tranform() const
     
     void GLCanvas3D::_picking_pass() const
     {
    -    const Pointf& pos = m_mouse.position;
    +    const Vec2d& pos = m_mouse.position;
     
    -    if (m_picking_enabled && !m_mouse.dragging && (pos != Pointf(DBL_MAX, DBL_MAX)))
    +    if (m_picking_enabled && !m_mouse.dragging && (pos != Vec2d(DBL_MAX, DBL_MAX)))
         {
             // Render the object for picking.
             // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing.
    @@ -4870,7 +4870,7 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs)
                 // Move a regular object.
                 ModelObject* model_object = m_model->objects[obj_idx];
                 const Vec3d& origin = volume->get_origin();
    -            model_object->instances[instance_idx]->offset = Pointf(origin(0), origin(1));
    +            model_object->instances[instance_idx]->offset = Vec2d(origin(0), origin(1));
                 model_object->invalidate_bounding_box();
                 object_moved = true;
             }
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    index 3d35495b42..3c317ba51c 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp
    @@ -314,7 +314,7 @@ public:
             };
     
             bool dragging;
    -        Pointf position;
    +        Vec2d position;
             Drag drag;
     
             Mouse();
    @@ -357,15 +357,15 @@ public:
             bool is_enabled() const;
             void set_enabled(bool enable);
     
    -        void update_hover_state(const GLCanvas3D& canvas, const Pointf& mouse_pos);
    -        void update_on_off_state(const GLCanvas3D& canvas, const Pointf& mouse_pos);
    +        void update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos);
    +        void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos);
             void reset_all_states();
     
             void set_hover_id(int id);
     
    -        bool overlay_contains_mouse(const GLCanvas3D& canvas, const Pointf& mouse_pos) const;
    +        bool overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const;
             bool grabber_contains_mouse() const;
    -        void update(const Pointf& mouse_pos);
    +        void update(const Vec2d& mouse_pos);
             void refresh();
     
             EType get_current_type() const;
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 62242ea8a0..107759e544 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -16,7 +16,7 @@ const float GLGizmoBase::BaseColor[3] = { 1.0f, 1.0f, 1.0f };
     const float GLGizmoBase::HighlightColor[3] = { 1.0f, 0.38f, 0.0f };
     
     GLGizmoBase::Grabber::Grabber()
    -    : center(Pointf(0.0, 0.0))
    +    : center(Vec2d(0.0, 0.0))
         , angle_z(0.0f)
     {
         color[0] = 1.0f;
    @@ -124,7 +124,7 @@ void GLGizmoBase::stop_dragging()
         on_stop_dragging();
     }
     
    -void GLGizmoBase::update(const Pointf& mouse_pos)
    +void GLGizmoBase::update(const Vec2d& mouse_pos)
     {
         if (m_hover_id != -1)
             on_update(mouse_pos);
    @@ -187,7 +187,7 @@ const float GLGizmoRotate::GrabberOffset = 5.0f;
     GLGizmoRotate::GLGizmoRotate()
         : GLGizmoBase()
         , m_angle_z(0.0f)
    -    , m_center(Pointf(0.0, 0.0))
    +    , m_center(Vec2d(0.0, 0.0))
         , m_radius(0.0f)
         , m_keep_radius(false)
     {
    @@ -232,10 +232,10 @@ void GLGizmoRotate::on_set_state()
         m_keep_radius = (m_state == On) ? false : true;
     }
     
    -void GLGizmoRotate::on_update(const Pointf& mouse_pos)
    +void GLGizmoRotate::on_update(const Vec2d& mouse_pos)
     {
    -    Vectorf orig_dir(1.0, 0.0);
    -    Vectorf new_dir = (mouse_pos - m_center).normalized();
    +    Vec2d orig_dir(1.0, 0.0);
    +    Vec2d new_dir = (mouse_pos - m_center).normalized();
         coordf_t theta = ::acos(clamp(-1.0, 1.0, new_dir.dot(orig_dir)));
         if (cross2(orig_dir, new_dir) < 0.0)
             theta = 2.0 * (coordf_t)PI - theta;
    @@ -440,9 +440,9 @@ void GLGizmoScale::on_start_dragging()
             m_starting_drag_position = m_grabbers[m_hover_id].center;
     }
     
    -void GLGizmoScale::on_update(const Pointf& mouse_pos)
    +void GLGizmoScale::on_update(const Vec2d& mouse_pos)
     {
    -    Pointf center(0.5 * (m_grabbers[1].center(0) + m_grabbers[0].center(0)), 0.5 * (m_grabbers[3].center(1) + m_grabbers[0].center(1)));
    +    Vec2d center(0.5 * (m_grabbers[1].center(0) + m_grabbers[0].center(0)), 0.5 * (m_grabbers[3].center(1) + m_grabbers[0].center(1)));
     
         coordf_t orig_len = (m_starting_drag_position - center).norm();
         coordf_t new_len = (mouse_pos - center).norm();
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index d6223a14d6..19d93bd303 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -23,7 +23,7 @@ protected:
             static const float HalfSize;
             static const float HoverOffset;
     
    -        Pointf center;
    +        Vec2d center;
             float angle_z;
             float color[3];
     
    @@ -64,7 +64,7 @@ public:
     
         void start_dragging();
         void stop_dragging();
    -    void update(const Pointf& mouse_pos);
    +    void update(const Vec2d& mouse_pos);
         void refresh();
     
         void render(const BoundingBoxf3& box) const;
    @@ -75,7 +75,7 @@ protected:
         virtual void on_set_state();
         virtual void on_start_dragging();
         virtual void on_stop_dragging();
    -    virtual void on_update(const Pointf& mouse_pos) = 0;
    +    virtual void on_update(const Vec2d& mouse_pos) = 0;
         virtual void on_refresh();
         virtual void on_render(const BoundingBoxf3& box) const = 0;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0;
    @@ -98,7 +98,7 @@ class GLGizmoRotate : public GLGizmoBase
     
         float m_angle_z;
     
    -    mutable Pointf m_center;
    +    mutable Vec2d m_center;
         mutable float m_radius;
         mutable bool m_keep_radius;
     
    @@ -111,7 +111,7 @@ public:
     protected:
         virtual bool on_init();
         virtual void on_set_state();
    -    virtual void on_update(const Pointf& mouse_pos);
    +    virtual void on_update(const Vec2d& mouse_pos);
         virtual void on_refresh();
         virtual void on_render(const BoundingBoxf3& box) const;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const;
    @@ -132,7 +132,7 @@ class GLGizmoScale : public GLGizmoBase
         float m_scale;
         float m_starting_scale;
     
    -    Pointf m_starting_drag_position;
    +    Vec2d m_starting_drag_position;
     
     public:
         GLGizmoScale();
    @@ -143,7 +143,7 @@ public:
     protected:
         virtual bool on_init();
         virtual void on_start_dragging();
    -    virtual void on_update(const Pointf& mouse_pos);
    +    virtual void on_update(const Vec2d& mouse_pos);
         virtual void on_render(const BoundingBoxf3& box) const;
         virtual void on_render_for_picking(const BoundingBoxf3& box) const;
     };
    diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
    index e5dca948c9..e0a8aa59d8 100644
    --- a/xs/src/slic3r/GUI/GUI.cpp
    +++ b/xs/src/slic3r/GUI/GUI.cpp
    @@ -608,10 +608,10 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
     			break;
     		case coPoints:{
     			if (opt_key.compare("bed_shape") == 0){
    -				config.option(opt_key)->values = boost::any_cast>(value);
    +				config.option(opt_key)->values = boost::any_cast>(value);
     				break;
     			}
    -			ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) };
    +			ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) };
     			config.option(opt_key)->set_at(vec_new, opt_index, 0);
     			}
     			break;
    diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h
    index e32717532b..c9e3636027 100644
    --- a/xs/src/xsinit.h
    +++ b/xs/src/xsinit.h
    @@ -197,9 +197,9 @@ void from_SV_check(SV* poly_sv, Polyline* THIS);
     SV* to_SV_pureperl(const Point* THIS);
     void from_SV(SV* point_sv, Point* point);
     void from_SV_check(SV* point_sv, Point* point);
    -SV* to_SV_pureperl(const Pointf* point);
    -bool from_SV(SV* point_sv, Pointf* point);
    -bool from_SV_check(SV* point_sv, Pointf* point);
    +SV* to_SV_pureperl(const Vec2d* point);
    +bool from_SV(SV* point_sv, Vec2d* point);
    +bool from_SV_check(SV* point_sv, Vec2d* point);
     void from_SV_check(SV* surface_sv, Surface* THIS);
     SV* to_SV(TriangleMesh* THIS);
     
    diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp
    index 46a6da98cb..a34cad0bc8 100644
    --- a/xs/xsp/BoundingBox.xsp
    +++ b/xs/xsp/BoundingBox.xsp
    @@ -56,15 +56,15 @@ new_from_points(CLASS, points)
         Clone clone()
             %code{% RETVAL = THIS; %};
         void merge(BoundingBoxf* bb) %code{% THIS->merge(*bb); %};
    -    void merge_point(Pointf* point) %code{% THIS->merge(*point); %};
    +    void merge_point(Vec2d* point) %code{% THIS->merge(*point); %};
         void scale(double factor);
         void translate(double x, double y);
    -    Clone size();
    -    Clone center();
    +    Clone size();
    +    Clone center();
         double radius();
         bool empty() %code{% RETVAL = empty(*THIS); %};
    -    Clone min_point() %code{% RETVAL = THIS->min; %};
    -    Clone max_point() %code{% RETVAL = THIS->max; %};
    +    Clone min_point() %code{% RETVAL = THIS->min; %};
    +    Clone max_point() %code{% RETVAL = THIS->max; %};
         double x_min() %code{% RETVAL = THIS->min(0); %};
         double x_max() %code{% RETVAL = THIS->max(0); %};
         double y_min() %code{% RETVAL = THIS->min(1); %};
    diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp
    index c1856ccf7b..9e04edd4c6 100644
    --- a/xs/xsp/GCode.xsp
    +++ b/xs/xsp/GCode.xsp
    @@ -35,9 +35,9 @@
                 }
             %};
     
    -    Ref origin()
    +    Ref origin()
             %code{% RETVAL = &(THIS->origin()); %};
    -    void set_origin(Pointf* pointf)
    +    void set_origin(Vec2d* pointf)
             %code{% THIS->set_origin(*pointf); %};
         Ref last_pos()
             %code{% RETVAL = &(THIS->last_pos()); %};
    diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp
    index b23bbeffa9..b7e92ba695 100644
    --- a/xs/xsp/Geometry.xsp
    +++ b/xs/xsp/Geometry.xsp
    @@ -8,7 +8,7 @@
     
     %package{Slic3r::Geometry};
     
    -Pointfs arrange(size_t total_parts, Pointf* part, coordf_t dist, BoundingBoxf* bb = NULL)
    +Pointfs arrange(size_t total_parts, Vec2d* part, coordf_t dist, BoundingBoxf* bb = NULL)
         %code{% 
             Pointfs points;
             if (! Slic3r::Geometry::arrange(total_parts, *part, dist, bb, points))
    diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp
    index d8e5095270..ac265a3b36 100644
    --- a/xs/xsp/Model.xsp
    +++ b/xs/xsp/Model.xsp
    @@ -81,7 +81,7 @@
     
         bool add_default_instances();
         Clone bounding_box();
    -    void center_instances_around_point(Pointf* point)
    +    void center_instances_around_point(Vec2d* point)
             %code%{ THIS->center_instances_around_point(*point); %};
         void translate(double x, double y, double z);
         Clone mesh();
    @@ -357,14 +357,14 @@ ModelMaterial::attributes()
             %code%{ RETVAL = THIS->rotation; %};
         double scaling_factor()
             %code%{ RETVAL = THIS->scaling_factor; %};
    -    Ref offset()
    +    Ref offset()
             %code%{ RETVAL = &THIS->offset; %};
     
         void set_rotation(double val)
             %code%{ THIS->rotation = val; THIS->get_object()->invalidate_bounding_box(); %};
         void set_scaling_factor(double val)
             %code%{ THIS->scaling_factor = val; THIS->get_object()->invalidate_bounding_box(); %};
    -    void set_offset(Pointf *offset)
    +    void set_offset(Vec2d *offset)
             %code%{ THIS->offset = *offset; %};
         
         void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
    diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp
    index 7671b7d033..209f1c2986 100644
    --- a/xs/xsp/Point.xsp
    +++ b/xs/xsp/Point.xsp
    @@ -91,10 +91,10 @@ Point::coincides_with(point_sv)
         std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld,%ld", (*THIS)(0), (*THIS)(1), (*THIS)(2)); RETVAL = buf; %};
     };
     
    -%name{Slic3r::Pointf} class Pointf {
    -    Pointf(double _x = 0, double _y = 0);
    -    ~Pointf();
    -    Clone clone()
    +%name{Slic3r::Pointf} class Vec2d {
    +    Vec2d(double _x = 0, double _y = 0);
    +    ~Vec2d();
    +    Clone clone()
             %code{% RETVAL = THIS; %};
         SV* arrayref()
             %code{% RETVAL = to_SV_pureperl(THIS); %};
    @@ -109,15 +109,15 @@ Point::coincides_with(point_sv)
         void set_y(double val)
             %code{% (*THIS)(1) = val; %};
         void translate(double x, double y)
    -        %code{% *THIS += Pointf(x, y); %};
    +        %code{% *THIS += Vec2d(x, y); %};
         void scale(double factor)
             %code{% *THIS *= factor; %};
    -    void rotate(double angle, Pointf* center)
    +    void rotate(double angle, Vec2d* center)
             %code{% *THIS = Eigen::Translation2d(*center) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- *center) * Eigen::Vector2d((*THIS)(0), (*THIS)(1)); %};
    -    Pointf* negative()
    -        %code{% RETVAL = new Pointf(- *THIS); %};
    -    Pointf* vector_to(Pointf* point)
    -        %code{% RETVAL = new Pointf(*point - *THIS); %};
    +    Vec2d* negative()
    +        %code{% RETVAL = new Vec2d(- *THIS); %};
    +    Vec2d* vector_to(Vec2d* point)
    +        %code{% RETVAL = new Vec2d(*point - *THIS); %};
         std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf", (*THIS)(0), (*THIS)(1)); RETVAL = buf; %};
     };
     
    diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
    index a6fb4f1b2a..9005e6e90e 100644
    --- a/xs/xsp/Print.xsp
    +++ b/xs/xsp/Print.xsp
    @@ -72,7 +72,7 @@ _constant()
         void set_shifted_copies(Points value)
             %code%{ THIS->_shifted_copies = value; %};
     
    -    bool add_copy(Pointf* point)
    +    bool add_copy(Vec2d* point)
             %code%{ RETVAL = THIS->add_copy(*point); %};
         bool delete_last_copy();
         bool delete_all_copies();
    diff --git a/xs/xsp/my.map b/xs/xsp/my.map
    index 4c3cc35222..b598531985 100644
    --- a/xs/xsp/my.map
    +++ b/xs/xsp/my.map
    @@ -66,9 +66,9 @@ Point3*                    O_OBJECT_SLIC3R
     Ref                O_OBJECT_SLIC3R_T
     Clone              O_OBJECT_SLIC3R_T
     
    -Pointf*                    O_OBJECT_SLIC3R
    -Ref                O_OBJECT_SLIC3R_T
    -Clone              O_OBJECT_SLIC3R_T
    +Vec2d*                     O_OBJECT_SLIC3R
    +Ref                 O_OBJECT_SLIC3R_T
    +Clone               O_OBJECT_SLIC3R_T
     
     Vec3d*                     O_OBJECT_SLIC3R
     Ref                 O_OBJECT_SLIC3R_T
    diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt
    index 7141487946..9cacb3d556 100644
    --- a/xs/xsp/typemap.xspt
    +++ b/xs/xsp/typemap.xspt
    @@ -22,9 +22,9 @@
     %typemap{Point3*};
     %typemap{Ref}{simple};
     %typemap{Clone}{simple};
    -%typemap{Pointf*};
    -%typemap{Ref}{simple};
    -%typemap{Clone}{simple};
    +%typemap{Vec2d*};
    +%typemap{Ref}{simple};
    +%typemap{Clone}{simple};
     %typemap{Vec3d*};
     %typemap{Ref}{simple};
     %typemap{Clone}{simple};
    
    From ac72cd779ff7030213d41312a000deff68957c3c Mon Sep 17 00:00:00 2001
    From: bubnikv 
    Date: Tue, 21 Aug 2018 22:14:47 +0200
    Subject: [PATCH 159/185] Replaced Point3 with Eigen Vec3crd, removed Point3
     from the Perl binding.
    
    ---
     xs/lib/Slic3r/XS.pm                    | 10 ------
     xs/src/libslic3r/BoundingBox.hpp       |  8 ++---
     xs/src/libslic3r/GCode/Analyzer.cpp    | 10 +++---
     xs/src/libslic3r/GCode/PreviewData.cpp |  2 +-
     xs/src/libslic3r/GCode/PreviewData.hpp |  4 +--
     xs/src/libslic3r/Line.hpp              | 10 +++---
     xs/src/libslic3r/MultiPoint.cpp        |  2 +-
     xs/src/libslic3r/MultiPoint.hpp        |  2 +-
     xs/src/libslic3r/Point.hpp             | 27 ++-------------
     xs/src/libslic3r/Print.hpp             |  2 +-
     xs/src/libslic3r/PrintObject.cpp       |  4 +--
     xs/src/libslic3r/Slicing.cpp           | 26 +++++++-------
     xs/src/libslic3r/TriangleMesh.cpp      | 48 +++++++++++++-------------
     xs/src/libslic3r/TriangleMesh.hpp      |  2 +-
     xs/src/perlglue.cpp                    |  1 -
     xs/src/slic3r/GUI/3DScene.cpp          | 10 +++---
     xs/src/slic3r/GUI/3DScene.hpp          |  2 +-
     xs/xsp/Point.xsp                       | 14 --------
     xs/xsp/Print.xsp                       |  2 --
     19 files changed, 68 insertions(+), 118 deletions(-)
    
    diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm
    index a4847fb45e..391b06dac6 100644
    --- a/xs/lib/Slic3r/XS.pm
    +++ b/xs/lib/Slic3r/XS.pm
    @@ -33,16 +33,6 @@ use overload
         '@{}' => sub { $_[0]->arrayref },
         'fallback' => 1;
     
    -package Slic3r::Point3;
    -use overload
    -    '@{}' => sub { [ $_[0]->x, $_[0]->y, $_[0]->z ] },  #,
    -    'fallback' => 1;
    -
    -sub pp {
    -    my ($self) = @_;
    -    return [ @$self ];
    -}
    -
     package Slic3r::Pointf;
     use overload
         '@{}' => sub { $_[0]->arrayref },
    diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp
    index 6190d4b939..8a8d446a9f 100644
    --- a/xs/src/libslic3r/BoundingBox.hpp
    +++ b/xs/src/libslic3r/BoundingBox.hpp
    @@ -120,12 +120,12 @@ public:
         friend BoundingBox get_extents_rotated(const Points &points, double angle);
     };
     
    -class BoundingBox3  : public BoundingBox3Base 
    +class BoundingBox3  : public BoundingBox3Base 
     {
     public:
    -    BoundingBox3() : BoundingBox3Base() {};
    -    BoundingBox3(const Point3 &pmin, const Point3 &pmax) : BoundingBox3Base(pmin, pmax) {};
    -    BoundingBox3(const Points3& points) : BoundingBox3Base(points) {};
    +    BoundingBox3() : BoundingBox3Base() {};
    +    BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base(pmin, pmax) {};
    +    BoundingBox3(const Points3& points) : BoundingBox3Base(points) {};
     };
     
     class BoundingBoxf : public BoundingBoxBase 
    diff --git a/xs/src/libslic3r/GCode/Analyzer.cpp b/xs/src/libslic3r/GCode/Analyzer.cpp
    index ce9a9ac8e3..51d5b1a067 100644
    --- a/xs/src/libslic3r/GCode/Analyzer.cpp
    +++ b/xs/src/libslic3r/GCode/Analyzer.cpp
    @@ -768,12 +768,12 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
                 polyline = Polyline3();
     
                 // add both vertices of the move
    -            polyline.append(Point3(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())));
    -            polyline.append(Point3(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z())));
    +            polyline.append(Vec3crd(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())));
    +            polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z())));
             }
             else
                 // append end vertex of the move to current polyline
    -            polyline.append(Point3(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z())));
    +            polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z())));
     
             // update current values
             position = move.end_position;
    @@ -804,7 +804,7 @@ void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_da
         for (const GCodeMove& move : retraction_moves->second)
         {
             // store position
    -        Point3 position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
    +        Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
             preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height);
         }
     }
    @@ -818,7 +818,7 @@ void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_
         for (const GCodeMove& move : unretraction_moves->second)
         {
             // store position
    -        Point3 position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
    +        Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
             preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height);
         }
     }
    diff --git a/xs/src/libslic3r/GCode/PreviewData.cpp b/xs/src/libslic3r/GCode/PreviewData.cpp
    index 3833bca06c..9cf9716e0a 100644
    --- a/xs/src/libslic3r/GCode/PreviewData.cpp
    +++ b/xs/src/libslic3r/GCode/PreviewData.cpp
    @@ -226,7 +226,7 @@ void GCodePreviewData::Travel::set_default()
     
     const GCodePreviewData::Color GCodePreviewData::Retraction::Default_Color = GCodePreviewData::Color(1.0f, 1.0f, 1.0f, 1.0f);
     
    -GCodePreviewData::Retraction::Position::Position(const Point3& position, float width, float height)
    +GCodePreviewData::Retraction::Position::Position(const Vec3crd& position, float width, float height)
         : position(position)
         , width(width)
         , height(height)
    diff --git a/xs/src/libslic3r/GCode/PreviewData.hpp b/xs/src/libslic3r/GCode/PreviewData.hpp
    index ea8ca6d58a..ab74993f55 100644
    --- a/xs/src/libslic3r/GCode/PreviewData.hpp
    +++ b/xs/src/libslic3r/GCode/PreviewData.hpp
    @@ -151,11 +151,11 @@ public:
     
             struct Position
             {
    -            Point3 position;
    +            Vec3crd position;
                 float width;
                 float height;
     
    -            Position(const Point3& position, float width, float height);
    +            Position(const Vec3crd& position, float width, float height);
             };
     
             typedef std::vector PositionsList;
    diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp
    index b1294f5223..96bc95f269 100644
    --- a/xs/src/libslic3r/Line.hpp
    +++ b/xs/src/libslic3r/Line.hpp
    @@ -58,14 +58,14 @@ public:
     class Line3
     {
     public:
    -    Line3() {}
    -    Line3(const Point3& _a, const Point3& _b) : a(_a), b(_b) {}
    +    Line3() : a(Vec3crd::Zero()), b(Vec3crd::Zero()) {}
    +    Line3(const Vec3crd& _a, const Vec3crd& _b) : a(_a), b(_b) {}
     
         double  length() const { return (this->a - this->b).cast().norm(); }
    -    Vector3 vector() const { return this->b - this->a; }
    +    Vec3crd vector() const { return this->b - this->a; }
     
    -    Point3 a;
    -    Point3 b;
    +    Vec3crd a;
    +    Vec3crd b;
     };
     
     class Linef
    diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp
    index 0be95fd556..f44897a046 100644
    --- a/xs/src/libslic3r/MultiPoint.cpp
    +++ b/xs/src/libslic3r/MultiPoint.cpp
    @@ -196,7 +196,7 @@ MultiPoint::_douglas_peucker(const Points &points, const double tolerance)
     
     void MultiPoint3::translate(double x, double y)
     {
    -    for (Point3 &p : points) {
    +    for (Vec3crd &p : points) {
             p(0) += x;
             p(1) += y;
         }
    diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp
    index c5d35050d5..1fef4083b7 100644
    --- a/xs/src/libslic3r/MultiPoint.hpp
    +++ b/xs/src/libslic3r/MultiPoint.hpp
    @@ -85,7 +85,7 @@ class MultiPoint3
     public:
         Points3 points;
     
    -    void append(const Point3& point) { this->points.push_back(point); }
    +    void append(const Vec3crd& point) { this->points.push_back(point); }
     
         void translate(double x, double y);
         void translate(const Point& vector);
    diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
    index 2d0304f4ea..b969b98390 100644
    --- a/xs/src/libslic3r/Point.hpp
    +++ b/xs/src/libslic3r/Point.hpp
    @@ -16,9 +16,7 @@ namespace Slic3r {
     class Line;
     class MultiPoint;
     class Point;
    -class Point3;
    -typedef Point                       Vector;
    -typedef Point3                      Vector3;
    +typedef Point Vector;
     
     // Eigen types, to replace the Slic3r's own types in the future.
     // Vector types with a fixed point coordinate base type.
    @@ -36,7 +34,7 @@ typedef Eigen::Matrix Vec3d;
     typedef std::vector                              Points;
     typedef std::vector                             PointPtrs;
     typedef std::vector                       PointConstPtrs;
    -typedef std::vector                             Points3;
    +typedef std::vector                            Points3;
     typedef std::vector                              Pointfs;
     typedef std::vector                              Pointf3s;
     
    @@ -221,27 +219,6 @@ private:
         coord_t  m_grid_log2;
     };
     
    -class Point3 : public Vec3crd
    -{
    -public:
    -    typedef coord_t coord_type;
    -
    -    explicit Point3() { (*this)(0) = (*this)(1) = (*this)(2) = 0; }
    -    explicit Point3(coord_t x, coord_t y, coord_t z) { (*this)(0) = x; (*this)(1) = y; (*this)(2) = z; }
    -    // This constructor allows you to construct Point3 from Eigen expressions
    -    template
    -    Point3(const Eigen::MatrixBase &other) : Vec3crd(other) {}
    -    static Point3 new_scale(coordf_t x, coordf_t y, coordf_t z) { return Point3(coord_t(scale_(x)), coord_t(scale_(y)), coord_t(scale_(z))); }
    -
    -    // This method allows you to assign Eigen expressions to MyVectorType
    -    template
    -    Point3& operator=(const Eigen::MatrixBase &other)
    -    {
    -        this->Vec3crd::operator=(other);
    -        return *this;
    -    }
    -};
    -
     std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf);
     
     } // namespace Slic3r
    diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp
    index ef43033dab..82fedbc7bd 100644
    --- a/xs/src/libslic3r/Print.hpp
    +++ b/xs/src/libslic3r/Print.hpp
    @@ -118,7 +118,7 @@ public:
         // so that next call to make_perimeters() performs a union() before computing loops
         bool typed_slices;
     
    -    Point3 size;           // XYZ in scaled coordinates
    +    Vec3crd size;           // XYZ in scaled coordinates
     
         // scaled coordinates to add to copies (to compensate for the alignment
         // operated when creating the object but still preserving a coherent API
    diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp
    index 54f7cb30a6..d03b994168 100644
    --- a/xs/src/libslic3r/PrintObject.cpp
    +++ b/xs/src/libslic3r/PrintObject.cpp
    @@ -38,6 +38,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding
         typed_slices(false),
         _print(print),
         _model_object(model_object),
    +    size(Vec3crd::Zero()),
         layer_height_profile_valid(false)
     {
         // Compute the translation to be applied to our meshes so that we work with smaller coordinates
    @@ -50,8 +51,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding
             // (copies are expressed in G-code coordinates and this translation is not publicly exposed).
             this->_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1));
             // Scale the object size and store it
    -        Vec3d size = modobj_bbox.size();
    -        this->size = Point3::new_scale(size(0), size(1), size(2));
    +        this->size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast();
         }
         
         this->reload_model_instances();
    diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp
    index bebff6c432..1bc38502b5 100644
    --- a/xs/src/libslic3r/Slicing.cpp
    +++ b/xs/src/libslic3r/Slicing.cpp
    @@ -561,15 +561,15 @@ int generate_layer_height_texture(
     	void *data, int rows, int cols, bool level_of_detail_2nd_level)
     {
     // https://github.com/aschn/gnuplot-colorbrewer
    -    std::vector palette_raw;
    -    palette_raw.push_back(Point3(0x01A, 0x098, 0x050));
    -    palette_raw.push_back(Point3(0x066, 0x0BD, 0x063));
    -    palette_raw.push_back(Point3(0x0A6, 0x0D9, 0x06A));
    -    palette_raw.push_back(Point3(0x0D9, 0x0F1, 0x0EB));
    -    palette_raw.push_back(Point3(0x0FE, 0x0E6, 0x0EB));
    -    palette_raw.push_back(Point3(0x0FD, 0x0AE, 0x061));
    -    palette_raw.push_back(Point3(0x0F4, 0x06D, 0x043));
    -    palette_raw.push_back(Point3(0x0D7, 0x030, 0x027));
    +    std::vector palette_raw;
    +    palette_raw.push_back(Vec3crd(0x01A, 0x098, 0x050));
    +    palette_raw.push_back(Vec3crd(0x066, 0x0BD, 0x063));
    +    palette_raw.push_back(Vec3crd(0x0A6, 0x0D9, 0x06A));
    +    palette_raw.push_back(Vec3crd(0x0D9, 0x0F1, 0x0EB));
    +    palette_raw.push_back(Vec3crd(0x0FE, 0x0E6, 0x0EB));
    +    palette_raw.push_back(Vec3crd(0x0FD, 0x0AE, 0x061));
    +    palette_raw.push_back(Vec3crd(0x0F4, 0x06D, 0x043));
    +    palette_raw.push_back(Vec3crd(0x0D7, 0x030, 0x027));
     
         // Clear the main texture and the 2nd LOD level.
     //	memset(data, 0, rows * cols * (level_of_detail_2nd_level ? 5 : 4));
    @@ -600,8 +600,8 @@ int generate_layer_height_texture(
                 int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf)));
                 int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1);
     			coordf_t t = idxf - coordf_t(idx1);
    -            const Point3 &color1 = palette_raw[idx1];
    -            const Point3 &color2 = palette_raw[idx2];
    +            const Vec3crd &color1 = palette_raw[idx1];
    +            const Vec3crd &color2 = palette_raw[idx2];
                 coordf_t z = cell_to_z * coordf_t(cell);
     			assert(z >= lo && z <= hi);
                 // Intensity profile to visualize the layers.
    @@ -636,8 +636,8 @@ int generate_layer_height_texture(
                     int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf)));
                     int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1);
         			coordf_t t = idxf - coordf_t(idx1);
    -                const Point3 &color1 = palette_raw[idx1];
    -                const Point3 &color2 = palette_raw[idx2];
    +                const Vec3crd &color1 = palette_raw[idx1];
    +                const Vec3crd &color2 = palette_raw[idx2];
                     // Color mapping from layer height to RGB.
                     Vec3d color(
                         lerp(coordf_t(color1(0)), coordf_t(color2(0)), t), 
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index b1d88f2c61..782c50cf5c 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -36,7 +36,7 @@ TriangleMesh::TriangleMesh()
         stl_initialize(&this->stl);
     }
     
    -TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets )
    +TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets )
         : repaired(false)
     {
         stl_initialize(&this->stl);
    @@ -1539,14 +1539,14 @@ TriangleMesh make_cube(double x, double y, double z) {
             Vec3d(0, y, 0), Vec3d(x, y, z), Vec3d(0, y, z), 
             Vec3d(0, 0, z), Vec3d(x, 0, z) 
         };
    -    Point3 fv[12] = { 
    -        Point3(0, 1, 2), Point3(0, 2, 3), Point3(4, 5, 6), 
    -        Point3(4, 6, 7), Point3(0, 4, 7), Point3(0, 7, 1), 
    -        Point3(1, 7, 6), Point3(1, 6, 2), Point3(2, 6, 5), 
    -        Point3(2, 5, 3), Point3(4, 0, 3), Point3(4, 3, 5) 
    +    Vec3crd fv[12] = { 
    +        Vec3crd(0, 1, 2), Vec3crd(0, 2, 3), Vec3crd(4, 5, 6), 
    +        Vec3crd(4, 6, 7), Vec3crd(0, 4, 7), Vec3crd(0, 7, 1), 
    +        Vec3crd(1, 7, 6), Vec3crd(1, 6, 2), Vec3crd(2, 6, 5), 
    +        Vec3crd(2, 5, 3), Vec3crd(4, 0, 3), Vec3crd(4, 3, 5) 
         };
     
    -    std::vector facets(&fv[0], &fv[0]+12);
    +    std::vector facets(&fv[0], &fv[0]+12);
         Pointf3s vertices(&pv[0], &pv[0]+8);
     
         TriangleMesh mesh(vertices ,facets);
    @@ -1558,7 +1558,7 @@ TriangleMesh make_cube(double x, double y, double z) {
     // Default is 360 sides, angle fa is in radians.
     TriangleMesh make_cylinder(double r, double h, double fa) {
         Pointf3s vertices;
    -    std::vector facets;
    +    std::vector facets;
     
         // 2 special vertices, top and bottom center, rest are relative to this
         vertices.emplace_back(Vec3d(0.0, 0.0, 0.0));
    @@ -1579,16 +1579,16 @@ TriangleMesh make_cylinder(double r, double h, double fa) {
             vertices.emplace_back(Vec3d(p(0), p(1), 0.));
             vertices.emplace_back(Vec3d(p(0), p(1), h));
             id = vertices.size() - 1;
    -        facets.emplace_back(Point3( 0, id - 1, id - 3)); // top
    -        facets.emplace_back(Point3(id,      1, id - 2)); // bottom
    -        facets.emplace_back(Point3(id, id - 2, id - 3)); // upper-right of side
    -        facets.emplace_back(Point3(id, id - 3, id - 1)); // bottom-left of side
    +        facets.emplace_back(Vec3crd( 0, id - 1, id - 3)); // top
    +        facets.emplace_back(Vec3crd(id,      1, id - 2)); // bottom
    +        facets.emplace_back(Vec3crd(id, id - 2, id - 3)); // upper-right of side
    +        facets.emplace_back(Vec3crd(id, id - 3, id - 1)); // bottom-left of side
         }
         // Connect the last set of vertices with the first.
    -    facets.emplace_back(Point3( 2, 0, id - 1));
    -    facets.emplace_back(Point3( 1, 3,     id));
    -    facets.emplace_back(Point3(id, 3,      2));
    -    facets.emplace_back(Point3(id, 2, id - 1));
    +    facets.emplace_back(Vec3crd( 2, 0, id - 1));
    +    facets.emplace_back(Vec3crd( 1, 3,     id));
    +    facets.emplace_back(Vec3crd(id, 3,      2));
    +    facets.emplace_back(Vec3crd(id, 2, id - 1));
         
         TriangleMesh mesh(vertices, facets);
         return mesh;
    @@ -1599,7 +1599,7 @@ TriangleMesh make_cylinder(double r, double h, double fa) {
     // Default angle is 1 degree.
     TriangleMesh make_sphere(double rho, double fa) {
         Pointf3s vertices;
    -    std::vector facets;
    +    std::vector facets;
     
         // Algorithm: 
         // Add points one-by-one to the sphere grid and form facets using relative coordinates.
    @@ -1627,7 +1627,7 @@ TriangleMesh make_sphere(double rho, double fa) {
             const double r = sqrt(abs(rho*rho - z*z));
             Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
             vertices.emplace_back(Vec3d(b(0), b(1), z));
    -        facets.emplace_back((i == 0) ? Point3(1, 0, ring.size()) : Point3(id, 0, id - 1));
    +        facets.emplace_back((i == 0) ? Vec3crd(1, 0, ring.size()) : Vec3crd(id, 0, id - 1));
             ++ id;
         }
     
    @@ -1641,11 +1641,11 @@ TriangleMesh make_sphere(double rho, double fa) {
                 vertices.emplace_back(Vec3d(b(0), b(1), z));
                 if (i == 0) {
                     // wrap around
    -                facets.emplace_back(Point3(id + ring.size() - 1 , id, id - 1)); 
    -                facets.emplace_back(Point3(id, id - ring.size(),  id - 1)); 
    +                facets.emplace_back(Vec3crd(id + ring.size() - 1 , id, id - 1)); 
    +                facets.emplace_back(Vec3crd(id, id - ring.size(),  id - 1)); 
                 } else {
    -                facets.emplace_back(Point3(id , id - ring.size(), (id - 1) - ring.size())); 
    -                facets.emplace_back(Point3(id, id - 1 - ring.size() ,  id - 1)); 
    +                facets.emplace_back(Vec3crd(id , id - ring.size(), (id - 1) - ring.size())); 
    +                facets.emplace_back(Vec3crd(id, id - 1 - ring.size() ,  id - 1)); 
                 }
                 id++;
             } 
    @@ -1658,9 +1658,9 @@ TriangleMesh make_sphere(double rho, double fa) {
         for (size_t i = 0; i < ring.size(); i++) {
             if (i == 0) {
                 // third vertex is on the other side of the ring.
    -            facets.emplace_back(Point3(id, id - ring.size(),  id - 1));
    +            facets.emplace_back(Vec3crd(id, id - ring.size(),  id - 1));
             } else {
    -            facets.emplace_back(Point3(id, id - ring.size() + i,  id - ring.size() + (i - 1)));
    +            facets.emplace_back(Vec3crd(id, id - ring.size() + i,  id - ring.size() + (i - 1)));
             }
         }
         id++;
    diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
    index b04d09f32f..1b1acd9e60 100644
    --- a/xs/src/libslic3r/TriangleMesh.hpp
    +++ b/xs/src/libslic3r/TriangleMesh.hpp
    @@ -21,7 +21,7 @@ class TriangleMesh
     {
     public:
         TriangleMesh();
    -    TriangleMesh(const Pointf3s &points, const std::vector &facets);
    +    TriangleMesh(const Pointf3s &points, const std::vector &facets);
         TriangleMesh(const TriangleMesh &other);
         TriangleMesh(TriangleMesh &&other);
         TriangleMesh& operator=(const TriangleMesh &other);
    diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp
    index 1ad31c8f82..ecde84a00b 100644
    --- a/xs/src/perlglue.cpp
    +++ b/xs/src/perlglue.cpp
    @@ -41,7 +41,6 @@ REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf");
     REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3");
     REGISTER_CLASS(BridgeDetector, "BridgeDetector");
     REGISTER_CLASS(Point, "Point");
    -REGISTER_CLASS(Point3, "Point3");
     __REGISTER_CLASS(Vec2d, "Pointf");
     __REGISTER_CLASS(Vec3d, "Pointf3");
     REGISTER_CLASS(DynamicPrintConfig, "Config");
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index e516ffbb5b..93b9a27ab4 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -661,7 +661,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
             // We'll now create the box with jagged edge. y-coordinates of the pre-generated model are shifted so that the front
             // edge has y=0 and centerline of the back edge has y=depth:
             Pointf3s points;
    -        std::vector facets;
    +        std::vector facets;
             float out_points_idx[][3] = {{0, -depth, 0}, {0, 0, 0}, {38.453, 0, 0}, {61.547, 0, 0}, {100, 0, 0}, {100, -depth, 0}, {55.7735, -10, 0}, {44.2265, 10, 0},
                                          {38.453, 0, 1}, {0, 0, 1}, {0, -depth, 1}, {100, -depth, 1}, {100, 0, 1}, {61.547, 0, 1}, {55.7735, -10, 1}, {44.2265, 10, 1}};
             int out_facets_idx[][3] = {{0, 1, 2}, {3, 4, 5}, {6, 5, 0}, {3, 5, 6}, {6, 2, 7}, {6, 0, 2}, {8, 9, 10}, {11, 12, 13}, {10, 11, 14}, {14, 11, 13}, {15, 8, 14},
    @@ -670,7 +670,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
             for (int i=0;i<16;++i)
                 points.push_back(Vec3d(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2]));
             for (int i=0;i<28;++i)
    -            facets.push_back(Point3(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2]));
    +            facets.push_back(Vec3crd(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2]));
             TriangleMesh tooth_mesh(points, facets);
     
             // We have the mesh ready. It has one tooth and width of min_width. We will now append several of these together until we are close to
    @@ -1442,7 +1442,7 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines,
     #undef BOTTOM
     }
     
    -static void point_to_indexed_vertex_array(const Point3& point,
    +static void point_to_indexed_vertex_array(const Vec3crd& point,
         double width,
         double height,
         GLIndexedVertexArray& volume)
    @@ -1512,7 +1512,7 @@ void _3DScene::thick_lines_to_verts(const Lines3& lines,
         thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, volume.indexed_vertex_array);
     }
     
    -static void thick_point_to_verts(const Point3& point,
    +static void thick_point_to_verts(const Vec3crd& point,
         double width,
         double height,
         GLVolume& volume)
    @@ -1618,7 +1618,7 @@ void _3DScene::polyline3_to_verts(const Polyline3& polyline, double width, doubl
         thick_lines_to_verts(lines, widths, heights, false, volume);
     }
     
    -void _3DScene::point3_to_verts(const Point3& point, double width, double height, GLVolume& volume)
    +void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume)
     {
         thick_point_to_verts(point, width, height, volume);
     }
    diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
    index 3fbfae1697..7ae6756271 100644
    --- a/xs/src/slic3r/GUI/3DScene.hpp
    +++ b/xs/src/slic3r/GUI/3DScene.hpp
    @@ -553,7 +553,7 @@ public:
         static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
         static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
         static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
    -    static void point3_to_verts(const Point3& point, double width, double height, GLVolume& volume);
    +    static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume);
     };
     
     }
    diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp
    index 209f1c2986..beefc62494 100644
    --- a/xs/xsp/Point.xsp
    +++ b/xs/xsp/Point.xsp
    @@ -77,20 +77,6 @@ Point::coincides_with(point_sv)
     
     };
     
    -%name{Slic3r::Point3} class Point3 {
    -    Point3(int _x = 0, int _y = 0, int _z = 0);
    -    ~Point3();
    -    Clone clone()
    -        %code{% RETVAL = THIS; %};
    -    int x()
    -        %code{% RETVAL = (*THIS)(0); %};
    -    int y()
    -        %code{% RETVAL = (*THIS)(1); %};
    -    int z()
    -        %code{% RETVAL = (*THIS)(2); %};
    -    std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld,%ld", (*THIS)(0), (*THIS)(1), (*THIS)(2)); RETVAL = buf; %};
    -};
    -
     %name{Slic3r::Pointf} class Vec2d {
         Vec2d(double _x = 0, double _y = 0);
         ~Vec2d();
    diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp
    index 9005e6e90e..ee9779f683 100644
    --- a/xs/xsp/Print.xsp
    +++ b/xs/xsp/Print.xsp
    @@ -63,8 +63,6 @@ _constant()
             %code%{ RETVAL = THIS->layer_height_ranges; %};
         std::vector layer_height_profile()
             %code%{ RETVAL = THIS->layer_height_profile; %};
    -    Ref size()
    -        %code%{ RETVAL = &THIS->size; %};
         Clone bounding_box();
         
         Points _shifted_copies()
    
    From 5cd4597d387264bb7739a882936f7bd150830495 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Wed, 22 Aug 2018 08:54:07 +0200
    Subject: [PATCH 160/185] Fix for last commit
    
    ---
     xs/src/slic3r/GUI/wxExtensions.cpp | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index 3ba0d38863..fe1018076d 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -767,7 +767,9 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
         m_min_value(minValue), m_max_value(maxValue),
         m_style(style)
     {
    +#ifndef __WXOSX__ // SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
         SetDoubleBuffered(true);
    +#endif //__WXOSX__
     
         if (m_style != wxSL_HORIZONTAL && m_style != wxSL_VERTICAL)
             m_style = wxSL_HORIZONTAL;
    
    From a5119a41a5a82d611d9e7f80a3edbc58fc8de193 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Wed, 22 Aug 2018 10:44:11 +0200
    Subject: [PATCH 161/185] Added overriding of the DoGetBestSize() to correct
     control sizing on OSX and Linux/GTK
    
    ---
     xs/src/slic3r/GUI/wxExtensions.cpp | 16 +++++++++++-----
     xs/src/slic3r/GUI/wxExtensions.hpp |  2 +-
     2 files changed, 12 insertions(+), 6 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index fe1018076d..9e229a0b0f 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -765,15 +765,12 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
         wxControl(parent, id, pos, size, wxWANTS_CHARS | wxBORDER_NONE),
         m_lower_value(lowerValue), m_higher_value (higherValue), 
         m_min_value(minValue), m_max_value(maxValue),
    -    m_style(style)
    +    m_style(style == wxSL_HORIZONTAL || style == wxSL_VERTICAL ? style: wxSL_HORIZONTAL)
     {
     #ifndef __WXOSX__ // SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
         SetDoubleBuffered(true);
     #endif //__WXOSX__
     
    -    if (m_style != wxSL_HORIZONTAL && m_style != wxSL_VERTICAL)
    -        m_style = wxSL_HORIZONTAL;
    -
         m_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("right_half_circle.png")) :
                                                              Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG);
         m_thumb_lower  = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) :
    @@ -789,7 +786,7 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
         Bind(wxEVT_LEFT_UP,     &PrusaDoubleSlider::OnLeftUp,   this);
         Bind(wxEVT_MOUSEWHEEL,  &PrusaDoubleSlider::OnWheel,    this);
         Bind(wxEVT_ENTER_WINDOW,&PrusaDoubleSlider::OnEnterWin, this);
    -    Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeaveWin,   this);
    +    Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeaveWin, this);
         Bind(wxEVT_KEY_DOWN,    &PrusaDoubleSlider::OnKeyDown,  this);
     
         // control's view variables
    @@ -807,6 +804,15 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
         segm_pens = { &DARK_ORANGE_PEN, &ORANGE_PEN, &LIGHT_ORANGE_PEN };
     }
     
    +wxSize PrusaDoubleSlider::DoGetBestSize() const
    +{
    +    wxSize size = wxControl::DoGetBestSize();
    +    if (size.x > 1 && size.y > 1)
    +        return size;
    +    const int new_size = is_horizontal() ? 80 : 120;
    +    return wxSize(new_size, new_size);
    +}
    +
     void PrusaDoubleSlider::SetLowerValue(const int lower_val)
     {
         m_lower_value = lower_val;
    diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
    index 59d27e448b..3625835474 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.hpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.hpp
    @@ -527,7 +527,7 @@ public:
         void SetHigherValue(int higher_val);
         void SetKoefForLabels(float koef){ m_label_koef = koef;}
     
    -    wxSize DoGetBestSize(){ return wxDefaultSize; }
    +    wxSize DoGetBestSize() const override;
     
         void OnPaint(wxPaintEvent& ){ render();}
         void OnLeftDown(wxMouseEvent& event);
    
    From 43f8f10445d46c582904eca8749116b115e0f39a Mon Sep 17 00:00:00 2001
    From: Martin Loidl 
    Date: Tue, 21 Aug 2018 22:56:29 +0200
    Subject: [PATCH 162/185] fixed timestamp for duet upload * Added missing time=
    
    ---
     xs/src/slic3r/Utils/Duet.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/Utils/Duet.cpp b/xs/src/slic3r/Utils/Duet.cpp
    index 517f024864..865d2b4187 100644
    --- a/xs/src/slic3r/Utils/Duet.cpp
    +++ b/xs/src/slic3r/Utils/Duet.cpp
    @@ -230,7 +230,7 @@ std::string Duet::timestamp_str() const
     	auto tm = *std::localtime(&t);
     
     	char buffer[BUFFER_SIZE];
    -	std::strftime(buffer, BUFFER_SIZE, "%Y-%d-%mT%H:%M:%S", &tm);
    +	std::strftime(buffer, BUFFER_SIZE, "time=%Y-%d-%mT%H:%M:%S", &tm);
     
     	return std::string(buffer);
     }
    
    From 0924bedd281227193683af69a7b5f18338ea42d0 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Wed, 22 Aug 2018 11:22:07 +0200
    Subject: [PATCH 163/185] Enhanced behaviour of scale 3D gizmo
    
    ---
     xs/src/libslic3r/Line.hpp     |   4 +
     xs/src/slic3r/GUI/GLGizmo.cpp | 162 +++++++++++++++++++++++-----------
     xs/src/slic3r/GUI/GLGizmo.hpp |   3 +
     3 files changed, 117 insertions(+), 52 deletions(-)
    
    diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp
    index 4826017ab5..7dd1a83d55 100644
    --- a/xs/src/libslic3r/Line.hpp
    +++ b/xs/src/libslic3r/Line.hpp
    @@ -89,6 +89,10 @@ class Linef3
         explicit Linef3(Pointf3 _a, Pointf3 _b): a(_a), b(_b) {};
         Pointf3 intersect_plane(double z) const;
         void scale(double factor);
    +
    +    double length() const { return this->a.distance_to(this->b); }
    +    Vectorf3 vector() const { return Vectorf3(this->b.x - this->a.x, this->b.y - this->a.y, this->b.z - this->a.z); }
    +    Vectorf3 unit_vector() const { return (length() == 0.0) ? Vectorf3(0.0, 0.0, 0.0) : normalize(vector()); }
     };
     
     } // namespace Slic3r
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 3af5dfc9e5..978b35495c 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -819,7 +819,10 @@ bool GLGizmoScale3D::on_init()
     void GLGizmoScale3D::on_start_dragging()
     {
         if (m_hover_id != -1)
    +    {
             m_starting_drag_position = m_grabbers[m_hover_id].center;
    +        m_starting_center = m_box.center();
    +    }
     }
     
     void GLGizmoScale3D::on_update(const Linef3& mouse_ray)
    @@ -991,76 +994,131 @@ Linef3 transform(const Linef3& line, const Eigen::Transform m = Eigen::Transform::Identity();
    -    m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)center.z));
    +    double ratio = calc_ratio(1, mouse_ray, m_starting_center);
     
    -    Pointf mouse_pos = transform(mouse_ray, m).intersect_plane(0.0);
    -
    -    coordf_t orig_len = length(m_starting_drag_position - center);
    -    coordf_t new_len = length(mouse_pos);
    -    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    -
    -    m_scale_x = m_starting_scale_x * (float)ratio;
    +    if (ratio > 0.0)
    +        m_scale_x = m_starting_scale_x * (float)ratio;
     }
     
     void GLGizmoScale3D::do_scale_y(const Linef3& mouse_ray)
     {
    -    // calculates the intersection of the mouse ray with the plane parallel to plane XY and passing through the box center
    -    const Pointf3& center = m_box.center();
    -    Eigen::Transform m = Eigen::Transform::Identity();
    -    m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)center.z));
    +    double ratio = calc_ratio(2, mouse_ray, m_starting_center);
     
    -    Pointf mouse_pos = transform(mouse_ray, m).intersect_plane(0.0);
    -
    -    coordf_t orig_len = length(m_starting_drag_position - center);
    -    coordf_t new_len = length(mouse_pos);
    -    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    -
    -    m_scale_x = m_starting_scale_y * (float)ratio;
    -//    m_scale_y = m_starting_scale_y * (float)ratio;
    +    if (ratio > 0.0)
    +        m_scale_x = m_starting_scale_y * (float)ratio;
    +//        m_scale_y = m_starting_scale_y * (float)ratio;
     }
     
     void GLGizmoScale3D::do_scale_z(const Linef3& mouse_ray)
     {
    -    // calculates the intersection of the mouse ray with the plane parallel to plane XZ and passing through the box center
    -    const Pointf3& center = m_box.center();
    -    Eigen::Transform m = Eigen::Transform::Identity();
    -    m.rotate(Eigen::AngleAxisf(0.5f * (float)PI, Eigen::Vector3f::UnitX()));
    -    m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)center.z));
    +    double ratio = calc_ratio(1, mouse_ray, m_starting_center);
     
    -    Pointf mouse_pos = transform(mouse_ray, m).intersect_plane(0.0);
    -
    -    coordf_t orig_len = length(m_starting_drag_position - center);
    -    coordf_t new_len = length(mouse_pos);
    -    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    -
    -    m_scale_x = m_starting_scale_z * (float)ratio;
    -//    m_scale_z = m_starting_scale_z * (float)ratio;
    -
    -    if (m_scale_x > 10.0)
    -    {
    -        int a = 0;
    -    }
    +    if (ratio > 0.0)
    +        m_scale_x = m_starting_scale_z * (float)ratio;
    +//        m_scale_z = m_starting_scale_z * (float)ratio;
     }
     
     void GLGizmoScale3D::do_scale_uniform(const Linef3& mouse_ray)
     {
    -    // calculates the intersection of the mouse ray with the plane parallel to plane XY and passing through the box min point
    -    const Pointf3& center = m_box.center();
    -    Eigen::Transform m = Eigen::Transform::Identity();
    -    m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)m_box.min.z));
    +    Pointf3 center = m_starting_center;
    +    center.z = m_box.min.z;
    +    double ratio = calc_ratio(0, mouse_ray, center);
     
    -    Pointf mouse_pos = transform(mouse_ray, m).intersect_plane(0.0);
    +    if (ratio > 0.0)
    +    {
    +        m_scale_x = m_starting_scale_x * (float)ratio;
    +        m_scale_y = m_starting_scale_y * (float)ratio;
    +        m_scale_z = m_starting_scale_z * (float)ratio;
    +    }
    +}
     
    -    coordf_t orig_len = length(m_starting_drag_position - center);
    -    coordf_t new_len = length(Vectorf3(mouse_pos.x, mouse_pos.y, m_box.min.z - center.z));
    -    coordf_t ratio = (orig_len != 0.0) ? new_len / orig_len : 1.0;
    +double GLGizmoScale3D::calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Pointf3& center) const
    +{
    +    double ratio = 0.0;
     
    -    m_scale_x = m_starting_scale_y * (float)ratio;
    -    m_scale_y = m_starting_scale_y * (float)ratio;
    -    m_scale_z = m_starting_scale_z * (float)ratio;
    +    Vectorf3 starting_vec = m_starting_drag_position - center;
    +    double len_starting_vec = length(starting_vec);
    +    if (len_starting_vec == 0.0)
    +        return ratio;
    +
    +    Vectorf3 starting_vec_dir = normalize(starting_vec);
    +    Vectorf3 mouse_dir = mouse_ray.unit_vector();
    +    unsigned int plane_id = preferred_plane_id;
    +
    +    // 1st try to see if the mouse direction is close enough to the preferred plane normal
    +    double dot_to_normal = 0.0;
    +    switch (plane_id)
    +    {
    +    case 0:
    +    {
    +        dot_to_normal = std::abs(dot(mouse_dir, Vectorf3(0.0, 0.0, 1.0)));
    +        break;
    +    }
    +    case 1:
    +    {
    +        dot_to_normal = std::abs(dot(mouse_dir, Vectorf3(0.0, -1.0, 0.0)));
    +        break;
    +    }
    +    case 2:
    +    {
    +        dot_to_normal = std::abs(dot(mouse_dir, Vectorf3(1.0, 0.0, 0.0)));
    +        break;
    +    }
    +    }
    +
    +    if (dot_to_normal < 0.1)
    +    {
    +        // if not, select the plane who's normal is closest to the mouse direction
    +
    +        typedef std::map ProjsMap;
    +        ProjsMap projs_map;
    +
    +        projs_map.insert(ProjsMap::value_type(std::abs(dot(mouse_dir, Vectorf3(0.0, 0.0, 1.0))), 0));  // plane xy
    +        projs_map.insert(ProjsMap::value_type(std::abs(dot(mouse_dir, Vectorf3(0.0, -1.0, 0.0))), 1)); // plane xz
    +        projs_map.insert(ProjsMap::value_type(std::abs(dot(mouse_dir, Vectorf3(1.0, 0.0, 0.0))), 2));  // plane yz
    +        plane_id = projs_map.rbegin()->second;
    +    }
    +
    +    switch (plane_id)
    +    {
    +    case 0:
    +    {
    +        // calculates the intersection of the mouse ray with the plane parallel to plane XY and passing through the given center
    +        Eigen::Transform m = Eigen::Transform::Identity();
    +        m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)center.z));
    +        Pointf mouse_pos_2d = transform(mouse_ray, m).intersect_plane(0.0);
    +
    +        // ratio is given by the projection of the calculated intersection on the starting vector divided by the starting vector length
    +        ratio = dot(Vectorf3(mouse_pos_2d.x, mouse_pos_2d.y, 0.0), starting_vec_dir) / len_starting_vec;
    +        break;
    +    }
    +    case 1:
    +    {
    +        // calculates the intersection of the mouse ray with the plane parallel to plane XZ and passing through the given center
    +        Eigen::Transform m = Eigen::Transform::Identity();
    +        m.rotate(Eigen::AngleAxisf(-0.5f * (float)PI, Eigen::Vector3f::UnitX()));
    +        m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)center.z));
    +        Pointf mouse_pos_2d = transform(mouse_ray, m).intersect_plane(0.0);
    +
    +        // ratio is given by the projection of the calculated intersection on the starting vector divided by the starting vector length
    +        ratio = dot(Vectorf3(mouse_pos_2d.x, 0.0, mouse_pos_2d.y), starting_vec_dir) / len_starting_vec;
    +        break;
    +    }
    +    case 2:
    +    {
    +        // calculates the intersection of the mouse ray with the plane parallel to plane YZ and passing through the given center
    +        Eigen::Transform m = Eigen::Transform::Identity();
    +        m.rotate(Eigen::AngleAxisf(-0.5f * (float)PI, Eigen::Vector3f::UnitY()));
    +        m.translate(Eigen::Vector3f(-(float)center.x, -(float)center.y, -(float)center.z));
    +        Pointf mouse_pos_2d = transform(mouse_ray, m).intersect_plane(0.0);
    +
    +        // ratio is given by the projection of the calculated intersection on the starting vector divided by the starting vector length
    +        ratio = dot(Vectorf3(0.0, mouse_pos_2d.y, -mouse_pos_2d.x), starting_vec_dir) / len_starting_vec;
    +        break;
    +    }
    +    }
    +
    +    return ratio;
     }
     
     } // namespace GUI
    diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp
    index 77b03fa863..b584286b41 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.hpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.hpp
    @@ -265,6 +265,7 @@ class GLGizmoScale3D : public GLGizmoBase
         float m_starting_scale_z;
     
         Pointf3 m_starting_drag_position;
    +    Pointf3 m_starting_center;
     
     public:
         GLGizmoScale3D();
    @@ -300,6 +301,8 @@ private:
         void do_scale_y(const Linef3& mouse_ray);
         void do_scale_z(const Linef3& mouse_ray);
         void do_scale_uniform(const Linef3& mouse_ray);
    +
    +    double calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Pointf3& center) const;
     };
     
     } // namespace GUI
    
    From d2282c4bf9ff834dac7534a5f87e51cdf7a6f6a5 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Wed, 22 Aug 2018 14:24:30 +0200
    Subject: [PATCH 164/185] Refactored code
    
    ---
     xs/src/slic3r/GUI/GUI.cpp          |   6 +-
     xs/src/slic3r/GUI/wxExtensions.cpp | 134 ++++++++++++++++-------------
     xs/src/slic3r/GUI/wxExtensions.hpp |  33 ++++---
     3 files changed, 97 insertions(+), 76 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
    index 67be479879..396c162579 100644
    --- a/xs/src/slic3r/GUI/GUI.cpp
    +++ b/xs/src/slic3r/GUI/GUI.cpp
    @@ -1053,11 +1053,13 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
     	add_objects_list(parent, sizer);
     
         // experiment with slider
    -    PrusaDoubleSlider* slider_h = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 200, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL);
    +    PrusaDoubleSlider* slider_h = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 200, wxDefaultPosition, 
    +                                                        wxSize(60, wxDefaultSize.y), wxSL_HORIZONTAL);
         sizer->AddSpacer(5);
         sizer->Add(slider_h, 0, wxEXPAND | wxLEFT, 20);
         sizer->AddSpacer(5);
    -    PrusaDoubleSlider* slider_v = new PrusaDoubleSlider(parent, wxID_ANY, 5, 7, 0, 10, wxDefaultPosition, wxSize(wxDefaultSize.x ,150), wxSL_VERTICAL);
    +    PrusaDoubleSlider* slider_v = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 200, wxDefaultPosition, 
    +                                                        wxSize(wxDefaultSize.x ,250), wxSL_VERTICAL);
         slider_v->SetKoefForLabels(0.15);
         sizer->AddSpacer(5);
         sizer->Add(slider_v, 0, wxLEFT, 20);
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index 9e229a0b0f..92f9e1e37e 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -775,8 +775,7 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
                                                              Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG);
         m_thumb_lower  = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) :
                                                              Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG);
    -    
    -    
    +    m_thumb_size = m_thumb_lower.GetSize();
         m_selection = ssUndef;
     
         // slider events
    @@ -806,7 +805,7 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
     
     wxSize PrusaDoubleSlider::DoGetBestSize() const
     {
    -    wxSize size = wxControl::DoGetBestSize();
    +    const wxSize size = wxControl::DoGetBestSize();
         if (size.x > 1 && size.y > 1)
             return size;
         const int new_size = is_horizontal() ? 80 : 120;
    @@ -904,85 +903,98 @@ void PrusaDoubleSlider::render()
         draw_scroll_line(dc, lower_pos, higher_pos);
     
         //lower slider:
    -    wxPoint pos = is_horizontal() ? wxPoint(lower_pos, height*0.5) : wxPoint(0.5*width, lower_pos);
    -    draw_lower_thumb(dc, pos);
    +    draw_thumb(dc, lower_pos, ssLower);
     
         //higher slider:
    -    pos = is_horizontal() ? wxPoint(higher_pos, height*0.5) : wxPoint(0.5*width, higher_pos);
    -    draw_higher_thumb(dc, pos);
    +    draw_thumb(dc, higher_pos, ssHigher);
     }
     
    -void PrusaDoubleSlider::draw_info_line(wxDC& dc, const wxPoint& pos, const wxSize& thumb_size, const SelectedSlider selection)
    +void PrusaDoubleSlider::draw_info_line(wxDC& dc, const wxPoint& pos, const SelectedSlider selection) const
     {
         if (m_selection == selection) {
             dc.SetPen(DARK_ORANGE_PEN);
    -        is_horizontal() ? dc.DrawLine(pos.x, pos.y - thumb_size.y, pos.x, pos.y + thumb_size.y):
    -                          dc.DrawLine(pos.x - thumb_size.x, pos.y-1, pos.x + thumb_size.x, pos.y-1);
    +        is_horizontal() ? dc.DrawLine(pos.x, pos.y - m_thumb_size.y, pos.x, pos.y + m_thumb_size.y):
    +                          dc.DrawLine(pos.x - m_thumb_size.x, pos.y-1, pos.x + m_thumb_size.x, pos.y-1);
         }
     }
     
    -wxString PrusaDoubleSlider::get_label(const int value)
    +wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const
     {
    +    const int value = selection == ssLower ? m_lower_value : m_higher_value;
         return m_label_koef == 1.0 ? wxString::Format("%d", value) :
                                      wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None);
    +
     }
     
    -void PrusaDoubleSlider::draw_lower_thumb(wxDC& dc, const wxPoint& pos)
    +void PrusaDoubleSlider::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const
     {
    +    if (selection == ssUndef) return;
    +    wxCoord text_width, text_height;
    +    const wxString label = get_label(selection);
    +    dc.GetTextExtent(label, &text_width, &text_height);
    +    wxPoint text_pos;
    +    if (selection ==ssLower)
    +        text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x) :
    +                           wxPoint(pos.x + m_thumb_size.x+1, pos.y - 0.5*text_height - 1);
    +    else
    +        text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x - text_height) :
    +                    wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5*text_height + 1);
    +    dc.DrawText(label, text_pos);
    +}
    +
    +void PrusaDoubleSlider::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection)
    +{
    +    wxCoord x_draw, y_draw;
    +    if (selection == ssLower) {
    +        if (is_horizontal()) {
    +            x_draw = pos.x - m_thumb_size.x;
    +            y_draw = pos.y - int(0.5*m_thumb_size.y);
    +        }
    +        else {
    +            x_draw = pos.x - int(0.5*m_thumb_size.x);
    +            y_draw = pos.y;
    +        }
    +    }
    +    else{
    +        if (is_horizontal()) {
    +            x_draw = pos.x;
    +            y_draw = pos.y - int(0.5*m_thumb_size.y);
    +        }
    +        else {
    +            x_draw = pos.x - int(0.5*m_thumb_size.x);
    +            y_draw = pos.y - m_thumb_size.y;
    +        }
    +    }
    +    dc.DrawBitmap(selection == ssLower ? m_thumb_lower : m_thumb_higher, x_draw, y_draw);
    +
    +    // Update thumb rect
    +    update_thumb_rect(x_draw, y_draw, selection);
    +}
    +
    +void PrusaDoubleSlider::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection)
    +{
    +    //calculate thumb position on slider line
    +    int width, height;
    +    GetSize(&width, &height);
    +    const wxPoint pos = is_horizontal() ? wxPoint(pos_coord, height*0.5) : wxPoint(0.5*width, pos_coord);
    +
         // Draw thumb
    -    wxCoord x_draw, y_draw;
    -    const wxSize thumb_size = m_thumb_lower.GetSize();
    -    if (is_horizontal()) {
    -        x_draw = pos.x - thumb_size.x;
    -        y_draw = pos.y - int(0.5*thumb_size.y);
    -    }
    -    else {
    -        x_draw = pos.x - int(0.5*thumb_size.x);
    -        y_draw = pos.y;
    -    }
    -    dc.DrawBitmap(m_thumb_lower, x_draw, y_draw);
    +    draw_thumb_item(dc, pos, selection);
    +
         // Draw info_line
    -    draw_info_line(dc, pos, thumb_size, ssLower);
    +    draw_info_line(dc, pos, selection);
     
         // Draw thumb text
    -    wxCoord text_width, text_height;
    -    wxString label = get_label(m_lower_value);
    -    dc.GetTextExtent(label, &text_width, &text_height);
    -    wxPoint text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + thumb_size.x) :
    -                                         wxPoint(pos.x + thumb_size.x+1, pos.y - 0.5*text_height - 1);
    -    dc.DrawText(label, text_pos);
    -
    -    // Update thumb rect
    -    m_rect_lower_thumb = wxRect(x_draw, y_draw, thumb_size.x, thumb_size.y);
    +    draw_thumb_text(dc, pos, selection);
     }
     
    -
    -void PrusaDoubleSlider::draw_higher_thumb(wxDC& dc, const wxPoint& pos)
    +void PrusaDoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection)
     {
    -    wxCoord x_draw, y_draw;
    -    const wxSize thumb_size = m_thumb_higher.GetSize();
    -    if (is_horizontal()) {
    -        x_draw = pos.x;
    -        y_draw = pos.y - int(0.5*thumb_size.y);
    -    }
    -    else {
    -        x_draw = pos.x - int(0.5*thumb_size.x);
    -        y_draw = pos.y - thumb_size.y;
    -    }
    -    dc.DrawBitmap(m_thumb_higher, x_draw, y_draw);
    -    // Draw info_line
    -    draw_info_line(dc, pos, thumb_size, ssHigher);
    -
    -    // Draw thumb text
    -    wxCoord text_width, text_height;
    -    wxString label = get_label(m_higher_value);
    -    dc.GetTextExtent(label, &text_width, &text_height);
    -    wxPoint text_pos = is_horizontal() ? wxPoint(pos.x - text_width-1, pos.y - thumb_size.x - text_height) :
    -                                         wxPoint(pos.x - text_width - 1 - thumb_size.x, pos.y - 0.5*text_height + 1);
    -    dc.DrawText(label, text_pos);
    -
    -    // Update thumb rect
    -    m_rect_higher_thumb = wxRect(x_draw, y_draw, thumb_size.x, thumb_size.y);
    +    const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y);
    +    if (selection == ssLower)
    +        m_rect_lower_thumb = rect;
    +    else
    +        m_rect_higher_thumb = rect;
     }
     
     int PrusaDoubleSlider::position_to_value(wxDC& dc, const wxCoord x, const wxCoord y)
    @@ -1030,6 +1042,8 @@ void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event)
         wxClientDC dc(this);
         wxPoint pos = event.GetLogicalPosition(dc);
         detect_selected_slider(pos);
    +    Refresh();
    +    Update();
         event.Skip();
     }
     
    @@ -1078,7 +1092,6 @@ void PrusaDoubleSlider::OnMotion(wxMouseEvent& event)
     void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event)
     {
         m_is_left_down = false;
    -    m_selection = ssUndef;
         Refresh();
         Update();
         event.Skip();
    @@ -1099,6 +1112,7 @@ void PrusaDoubleSlider::OnEnterWin(wxMouseEvent& event)
     void PrusaDoubleSlider::OnLeaveWin(wxMouseEvent& event)
     {
         m_is_focused = false;
    +    m_selection = ssUndef;
         OnLeftUp(event);
     }
     
    diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
    index 3625835474..6f000015b7 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.hpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.hpp
    @@ -523,11 +523,10 @@ public:
         int GetHigherValue() {
             return m_higher_value;
         }
    +    wxSize DoGetBestSize() const override;
         void SetLowerValue(int lower_val);
         void SetHigherValue(int higher_val);
    -    void SetKoefForLabels(float koef){ m_label_koef = koef;}
    -
    -    wxSize DoGetBestSize() const override;
    +    void SetKoefForLabels(const double koef){ m_label_koef = koef;}
     
         void OnPaint(wxPaintEvent& ){ render();}
         void OnLeftDown(wxMouseEvent& event);
    @@ -541,21 +540,26 @@ public:
     protected:
      
         void    render();
    -    void    draw_info_line(wxDC& dc, const wxPoint& pos, const wxSize& thumb_size, SelectedSlider selection);
    -    wxString    get_label(const int value);
    +    void    draw_focus_rect();
    +    void    draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
    +    void    draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection);
    +    void    draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
    +    void    draw_info_line(wxDC& dc, const wxPoint& pos, SelectedSlider selection) const;
    +    void    draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
    +
    +    void    update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection);
    +    void    detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false);
         void    correct_lower_value();
         void    correct_higher_value();
    -    void    draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
    -    double  get_scroll_step();
    -    void    get_lower_and_higher_position(int& lower_pos, int& higher_pos);
    -    void    draw_focus_rect();
    -    void    draw_lower_thumb(wxDC& dc, const wxPoint& pos);
    -    void    draw_higher_thumb(wxDC& dc, const wxPoint& pos);
    -    int     position_to_value(wxDC& dc, const wxCoord x, const wxCoord y);
    -    void    detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false);
    +    void    move_current_thumb(const bool condition);
    +
         bool    is_point_in_rect(const wxPoint& pt, const wxRect& rect);
         bool    is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
    -    void    move_current_thumb(const bool condition);
    +
    +    double      get_scroll_step();
    +    wxString    get_label(const SelectedSlider& selection) const;
    +    void        get_lower_and_higher_position(int& lower_pos, int& higher_pos);
    +    int         position_to_value(wxDC& dc, const wxCoord x, const wxCoord y);
     
     private:
         int         m_min_value;
    @@ -570,6 +574,7 @@ private:
     
         wxRect      m_rect_lower_thumb;
         wxRect      m_rect_higher_thumb;
    +    wxSize      m_thumb_size;
         long        m_style;
         float       m_label_koef = 1.0;
     
    
    From 6829704475e4feb66c337d37c34941edb529fe1d Mon Sep 17 00:00:00 2001
    From: bubnikv 
    Date: Wed, 22 Aug 2018 15:03:35 +0200
    Subject: [PATCH 165/185] d ..
    
    ---
     xs/src/admesh/connect.cpp            | 333 ++++++++++---------------
     xs/src/admesh/normals.cpp            | 106 ++------
     xs/src/admesh/shared.cpp             |   8 +-
     xs/src/admesh/stl.h                  |  59 +++--
     xs/src/admesh/stl_io.cpp             |  95 +++----
     xs/src/admesh/stlinit.cpp            | 130 +++-------
     xs/src/admesh/util.cpp               | 358 ++++++++++-----------------
     xs/src/libslic3r/Format/3mf.cpp      |   8 +-
     xs/src/libslic3r/Format/AMF.cpp      |   8 +-
     xs/src/libslic3r/Format/OBJ.cpp      |  34 ++-
     xs/src/libslic3r/Format/PRUS.cpp     |  16 +-
     xs/src/libslic3r/Model.cpp           |  48 +---
     xs/src/libslic3r/SlicingAdaptive.cpp |   6 +-
     xs/src/libslic3r/TriangleMesh.cpp    | 192 ++++++--------
     xs/src/slic3r/GUI/3DScene.cpp        |   4 +-
     xs/xsp/TriangleMesh.xsp              |  42 ++--
     16 files changed, 539 insertions(+), 908 deletions(-)
    
    diff --git a/xs/src/admesh/connect.cpp b/xs/src/admesh/connect.cpp
    index e9129d0079..da5b667209 100644
    --- a/xs/src/admesh/connect.cpp
    +++ b/xs/src/admesh/connect.cpp
    @@ -25,11 +25,11 @@
     #include 
     #include 
     
    +#include 
    +
     #include "stl.h"
     
     
    -static void stl_match_neighbors_exact(stl_file *stl,
    -                                      stl_hash_edge *edge_a, stl_hash_edge *edge_b);
     static void stl_match_neighbors_nearby(stl_file *stl,
                                            stl_hash_edge *edge_a, stl_hash_edge *edge_b);
     static void stl_record_neighbors(stl_file *stl,
    @@ -43,7 +43,6 @@ static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge,
     static void insert_hash_edge(stl_file *stl, stl_hash_edge edge,
                                  void (*match_neighbors)(stl_file *stl,
                                      stl_hash_edge *edge_a, stl_hash_edge *edge_b));
    -static int stl_get_hash_for_edge(int M, stl_hash_edge *edge);
     static int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b);
     static void stl_free_edges(stl_file *stl);
     static void stl_remove_facet(stl_file *stl, int facet_number);
    @@ -82,37 +81,20 @@ stl_check_facets_exact(stl_file *stl) {
     
       for(i = 0; i < stl->stats.number_of_facets; i++) {
         facet = stl->facet_start[i];
    -    // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit.
    -    // When using a memcmp on raw floats, those numbers report to be different.
    -    // Unify all +0 and -0 to +0 to make the floats equal under memcmp.
    -    {
    -      uint32_t *f = (uint32_t*)&facet;
    -      for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats
    -        if (*f == 0x80000000)
    -            // Negative zero, switch to positive zero.
    -            *f = 0;
    -    }
    -
    -    /* If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. */
    -    if(   !memcmp(&facet.vertex[0], &facet.vertex[1],
    -                  sizeof(stl_vertex))
    -          || !memcmp(&facet.vertex[1], &facet.vertex[2],
    -                     sizeof(stl_vertex))
    -          || !memcmp(&facet.vertex[0], &facet.vertex[2],
    -                     sizeof(stl_vertex))) {
    +    // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet.
    +    if (facet.vertex[0] == facet.vertex[1] ||
    +        facet.vertex[1] == facet.vertex[2] ||
    +        facet.vertex[0] == facet.vertex[2]) {
           stl->stats.degenerate_facets += 1;
           stl_remove_facet(stl, i);
    -      i--;
    +      -- i;
           continue;
    -
         }
         for(j = 0; j < 3; j++) {
           edge.facet_number = i;
           edge.which_edge = j;
    -      stl_load_edge_exact(stl, &edge, &facet.vertex[j],
    -                          &facet.vertex[(j + 1) % 3]);
    -
    -      insert_hash_edge(stl, edge, stl_match_neighbors_exact);
    +      stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]);
    +      insert_hash_edge(stl, edge, stl_record_neighbors);
         }
       }
       stl_free_edges(stl);
    @@ -131,28 +113,33 @@ stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge,
       if (stl->error) return;
     
       {
    -    float diff_x = ABS(a->x - b->x);
    -    float diff_y = ABS(a->y - b->y);
    -    float diff_z = ABS(a->z - b->z);
    -    float max_diff = STL_MAX(diff_x, diff_y);
    -    max_diff = STL_MAX(diff_z, max_diff);
    -    stl->stats.shortest_edge = STL_MIN(max_diff, stl->stats.shortest_edge);
    +    stl_vertex diff = (*a - *b).cwiseAbs();
    +    float max_diff = std::max(diff(0), std::max(diff(1), diff(2)));
    +    stl->stats.shortest_edge = std::min(max_diff, stl->stats.shortest_edge);
       }
     
       // Ensure identical vertex ordering of equal edges.
       // This method is numerically robust.
    -  if ((a->x != b->x) ? 
    -        (a->x < b->x) : 
    -        ((a->y != b->y) ? 
    -            (a->y < b->y) : 
    -            (a->z < b->z))) {
    -    memcpy(&edge->key[0], a, sizeof(stl_vertex));
    -    memcpy(&edge->key[3], b, sizeof(stl_vertex));
    +  if (stl_vertex_lower(*a, *b)) {
       } else {
    -    memcpy(&edge->key[0], b, sizeof(stl_vertex));
    -    memcpy(&edge->key[3], a, sizeof(stl_vertex));
    +    std::swap(a, b);
         edge->which_edge += 3; /* this edge is loaded backwards */
       }
    +  memcpy(&edge->key[0],                  a->data(), sizeof(stl_vertex));
    +  memcpy(&edge->key[sizeof(stl_vertex)], b->data(), sizeof(stl_vertex));
    +  // Switch negative zeros to positive zeros, so memcmp will consider them to be equal.
    +  for (size_t i = 0; i < 6; ++ i) {
    +    unsigned char *p = edge->key + i * 4;
    +#ifdef BOOST_LITTLE_ENDIAN
    +    if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80)
    +      // Negative zero, switch to positive zero.
    +      p[3] = 0;
    +#else /* BOOST_LITTLE_ENDIAN */
    +    if (p[0] == 0x80 && p[1] == 0 && p[2] == 0 && p[3] == 0)
    +      // Negative zero, switch to positive zero.
    +      p[0] = 0;
    +#endif /* BOOST_LITTLE_ENDIAN */
    +  }
     }
     
     static void
    @@ -188,21 +175,17 @@ stl_initialize_facet_check_exact(stl_file *stl) {
       }
     }
     
    -static void
    -insert_hash_edge(stl_file *stl, stl_hash_edge edge,
    +static void insert_hash_edge(stl_file *stl, stl_hash_edge edge,
                      void (*match_neighbors)(stl_file *stl,
    -                     stl_hash_edge *edge_a, stl_hash_edge *edge_b)) {
    -  stl_hash_edge *link;
    -  stl_hash_edge *new_edge;
    -  stl_hash_edge *temp;
    -  int            chain_number;
    -
    +                     stl_hash_edge *edge_a, stl_hash_edge *edge_b))
    +{
       if (stl->error) return;
     
    -  chain_number = stl_get_hash_for_edge(stl->M, &edge);
    -
    -  link = stl->heads[chain_number];
    +  int            chain_number = edge.hash(stl->M);
    +  stl_hash_edge *link = stl->heads[chain_number];
     
    +  stl_hash_edge *new_edge;
    +  stl_hash_edge *temp;
       if(link == stl->tail) {
         /* This list doesn't have any edges currently in it.  Add this one. */
         new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge));
    @@ -252,30 +235,17 @@ insert_hash_edge(stl_file *stl, stl_hash_edge edge,
       }
     }
     
    -
    -static int
    -stl_get_hash_for_edge(int M, stl_hash_edge *edge) {
    -  return ((edge->key[0] / 23 + edge->key[1] / 19 + edge->key[2] / 17
    -           + edge->key[3] /13  + edge->key[4] / 11 + edge->key[5] / 7 ) % M);
    +// Return 1 if the edges are not matched.
    +static inline int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b)
    +{
    +    // Don't match edges of the same facet
    +    return (edge_a->facet_number == edge_b->facet_number) || (*edge_a != *edge_b);
     }
     
    -static int
    -stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b) {
    -  if(edge_a->facet_number == edge_b->facet_number) {
    -    return 1;			/* Don't match edges of the same facet */
    -  } else {
    -    return memcmp(edge_a, edge_b, SIZEOF_EDGE_SORT);
    -  }
    -}
    -
    -void
    -stl_check_facets_nearby(stl_file *stl, float tolerance) {
    -  stl_hash_edge  edge[3];
    -  stl_facet      facet;
    -  int            i;
    -  int            j;
    -
    -  if (stl->error) return;
    +void stl_check_facets_nearby(stl_file *stl, float tolerance)
    +{
    +  if (stl->error)
    +    return;
     
       if(   (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets)
             && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets)
    @@ -286,27 +256,19 @@ stl_check_facets_nearby(stl_file *stl, float tolerance) {
     
       stl_initialize_facet_check_nearby(stl);
     
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    -    facet = stl->facet_start[i];
    -    // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit.
    -    // When using a memcmp on raw floats, those numbers report to be different.
    -    // Unify all +0 and -0 to +0 to make the floats equal under memcmp.
    -    {
    -      uint32_t *f = (uint32_t*)&facet;
    -      for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats
    -        if (*f == 0x80000000)
    -            // Negative zero, switch to positive zero.
    -            *f = 0;
    -    }
    -    for(j = 0; j < 3; j++) {
    +  for (int i = 0; i < stl->stats.number_of_facets; ++ i) {
    +    //FIXME is the copy necessary?
    +    stl_facet facet = stl->facet_start[i];
    +    for (int j = 0; j < 3; j++) {
           if(stl->neighbors_start[i].neighbor[j] == -1) {
    -        edge[j].facet_number = i;
    -        edge[j].which_edge = j;
    -        if(stl_load_edge_nearby(stl, &edge[j], &facet.vertex[j],
    +        stl_hash_edge edge;
    +        edge.facet_number = i;
    +        edge.which_edge = j;
    +        if(stl_load_edge_nearby(stl, &edge, &facet.vertex[j],
                                     &facet.vertex[(j + 1) % 3],
                                     tolerance)) {
               /* only insert edges that have different keys */
    -          insert_hash_edge(stl, edge[j], stl_match_neighbors_nearby);
    +          insert_hash_edge(stl, edge, stl_match_neighbors_nearby);
             }
           }
         }
    @@ -315,27 +277,17 @@ stl_check_facets_nearby(stl_file *stl, float tolerance) {
       stl_free_edges(stl);
     }
     
    -static int
    -stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge,
    -                     stl_vertex *a, stl_vertex *b, float tolerance) {
    +static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance)
    +{
       // Index of a grid cell spaced by tolerance.
    -  uint32_t vertex1[3] = {
    -    (uint32_t)((a->x - stl->stats.min.x) / tolerance),
    -    (uint32_t)((a->y - stl->stats.min.y) / tolerance),
    -    (uint32_t)((a->z - stl->stats.min.z) / tolerance)
    -  };
    -  uint32_t vertex2[3] = {
    -    (uint32_t)((b->x - stl->stats.min.x) / tolerance),
    -    (uint32_t)((b->y - stl->stats.min.y) / tolerance),
    -    (uint32_t)((b->z - stl->stats.min.z) / tolerance)
    -  };
    +  typedef Eigen::Matrix Vec3i;
    +  Vec3i vertex1 = (*a / tolerance).cast();
    +  Vec3i vertex2 = (*b / tolerance).cast();
    +  static_assert(sizeof(Vec3i) == 12, "size of Vec3i incorrect");
     
    -  if(   (vertex1[0] == vertex2[0])
    -        && (vertex1[1] == vertex2[1])
    -        && (vertex1[2] == vertex2[2])) {
    -    /* Both vertices hash to the same value */
    +  if (vertex1 == vertex2)
    +    // Both vertices hash to the same value
         return 0;
    -  }
     
       // Ensure identical vertex ordering of edges, which vertices land into equal grid cells.
       // This method is numerically robust.
    @@ -344,30 +296,27 @@ stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge,
             ((vertex1[1] != vertex2[1]) ? 
                 (vertex1[1] < vertex2[1]) : 
                 (vertex1[2] < vertex2[2]))) {
    -    memcpy(&edge->key[0], vertex1, sizeof(stl_vertex));
    -    memcpy(&edge->key[3], vertex2, sizeof(stl_vertex));
    +    memcpy(&edge->key[0],                  vertex1.data(), sizeof(stl_vertex));
    +    memcpy(&edge->key[sizeof(stl_vertex)], vertex2.data(), sizeof(stl_vertex));
       } else {
    -    memcpy(&edge->key[0], vertex2, sizeof(stl_vertex));
    -    memcpy(&edge->key[3], vertex1, sizeof(stl_vertex));
    +    memcpy(&edge->key[0],                  vertex2.data(), sizeof(stl_vertex));
    +    memcpy(&edge->key[sizeof(stl_vertex)], vertex1.data(), sizeof(stl_vertex));
         edge->which_edge += 3; /* this edge is loaded backwards */
       }
       return 1;
     }
     
    -static void
    -stl_free_edges(stl_file *stl) {
    -  int i;
    -  stl_hash_edge *temp;
    -
    -  if (stl->error) return;
    +static void stl_free_edges(stl_file *stl)
    +{
    +  if (stl->error)
    +    return;
     
       if(stl->stats.malloced != stl->stats.freed) {
    -    for(i = 0; i < stl->M; i++) {
    -      for(temp = stl->heads[i]; stl->heads[i] != stl->tail;
    -          temp = stl->heads[i]) {
    +    for (int i = 0; i < stl->M; i++) {
    +      for (stl_hash_edge *temp = stl->heads[i]; stl->heads[i] != stl->tail; temp = stl->heads[i]) {
             stl->heads[i] = stl->heads[i]->next;
             free(temp);
    -        stl->stats.freed++;
    +        ++ stl->stats.freed;
           }
         }
       }
    @@ -375,8 +324,8 @@ stl_free_edges(stl_file *stl) {
       free(stl->tail);
     }
     
    -static void
    -stl_initialize_facet_check_nearby(stl_file *stl) {
    +static void stl_initialize_facet_check_nearby(stl_file *stl)
    +{
       int i;
     
       if (stl->error) return;
    @@ -467,16 +416,8 @@ stl_record_neighbors(stl_file *stl,
       }
     }
     
    -static void
    -stl_match_neighbors_exact(stl_file *stl,
    -                          stl_hash_edge *edge_a, stl_hash_edge *edge_b) {
    -  if (stl->error) return;
    -  stl_record_neighbors(stl, edge_a, edge_b);
    -}
    -
    -static void
    -stl_match_neighbors_nearby(stl_file *stl,
    -                           stl_hash_edge *edge_a, stl_hash_edge *edge_b) {
    +static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b)
    +{
       int facet1;
       int facet2;
       int vertex1;
    @@ -517,9 +458,7 @@ stl_match_neighbors_nearby(stl_file *stl,
     }
     
     
    -static void
    -stl_change_vertices(stl_file *stl, int facet_num, int vnot,
    -                    stl_vertex new_vertex) {
    +static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, stl_vertex new_vertex) {
       int first_facet;
       int direction;
       int next_edge;
    @@ -551,30 +490,30 @@ stl_change_vertices(stl_file *stl, int facet_num, int vnot,
           }
         }
     #if 0
    -    if (stl->facet_start[facet_num].vertex[pivot_vertex].x == new_vertex.x &&
    -        stl->facet_start[facet_num].vertex[pivot_vertex].y == new_vertex.y &&
    -        stl->facet_start[facet_num].vertex[pivot_vertex].z == new_vertex.z)
    +    if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) &&
    +        stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) &&
    +        stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2))
           printf("Changing vertex %f,%f,%f: Same !!!\r\n", 
    -        new_vertex.x, new_vertex.y, new_vertex.z);
    +        new_vertex(0), new_vertex(1), new_vertex(2));
         else {
    -      if (stl->facet_start[facet_num].vertex[pivot_vertex].x != new_vertex.x)
    +      if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0))
             printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", 
    -          stl->facet_start[facet_num].vertex[pivot_vertex].x,
    -          *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].x),
    -          new_vertex.x,
    -          *reinterpret_cast(&new_vertex.x));
    -      if (stl->facet_start[facet_num].vertex[pivot_vertex].y != new_vertex.y)
    +          stl->facet_start[facet_num].vertex[pivot_vertex](0),
    +          *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](0)),
    +          new_vertex(0),
    +          *reinterpret_cast(&new_vertex(0)));
    +      if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1))
             printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", 
    -          stl->facet_start[facet_num].vertex[pivot_vertex].y,
    -          *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].y),
    -          new_vertex.y,
    -          *reinterpret_cast(&new_vertex.y));
    -      if (stl->facet_start[facet_num].vertex[pivot_vertex].z != new_vertex.z)
    +          stl->facet_start[facet_num].vertex[pivot_vertex](1),
    +          *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](1)),
    +          new_vertex(1),
    +          *reinterpret_cast(&new_vertex(1)));
    +      if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2))
             printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", 
    -          stl->facet_start[facet_num].vertex[pivot_vertex].z,
    -          *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].z),
    -          new_vertex.z,
    -          *reinterpret_cast(&new_vertex.z));
    +          stl->facet_start[facet_num].vertex[pivot_vertex](2),
    +          *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](2)),
    +          new_vertex(2),
    +          *reinterpret_cast(&new_vertex(2)));
         }
     #endif
         stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex;
    @@ -595,7 +534,6 @@ Try using a smaller tolerance or don't do a nearby check\n");
       }
     }
     
    -
     static void
     stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a,
                                  stl_hash_edge *edge_b, int *facet1, int *vertex1,
    @@ -622,11 +560,10 @@ stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a,
         v1b = (edge_b->which_edge + 1) % 3;
       }
     
    -  /* Of the first pair, which vertex, if any, should be changed */
    -  if(!memcmp(&stl->facet_start[edge_a->facet_number].vertex[v1a],
    -             &stl->facet_start[edge_b->facet_number].vertex[v1b],
    -             sizeof(stl_vertex))) {
    -    /* These facets are already equal.  No need to change. */
    +  // Of the first pair, which vertex, if any, should be changed
    +  if(stl->facet_start[edge_a->facet_number].vertex[v1a] == 
    +     stl->facet_start[edge_b->facet_number].vertex[v1b]) {
    +    // These facets are already equal.  No need to change.
         *facet1 = -1;
       } else {
         if(   (stl->neighbors_start[edge_a->facet_number].neighbor[v1a] == -1)
    @@ -644,10 +581,9 @@ stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a,
       }
     
       /* Of the second pair, which vertex, if any, should be changed */
    -  if(!memcmp(&stl->facet_start[edge_a->facet_number].vertex[v2a],
    -             &stl->facet_start[edge_b->facet_number].vertex[v2b],
    -             sizeof(stl_vertex))) {
    -    /* These facets are already equal.  No need to change. */
    +  if(stl->facet_start[edge_a->facet_number].vertex[v2a] == 
    +     stl->facet_start[edge_b->facet_number].vertex[v2b]) {
    +    // These facets are already equal.  No need to change.
         *facet2 = -1;
       } else {
         if(   (stl->neighbors_start[edge_a->facet_number].neighbor[v2a] == -1)
    @@ -718,40 +654,35 @@ in stl_remove_facet: neighbor = %d numfacets = %d this is wrong\n",
       }
     }
     
    -void
    -stl_remove_unconnected_facets(stl_file *stl) {
    +void stl_remove_unconnected_facets(stl_file *stl)
    +{
       /* A couple of things need to be done here.  One is to remove any */
       /* completely unconnected facets (0 edges connected) since these are */
       /* useless and could be completely wrong.   The second thing that needs to */
       /* be done is to remove any degenerate facets that were created during */
       /* stl_check_facets_nearby(). */
    +  if (stl->error)
    +    return;
     
    -  int i;
    -
    -  if (stl->error) return;
    -
    -  /* remove degenerate facets */
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    -    if(   !memcmp(&stl->facet_start[i].vertex[0],
    -                  &stl->facet_start[i].vertex[1], sizeof(stl_vertex))
    -          || !memcmp(&stl->facet_start[i].vertex[1],
    -                     &stl->facet_start[i].vertex[2], sizeof(stl_vertex))
    -          || !memcmp(&stl->facet_start[i].vertex[0],
    -                     &stl->facet_start[i].vertex[2], sizeof(stl_vertex))) {
    +  // remove degenerate facets
    +  for (int i = 0; i < stl->stats.number_of_facets; ++ i) {
    +    if(stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[1] ||
    +       stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[2] ||
    +       stl->facet_start[i].vertex[1] == stl->facet_start[i].vertex[2]) {
           stl_remove_degenerate(stl, i);
           i--;
         }
       }
     
       if(stl->stats.connected_facets_1_edge < stl->stats.number_of_facets) {
    -    /* remove completely unconnected facets */
    -    for(i = 0; i < stl->stats.number_of_facets; i++) {
    -      if(   (stl->neighbors_start[i].neighbor[0] == -1)
    -            && (stl->neighbors_start[i].neighbor[1] == -1)
    -            && (stl->neighbors_start[i].neighbor[2] == -1)) {
    -        /* This facet is completely unconnected.  Remove it. */
    +    // remove completely unconnected facets
    +    for (int i = 0; i < stl->stats.number_of_facets; i++) {
    +      if (stl->neighbors_start[i].neighbor[0] == -1 &&
    +          stl->neighbors_start[i].neighbor[1] == -1 &&
    +          stl->neighbors_start[i].neighbor[2] == -1) {
    +        // This facet is completely unconnected.  Remove it.
             stl_remove_facet(stl, i);
    -        i--;
    +        -- i;
           }
         }
       }
    @@ -771,30 +702,24 @@ stl_remove_degenerate(stl_file *stl, int facet) {
     
       if (stl->error) return;
     
    -  if(   !memcmp(&stl->facet_start[facet].vertex[0],
    -                &stl->facet_start[facet].vertex[1], sizeof(stl_vertex))
    -        && !memcmp(&stl->facet_start[facet].vertex[1],
    -                   &stl->facet_start[facet].vertex[2], sizeof(stl_vertex))) {
    +  if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1] &&
    +      stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) {
         /* all 3 vertices are equal.  Just remove the facet.  I don't think*/
         /* this is really possible, but just in case... */
         printf("removing a facet in stl_remove_degenerate\n");
    -
         stl_remove_facet(stl, facet);
         return;
       }
     
    -  if(!memcmp(&stl->facet_start[facet].vertex[0],
    -             &stl->facet_start[facet].vertex[1], sizeof(stl_vertex))) {
    +  if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) {
         edge1 = 1;
         edge2 = 2;
         edge3 = 0;
    -  } else if(!memcmp(&stl->facet_start[facet].vertex[1],
    -                    &stl->facet_start[facet].vertex[2], sizeof(stl_vertex))) {
    +  } else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) {
         edge1 = 0;
         edge2 = 2;
         edge3 = 1;
    -  } else if(!memcmp(&stl->facet_start[facet].vertex[2],
    -                    &stl->facet_start[facet].vertex[0], sizeof(stl_vertex))) {
    +  } else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) {
         edge1 = 0;
         edge2 = 1;
         edge3 = 2;
    @@ -883,7 +808,7 @@ stl_fill_holes(stl_file *stl) {
           stl_load_edge_exact(stl, &edge, &facet.vertex[j],
                               &facet.vertex[(j + 1) % 3]);
     
    -      insert_hash_edge(stl, edge, stl_match_neighbors_exact);
    +      insert_hash_edge(stl, edge, stl_record_neighbors);
         }
       }
     
    @@ -939,7 +864,7 @@ stl_fill_holes(stl_file *stl) {
                 stl_load_edge_exact(stl, &edge, &new_facet.vertex[k],
                                     &new_facet.vertex[(k + 1) % 3]);
     
    -            insert_hash_edge(stl, edge, stl_match_neighbors_exact);
    +            insert_hash_edge(stl, edge, stl_record_neighbors);
               }
               break;
             } else {
    @@ -977,9 +902,7 @@ stl_add_facet(stl_file *stl, stl_facet *new_facet) {
       stl->facet_start[stl->stats.number_of_facets] = *new_facet;
     
       /* note that the normal vector is not set here, just initialized to 0 */
    -  stl->facet_start[stl->stats.number_of_facets].normal.x = 0.0;
    -  stl->facet_start[stl->stats.number_of_facets].normal.y = 0.0;
    -  stl->facet_start[stl->stats.number_of_facets].normal.z = 0.0;
    +  stl->facet_start[stl->stats.number_of_facets].normal = stl_normal::Zero();
     
       stl->neighbors_start[stl->stats.number_of_facets].neighbor[0] = -1;
       stl->neighbors_start[stl->stats.number_of_facets].neighbor[1] = -1;
    diff --git a/xs/src/admesh/normals.cpp b/xs/src/admesh/normals.cpp
    index b7cf9a8ab7..a8faa44bd2 100644
    --- a/xs/src/admesh/normals.cpp
    +++ b/xs/src/admesh/normals.cpp
    @@ -27,12 +27,6 @@
     
     #include "stl.h"
     
    -static void stl_reverse_vector(float v[]) {
    -  v[0] *= -1;
    -  v[1] *= -1;
    -  v[2] *= -1;
    -}
    -
     static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag);
     
     static void
    @@ -228,102 +222,52 @@ static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_
       /* Returns 2 if the normal is not within tolerance and backwards */
       /* Returns 4 if the status is unknown. */
     
    -  float normal[3];
    -  float test_norm[3];
       stl_facet *facet;
     
       facet = &stl->facet_start[facet_num];
     
    +  stl_normal normal;
       stl_calculate_normal(normal, facet);
       stl_normalize_vector(normal);
    +  stl_normal normal_dif = (normal - facet->normal).cwiseAbs();
     
    -  if(   (ABS(normal[0] - facet->normal.x) < 0.001)
    -        && (ABS(normal[1] - facet->normal.y) < 0.001)
    -        && (ABS(normal[2] - facet->normal.z) < 0.001)) {
    +  const float eps = 0.001f;
    +  if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
         /* It is not really necessary to change the values here */
         /* but just for consistency, I will. */
    -    facet->normal.x = normal[0];
    -    facet->normal.y = normal[1];
    -    facet->normal.z = normal[2];
    +    facet->normal = normal;
         return 0;
       }
     
    -  test_norm[0] = facet->normal.x;
    -  test_norm[1] = facet->normal.y;
    -  test_norm[2] = facet->normal.z;
    -
    +  stl_normal test_norm = facet->normal;
       stl_normalize_vector(test_norm);
    -  if(   (ABS(normal[0] - test_norm[0]) < 0.001)
    -        && (ABS(normal[1] - test_norm[1]) < 0.001)
    -        && (ABS(normal[2] - test_norm[2]) < 0.001)) {
    +  normal_dif = (normal - test_norm).cwiseAbs();
    +  if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
         if(normal_fix_flag) {
    -      facet->normal.x = normal[0];
    -      facet->normal.y = normal[1];
    -      facet->normal.z = normal[2];
    +      facet->normal = normal;
           stl->stats.normals_fixed += 1;
         }
         return 1;
       }
     
    -  stl_reverse_vector(test_norm);
    -  if(   (ABS(normal[0] - test_norm[0]) < 0.001)
    -        && (ABS(normal[1] - test_norm[1]) < 0.001)
    -        && (ABS(normal[2] - test_norm[2]) < 0.001)) {
    -    /* Facet is backwards. */
    +  test_norm *= -1.f;
    +  normal_dif = (normal - test_norm).cwiseAbs();
    +  if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) {
    +    // Facet is backwards.
         if(normal_fix_flag) {
    -      facet->normal.x = normal[0];
    -      facet->normal.y = normal[1];
    -      facet->normal.z = normal[2];
    +      facet->normal = normal;
           stl->stats.normals_fixed += 1;
         }
         return 2;
       }
       if(normal_fix_flag) {
    -    facet->normal.x = normal[0];
    -    facet->normal.y = normal[1];
    -    facet->normal.z = normal[2];
    +    facet->normal = normal;
         stl->stats.normals_fixed += 1;
       }
       return 4;
     }
     
    -void stl_calculate_normal(float normal[], stl_facet *facet) {
    -  float v1[3] = {
    -    facet->vertex[1].x - facet->vertex[0].x,
    -    facet->vertex[1].y - facet->vertex[0].y,
    -    facet->vertex[1].z - facet->vertex[0].z
    -  };
    -  float v2[3] = {
    -    facet->vertex[2].x - facet->vertex[0].x,
    -    facet->vertex[2].y - facet->vertex[0].y,
    -    facet->vertex[2].z - facet->vertex[0].z
    -  };
    -  normal[0] = (float)((double)v1[1] * (double)v2[2]) - ((double)v1[2] * (double)v2[1]);
    -  normal[1] = (float)((double)v1[2] * (double)v2[0]) - ((double)v1[0] * (double)v2[2]);
    -  normal[2] = (float)((double)v1[0] * (double)v2[1]) - ((double)v1[1] * (double)v2[0]);
    -}
    -
    -void stl_normalize_vector(float v[]) {
    -  double length;
    -  double factor;
    -  float min_normal_length;
    -
    -  length = sqrt((double)v[0] * (double)v[0] + (double)v[1] * (double)v[1] + (double)v[2] * (double)v[2]);
    -  min_normal_length = 0.000000000001;
    -  if(length < min_normal_length) {
    -    v[0] = 0.0;
    -    v[1] = 0.0;
    -    v[2] = 0.0;
    -    return;
    -  }
    -  factor = 1.0 / length;
    -  v[0] *= factor;
    -  v[1] *= factor;
    -  v[2] *= factor;
    -}
    -
    -void
    -stl_fix_normal_values(stl_file *stl) {
    +void stl_fix_normal_values(stl_file *stl) {
       int i;
     
       if (stl->error) return;
    @@ -333,20 +277,16 @@ stl_fix_normal_values(stl_file *stl) {
       }
     }
     
    -void
    -stl_reverse_all_facets(stl_file *stl) {
    -  int i;
    -  float normal[3];
    +void stl_reverse_all_facets(stl_file *stl)
    +{
    +  if (stl->error)
    +  	return;
     
    -  if (stl->error) return;
    -
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    +  stl_normal normal;
    +  for(int i = 0; i < stl->stats.number_of_facets; i++) {
         stl_reverse_facet(stl, i);
         stl_calculate_normal(normal, &stl->facet_start[i]);
         stl_normalize_vector(normal);
    -    stl->facet_start[i].normal.x = normal[0];
    -    stl->facet_start[i].normal.y = normal[1];
    -    stl->facet_start[i].normal.z = normal[2];
    +    stl->facet_start[i].normal = normal;
       }
     }
    -
    diff --git a/xs/src/admesh/shared.cpp b/xs/src/admesh/shared.cpp
    index 8080f3574e..91bb82e006 100644
    --- a/xs/src/admesh/shared.cpp
    +++ b/xs/src/admesh/shared.cpp
    @@ -169,7 +169,7 @@ stl_write_off(stl_file *stl, char *file) {
     
       for(i = 0; i < stl->stats.shared_vertices; i++) {
         fprintf(fp, "\t%f %f %f\n",
    -            stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z);
    +            stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2));
       }
       for(i = 0; i < stl->stats.number_of_facets; i++) {
         fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0],
    @@ -216,10 +216,10 @@ stl_write_vrml(stl_file *stl, char *file) {
     
       for(i = 0; i < (stl->stats.shared_vertices - 1); i++) {
         fprintf(fp, "\t\t\t\t%f %f %f,\n",
    -            stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z);
    +            stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2));
       }
       fprintf(fp, "\t\t\t\t%f %f %f]\n",
    -          stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z);
    +          stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2));
       fprintf(fp, "\t\t}\n");
       fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n");
       fprintf(fp, "\t\t\tcoordIndex [\n");
    @@ -254,7 +254,7 @@ void stl_write_obj (stl_file *stl, char *file) {
       }
     
       for (i = 0; i < stl->stats.shared_vertices; i++) {
    -    fprintf(fp, "v %f %f %f\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z);
    +    fprintf(fp, "v %f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2));
       }
       for (i = 0; i < stl->stats.number_of_facets; i++) {
         fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1);
    diff --git a/xs/src/admesh/stl.h b/xs/src/admesh/stl.h
    index dc0f7ae195..5f7a3c5c14 100644
    --- a/xs/src/admesh/stl.h
    +++ b/xs/src/admesh/stl.h
    @@ -27,9 +27,7 @@
     #include 
     #include 
     
    -#define STL_MAX(A,B) ((A)>(B)? (A):(B))
    -#define STL_MIN(A,B) ((A)<(B)? (A):(B))
    -#define ABS(X)  ((X) < 0 ? -(X) : (X))
    +#include  
     
     // Size of the binary STL header, free form.
     #define LABEL_SIZE             80
    @@ -39,31 +37,16 @@
     #define HEADER_SIZE            84
     #define STL_MIN_FILE_SIZE      284
     #define ASCII_LINES_PER_FACET  7
    -// Comparing an edge by memcmp, 2x3x4 bytes = 24
    -#define SIZEOF_EDGE_SORT       24
    -
    -typedef struct {
    -  float x;
    -  float y;
    -  float z;
    -} stl_vertex;
     
    +typedef Eigen::Matrix stl_vertex;
    +typedef Eigen::Matrix stl_normal;
     static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect");
    -
    -typedef struct {
    -  float x;
    -  float y;
    -  float z;
    -} stl_normal;
    -
     static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect");
     
    -typedef char stl_extra[2];
    -
     typedef struct {
       stl_normal normal;
       stl_vertex vertex[3];
    -  stl_extra  extra;
    +  char       extra[2];
     } stl_facet;
     #define SIZEOF_STL_FACET       50
     
    @@ -81,8 +64,12 @@ typedef struct {
     } stl_edge;
     
     typedef struct stl_hash_edge {
    -  // Key of a hash edge: 2x binary copy of a floating point vertex.
    -  uint32_t       key[6];
    +  // Key of a hash edge: sorted vertices of the edge.
    +  unsigned char key[2 * sizeof(stl_vertex)];
    +  // Compare two keys.
    +  bool operator==(const stl_hash_edge &rhs) { return memcmp(key, rhs.key, sizeof(key)) == 0; }
    +  bool operator!=(const stl_hash_edge &rhs) { return ! (*this == rhs); }
    +  int  hash(int M) const { return ((key[0] / 23 + key[1] / 19 + key[2] / 17 + key[3] /13  + key[4] / 11 + key[5] / 7 ) % M); }
       // Index of a facet owning this edge.
       int            facet_number;
       // Index of this edge inside the facet with an index of facet_number.
    @@ -91,8 +78,6 @@ typedef struct stl_hash_edge {
       struct stl_hash_edge  *next;
     } stl_hash_edge;
     
    -static_assert(offsetof(stl_hash_edge, facet_number) == SIZEOF_EDGE_SORT, "size of stl_hash_edge.key incorrect");
    -
     typedef struct {
       // Index of a neighbor facet.
       int   neighbor[3];
    @@ -179,8 +164,8 @@ extern void stl_fix_normal_values(stl_file *stl);
     extern void stl_reverse_all_facets(stl_file *stl);
     extern void stl_translate(stl_file *stl, float x, float y, float z);
     extern void stl_translate_relative(stl_file *stl, float x, float y, float z);
    -extern void stl_scale_versor(stl_file *stl, float versor[3]);
    -extern void stl_scale(stl_file *stl, float factor);
    +extern void stl_scale_versor(stl_file *stl, const stl_vertex &versor);
    +inline void stl_scale(stl_file *stl, float factor) { stl_scale_versor(stl, stl_vertex(factor, factor, factor)); }
     extern void stl_rotate_x(stl_file *stl, float angle);
     extern void stl_rotate_y(stl_file *stl, float angle);
     extern void stl_rotate_z(stl_file *stl, float angle);
    @@ -195,8 +180,20 @@ extern void stl_write_obj(stl_file *stl, char *file);
     extern void stl_write_off(stl_file *stl, char *file);
     extern void stl_write_dxf(stl_file *stl, char *file, char *label);
     extern void stl_write_vrml(stl_file *stl, char *file);
    -extern void stl_calculate_normal(float normal[], stl_facet *facet);
    -extern void stl_normalize_vector(float v[]);
    +inline void stl_calculate_normal(stl_normal &normal, stl_facet *facet) {
    +  normal = (facet->vertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]);
    +}
    +inline void stl_normalize_vector(stl_normal &normal) {
    +  double length = normal.cast().norm();
    +  if (length < 0.000000000001)
    +    normal = stl_normal::Zero();
    +  else
    +    normal *= (1.0 / length);
    +}
    +inline bool stl_vertex_lower(const stl_vertex &a, const stl_vertex &b) {
    +  return (a(0) != b(0)) ? (a(0) < b(0)) :
    +        ((a(1) != b(1)) ? (a(1) < b(1)) : (a(2) < b(2)));
    +}
     extern void stl_calculate_volume(stl_file *stl);
     
     extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag);
    @@ -204,8 +201,8 @@ extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int toler
     extern void stl_initialize(stl_file *stl);
     extern void stl_count_facets(stl_file *stl, const char *file);
     extern void stl_allocate(stl_file *stl);
    -extern void stl_read(stl_file *stl, int first_facet, int first);
    -extern void stl_facet_stats(stl_file *stl, stl_facet facet, int first);
    +extern void stl_read(stl_file *stl, int first_facet, bool first);
    +extern void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first);
     extern void stl_reallocate(stl_file *stl);
     extern void stl_add_facet(stl_file *stl, stl_facet *new_facet);
     extern void stl_get_size(stl_file *stl);
    diff --git a/xs/src/admesh/stl_io.cpp b/xs/src/admesh/stl_io.cpp
    index 1603981fcb..81e29d2860 100644
    --- a/xs/src/admesh/stl_io.cpp
    +++ b/xs/src/admesh/stl_io.cpp
    @@ -44,9 +44,9 @@ stl_print_edges(stl_file *stl, FILE *file) {
       for(i = 0; i < edges_allocated; i++) {
         fprintf(file, "%d, %f, %f, %f, %f, %f, %f\n",
                 stl->edge_start[i].facet_number,
    -            stl->edge_start[i].p1.x, stl->edge_start[i].p1.y,
    -            stl->edge_start[i].p1.z, stl->edge_start[i].p2.x,
    -            stl->edge_start[i].p2.y, stl->edge_start[i].p2.z);
    +            stl->edge_start[i].p1(0), stl->edge_start[i].p1(1),
    +            stl->edge_start[i].p1(2), stl->edge_start[i].p2(0),
    +            stl->edge_start[i].p2(1), stl->edge_start[i].p2(2));
       }
     }
     
    @@ -75,11 +75,11 @@ File type          : ASCII STL file\n");
     Header             : %s\n", stl->stats.header);
       fprintf(file, "============== Size ==============\n");
       fprintf(file, "Min X = % f, Max X = % f\n",
    -          stl->stats.min.x, stl->stats.max.x);
    +          stl->stats.min(0), stl->stats.max(0));
       fprintf(file, "Min Y = % f, Max Y = % f\n",
    -          stl->stats.min.y, stl->stats.max.y);
    +          stl->stats.min(1), stl->stats.max(1));
       fprintf(file, "Min Z = % f, Max Z = % f\n",
    -          stl->stats.min.z, stl->stats.max.z);
    +          stl->stats.min(2), stl->stats.max(2));
     
       fprintf(file, "\
     ========= Facet Status ========== Original ============ Final ====\n");
    @@ -149,18 +149,18 @@ stl_write_ascii(stl_file *stl, const char *file, const char *label) {
     
       for(i = 0; i < stl->stats.number_of_facets; i++) {
         fprintf(fp, "  facet normal % .8E % .8E % .8E\n",
    -            stl->facet_start[i].normal.x, stl->facet_start[i].normal.y,
    -            stl->facet_start[i].normal.z);
    +            stl->facet_start[i].normal(0), stl->facet_start[i].normal(1),
    +            stl->facet_start[i].normal(2));
         fprintf(fp, "    outer loop\n");
         fprintf(fp, "      vertex % .8E % .8E % .8E\n",
    -            stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y,
    -            stl->facet_start[i].vertex[0].z);
    +            stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1),
    +            stl->facet_start[i].vertex[0](2));
         fprintf(fp, "      vertex % .8E % .8E % .8E\n",
    -            stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y,
    -            stl->facet_start[i].vertex[1].z);
    +            stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1),
    +            stl->facet_start[i].vertex[1](2));
         fprintf(fp, "      vertex % .8E % .8E % .8E\n",
    -            stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y,
    -            stl->facet_start[i].vertex[2].z);
    +            stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1),
    +            stl->facet_start[i].vertex[2](2));
         fprintf(fp, "    endloop\n");
         fprintf(fp, "  endfacet\n");
       }
    @@ -264,9 +264,9 @@ void
     stl_write_vertex(stl_file *stl, int facet, int vertex) {
       if (stl->error) return;
       printf("  vertex %d/%d % .8E % .8E % .8E\n", vertex, facet,
    -         stl->facet_start[facet].vertex[vertex].x,
    -         stl->facet_start[facet].vertex[vertex].y,
    -         stl->facet_start[facet].vertex[vertex].z);
    +         stl->facet_start[facet].vertex[vertex](0),
    +         stl->facet_start[facet].vertex[vertex](1),
    +         stl->facet_start[facet].vertex[vertex](2));
     }
     
     void
    @@ -309,10 +309,10 @@ stl_write_quad_object(stl_file *stl, char *file) {
       int       i;
       int       j;
       char      *error_msg;
    -  stl_vertex connect_color;
    -  stl_vertex uncon_1_color;
    -  stl_vertex uncon_2_color;
    -  stl_vertex uncon_3_color;
    +  stl_vertex connect_color = stl_vertex::Zero();
    +  stl_vertex uncon_1_color = stl_vertex::Zero();
    +  stl_vertex uncon_2_color = stl_vertex::Zero();
    +  stl_vertex uncon_3_color = stl_vertex::Zero();
       stl_vertex color;
     
       if (stl->error) return;
    @@ -330,19 +330,6 @@ stl_write_quad_object(stl_file *stl, char *file) {
         return;
       }
     
    -  connect_color.x = 0.0;
    -  connect_color.y = 0.0;
    -  connect_color.z = 1.0;
    -  uncon_1_color.x = 0.0;
    -  uncon_1_color.y = 1.0;
    -  uncon_1_color.z = 0.0;
    -  uncon_2_color.x = 1.0;
    -  uncon_2_color.y = 1.0;
    -  uncon_2_color.z = 1.0;
    -  uncon_3_color.x = 1.0;
    -  uncon_3_color.y = 0.0;
    -  uncon_3_color.z = 0.0;
    -
       fprintf(fp, "CQUAD\n");
       for(i = 0; i < stl->stats.number_of_facets; i++) {
         j = ((stl->neighbors_start[i].neighbor[0] == -1) +
    @@ -358,21 +345,21 @@ stl_write_quad_object(stl_file *stl, char *file) {
           color = uncon_3_color;
         }
         fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n",
    -            stl->facet_start[i].vertex[0].x,
    -            stl->facet_start[i].vertex[0].y,
    -            stl->facet_start[i].vertex[0].z, color.x, color.y, color.z);
    +            stl->facet_start[i].vertex[0](0),
    +            stl->facet_start[i].vertex[0](1),
    +            stl->facet_start[i].vertex[0](2), color(0), color(1), color(2));
         fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n",
    -            stl->facet_start[i].vertex[1].x,
    -            stl->facet_start[i].vertex[1].y,
    -            stl->facet_start[i].vertex[1].z, color.x, color.y, color.z);
    +            stl->facet_start[i].vertex[1](0),
    +            stl->facet_start[i].vertex[1](1),
    +            stl->facet_start[i].vertex[1](2), color(0), color(1), color(2));
         fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n",
    -            stl->facet_start[i].vertex[2].x,
    -            stl->facet_start[i].vertex[2].y,
    -            stl->facet_start[i].vertex[2].z, color.x, color.y, color.z);
    +            stl->facet_start[i].vertex[2](0),
    +            stl->facet_start[i].vertex[2](1),
    +            stl->facet_start[i].vertex[2](2), color(0), color(1), color(2));
         fprintf(fp, "%f %f %f    %1.1f %1.1f %1.1f 1\n",
    -            stl->facet_start[i].vertex[2].x,
    -            stl->facet_start[i].vertex[2].y,
    -            stl->facet_start[i].vertex[2].z, color.x, color.y, color.z);
    +            stl->facet_start[i].vertex[2](0),
    +            stl->facet_start[i].vertex[2](1),
    +            stl->facet_start[i].vertex[2](2), color(0), color(1), color(2));
       }
       fclose(fp);
     }
    @@ -409,17 +396,17 @@ stl_write_dxf(stl_file *stl, char *file, char *label) {
       for(i = 0; i < stl->stats.number_of_facets; i++) {
         fprintf(fp, "0\n3DFACE\n8\n0\n");
         fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n",
    -            stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y,
    -            stl->facet_start[i].vertex[0].z);
    +            stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1),
    +            stl->facet_start[i].vertex[0](2));
         fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n",
    -            stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y,
    -            stl->facet_start[i].vertex[1].z);
    +            stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1),
    +            stl->facet_start[i].vertex[1](2));
         fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n",
    -            stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y,
    -            stl->facet_start[i].vertex[2].z);
    +            stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1),
    +            stl->facet_start[i].vertex[2](2));
         fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n",
    -            stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y,
    -            stl->facet_start[i].vertex[2].z);
    +            stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1),
    +            stl->facet_start[i].vertex[2](2));
       }
     
       fprintf(fp, "0\nENDSEC\n0\nEOF\n");
    diff --git a/xs/src/admesh/stlinit.cpp b/xs/src/admesh/stlinit.cpp
    index e572ce9303..6bb4bf6338 100644
    --- a/xs/src/admesh/stlinit.cpp
    +++ b/xs/src/admesh/stlinit.cpp
    @@ -40,7 +40,7 @@ stl_open(stl_file *stl, const char *file) {
       stl_initialize(stl);
       stl_count_facets(stl, file);
       stl_allocate(stl);
    -  stl_read(stl, 0, 1);
    +  stl_read(stl, 0, true);
       if (!stl->error) fclose(stl->fp);
     }
     
    @@ -227,7 +227,7 @@ stl_open_merge(stl_file *stl, char *file_to_merge) {
          Start at num_facets_so_far, the index to the first unused facet.  Also say
          that this isn't our first time so we should augment stats like min and max
          instead of erasing them. */
    -  stl_read(stl, num_facets_so_far, 0);
    +  stl_read(stl, num_facets_so_far, false);
     
       /* Restore the stl information we overwrote (for stl_read) so that it still accurately
          reflects the subject part: */
    @@ -255,8 +255,7 @@ stl_reallocate(stl_file *stl) {
     /* Reads the contents of the file pointed to by stl->fp into the stl structure,
        starting at facet first_facet.  The second argument says if it's our first
        time running this for the stl and therefore we should reset our max and min stats. */
    -void
    -stl_read(stl_file *stl, int first_facet, int first) {
    +void stl_read(stl_file *stl, int first_facet, bool first) {
       stl_facet facet;
       int   i;
     
    @@ -294,11 +293,11 @@ stl_read(stl_file *stl, int first_facet, int first) {
           assert(res_normal == 3);
           int res_outer_loop = fscanf(stl->fp, " outer loop");
           assert(res_outer_loop == 0);
    -      int res_vertex1    = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z);
    +      int res_vertex1    = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2));
           assert(res_vertex1 == 3);
    -      int res_vertex2    = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z);
    +      int res_vertex2    = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2));
           assert(res_vertex2 == 3);
    -      int res_vertex3    = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z);
    +      int res_vertex3    = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2));
           assert(res_vertex3 == 3);
           int res_endloop    = fscanf(stl->fp, " endloop");
           assert(res_endloop == 0);
    @@ -311,9 +310,9 @@ stl_read(stl_file *stl, int first_facet, int first) {
           }
     
           // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
    -	  if (sscanf(normal_buf[0], "%f", &facet.normal.x) != 1 ||
    -		  sscanf(normal_buf[1], "%f", &facet.normal.y) != 1 ||
    -		  sscanf(normal_buf[2], "%f", &facet.normal.z) != 1) {
    +	  if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 ||
    +		  sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 ||
    +		  sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
     		  // Normal was mangled. Maybe denormals or "not a number" were stored?
     		  // Just reset the normal and silently ignore it.
     		  memset(&facet.normal, 0, sizeof(facet.normal));
    @@ -326,104 +325,45 @@ stl_read(stl_file *stl, int first_facet, int first) {
           // It may be worth to round these numbers to zero during loading to reduce the number of errors reported
           // during the STL import.
           for (size_t j = 0; j < 3; ++ j) {
    -        if (facet.vertex[j].x > -1e-12f && facet.vertex[j].x < 1e-12f)
    -            printf("stl_read: facet %d.x = %e\r\n", j, facet.vertex[j].x);
    -        if (facet.vertex[j].y > -1e-12f && facet.vertex[j].y < 1e-12f)
    -            printf("stl_read: facet %d.y = %e\r\n", j, facet.vertex[j].y);
    -        if (facet.vertex[j].z > -1e-12f && facet.vertex[j].z < 1e-12f)
    -            printf("stl_read: facet %d.z = %e\r\n", j, facet.vertex[j].z);
    +        if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f)
    +            printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0));
    +        if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f)
    +            printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1));
    +        if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f)
    +            printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2));
           }
     #endif
     
    -#if 1
    -    {
    -      // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit.
    -      // When using a memcmp on raw floats, those numbers report to be different.
    -      // Unify all +0 and -0 to +0 to make the floats equal under memcmp.
    -      uint32_t *f = (uint32_t*)&facet;
    -      for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats
    -        if (*f == 0x80000000)
    -          // Negative zero, switch to positive zero.
    -          *f = 0;
    -    }
    -#else
    -    {
    -      // Due to the nature of the floating point numbers, close to zero values may be represented with singificantly higher precision 
    -      // than the rest of the vertices. Round them to zero.
    -      float *f = (float*)&facet;
    -      for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats
    -        if (*f > -1e-12f && *f < 1e-12f)
    -          // Negative zero, switch to positive zero.
    -          *f = 0;
    -    }
    -#endif
         /* Write the facet into memory. */
    -    memcpy(stl->facet_start+i, &facet, SIZEOF_STL_FACET);
    +    stl->facet_start[i] = facet;
         stl_facet_stats(stl, facet, first);
    -    first = 0;
       }
    -  stl->stats.size.x = stl->stats.max.x - stl->stats.min.x;
    -  stl->stats.size.y = stl->stats.max.y - stl->stats.min.y;
    -  stl->stats.size.z = stl->stats.max.z - stl->stats.min.z;
    -  stl->stats.bounding_diameter = sqrt(
    -                                   stl->stats.size.x * stl->stats.size.x +
    -                                   stl->stats.size.y * stl->stats.size.y +
    -                                   stl->stats.size.z * stl->stats.size.z
    -                                 );
    +  stl->stats.size = stl->stats.max - stl->stats.min;
    +  stl->stats.bounding_diameter = stl->stats.size.norm();
     }
     
    -void
    -stl_facet_stats(stl_file *stl, stl_facet facet, int first) {
    -  float diff_x;
    -  float diff_y;
    -  float diff_z;
    -  float max_diff;
    +void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first)
    +{
    +  if (stl->error)
    +  	return;
     
    -  if (stl->error) return;
    +  // While we are going through all of the facets, let's find the
    +  // maximum and minimum values for x, y, and z
     
    -  /* while we are going through all of the facets, let's find the  */
    -  /* maximum and minimum values for x, y, and z  */
    -
    -  /* Initialize the max and min values the first time through*/
       if (first) {
    -    stl->stats.max.x = facet.vertex[0].x;
    -    stl->stats.min.x = facet.vertex[0].x;
    -    stl->stats.max.y = facet.vertex[0].y;
    -    stl->stats.min.y = facet.vertex[0].y;
    -    stl->stats.max.z = facet.vertex[0].z;
    -    stl->stats.min.z = facet.vertex[0].z;
    -
    -    diff_x = ABS(facet.vertex[0].x - facet.vertex[1].x);
    -    diff_y = ABS(facet.vertex[0].y - facet.vertex[1].y);
    -    diff_z = ABS(facet.vertex[0].z - facet.vertex[1].z);
    -    max_diff = STL_MAX(diff_x, diff_y);
    -    max_diff = STL_MAX(diff_z, max_diff);
    -    stl->stats.shortest_edge = max_diff;
    -
    -    first = 0;
    +	// Initialize the max and min values the first time through
    +    stl->stats.min = facet.vertex[0];
    +    stl->stats.max = facet.vertex[0];
    +    stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs();
    +    stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2)));
    +    first = false;
       }
     
    -  /* now find the max and min values */
    -  stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[0].x);
    -  stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[0].x);
    -  stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[0].y);
    -  stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[0].y);
    -  stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[0].z);
    -  stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[0].z);
    -
    -  stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[1].x);
    -  stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[1].x);
    -  stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[1].y);
    -  stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[1].y);
    -  stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[1].z);
    -  stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[1].z);
    -
    -  stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[2].x);
    -  stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[2].x);
    -  stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[2].y);
    -  stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[2].y);
    -  stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[2].z);
    -  stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[2].z);
    +  // Now find the max and min values.
    +  for (size_t i = 0; i < 3; ++ i) {
    +  	stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]);
    +  	stl->stats.max = stl->stats.max.cwiseMin(facet.vertex[i]);
    +  }
     }
     
     void
    diff --git a/xs/src/admesh/util.cpp b/xs/src/admesh/util.cpp
    index f3bf59b56e..fba1ee76bf 100644
    --- a/xs/src/admesh/util.cpp
    +++ b/xs/src/admesh/util.cpp
    @@ -62,7 +62,7 @@ stl_verify_neighbors(stl_file *stl) {
             edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3];
             edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3];
           }
    -      if(memcmp(&edge_a, &edge_b, SIZEOF_EDGE_SORT) != 0) {
    +      if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) {
             /* These edges should match but they don't.  Print results. */
             printf("edge %d of facet %d doesn't match edge %d of facet %d\n",
                    j, i, vnot + 1, neighbor);
    @@ -73,114 +73,67 @@ stl_verify_neighbors(stl_file *stl) {
       }
     }
     
    -void
    -stl_translate(stl_file *stl, float x, float y, float z) {
    -  int i;
    -  int j;
    -
    -  if (stl->error) return;
    -
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    -    for(j = 0; j < 3; j++) {
    -      stl->facet_start[i].vertex[j].x -= (stl->stats.min.x - x);
    -      stl->facet_start[i].vertex[j].y -= (stl->stats.min.y - y);
    -      stl->facet_start[i].vertex[j].z -= (stl->stats.min.z - z);
    -    }
    -  }
    -  stl->stats.max.x -= (stl->stats.min.x - x);
    -  stl->stats.max.y -= (stl->stats.min.y - y);
    -  stl->stats.max.z -= (stl->stats.min.z - z);
    -  stl->stats.min.x = x;
    -  stl->stats.min.y = y;
    -  stl->stats.min.z = z;
    +void stl_translate(stl_file *stl, float x, float y, float z)
    +{
    +  if (stl->error)
    +  	return;
     
    +  stl_vertex new_min(x, y, z);
    +  stl_vertex shift = new_min - stl->stats.min;
    +  for (int i = 0; i < stl->stats.number_of_facets; ++ i)
    +    for (int j = 0; j < 3; ++ j)
    +      stl->facet_start[i].vertex[j] += shift;
    +  stl->stats.min = new_min;
    +  stl->stats.max += shift;
       stl_invalidate_shared_vertices(stl);
     }
     
     /* Translates the stl by x,y,z, relatively from wherever it is currently */
    -void
    -stl_translate_relative(stl_file *stl, float x, float y, float z) {
    -  int i;
    -  int j;
    -
    -  if (stl->error) return;
    -
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    -    for(j = 0; j < 3; j++) {
    -      stl->facet_start[i].vertex[j].x += x;
    -      stl->facet_start[i].vertex[j].y += y;
    -      stl->facet_start[i].vertex[j].z += z;
    -    }
    -  }
    -  stl->stats.min.x += x;
    -  stl->stats.min.y += y;
    -  stl->stats.min.z += z;
    -  stl->stats.max.x += x;
    -  stl->stats.max.y += y;
    -  stl->stats.max.z += z;
    +void stl_translate_relative(stl_file *stl, float x, float y, float z)
    +{
    +  if (stl->error)
    +  	return;
     
    +  stl_vertex shift(x, y, z);
    +  for (int i = 0; i < stl->stats.number_of_facets; ++ i)
    +    for (int j = 0; j < 3; ++ j)
    +      stl->facet_start[i].vertex[j] += shift;
    +  stl->stats.min += shift;
    +  stl->stats.max += shift;
       stl_invalidate_shared_vertices(stl);
     }
     
    -void
    -stl_scale_versor(stl_file *stl, float versor[3]) {
    -  int i;
    -  int j;
    -
    -  if (stl->error) return;
    -
    -  /* scale extents */
    -  stl->stats.min.x *= versor[0];
    -  stl->stats.min.y *= versor[1];
    -  stl->stats.min.z *= versor[2];
    -  stl->stats.max.x *= versor[0];
    -  stl->stats.max.y *= versor[1];
    -  stl->stats.max.z *= versor[2];
    -
    -  /* scale size */
    -  stl->stats.size.x *= versor[0];
    -  stl->stats.size.y *= versor[1];
    -  stl->stats.size.z *= versor[2];
    -
    -  /* scale volume */
    -  if (stl->stats.volume > 0.0) {
    -    stl->stats.volume *= (versor[0] * versor[1] * versor[2]);
    -  }
    -
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    -    for(j = 0; j < 3; j++) {
    -      stl->facet_start[i].vertex[j].x *= versor[0];
    -      stl->facet_start[i].vertex[j].y *= versor[1];
    -      stl->facet_start[i].vertex[j].z *= versor[2];
    -    }
    -  }
    +void stl_scale_versor(stl_file *stl, const stl_vertex &versor)
    +{
    +  if (stl->error)
    +  	return;
     
    +  // Scale extents.
    +  auto s = versor.array();
    +  stl->stats.min.array() *= s;
    +  stl->stats.max.array() *= s;
    +  // Scale size.
    +  stl->stats.size.array() *= s;
    +  // Scale volume.
    +  if (stl->stats.volume > 0.0)
    +    stl->stats.volume *= versor(0) * versor(1) * versor(2);
    +  // Scale the mesh.
    +  for (int i = 0; i < stl->stats.number_of_facets; ++ i)
    +    for (int j = 0; j < 3; ++ j)
    +      stl->facet_start[i].vertex[j].array() *= s;
       stl_invalidate_shared_vertices(stl);
     }
     
    -void
    -stl_scale(stl_file *stl, float factor) {
    -  float versor[3];
    -
    -  if (stl->error) return;
    -
    -  versor[0] = factor;
    -  versor[1] = factor;
    -  versor[2] = factor;
    -  stl_scale_versor(stl, versor);
    -}
    -
    -static void calculate_normals(stl_file *stl) {
    -  float normal[3];
    -
    -  if (stl->error) return;
    +static void calculate_normals(stl_file *stl) 
    +{
    +  if (stl->error)
    +  	return;
     
    +  stl_normal normal;
       for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) {
         stl_calculate_normal(normal, &stl->facet_start[i]);
         stl_normalize_vector(normal);
    -    stl->facet_start[i].normal.x = normal[0];
    -    stl->facet_start[i].normal.y = normal[1];
    -    stl->facet_start[i].normal.z = normal[2];
    +    stl->facet_start[i].normal = normal;
       }
     }
     
    @@ -193,9 +146,9 @@ void stl_transform(stl_file *stl, float *trafo3x4) {
         for (i_vertex = 0; i_vertex < 3; ++ i_vertex) {
           stl_vertex &v_dst = vertices[i_vertex];
           stl_vertex  v_src = v_dst;
    -      v_dst.x = trafo3x4[0] * v_src.x + trafo3x4[1] * v_src.y + trafo3x4[2]  * v_src.z + trafo3x4[3];
    -      v_dst.y = trafo3x4[4] * v_src.x + trafo3x4[5] * v_src.y + trafo3x4[6]  * v_src.z + trafo3x4[7];
    -      v_dst.z = trafo3x4[8] * v_src.x + trafo3x4[9] * v_src.y + trafo3x4[10] * v_src.z + trafo3x4[11];
    +      v_dst(0) = trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2]  * v_src(2) + trafo3x4[3];
    +      v_dst(1) = trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6]  * v_src(2) + trafo3x4[7];
    +      v_dst(2) = trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11];
         }
       }
       stl_get_size(stl);
    @@ -214,8 +167,8 @@ stl_rotate_x(stl_file *stl, float angle) {
     
       for(i = 0; i < stl->stats.number_of_facets; i++) {
         for(j = 0; j < 3; j++) {
    -      stl_rotate(&stl->facet_start[i].vertex[j].y,
    -                 &stl->facet_start[i].vertex[j].z, c, s);
    +      stl_rotate(&stl->facet_start[i].vertex[j](1),
    +                 &stl->facet_start[i].vertex[j](2), c, s);
         }
       }
       stl_get_size(stl);
    @@ -234,8 +187,8 @@ stl_rotate_y(stl_file *stl, float angle) {
     
       for(i = 0; i < stl->stats.number_of_facets; i++) {
         for(j = 0; j < 3; j++) {
    -      stl_rotate(&stl->facet_start[i].vertex[j].z,
    -                 &stl->facet_start[i].vertex[j].x, c, s);
    +      stl_rotate(&stl->facet_start[i].vertex[j](2),
    +                 &stl->facet_start[i].vertex[j](0), c, s);
         }
       }
       stl_get_size(stl);
    @@ -254,8 +207,8 @@ stl_rotate_z(stl_file *stl, float angle) {
     
       for(i = 0; i < stl->stats.number_of_facets; i++) {
         for(j = 0; j < 3; j++) {
    -      stl_rotate(&stl->facet_start[i].vertex[j].x,
    -                 &stl->facet_start[i].vertex[j].y, c, s);
    +      stl_rotate(&stl->facet_start[i].vertex[j](0),
    +                 &stl->facet_start[i].vertex[j](1), c, s);
         }
       }
       stl_get_size(stl);
    @@ -272,142 +225,98 @@ stl_rotate(float *x, float *y, const double c, const double s) {
       *y = float(s * xold + c * yold);
     }
     
    -extern void
    -stl_get_size(stl_file *stl) {
    -  int i;
    -  int j;
    -
    -  if (stl->error) return;
    -  if (stl->stats.number_of_facets == 0) return;
    -
    -  stl->stats.min.x = stl->facet_start[0].vertex[0].x;
    -  stl->stats.min.y = stl->facet_start[0].vertex[0].y;
    -  stl->stats.min.z = stl->facet_start[0].vertex[0].z;
    -  stl->stats.max.x = stl->facet_start[0].vertex[0].x;
    -  stl->stats.max.y = stl->facet_start[0].vertex[0].y;
    -  stl->stats.max.z = stl->facet_start[0].vertex[0].z;
    -
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    -    for(j = 0; j < 3; j++) {
    -      stl->stats.min.x = STL_MIN(stl->stats.min.x,
    -                                 stl->facet_start[i].vertex[j].x);
    -      stl->stats.min.y = STL_MIN(stl->stats.min.y,
    -                                 stl->facet_start[i].vertex[j].y);
    -      stl->stats.min.z = STL_MIN(stl->stats.min.z,
    -                                 stl->facet_start[i].vertex[j].z);
    -      stl->stats.max.x = STL_MAX(stl->stats.max.x,
    -                                 stl->facet_start[i].vertex[j].x);
    -      stl->stats.max.y = STL_MAX(stl->stats.max.y,
    -                                 stl->facet_start[i].vertex[j].y);
    -      stl->stats.max.z = STL_MAX(stl->stats.max.z,
    -                                 stl->facet_start[i].vertex[j].z);
    +void stl_get_size(stl_file *stl)
    +{
    +  if (stl->error || stl->stats.number_of_facets == 0)
    +  	return;
    +  stl->stats.min = stl->facet_start[0].vertex[0];
    +  stl->stats.max = stl->stats.min;
    +  for (int i = 0; i < stl->stats.number_of_facets; ++ i) {
    +  	const stl_facet &face = stl->facet_start[i];
    +    for (int j = 0; j < 3; ++ j) {
    +      stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]);
    +      stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]);
         }
       }
    -  stl->stats.size.x = stl->stats.max.x - stl->stats.min.x;
    -  stl->stats.size.y = stl->stats.max.y - stl->stats.min.y;
    -  stl->stats.size.z = stl->stats.max.z - stl->stats.min.z;
    -  stl->stats.bounding_diameter = sqrt(
    -                                   stl->stats.size.x * stl->stats.size.x +
    -                                   stl->stats.size.y * stl->stats.size.y +
    -                                   stl->stats.size.z * stl->stats.size.z
    -                                 );
    +  stl->stats.size = stl->stats.max - stl->stats.min;
    +  stl->stats.bounding_diameter = stl->stats.size.norm();
     }
     
    -void
    -stl_mirror_xy(stl_file *stl) {
    -  int i;
    -  int j;
    -  float temp_size;
    +void stl_mirror_xy(stl_file *stl)
    +{
    +  if (stl->error) 
    +  	return;
     
    -  if (stl->error) return;
    -
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    -    for(j = 0; j < 3; j++) {
    -      stl->facet_start[i].vertex[j].z *= -1.0;
    +  for(int i = 0; i < stl->stats.number_of_facets; i++) {
    +    for(int j = 0; j < 3; j++) {
    +      stl->facet_start[i].vertex[j](2) *= -1.0;
         }
       }
    -  temp_size = stl->stats.min.z;
    -  stl->stats.min.z = stl->stats.max.z;
    -  stl->stats.max.z = temp_size;
    -  stl->stats.min.z *= -1.0;
    -  stl->stats.max.z *= -1.0;
    +  float temp_size = stl->stats.min(2);
    +  stl->stats.min(2) = stl->stats.max(2);
    +  stl->stats.max(2) = temp_size;
    +  stl->stats.min(2) *= -1.0;
    +  stl->stats.max(2) *= -1.0;
       stl_reverse_all_facets(stl);
       stl->stats.facets_reversed -= stl->stats.number_of_facets;  /* for not altering stats */
     }
     
    -void
    -stl_mirror_yz(stl_file *stl) {
    -  int i;
    -  int j;
    -  float temp_size;
    -
    +void stl_mirror_yz(stl_file *stl)
    +{
       if (stl->error) return;
     
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    -    for(j = 0; j < 3; j++) {
    -      stl->facet_start[i].vertex[j].x *= -1.0;
    +  for (int i = 0; i < stl->stats.number_of_facets; i++) {
    +    for (int j = 0; j < 3; j++) {
    +      stl->facet_start[i].vertex[j](0) *= -1.0;
         }
       }
    -  temp_size = stl->stats.min.x;
    -  stl->stats.min.x = stl->stats.max.x;
    -  stl->stats.max.x = temp_size;
    -  stl->stats.min.x *= -1.0;
    -  stl->stats.max.x *= -1.0;
    +  float temp_size = stl->stats.min(0);
    +  stl->stats.min(0) = stl->stats.max(0);
    +  stl->stats.max(0) = temp_size;
    +  stl->stats.min(0) *= -1.0;
    +  stl->stats.max(0) *= -1.0;
       stl_reverse_all_facets(stl);
       stl->stats.facets_reversed -= stl->stats.number_of_facets;  /* for not altering stats */
     }
     
    -void
    -stl_mirror_xz(stl_file *stl) {
    -  int i;
    -  int j;
    -  float temp_size;
    +void stl_mirror_xz(stl_file *stl)
    +{
    +  if (stl->error)
    +  	return;
     
    -  if (stl->error) return;
    -
    -  for(i = 0; i < stl->stats.number_of_facets; i++) {
    -    for(j = 0; j < 3; j++) {
    -      stl->facet_start[i].vertex[j].y *= -1.0;
    +  for (int i = 0; i < stl->stats.number_of_facets; i++) {
    +    for (int j = 0; j < 3; j++) {
    +      stl->facet_start[i].vertex[j](1) *= -1.0;
         }
       }
    -  temp_size = stl->stats.min.y;
    -  stl->stats.min.y = stl->stats.max.y;
    -  stl->stats.max.y = temp_size;
    -  stl->stats.min.y *= -1.0;
    -  stl->stats.max.y *= -1.0;
    +  float temp_size = stl->stats.min(1);
    +  stl->stats.min(1) = stl->stats.max(1);
    +  stl->stats.max(1) = temp_size;
    +  stl->stats.min(1) *= -1.0;
    +  stl->stats.max(1) *= -1.0;
       stl_reverse_all_facets(stl);
       stl->stats.facets_reversed -= stl->stats.number_of_facets;  /* for not altering stats */
     }
     
    -static float get_volume(stl_file *stl) {
    -  stl_vertex p0;
    -  stl_vertex p;
    -  stl_normal n;
    -  float height;
    -  float area;
    -  float volume = 0.0;
    +static float get_volume(stl_file *stl)
    +{
    +  if (stl->error)
    +  	return 0;
     
    -  if (stl->error) return 0;
    -
    -  /* Choose a point, any point as the reference */
    -  p0.x = stl->facet_start[0].vertex[0].x;
    -  p0.y = stl->facet_start[0].vertex[0].y;
    -  p0.z = stl->facet_start[0].vertex[0].z;
    -
    -  for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) {
    -    p.x = stl->facet_start[i].vertex[0].x - p0.x;
    -    p.y = stl->facet_start[i].vertex[0].y - p0.y;
    -    p.z = stl->facet_start[i].vertex[0].z - p0.z;
    -    /* Do dot product to get distance from point to plane */
    -    n = stl->facet_start[i].normal;
    -    height = (n.x * p.x) + (n.y * p.y) + (n.z * p.z);
    -    area = get_area(&stl->facet_start[i]);
    +  // Choose a point, any point as the reference.
    +  stl_vertex p0 = stl->facet_start[0].vertex[0];
    +  float volume = 0.f;
    +  for(uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) {
    +    // Do dot product to get distance from point to plane.
    +    float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0);
    +    float area   = get_area(&stl->facet_start[i]);
         volume += (area * height) / 3.0f;
       }
       return volume;
     }
     
    -void stl_calculate_volume(stl_file *stl) {
    +void stl_calculate_volume(stl_file *stl)
    +{
       if (stl->error) return;
       stl->stats.volume = get_volume(stl);
       if(stl->stats.volume < 0.0) {
    @@ -416,35 +325,32 @@ void stl_calculate_volume(stl_file *stl) {
       }
     }
     
    -static float get_area(stl_facet *facet) {
    -  double cross[3][3];
    -  float sum[3];
    -  float n[3];
    -  float area;
    -  int i;
    -
    +static float get_area(stl_facet *facet)
    +{
       /* cast to double before calculating cross product because large coordinates
          can result in overflowing product
         (bad area is responsible for bad volume and bad facets reversal) */
    -  for(i = 0; i < 3; i++) {
    -    cross[i][0]=(((double)facet->vertex[i].y * (double)facet->vertex[(i + 1) % 3].z) -
    -                 ((double)facet->vertex[i].z * (double)facet->vertex[(i + 1) % 3].y));
    -    cross[i][1]=(((double)facet->vertex[i].z * (double)facet->vertex[(i + 1) % 3].x) -
    -                 ((double)facet->vertex[i].x * (double)facet->vertex[(i + 1) % 3].z));
    -    cross[i][2]=(((double)facet->vertex[i].x * (double)facet->vertex[(i + 1) % 3].y) -
    -                 ((double)facet->vertex[i].y * (double)facet->vertex[(i + 1) % 3].x));
    +  double cross[3][3];
    +  for (int i = 0; i < 3; i++) {
    +    cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) -
    +                 ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1)));
    +    cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) -
    +                 ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2)));
    +    cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) -
    +                 ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0)));
       }
     
    -  sum[0] = cross[0][0] + cross[1][0] + cross[2][0];
    -  sum[1] = cross[0][1] + cross[1][1] + cross[2][1];
    -  sum[2] = cross[0][2] + cross[1][2] + cross[2][2];
    +  stl_normal sum;
    +  sum(0) = cross[0][0] + cross[1][0] + cross[2][0];
    +  sum(1) = cross[0][1] + cross[1][1] + cross[2][1];
    +  sum(2) = cross[0][2] + cross[1][2] + cross[2][2];
     
    -  /* This should already be done.  But just in case, let's do it again */
    +  // This should already be done.  But just in case, let's do it again.
    +  //FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy.
    +  stl_normal n;
       stl_calculate_normal(n, facet);
       stl_normalize_vector(n);
    -
    -  area = 0.5 * (n[0] * sum[0] + n[1] * sum[1] + n[2] * sum[2]);
    -  return area;
    +  return 0.5f * n.dot(sum);
     }
     
     void stl_repair(stl_file *stl,
    diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp
    index cd6f45c704..6337601cd5 100644
    --- a/xs/src/libslic3r/Format/3mf.cpp
    +++ b/xs/src/libslic3r/Format/3mf.cpp
    @@ -1485,7 +1485,7 @@ namespace Slic3r {
                     stl_facet& facet = stl.facet_start[i];
                     for (unsigned int v = 0; v < 3; ++v)
                     {
    -                    ::memcpy((void*)&facet.vertex[v].x, (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float));
    +                    ::memcpy(facet.vertex[v].data(), (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float));
                     }
                 }
     
    @@ -1844,9 +1844,9 @@ namespace Slic3r {
                 for (int i = 0; i < stl.stats.shared_vertices; ++i)
                 {
                     stream << "     <" << VERTEX_TAG << " ";
    -                stream << "x=\"" << stl.v_shared[i].x << "\" ";
    -                stream << "y=\"" << stl.v_shared[i].y << "\" ";
    -                stream << "z=\"" << stl.v_shared[i].z << "\" />\n";
    +                stream << "x=\"" << stl.v_shared[i](0) << "\" ";
    +                stream << "y=\"" << stl.v_shared[i](1) << "\" ";
    +                stream << "z=\"" << stl.v_shared[i](2) << "\" />\n";
                 }
             }
     
    diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp
    index 1ad0c082aa..136b230605 100644
    --- a/xs/src/libslic3r/Format/AMF.cpp
    +++ b/xs/src/libslic3r/Format/AMF.cpp
    @@ -402,7 +402,7 @@ void AMFParserContext::endElement(const char * /* name */)
             for (size_t i = 0; i < m_volume_facets.size();) {
                 stl_facet &facet = stl.facet_start[i/3];
                 for (unsigned int v = 0; v < 3; ++ v)
    -                memcpy(&facet.vertex[v].x, &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float));
    +                memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float));
             }
             stl_get_size(&stl);
             m_volume->mesh.repair();
    @@ -760,9 +760,9 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
                 for (size_t i = 0; i < stl.stats.shared_vertices; ++ i) {
                     stream << "         \n";
                     stream << "           \n";
    -                stream << "             " << stl.v_shared[i].x << "\n";
    -                stream << "             " << stl.v_shared[i].y << "\n";
    -                stream << "             " << stl.v_shared[i].z << "\n";
    +                stream << "             " << stl.v_shared[i](0) << "\n";
    +                stream << "             " << stl.v_shared[i](1) << "\n";
    +                stream << "             " << stl.v_shared[i](2) << "\n";
                     stream << "           \n";
                     stream << "         \n";
                 }
    diff --git a/xs/src/libslic3r/Format/OBJ.cpp b/xs/src/libslic3r/Format/OBJ.cpp
    index ea6b5604c4..ee5756083d 100644
    --- a/xs/src/libslic3r/Format/OBJ.cpp
    +++ b/xs/src/libslic3r/Format/OBJ.cpp
    @@ -57,14 +57,14 @@ bool load_obj(const char *path, Model *model, const char *object_name_in)
                 continue;
             stl_facet &facet = stl.facet_start[i_face ++];
             size_t     num_normals = 0;
    -        stl_normal normal = { 0.f };
    +        stl_normal normal(stl_normal::Zero());
             for (unsigned int v = 0; v < 3; ++ v) {
                 const ObjParser::ObjVertex &vertex = data.vertices[i++];
    -            memcpy(&facet.vertex[v].x, &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float));
    +            memcpy(facet.vertex[v].data(), &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float));
                 if (vertex.normalIdx != -1) {
    -                normal.x += data.normals[vertex.normalIdx*3];
    -                normal.y += data.normals[vertex.normalIdx*3+1];
    -                normal.z += data.normals[vertex.normalIdx*3+2];
    +                normal(0) += data.normals[vertex.normalIdx*3];
    +                normal(1) += data.normals[vertex.normalIdx*3+1];
    +                normal(2) += data.normals[vertex.normalIdx*3+2];
                     ++ num_normals;
                 }
             }
    @@ -74,33 +74,27 @@ bool load_obj(const char *path, Model *model, const char *object_name_in)
                 facet2.vertex[0] = facet.vertex[0];
                 facet2.vertex[1] = facet.vertex[2];
     			const ObjParser::ObjVertex &vertex = data.vertices[i++];
    -			memcpy(&facet2.vertex[2].x, &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
    +			memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
     			if (vertex.normalIdx != -1) {
    -                normal.x += data.normals[vertex.normalIdx*3];
    -                normal.y += data.normals[vertex.normalIdx*3+1];
    -                normal.z += data.normals[vertex.normalIdx*3+2];
    +                normal(0) += data.normals[vertex.normalIdx*3];
    +                normal(1) += data.normals[vertex.normalIdx*3+1];
    +                normal(2) += data.normals[vertex.normalIdx*3+2];
                     ++ num_normals;
                 }
                 if (num_normals == 4) {
                     // Normalize an average normal of a quad.
    -                float len = sqrt(facet.normal.x*facet.normal.x + facet.normal.y*facet.normal.y + facet.normal.z*facet.normal.z);
    +                float len = facet.normal.norm();
                     if (len > EPSILON) {
    -                    normal.x /= len;
    -                    normal.y /= len;
    -                    normal.z /= len;
    +                    normal /= len;
                         facet.normal = normal;
                         facet2.normal = normal;
                     }
                 }
             } else if (num_normals == 3) {
                 // Normalize an average normal of a triangle.
    -            float len = sqrt(facet.normal.x*facet.normal.x + facet.normal.y*facet.normal.y + facet.normal.z*facet.normal.z);
    -            if (len > EPSILON) {
    -                normal.x /= len;
    -                normal.y /= len;
    -                normal.z /= len;
    -                facet.normal = normal;
    -            }
    +            float len = facet.normal.norm();
    +            if (len > EPSILON)
    +                facet.normal = normal / len;
             }
     	}
         stl_get_size(&stl);
    diff --git a/xs/src/libslic3r/Format/PRUS.cpp b/xs/src/libslic3r/Format/PRUS.cpp
    index 9f42fc151b..3cf3fc075d 100644
    --- a/xs/src/libslic3r/Format/PRUS.cpp
    +++ b/xs/src/libslic3r/Format/PRUS.cpp
    @@ -260,8 +260,8 @@ bool load_prus(const char *path, Model *model)
     							mesh.repair();
     							// Transform the model.
     							stl_transform(&stl, &trafo[0][0]);
    -							if (std::abs(stl.stats.min.z) < EPSILON)
    -								stl.stats.min.z = 0.;
    +							if (std::abs(stl.stats.min(2)) < EPSILON)
    +								stl.stats.min(2) = 0.;
     							// Add a mesh to a model.
     							if (mesh.facets_count() > 0)
                                     mesh_valid = true;
    @@ -309,11 +309,11 @@ bool load_prus(const char *path, Model *model)
     						assert(res_normal == 3);
                             int res_outer_loop	= line_reader.next_line_scanf(" outer loop");
     						assert(res_outer_loop == 0);
    -						int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z);
    +						int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2));
     						assert(res_vertex1 == 3);
    -						int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z);
    +						int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2));
     						assert(res_vertex2 == 3);
    -						int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z);
    +						int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2));
     						assert(res_vertex3 == 3);
     						int res_endloop = line_reader.next_line_scanf(" endloop");
     						assert(res_endloop == 0);
    @@ -324,9 +324,9 @@ bool load_prus(const char *path, Model *model)
                                 break;
                             }
                             // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
    -                        if (sscanf(normal_buf[0], "%f", &facet.normal.x) != 1 ||
    -                            sscanf(normal_buf[1], "%f", &facet.normal.y) != 1 ||
    -                            sscanf(normal_buf[2], "%f", &facet.normal.z) != 1) {
    +                        if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 ||
    +                            sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 ||
    +                            sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
                                 // Normal was mangled. Maybe denormals or "not a number" were stored?
                                 // Just reset the normal and silently ignore it.
                                 memset(&facet.normal, 0, sizeof(facet.normal));
    diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
    index e0f2264763..60f949837d 100644
    --- a/xs/src/libslic3r/Model.cpp
    +++ b/xs/src/libslic3r/Model.cpp
    @@ -642,24 +642,13 @@ BoundingBoxf3 ModelObject::tight_bounding_box(bool include_modifiers) const
                         {
                             // original point
                             const stl_vertex& v = facet.vertex[i];
    -                        Vec3d p((double)v.x, (double)v.y, (double)v.z);
    -
                             // scale
    -                        p(0) *= inst->scaling_factor;
    -                        p(1) *= inst->scaling_factor;
    -                        p(2) *= inst->scaling_factor;
    -
    -                        // rotate Z
    -                        double x = p(0);
    -                        double y = p(1);
    -                        p(0) = c * x - s * y;
    -                        p(1) = s * x + c * y;
    -
    -                        // translate
    -                        p(0) += inst->offset(0);
    -                        p(1) += inst->offset(1);
    -
    -                        bb.merge(p);
    +                        Vec3d p = v.cast() * inst->scaling_factor;
    +                        // rotate Z, translate
    +                        Vec3d q(c * p(0) - s * p(1) + inst->offset(0),
    +                                s * p(0) + c * p(1) + inst->offset(1), 
    +                                p(2));
    +                        bb.merge(q);
                         }
                     }
                 }
    @@ -770,7 +759,7 @@ void ModelObject::rotate(float angle, const Axis &axis)
         for (ModelVolume *v : this->volumes)
         {
             v->mesh.rotate(angle, axis);
    -        min_z = std::min(min_z, v->mesh.stl.stats.min.z);
    +        min_z = std::min(min_z, v->mesh.stl.stats.min(2));
         }
     
         if (min_z != 0.0f)
    @@ -927,24 +916,13 @@ void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_
                         {
                             // original point
                             const stl_vertex& v = facet.vertex[i];
    -                        Vec3d p((double)v.x, (double)v.y, (double)v.z);
    -
                             // scale
    -                        p(0) *= inst->scaling_factor;
    -                        p(1) *= inst->scaling_factor;
    -                        p(2) *= inst->scaling_factor;
    -
    +                        Vec3d p = v.cast() * inst->scaling_factor;
                             // rotate Z
    -                        double x = p(0);
    -                        double y = p(1);
    -                        p(0) = c * x - s * y;
    -                        p(1) = s * x + c * y;
    -
    -                        // translate
    -                        p(0) += inst->offset(0);
    -                        p(1) += inst->offset(1);
    -
    -                        bb.merge(p);
    +                        bb.merge(Vec3d(
    +                            c * p(0) - s * p(1) + inst->offset(0),
    +                            s * p(0) + c * p(1) + inst->offset(1), 
    +                            p(2)));
                         }
                     }
     
    @@ -1081,7 +1059,7 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes
             const stl_facet &facet = mesh->stl.facet_start[i];
             for (int j = 0; j < 3; ++ j) {
                 const stl_vertex &v = facet.vertex[j];
    -			bbox.merge(Vec3d(c * v.x - s * v.y, s * v.x + c * v.y, v.z));
    +			bbox.merge(Vec3d(c * v(0) - s * v(1), s * v(0) + c * v(1), v(2)));
             }
         }
         if (! empty(bbox)) {
    diff --git a/xs/src/libslic3r/SlicingAdaptive.cpp b/xs/src/libslic3r/SlicingAdaptive.cpp
    index ff0da76365..2ef4aec8c6 100644
    --- a/xs/src/libslic3r/SlicingAdaptive.cpp
    +++ b/xs/src/libslic3r/SlicingAdaptive.cpp
    @@ -15,8 +15,8 @@ void SlicingAdaptive::clear()
     std::pair face_z_span(const stl_facet *f)
     {
     	return std::pair(
    -		std::min(std::min(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z),
    -		std::max(std::max(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z));
    +		std::min(std::min(f->vertex[0](2), f->vertex[1](2)), f->vertex[2](2)),
    +		std::max(std::max(f->vertex[0](2), f->vertex[1](2)), f->vertex[2](2)));
     }
     
     void SlicingAdaptive::prepare()
    @@ -40,7 +40,7 @@ void SlicingAdaptive::prepare()
     	// 3) Generate Z components of the facet normals.
     	m_face_normal_z.assign(m_faces.size(), 0.f);
         for (size_t iface = 0; iface < m_faces.size(); ++ iface)
    -    	m_face_normal_z[iface] = m_faces[iface]->normal.z;
    +    	m_face_normal_z[iface] = m_faces[iface]->normal(2);
     }
     
     float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet)
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index 782c50cf5c..3d090bb7b5 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -51,31 +51,16 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f
     
         for (int i = 0; i < stl.stats.number_of_facets; i++) {
             stl_facet facet;
    -
    -        const Vec3d& ref_f1 = points[facets[i](0)];
    -        facet.vertex[0].x = ref_f1(0);
    -        facet.vertex[0].y = ref_f1(1);
    -        facet.vertex[0].z = ref_f1(2);
    -
    -        const Vec3d& ref_f2 = points[facets[i](1)];
    -        facet.vertex[1].x = ref_f2(0);
    -        facet.vertex[1].y = ref_f2(1);
    -        facet.vertex[1].z = ref_f2(2);
    -
    -        const Vec3d& ref_f3 = points[facets[i](2)];
    -        facet.vertex[2].x = ref_f3(0);
    -        facet.vertex[2].y = ref_f3(1);
    -        facet.vertex[2].z = ref_f3(2);
    -        
    +        facet.vertex[0] = points[facets[i](0)].cast();
    +        facet.vertex[1] = points[facets[i](1)].cast();
    +        facet.vertex[2] = points[facets[i](2)].cast();
             facet.extra[0] = 0;
             facet.extra[1] = 0;
     
    -        float normal[3];
    +        stl_normal normal;
             stl_calculate_normal(normal, &facet);
             stl_normalize_vector(normal);
    -        facet.normal.x = normal[0];
    -        facet.normal.y = normal[1];
    -        facet.normal.z = normal[2];
    +        facet.normal = normal;
     
             stl.facet_start[i] = facet;
         }
    @@ -302,11 +287,7 @@ void TriangleMesh::scale(float factor)
     
     void TriangleMesh::scale(const Vec3d &versor)
     {
    -    float fversor[3];
    -    fversor[0] = versor(0);
    -    fversor[1] = versor(1);
    -    fversor[2] = versor(2);
    -    stl_scale_versor(&this->stl, fversor);
    +    stl_scale_versor(&this->stl, versor.cast());
         stl_invalidate_shared_vertices(&this->stl);
     }
     
    @@ -390,10 +371,9 @@ void TriangleMesh::transform(const float* matrix3x4)
     void TriangleMesh::align_to_origin()
     {
         this->translate(
    -        -(this->stl.stats.min.x),
    -        -(this->stl.stats.min.y),
    -        -(this->stl.stats.min.z)
    -    );
    +        - this->stl.stats.min(0),
    +        - this->stl.stats.min(1),
    +        - this->stl.stats.min(2));
     }
     
     void TriangleMesh::rotate(double angle, Point* center)
    @@ -518,7 +498,7 @@ TriangleMesh::split() const
             stl_clear_error(&mesh->stl);
             stl_allocate(&mesh->stl);
             
    -        int first = 1;
    +        bool first = true;
             for (std::deque::const_iterator facet = facets.begin(); facet != facets.end(); ++facet) {
                 mesh->stl.facet_start[facet - facets.begin()] = this->stl.facet_start[*facet];
                 stl_facet_stats(&mesh->stl, this->stl.facet_start[*facet], first);
    @@ -561,9 +541,9 @@ ExPolygons TriangleMesh::horizontal_projection() const
             stl_facet* facet = &this->stl.facet_start[i];
             Polygon p;
             p.points.resize(3);
    -        p.points[0] = Point::new_scale(facet->vertex[0].x, facet->vertex[0].y);
    -        p.points[1] = Point::new_scale(facet->vertex[1].x, facet->vertex[1].y);
    -        p.points[2] = Point::new_scale(facet->vertex[2].x, facet->vertex[2].y);
    +        p.points[0] = Point::new_scale(facet->vertex[0](0), facet->vertex[0](1));
    +        p.points[1] = Point::new_scale(facet->vertex[1](0), facet->vertex[1](1));
    +        p.points[2] = Point::new_scale(facet->vertex[2](0), facet->vertex[2](1));
             p.make_counter_clockwise();  // do this after scaling, as winding order might change while doing that
             pp.emplace_back(p);
         }
    @@ -578,8 +558,8 @@ Polygon TriangleMesh::convex_hull()
         Points pp;
         pp.reserve(this->stl.stats.shared_vertices);
         for (int i = 0; i < this->stl.stats.shared_vertices; ++ i) {
    -        stl_vertex* v = &this->stl.v_shared[i];
    -        pp.emplace_back(Point::new_scale(v->x, v->y));
    +        const stl_vertex &v = this->stl.v_shared[i];
    +        pp.emplace_back(Point::new_scale(v(0), v(1)));
         }
         return Slic3r::Geometry::convex_hull(pp);
     }
    @@ -589,12 +569,8 @@ TriangleMesh::bounding_box() const
     {
         BoundingBoxf3 bb;
         bb.defined = true;
    -    bb.min(0) = this->stl.stats.min.x;
    -    bb.min(1) = this->stl.stats.min.y;
    -    bb.min(2) = this->stl.stats.min.z;
    -    bb.max(0) = this->stl.stats.max.x;
    -    bb.max(1) = this->stl.stats.max.y;
    -    bb.max(2) = this->stl.stats.max.z;
    +    bb.min = this->stl.stats.min.cast();
    +    bb.max = this->stl.stats.max.cast();
         return bb;
     }
     
    @@ -619,11 +595,8 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
         facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1);
         v_scaled_shared.assign(_mesh->stl.v_shared, _mesh->stl.v_shared + _mesh->stl.stats.shared_vertices);
         // Scale the copied vertices.
    -    for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i) {
    -        this->v_scaled_shared[i].x /= float(SCALING_FACTOR);
    -        this->v_scaled_shared[i].y /= float(SCALING_FACTOR);
    -        this->v_scaled_shared[i].z /= float(SCALING_FACTOR);
    -    }
    +    for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i)
    +        this->v_scaled_shared[i] *= float(1. / SCALING_FACTOR);
     
         // Create a mapping from triangle edge into face.
         struct EdgeToFace {
    @@ -779,14 +752,14 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vectormesh->stl.facet_start[facet_idx];
         
         // find facet extents
    -    const float min_z = fminf(facet.vertex[0].z, fminf(facet.vertex[1].z, facet.vertex[2].z));
    -    const float max_z = fmaxf(facet.vertex[0].z, fmaxf(facet.vertex[1].z, facet.vertex[2].z));
    +    const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2)));
    +    const float max_z = fmaxf(facet.vertex[0](2), fmaxf(facet.vertex[1](2), facet.vertex[2](2)));
         
         #ifdef SLIC3R_DEBUG
         printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx,
    -        facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z,
    -        facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z,
    -        facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z);
    +        facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0](2),
    +        facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1](2),
    +        facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2](2));
         printf("z: min = %.2f, max = %.2f\n", min_z, max_z);
         #endif
         
    @@ -806,18 +779,18 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vectormesh->stl.v_indices[facet_idx].vertex;
    -                const bool reverse  = this->mesh->stl.facet_start[facet_idx].normal.z < 0;
    +                const bool reverse  = this->mesh->stl.facet_start[facet_idx].normal(2) < 0;
                     for (int j = 0; j < 3; ++ j) {
                         int               a_id     = vertices[j % 3];
                         int               b_id     = vertices[(j+1) % 3];
                         if (reverse)
                             std::swap(a_id, b_id);
    -                    const stl_vertex *a = &this->v_scaled_shared[a_id];
    -                    const stl_vertex *b = &this->v_scaled_shared[b_id];
    -                    il.a(0)    = a->x;
    -                    il.a(1)    = a->y;
    -                    il.b(0)    = b->x;
    -                    il.b(1)    = b->y;
    +                    const stl_vertex &a = this->v_scaled_shared[a_id];
    +                    const stl_vertex &b = this->v_scaled_shared[b_id];
    +                    il.a(0)    = a(0);
    +                    il.a(1)    = a(1);
    +                    il.b(0)    = b(0);
    +                    il.b(1)    = b(1);
                         il.a_id   = a_id;
                         il.b_id   = b_id;
                         (*lines)[layer_idx].emplace_back(il);
    @@ -863,66 +836,63 @@ bool TriangleMeshSlicer::slice_facet(
         // Reorder vertices so that the first one is the one with lowest Z.
         // This is needed to get all intersection lines in a consistent order
         // (external on the right of the line)
    -    int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0);
    +    int i = (facet.vertex[1](2) == min_z) ? 1 : ((facet.vertex[2](2) == min_z) ? 2 : 0);
         for (int j = i; j - i < 3; ++ j) {  // loop through facet edges
             int               edge_id  = this->facets_edges[facet_idx * 3 + (j % 3)];
             const int        *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
             int               a_id     = vertices[j % 3];
             int               b_id     = vertices[(j+1) % 3];
    -        const stl_vertex *a = &this->v_scaled_shared[a_id];
    -        const stl_vertex *b = &this->v_scaled_shared[b_id];
    +        const stl_vertex &a = this->v_scaled_shared[a_id];
    +        const stl_vertex &b = this->v_scaled_shared[b_id];
             
             // Is edge or face aligned with the cutting plane?
    -        if (a->z == slice_z && b->z == slice_z) {
    +        if (a(2) == slice_z && b(2) == slice_z) {
                 // Edge is horizontal and belongs to the current layer.
                 const stl_vertex &v0 = this->v_scaled_shared[vertices[0]];
                 const stl_vertex &v1 = this->v_scaled_shared[vertices[1]];
                 const stl_vertex &v2 = this->v_scaled_shared[vertices[2]];
    +            bool              swap = false;
                 if (min_z == max_z) {
                     // All three vertices are aligned with slice_z.
                     line_out->edge_type = feHorizontal;
    -                if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) {
    +                if (this->mesh->stl.facet_start[facet_idx].normal(2) < 0) {
                         // If normal points downwards this is a bottom horizontal facet so we reverse its point order.
    -                    std::swap(a, b);
    -                    std::swap(a_id, b_id);
    +                    swap = true;
                     }
    -            } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) {
    +            } else if (v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z) {
                     // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane.
                     line_out->edge_type = feTop;
    -                std::swap(a, b);
    -                std::swap(a_id, b_id);
    +                swap = true;
                 } else {
                     // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
                     line_out->edge_type = feBottom;
                 }
    -            line_out->a(0)  = a->x;
    -            line_out->a(1)  = a->y;
    -            line_out->b(0)  = b->x;
    -            line_out->b(1)  = b->y;
    -            line_out->a_id   = a_id;
    -            line_out->b_id   = b_id;
    +            line_out->a = to_2d(swap ? b : a).cast();
    +            line_out->b = to_2d(swap ? a : b).cast();
    +            line_out->a_id = swap ? b_id : a_id;
    +            line_out->b_id = swap ? a_id : b_id;
                 return true;
             }
     
    -        if (a->z == slice_z) {
    +        if (a(2) == slice_z) {
                 // Only point a alings with the cutting plane.
                 points_on_layer[num_points_on_layer ++] = num_points;
                 IntersectionPoint &point = points[num_points ++];
    -            point(0)       = a->x;
    -            point(1)       = a->y;
    +            point(0)       = a(0);
    +            point(1)       = a(1);
                 point.point_id  = a_id;
    -        } else if (b->z == slice_z) {
    +        } else if (b(2) == slice_z) {
                 // Only point b alings with the cutting plane.
                 points_on_layer[num_points_on_layer ++] = num_points;
                 IntersectionPoint &point = points[num_points ++];
    -            point(0)       = b->x;
    -            point(1)       = b->y;
    +            point(0)       = b(0);
    +            point(1)       = b(1);
                 point.point_id  = b_id;
    -        } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) {
    +        } else if ((a(2) < slice_z && b(2) > slice_z) || (b(2) < slice_z && a(2) > slice_z)) {
                 // A general case. The face edge intersects the cutting plane. Calculate the intersection point.
                 IntersectionPoint &point = points[num_points ++];
    -            point(0)       = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z);
    -            point(1)       = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z);
    +            point(0)       = b(0) + (a(0) - b(0)) * (slice_z - b(2)) / (a(2) - b(2));
    +            point(1)       = b(1) + (a(1) - b(1)) * (slice_z - b(2)) / (a(2) - b(2));
                 point.edge_id   = edge_id;
             }
         }
    @@ -1389,8 +1359,8 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
             stl_facet* facet = &this->mesh->stl.facet_start[facet_idx];
             
             // find facet extents
    -        float min_z = std::min(facet->vertex[0].z, std::min(facet->vertex[1].z, facet->vertex[2].z));
    -        float max_z = std::max(facet->vertex[0].z, std::max(facet->vertex[1].z, facet->vertex[2].z));
    +        float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2)));
    +        float max_z = std::max(facet->vertex[0](2), std::max(facet->vertex[1](2), facet->vertex[2](2)));
             
             // intersect facet with cutting plane
             IntersectionLine line;
    @@ -1417,47 +1387,47 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
     
                 // look for the vertex on whose side of the slicing plane there are no other vertices
                 int isolated_vertex;
    -            if ( (facet->vertex[0].z > z) == (facet->vertex[1].z > z) ) {
    +            if ( (facet->vertex[0](2) > z) == (facet->vertex[1](2) > z) ) {
                     isolated_vertex = 2;
    -            } else if ( (facet->vertex[1].z > z) == (facet->vertex[2].z > z) ) {
    +            } else if ( (facet->vertex[1](2) > z) == (facet->vertex[2](2) > z) ) {
                     isolated_vertex = 0;
                 } else {
                     isolated_vertex = 1;
                 }
                 
                 // get vertices starting from the isolated one
    -            stl_vertex* v0 = &facet->vertex[isolated_vertex];
    -            stl_vertex* v1 = &facet->vertex[(isolated_vertex+1) % 3];
    -            stl_vertex* v2 = &facet->vertex[(isolated_vertex+2) % 3];
    +            const stl_vertex &v0 = facet->vertex[isolated_vertex];
    +            const stl_vertex &v1 = facet->vertex[(isolated_vertex+1) % 3];
    +            const stl_vertex &v2 = facet->vertex[(isolated_vertex+2) % 3];
                 
                 // intersect v0-v1 and v2-v0 with cutting plane and make new vertices
                 stl_vertex v0v1, v2v0;
    -            v0v1.x = v1->x + (v0->x - v1->x) * (z - v1->z) / (v0->z - v1->z);
    -            v0v1.y = v1->y + (v0->y - v1->y) * (z - v1->z) / (v0->z - v1->z);
    -            v0v1.z = z;
    -            v2v0.x = v2->x + (v0->x - v2->x) * (z - v2->z) / (v0->z - v2->z);
    -            v2v0.y = v2->y + (v0->y - v2->y) * (z - v2->z) / (v0->z - v2->z);
    -            v2v0.z = z;
    +            v0v1(0) = v1(0) + (v0(0) - v1(0)) * (z - v1(2)) / (v0(2) - v1(2));
    +            v0v1(1) = v1(1) + (v0(1) - v1(1)) * (z - v1(2)) / (v0(2) - v1(2));
    +            v0v1(2) = z;
    +            v2v0(0) = v2(0) + (v0(0) - v2(0)) * (z - v2(2)) / (v0(2) - v2(2));
    +            v2v0(1) = v2(1) + (v0(1) - v2(1)) * (z - v2(2)) / (v0(2) - v2(2));
    +            v2v0(2) = z;
                 
                 // build the triangular facet
                 stl_facet triangle;
                 triangle.normal = facet->normal;
    -            triangle.vertex[0] = *v0;
    +            triangle.vertex[0] = v0;
                 triangle.vertex[1] = v0v1;
                 triangle.vertex[2] = v2v0;
                 
                 // build the facets forming a quadrilateral on the other side
                 stl_facet quadrilateral[2];
                 quadrilateral[0].normal = facet->normal;
    -            quadrilateral[0].vertex[0] = *v1;
    -            quadrilateral[0].vertex[1] = *v2;
    +            quadrilateral[0].vertex[0] = v1;
    +            quadrilateral[0].vertex[1] = v2;
                 quadrilateral[0].vertex[2] = v0v1;
                 quadrilateral[1].normal = facet->normal;
    -            quadrilateral[1].vertex[0] = *v2;
    +            quadrilateral[1].vertex[0] = v2;
                 quadrilateral[1].vertex[1] = v2v0;
                 quadrilateral[1].vertex[2] = v0v1;
                 
    -            if (v0->z > z) {
    +            if (v0(2) > z) {
                     if (upper != NULL) stl_add_facet(&upper->stl, &triangle);
                     if (lower != NULL) {
                         stl_add_facet(&lower->stl, &quadrilateral[0]);
    @@ -1489,13 +1459,11 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
                 Polygon p = *polygon;
                 p.reverse();
                 stl_facet facet;
    -            facet.normal.x = 0;
    -            facet.normal.y = 0;
    -            facet.normal.z = -1;
    +            facet.normal = stl_normal(0, 0, -1.f);
                 for (size_t i = 0; i <= 2; ++i) {
    -                facet.vertex[i].x = unscale(p.points[i](0));
    -                facet.vertex[i].y = unscale(p.points[i](1));
    -                facet.vertex[i].z = z;
    +                facet.vertex[i](0) = unscale(p.points[i](0));
    +                facet.vertex[i](1) = unscale(p.points[i](1));
    +                facet.vertex[i](2) = z;
                 }
                 stl_add_facet(&upper->stl, &facet);
             }
    @@ -1515,13 +1483,11 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
             // convert triangles to facets and append them to mesh
             for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) {
                 stl_facet facet;
    -            facet.normal.x = 0;
    -            facet.normal.y = 0;
    -            facet.normal.z = 1;
    +            facet.normal = stl_normal(0, 0, 1.f);
                 for (size_t i = 0; i <= 2; ++i) {
    -                facet.vertex[i].x = unscale(polygon->points[i](0));
    -                facet.vertex[i].y = unscale(polygon->points[i](1));
    -                facet.vertex[i].z = z;
    +                facet.vertex[i](0) = unscale(polygon->points[i](0));
    +                facet.vertex[i](1) = unscale(polygon->points[i](1));
    +                facet.vertex[i](2) = z;
                 }
                 stl_add_facet(&lower->stl, &facet);
             }
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 93b9a27ab4..d02a9c082d 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -39,7 +39,7 @@ void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh)
         for (int i = 0; i < mesh.stl.stats.number_of_facets; ++ i) {
             const stl_facet &facet = mesh.stl.facet_start[i];
             for (int j = 0; j < 3; ++ j)
    -            this->push_geometry(facet.vertex[j].x, facet.vertex[j].y, facet.vertex[j].z, facet.normal.x, facet.normal.y, facet.normal.z);
    +            this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
         }
     }
     
    @@ -55,7 +55,7 @@ void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh)
         for (int i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
             const stl_facet &facet = mesh.stl.facet_start[i];
             for (int j = 0; j < 3; ++j)
    -            this->push_geometry(facet.vertex[j].x, facet.vertex[j].y, facet.vertex[j].z, facet.normal.x, facet.normal.y, facet.normal.z);
    +            this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
     
             this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
             vertices_count += 3;
    diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp
    index 4a269bd847..2bc20da3a0 100644
    --- a/xs/xsp/TriangleMesh.xsp
    +++ b/xs/xsp/TriangleMesh.xsp
    @@ -60,14 +60,14 @@ TriangleMesh::ReadFromPerl(vertices, facets)
             for (int i = 0; i < stl.stats.number_of_facets; i++) {
                 AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0));
                 stl_facet facet;
    -            facet.normal.x = 0;
    -            facet.normal.y = 0;
    -            facet.normal.z = 0;
    +            facet.normal(0) = 0;
    +            facet.normal(1) = 0;
    +            facet.normal(2) = 0;
                 for (unsigned int v = 0; v <= 2; v++) {
                     AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, SvIV(*av_fetch(facet_av, v, 0)), 0));
    -                facet.vertex[v].x = SvNV(*av_fetch(vertex_av, 0, 0));
    -                facet.vertex[v].y = SvNV(*av_fetch(vertex_av, 1, 0));
    -                facet.vertex[v].z = SvNV(*av_fetch(vertex_av, 2, 0));
    +                facet.vertex[v](0) = SvNV(*av_fetch(vertex_av, 0, 0));
    +                facet.vertex[v](1) = SvNV(*av_fetch(vertex_av, 1, 0));
    +                facet.vertex[v](2) = SvNV(*av_fetch(vertex_av, 2, 0));
                 }
                 facet.extra[0] = 0;
                 facet.extra[1] = 0;
    @@ -110,9 +110,9 @@ TriangleMesh::vertices()
                 AV* vertex = newAV();
                 av_store(vertices, i, newRV_noinc((SV*)vertex));
                 av_extend(vertex, 2);
    -            av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i].x));
    -            av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i].y));
    -            av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i].z));
    +            av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i](0)));
    +            av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i](1)));
    +            av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i](2)));
             }
             
             RETVAL = newRV_noinc((SV*)vertices);
    @@ -155,9 +155,9 @@ TriangleMesh::normals()
                 AV* facet = newAV();
                 av_store(normals, i, newRV_noinc((SV*)facet));
                 av_extend(facet, 2);
    -            av_store(facet, 0, newSVnv(THIS->stl.facet_start[i].normal.x));
    -            av_store(facet, 1, newSVnv(THIS->stl.facet_start[i].normal.y));
    -            av_store(facet, 2, newSVnv(THIS->stl.facet_start[i].normal.z));
    +            av_store(facet, 0, newSVnv(THIS->stl.facet_start[i].normal(0)));
    +            av_store(facet, 1, newSVnv(THIS->stl.facet_start[i].normal(1)));
    +            av_store(facet, 2, newSVnv(THIS->stl.facet_start[i].normal(2)));
             }
             
             RETVAL = newRV_noinc((SV*)normals);
    @@ -169,9 +169,9 @@ TriangleMesh::size()
         CODE:
             AV* size = newAV();
             av_extend(size, 2);
    -        av_store(size, 0, newSVnv(THIS->stl.stats.size.x));
    -        av_store(size, 1, newSVnv(THIS->stl.stats.size.y));
    -        av_store(size, 2, newSVnv(THIS->stl.stats.size.z));
    +        av_store(size, 0, newSVnv(THIS->stl.stats.size(0)));
    +        av_store(size, 1, newSVnv(THIS->stl.stats.size(1)));
    +        av_store(size, 2, newSVnv(THIS->stl.stats.size(2)));
             RETVAL = newRV_noinc((SV*)size);
         OUTPUT:
             RETVAL
    @@ -216,12 +216,12 @@ TriangleMesh::cut(z, upper, lower)
     std::vector
     TriangleMesh::bb3()
         CODE:
    -        RETVAL.push_back(THIS->stl.stats.min.x);
    -        RETVAL.push_back(THIS->stl.stats.min.y);
    -        RETVAL.push_back(THIS->stl.stats.max.x);
    -        RETVAL.push_back(THIS->stl.stats.max.y);
    -        RETVAL.push_back(THIS->stl.stats.min.z);
    -        RETVAL.push_back(THIS->stl.stats.max.z);
    +        RETVAL.push_back(THIS->stl.stats.min(0));
    +        RETVAL.push_back(THIS->stl.stats.min(1));
    +        RETVAL.push_back(THIS->stl.stats.max(0));
    +        RETVAL.push_back(THIS->stl.stats.max(1));
    +        RETVAL.push_back(THIS->stl.stats.min(2));
    +        RETVAL.push_back(THIS->stl.stats.max(2));
         OUTPUT:
             RETVAL
     
    
    From 76d60070eb8ccb10f60aebb8036075a2065a9fb8 Mon Sep 17 00:00:00 2001
    From: bubnikv 
    Date: Wed, 22 Aug 2018 15:34:03 +0200
    Subject: [PATCH 166/185] Eigenized the admesh structures (stl_vertex,
     stl_normal).
    
    ---
     xs/src/admesh/stlinit.cpp         |   2 +-
     xs/src/libslic3r/TriangleMesh.cpp | 151 +++++-------------------------
     xs/src/libslic3r/TriangleMesh.hpp |  34 +++----
     3 files changed, 43 insertions(+), 144 deletions(-)
    
    diff --git a/xs/src/admesh/stlinit.cpp b/xs/src/admesh/stlinit.cpp
    index 6bb4bf6338..47a37f0a54 100644
    --- a/xs/src/admesh/stlinit.cpp
    +++ b/xs/src/admesh/stlinit.cpp
    @@ -362,7 +362,7 @@ void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first)
       // Now find the max and min values.
       for (size_t i = 0; i < 3; ++ i) {
       	stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]);
    -  	stl->stats.max = stl->stats.max.cwiseMin(facet.vertex[i]);
    +  	stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]);
       }
     }
     
    diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
    index 3d090bb7b5..f61959066f 100644
    --- a/xs/src/libslic3r/TriangleMesh.cpp
    +++ b/xs/src/libslic3r/TriangleMesh.cpp
    @@ -30,12 +30,6 @@
     
     namespace Slic3r {
     
    -TriangleMesh::TriangleMesh()
    -    : repaired(false)
    -{
    -    stl_initialize(&this->stl);
    -}
    -
     TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets )
         : repaired(false)
     {
    @@ -67,20 +61,6 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f
         stl_get_size(&stl);
     }
     
    -TriangleMesh::TriangleMesh(const TriangleMesh &other) :
    -    repaired(false)
    -{
    -    stl_initialize(&this->stl);
    -    *this = other;
    -}
    -
    -TriangleMesh::TriangleMesh(TriangleMesh &&other) : 
    -    repaired(false)
    -{
    -    stl_initialize(&this->stl);
    -    this->swap(other);
    -}
    -
     TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other)
     {
         stl_close(&this->stl);
    @@ -108,42 +88,8 @@ TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other)
         return *this;
     }
     
    -TriangleMesh& TriangleMesh::operator=(TriangleMesh &&other)
    +void TriangleMesh::repair()
     {
    -    this->swap(other);
    -    return *this;
    -}
    -
    -void
    -TriangleMesh::swap(TriangleMesh &other)
    -{
    -    std::swap(this->stl,      other.stl);
    -    std::swap(this->repaired, other.repaired);
    -}
    -
    -TriangleMesh::~TriangleMesh() {
    -    stl_close(&this->stl);
    -}
    -
    -void
    -TriangleMesh::ReadSTLFile(const char* input_file) {
    -    stl_open(&stl, input_file);
    -}
    -
    -void
    -TriangleMesh::write_ascii(const char* output_file)
    -{
    -    stl_write_ascii(&this->stl, output_file, "");
    -}
    -
    -void
    -TriangleMesh::write_binary(const char* output_file)
    -{
    -    stl_write_binary(&this->stl, output_file, "");
    -}
    -
    -void
    -TriangleMesh::repair() {
         if (this->repaired) return;
         
         // admesh fails when repairing empty meshes
    @@ -240,13 +186,7 @@ void TriangleMesh::check_topology()
         }
     }
     
    -bool TriangleMesh::is_manifold() const
    -{
    -    return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets;
    -}
    -
    -void
    -TriangleMesh::reset_repair_stats() {
    +void TriangleMesh::reset_repair_stats() {
         this->stl.stats.degenerate_facets   = 0;
         this->stl.stats.edges_fixed         = 0;
         this->stl.stats.facets_removed      = 0;
    @@ -256,8 +196,7 @@ TriangleMesh::reset_repair_stats() {
         this->stl.stats.normals_fixed       = 0;
     }
     
    -bool
    -TriangleMesh::needed_repair() const
    +bool TriangleMesh::needed_repair() const
     {
         return this->stl.stats.degenerate_facets    > 0
             || this->stl.stats.edges_fixed          > 0
    @@ -267,14 +206,8 @@ TriangleMesh::needed_repair() const
             || this->stl.stats.backwards_edges      > 0;
     }
     
    -size_t
    -TriangleMesh::facets_count() const
    +void TriangleMesh::WriteOBJFile(char* output_file)
     {
    -    return this->stl.stats.number_of_facets;
    -}
    -
    -void
    -TriangleMesh::WriteOBJFile(char* output_file) {
         stl_generate_shared_vertices(&stl);
         stl_write_obj(&stl, output_file);
     }
    @@ -317,21 +250,6 @@ void TriangleMesh::rotate(float angle, const Axis &axis)
         stl_invalidate_shared_vertices(&this->stl);
     }
     
    -void TriangleMesh::rotate_x(float angle)
    -{
    -    this->rotate(angle, X);
    -}
    -
    -void TriangleMesh::rotate_y(float angle)
    -{
    -    this->rotate(angle, Y);
    -}
    -
    -void TriangleMesh::rotate_z(float angle)
    -{
    -    this->rotate(angle, Z);
    -}
    -
     void TriangleMesh::mirror(const Axis &axis)
     {
         if (axis == X) {
    @@ -344,21 +262,6 @@ void TriangleMesh::mirror(const Axis &axis)
         stl_invalidate_shared_vertices(&this->stl);
     }
     
    -void TriangleMesh::mirror_x()
    -{
    -    this->mirror(X);
    -}
    -
    -void TriangleMesh::mirror_y()
    -{
    -    this->mirror(Y);
    -}
    -
    -void TriangleMesh::mirror_z()
    -{
    -    this->mirror(Z);
    -}
    -
     void TriangleMesh::transform(const float* matrix3x4)
     {
         if (matrix3x4 == nullptr)
    @@ -456,14 +359,14 @@ size_t TriangleMesh::number_of_patches() const
         return num_bodies;
     }
     
    -TriangleMeshPtrs
    -TriangleMesh::split() const
    +TriangleMeshPtrs TriangleMesh::split() const
     {
    -    TriangleMeshPtrs meshes;
    -    std::set seen_facets;
    +    TriangleMeshPtrs            meshes;
    +    std::vector  facet_visited(this->stl.stats.number_of_facets, false);
         
         // we need neighbors
    -    if (!this->repaired) CONFESS("split() requires repair()");
    +    if (!this->repaired)
    +        CONFESS("split() requires repair()");
         
         // loop while we have remaining facets
         for (;;) {
    @@ -471,25 +374,26 @@ TriangleMesh::split() const
             std::queue facet_queue;
             std::deque facets;
             for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; facet_idx++) {
    -            if (seen_facets.find(facet_idx) == seen_facets.end()) {
    +            if (! facet_visited[facet_idx]) {
                     // if facet was not seen put it into queue and start searching
                     facet_queue.push(facet_idx);
                     break;
                 }
             }
    -        if (facet_queue.empty()) break;
    -        
    -        while (!facet_queue.empty()) {
    +        if (facet_queue.empty())
    +            break;
    +
    +        while (! facet_queue.empty()) {
                 int facet_idx = facet_queue.front();
                 facet_queue.pop();
    -            if (seen_facets.find(facet_idx) != seen_facets.end()) continue;
    -            facets.emplace_back(facet_idx);
    -            for (int j = 0; j <= 2; j++) {
    -                facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]);
    +            if (! facet_visited[facet_idx]) {
    +                facets.emplace_back(facet_idx);
    +                for (int j = 0; j < 3; ++ j)
    +                    facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]);
    +                facet_visited[facet_idx] = true;
                 }
    -            seen_facets.insert(facet_idx);
             }
    -        
    +
             TriangleMesh* mesh = new TriangleMesh;
             meshes.emplace_back(mesh);
             mesh->stl.stats.type = inmemory;
    @@ -499,18 +403,16 @@ TriangleMesh::split() const
             stl_allocate(&mesh->stl);
             
             bool first = true;
    -        for (std::deque::const_iterator facet = facets.begin(); facet != facets.end(); ++facet) {
    +        for (std::deque::const_iterator facet = facets.begin(); facet != facets.end(); ++ facet) {
                 mesh->stl.facet_start[facet - facets.begin()] = this->stl.facet_start[*facet];
                 stl_facet_stats(&mesh->stl, this->stl.facet_start[*facet], first);
    -            first = 0;
             }
         }
         
         return meshes;
     }
     
    -void
    -TriangleMesh::merge(const TriangleMesh &mesh)
    +void TriangleMesh::merge(const TriangleMesh &mesh)
     {
         // reset stats and metadata
         int number_of_facets = this->stl.stats.number_of_facets;
    @@ -564,8 +466,7 @@ Polygon TriangleMesh::convex_hull()
         return Slic3r::Geometry::convex_hull(pp);
     }
     
    -BoundingBoxf3
    -TriangleMesh::bounding_box() const
    +BoundingBoxf3 TriangleMesh::bounding_box() const
     {
         BoundingBoxf3 bb;
         bb.defined = true;
    @@ -574,8 +475,7 @@ TriangleMesh::bounding_box() const
         return bb;
     }
     
    -void
    -TriangleMesh::require_shared_vertices()
    +void TriangleMesh::require_shared_vertices()
     {
         BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start";
         if (!this->repaired) 
    @@ -670,8 +570,7 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) :
         }
     }
     
    -void
    -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const
    +void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const
     {
         BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice";
     
    diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
    index 1b1acd9e60..3f62f4986d 100644
    --- a/xs/src/libslic3r/TriangleMesh.hpp
    +++ b/xs/src/libslic3r/TriangleMesh.hpp
    @@ -20,33 +20,33 @@ typedef std::vector TriangleMeshPtrs;
     class TriangleMesh
     {
     public:
    -    TriangleMesh();
    +    TriangleMesh() : repaired(false) { stl_initialize(&this->stl); }
         TriangleMesh(const Pointf3s &points, const std::vector &facets);
    -    TriangleMesh(const TriangleMesh &other);
    -    TriangleMesh(TriangleMesh &&other);
    +    TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; }
    +    TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); }
    +    ~TriangleMesh() { stl_close(&this->stl); }
         TriangleMesh& operator=(const TriangleMesh &other);
    -    TriangleMesh& operator=(TriangleMesh &&other);
    -    void swap(TriangleMesh &other);
    -    ~TriangleMesh();
    -    void ReadSTLFile(const char* input_file);
    -    void write_ascii(const char* output_file);
    -    void write_binary(const char* output_file);
    +    TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; }
    +    void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); }
    +    void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); }
    +    void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); }
    +    void write_binary(const char* output_file) { stl_write_binary(&this->stl, output_file, ""); }
         void repair();
         float volume();
         void check_topology();
    -    bool is_manifold() const;
    +    bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets; }
         void WriteOBJFile(char* output_file);
         void scale(float factor);
         void scale(const Vec3d &versor);
         void translate(float x, float y, float z);
         void rotate(float angle, const Axis &axis);
    -    void rotate_x(float angle);
    -    void rotate_y(float angle);
    -    void rotate_z(float angle);
    +    void rotate_x(float angle) { this->rotate(angle, X); }
    +    void rotate_y(float angle) { this->rotate(angle, Y); }
    +    void rotate_z(float angle) { this->rotate(angle, Z); }
         void mirror(const Axis &axis);
    -    void mirror_x();
    -    void mirror_y();
    -    void mirror_z();
    +    void mirror_x() { this->mirror(X); }
    +    void mirror_y() { this->mirror(Y); }
    +    void mirror_z() { this->mirror(Z); }
         void transform(const float* matrix3x4);
         void align_to_origin();
         void rotate(double angle, Point* center);
    @@ -57,7 +57,7 @@ public:
         BoundingBoxf3 bounding_box() const;
         void reset_repair_stats();
         bool needed_repair() const;
    -    size_t facets_count() const;
    +    size_t facets_count() const { return this->stl.stats.number_of_facets; }
     
         // Returns true, if there are two and more connected patches in the mesh.
         // Returns false, if one or zero connected patch is in the mesh.
    
    From 8a9bdc55eed88f0216c1fc9acc005bd5e88be7a9 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Wed, 22 Aug 2018 18:00:48 +0200
    Subject: [PATCH 167/185] Added functions for tick adding/deleting
    
    ---
     xs/src/slic3r/GUI/wxExtensions.cpp | 46 ++++++++++++++++++++++++++++--
     xs/src/slic3r/GUI/wxExtensions.hpp |  4 +++
     2 files changed, 48 insertions(+), 2 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index 92f9e1e37e..21ca75ac9b 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -787,6 +787,7 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
         Bind(wxEVT_ENTER_WINDOW,&PrusaDoubleSlider::OnEnterWin, this);
         Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeaveWin, this);
         Bind(wxEVT_KEY_DOWN,    &PrusaDoubleSlider::OnKeyDown,  this);
    +    Bind(wxEVT_RIGHT_DOWN,  &PrusaDoubleSlider::OnRightDown,this);
     
         // control's view variables
         SLIDER_MARGIN     = 2 + (style == wxSL_HORIZONTAL ? m_thumb_higher.GetWidth() : m_thumb_higher.GetHeight());
    @@ -862,6 +863,14 @@ double PrusaDoubleSlider::get_scroll_step()
         return double(slider_len - SLIDER_MARGIN * 2) / (m_max_value - m_min_value);
     }
     
    +// get position on the slider line from entered value
    +wxCoord PrusaDoubleSlider::get_position_from_value(const int value)
    +{
    +    const double step = get_scroll_step();
    +    const int val = is_horizontal() ? value : m_max_value - value;
    +    return wxCoord(SLIDER_MARGIN + int(val*step + 0.5));
    +}
    +
     void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos)
     {
         const double step = get_scroll_step();
    @@ -896,8 +905,8 @@ void PrusaDoubleSlider::render()
         int width, height;
         GetSize(&width, &height);
     
    -    int lower_pos, higher_pos;
    -    get_lower_and_higher_position(lower_pos, higher_pos);
    +    const wxCoord lower_pos = get_position_from_value(m_lower_value);
    +    const wxCoord higher_pos = get_position_from_value(m_higher_value);
     
         // draw line
         draw_scroll_line(dc, lower_pos, higher_pos);
    @@ -907,6 +916,9 @@ void PrusaDoubleSlider::render()
     
         //higher slider:
         draw_thumb(dc, higher_pos, ssHigher);
    +
    +    //draw color print ticks
    +    draw_ticks(dc);
     }
     
     void PrusaDoubleSlider::draw_info_line(wxDC& dc, const wxPoint& pos, const SelectedSlider selection) const
    @@ -988,6 +1000,23 @@ void PrusaDoubleSlider::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const Sel
         draw_thumb_text(dc, pos, selection);
     }
     
    +void PrusaDoubleSlider::draw_ticks(wxDC& dc)
    +{
    +    dc.SetPen(DARK_GREY_PEN);
    +    int height, width;
    +    GetSize(&width, &height);
    +    const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width;
    +    for (auto tick : m_ticks)
    +    {
    +        const wxCoord pos = get_position_from_value(tick);
    +
    +        is_horizontal() ?   dc.DrawLine(pos, mid-14, pos, mid-9) :
    +                            dc.DrawLine(mid - 14, pos - 1, mid - 9, pos - 1);
    +        is_horizontal() ?   dc.DrawLine(pos, mid+14, pos, mid+9) :
    +                            dc.DrawLine(mid + 14, pos - 1, mid + 9, pos - 1);
    +    }
    +}
    +
     void PrusaDoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection)
     {
         const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y);
    @@ -1174,4 +1203,17 @@ void PrusaDoubleSlider::OnKeyDown(wxKeyEvent &event)
         }
     }
     
    +void PrusaDoubleSlider::OnRightDown(wxMouseEvent& event)
    +{
    +    if (m_selection == ssUndef)
    +        return;
    +
    +    int new_tick = m_selection == ssLower ? m_lower_value : m_higher_value;
    +
    +    if (!m_ticks.insert(new_tick).second)
    +        m_ticks.erase(new_tick);
    +    Refresh();
    +    Update();
    +}
    +
     // *****************************************************************************
    diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
    index 6f000015b7..36892fe47d 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.hpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.hpp
    @@ -536,6 +536,7 @@ public:
         void OnLeaveWin(wxMouseEvent& event);
         void OnWheel(wxMouseEvent& event);
         void OnKeyDown(wxKeyEvent &event);
    +    void OnRightDown(wxMouseEvent& event);
     
     protected:
      
    @@ -543,6 +544,7 @@ protected:
         void    draw_focus_rect();
         void    draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
         void    draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection);
    +    void    draw_ticks(wxDC& dc);
         void    draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
         void    draw_info_line(wxDC& dc, const wxPoint& pos, SelectedSlider selection) const;
         void    draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
    @@ -560,6 +562,7 @@ protected:
         wxString    get_label(const SelectedSlider& selection) const;
         void        get_lower_and_higher_position(int& lower_pos, int& higher_pos);
         int         position_to_value(wxDC& dc, const wxCoord x, const wxCoord y);
    +    wxCoord     get_position_from_value(const int value);
     
     private:
         int         m_min_value;
    @@ -591,6 +594,7 @@ private:
     
         std::vector line_pens;
         std::vector segm_pens;
    +    std::set       m_ticks;
     };
     // ******************************************************************************************
     
    
    From 2a7059edb30ee4bee1fea3f2aa834f07aa9816a4 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Thu, 23 Aug 2018 13:01:18 +0200
    Subject: [PATCH 168/185] Added active icon for selected tick
    
    ---
     resources/icons/colorchange_add_off.png    | Bin 0 -> 600 bytes
     resources/icons/colorchange_add_on.png     | Bin 0 -> 695 bytes
     resources/icons/colorchange_delete_off.png | Bin 0 -> 589 bytes
     resources/icons/colorchange_delete_on.png  | Bin 0 -> 628 bytes
     xs/src/slic3r/GUI/GUI.cpp                  |   6 +-
     xs/src/slic3r/GUI/wxExtensions.cpp         | 132 +++++++++++++++------
     xs/src/slic3r/GUI/wxExtensions.hpp         |  15 ++-
     7 files changed, 116 insertions(+), 37 deletions(-)
     create mode 100644 resources/icons/colorchange_add_off.png
     create mode 100644 resources/icons/colorchange_add_on.png
     create mode 100644 resources/icons/colorchange_delete_off.png
     create mode 100644 resources/icons/colorchange_delete_on.png
    
    diff --git a/resources/icons/colorchange_add_off.png b/resources/icons/colorchange_add_off.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..6ddeccbe0a2a82019c5c2efd1773c20c0833c4d4
    GIT binary patch
    literal 600
    zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmSN`?>!lvI6-E$sR$z
    z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBC{YpM6XNqfL!5oT`ng2Uv7KO-
    z`>oK>c;wi#w-Y8-9OfwbtPfPmnB?v5!uX#__a2bLS>O>_%)p?h48n{ROYO^mg6t)p
    zzOL*KxFrR+#s37PM*)TAdb&7gOf=4WlTK;F^nd^Dc_ijpxxR1T
    zX#3n&TDl=r(%}>cpt3=meXk`S|u=mmKH$V*x
    Mp00i_>zopr04OBpdH?_b
    
    literal 0
    HcmV?d00001
    
    diff --git a/resources/icons/colorchange_add_on.png b/resources/icons/colorchange_add_on.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..cc800b81e8a6355877fa8cf58fee3731540f5dbb
    GIT binary patch
    literal 695
    zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmSN`?>!lvI6-E$sR$z
    z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD6u8LC&cwX?ZDQK|Lu1F
    z3#9(1u>Ehd{lBC4e=5iS1cv`9?Ekm-{NK>{f7gWnt9$>~IQ`$*4|2u&`v2?e{dfTo%*MD>FcgxMz#tc&>wjKYvlkv2}>W3ZX<P)+_zJD`=!SuPm-m%Y^cA85^??v;%fYzs559euLSj6?CPDVv#
    z8OPo07q)BrukMjxdUaSr{b2Kv9pC;|`Sb4o|Mpw@uyeDVx
    zW$1|Bsj=8bi@Q>L-?mjR+Ky$nt-EqlSb4pc^PF5&HlSxzOI#yLQW8s2t&)pUffR$0
    zfuWhMp`oskX^5ejm7%GXsj;?!p_PHb`H8EHQ8eV{r(~v8qH8d;GO~ndkeu{n5l{nz
    Mr>mdKI;Vst0C-YMUjP6A
    
    literal 0
    HcmV?d00001
    
    diff --git a/resources/icons/colorchange_delete_off.png b/resources/icons/colorchange_delete_off.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..c16655271a7e10d9d83047189086d4d6b509abc3
    GIT binary patch
    literal 589
    zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S0wixl{&NRXEa{HEjtmSN`?>!lvI6-E$sR$z
    z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZEE?e4>iGH4#PT$HOWYUVmw=D)W4<$2R?NV2M}KA?FdtNTmJp~!15S*|LT8;MoC
    zXsZ3%W24$-moh2c@9l*fmo+q>Gq62KSXA699k*xgMiDE{G|yhA4QhurtG<}@uu!C_
    z<$>}m-=I62B~!k9UHS2l6URxP__|{b{!P~`Som^z%$(k+UE0pWDc5vL>wMPBh1~ac
    zOgPu;&>~>6W*@_Frwy&D=l0S8q@B+Qm*U`007}-kq7#>;Nm&p5M&yqHoBEQzy
    zY0CSzuP^(vnT8meSs9vI
    xnHp;w7+M(^oS(SL7)3*FeoAIqCAtPfD`O*whU@kljsZ0=c)I$ztaD0e0ssxu+3)}W
    
    literal 0
    HcmV?d00001
    
    diff --git a/resources/icons/colorchange_delete_on.png b/resources/icons/colorchange_delete_on.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..8f27ce9fe684cca59295bd787f0c564f14a56a25
    GIT binary patch
    literal 628
    zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S0wixl{&NRXEa{HEjtmSN`?>!lvI6-E$sR$z
    z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZEE?e4yA$Thpan!eyJ5ZE|lwT7;ffcnvE
    zS;w+UCtS;Nk}WE(*0icP_POSv<^KD{Mb$yQ(FI4REZ!IK`2_R)UyEa!cz3P+`04`t
    z{hZU%K_&mTt8UMEpM2aV&hod9dl<8x*4CJ9HxKi5&!3)Df4S#W*Q&b>yI22j=vX^V
    zN2n)Lr+H5h+lo`osZwf9*@ahFFK4ad3^C7@C^3O
    zPrjD~3a37HGZKiN!`D`B)H&;x+upMdZvr^>@ujWVCAfaBxAC{SdtoWHFMjlkyh{DG
    za*j&_)BC37>U<7=kI(V^<2moiw_|H`-C}#RDkiV9dRkkwNm};kgU!{O7tC!`5!kGA
    z^GE;ee@5|{S6i4p0)jJVZr^=>`yT6c`+I)XS|^=q?fadV_WRo97S>|Fv|GC`%Kv5L
    zl`d>pzF+$gFcwrxTq8h@qL4p{bRrv9^Jsm4U(e
    niK~oJH00)|WTsW3YcRAjHic*qn$hP0)WG2B>gTe~DWM4f)HnL<
    
    literal 0
    HcmV?d00001
    
    diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp
    index 396c162579..8701db511c 100644
    --- a/xs/src/slic3r/GUI/GUI.cpp
    +++ b/xs/src/slic3r/GUI/GUI.cpp
    @@ -1058,9 +1058,9 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
         sizer->AddSpacer(5);
         sizer->Add(slider_h, 0, wxEXPAND | wxLEFT, 20);
         sizer->AddSpacer(5);
    -    PrusaDoubleSlider* slider_v = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 200, wxDefaultPosition, 
    -                                                        wxSize(wxDefaultSize.x ,250), wxSL_VERTICAL);
    -    slider_v->SetKoefForLabels(0.15);
    +    PrusaDoubleSlider* slider_v = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 100, wxDefaultPosition, 
    +                                                        wxSize(120 ,200), wxSL_VERTICAL);
    +    slider_v->SetKoefForLabels(2.25);
         sizer->AddSpacer(5);
         sizer->Add(slider_v, 0, wxLEFT, 20);
         sizer->AddSpacer(5);
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index 21ca75ac9b..10ba4efb53 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -776,6 +776,13 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
         m_thumb_lower  = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) :
                                                              Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG);
         m_thumb_size = m_thumb_lower.GetSize();
    +
    +    m_add_tick_on  = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_on.png")), wxBITMAP_TYPE_PNG);
    +    m_add_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_off.png")), wxBITMAP_TYPE_PNG);
    +    m_del_tick_on  = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG);
    +    m_del_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_off.png")), wxBITMAP_TYPE_PNG);
    +    m_tick_icon_dim = m_add_tick_on.GetSize().x;
    +
         m_selection = ssUndef;
     
         // slider events
    @@ -790,7 +797,7 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
         Bind(wxEVT_RIGHT_DOWN,  &PrusaDoubleSlider::OnRightDown,this);
     
         // control's view variables
    -    SLIDER_MARGIN     = 2 + (style == wxSL_HORIZONTAL ? m_thumb_higher.GetWidth() : m_thumb_higher.GetHeight());
    +    SLIDER_MARGIN     = 4 + (style == wxSL_HORIZONTAL ? m_thumb_higher.GetWidth() : m_thumb_higher.GetHeight());
     
         DARK_ORANGE_PEN   = wxPen(wxColour(253, 84, 2));
         ORANGE_PEN        = wxPen(wxColour(253, 126, 66));
    @@ -901,9 +908,10 @@ void PrusaDoubleSlider::render()
         SetBackgroundColour(GetParent()->GetBackgroundColour());
         draw_focus_rect();
     
    -    wxPaintDC dc(this);    
    -    int width, height;
    -    GetSize(&width, &height);
    +    wxPaintDC dc(this);
    +    wxFont font = dc.GetFont();
    +    const wxFont smaller_font = font.Smaller();
    +    dc.SetFont(smaller_font);
     
         const wxCoord lower_pos = get_position_from_value(m_lower_value);
         const wxCoord higher_pos = get_position_from_value(m_higher_value);
    @@ -921,21 +929,49 @@ void PrusaDoubleSlider::render()
         draw_ticks(dc);
     }
     
    -void PrusaDoubleSlider::draw_info_line(wxDC& dc, const wxPoint& pos, const SelectedSlider selection) const
    +void PrusaDoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end)
    +{
    +    const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
    +    wxBitmap* icon = m_is_action_icon_focesed ? &m_add_tick_off :&m_add_tick_on;
    +    if (m_ticks.find(tick) != m_ticks.end())
    +        icon = m_is_action_icon_focesed ? &m_del_tick_off :&m_del_tick_on;
    +
    +    wxCoord x_draw, y_draw;
    +    is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim;
    +    if (m_selection == ssLower)
    +        is_horizontal() ? y_draw = pt_end.y + 3 : x_draw = pt_beg.x - m_tick_icon_dim-2;
    +    else
    +        is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3;
    +
    +    dc.DrawBitmap(*icon, x_draw, y_draw);
    +
    +    //update rect of the tick action icon
    +    m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim);
    +}
    +
    +void PrusaDoubleSlider::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, const SelectedSlider selection)
     {
         if (m_selection == selection) {
    +        //draw info line
             dc.SetPen(DARK_ORANGE_PEN);
    -        is_horizontal() ? dc.DrawLine(pos.x, pos.y - m_thumb_size.y, pos.x, pos.y + m_thumb_size.y):
    -                          dc.DrawLine(pos.x - m_thumb_size.x, pos.y-1, pos.x + m_thumb_size.x, pos.y-1);
    +        const wxPoint pt_beg = is_horizontal() ? wxPoint(pos.x, pos.y - m_thumb_size.y) : wxPoint(pos.x - m_thumb_size.x, pos.y - 1);
    +        const wxPoint pt_end = is_horizontal() ? wxPoint(pos.x, pos.y + m_thumb_size.y) : wxPoint(pos.x + m_thumb_size.x, pos.y - 1);
    +        dc.DrawLine(pt_beg, pt_end);
    +
    +        //draw action icon
    +        draw_action_icon(dc, pt_beg, pt_end);
         }
     }
     
     wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const
     {
         const int value = selection == ssLower ? m_lower_value : m_higher_value;
    -    return m_label_koef == 1.0 ? wxString::Format("%d", value) :
    -                                 wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None);
     
    +    if (m_label_koef == 1.0)
    +        return wxString::Format("%d", value);
    +
    +    const wxString str = wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None);
    +    return wxString::Format("%s\n(%d)", str, value);
     }
     
     void PrusaDoubleSlider::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const
    @@ -943,7 +979,7 @@ void PrusaDoubleSlider::draw_thumb_text(wxDC& dc, const wxPoint& pos, const Sele
         if (selection == ssUndef) return;
         wxCoord text_width, text_height;
         const wxString label = get_label(selection);
    -    dc.GetTextExtent(label, &text_width, &text_height);
    +    dc.GetMultiLineTextExtent(label, &text_width, &text_height);
         wxPoint text_pos;
         if (selection ==ssLower)
             text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x) :
    @@ -994,7 +1030,7 @@ void PrusaDoubleSlider::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const Sel
         draw_thumb_item(dc, pos, selection);
     
         // Draw info_line
    -    draw_info_line(dc, pos, selection);
    +    draw_info_line_with_icon(dc, pos, selection);
     
         // Draw thumb text
         draw_thumb_text(dc, pos, selection);
    @@ -1067,9 +1103,14 @@ bool PrusaDoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect)
     
     void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event)
     {
    -    m_is_left_down = true;
         wxClientDC dc(this);
         wxPoint pos = event.GetLogicalPosition(dc);
    +    if (is_point_in_rect(pos, m_rect_tick_action)) {
    +        OnRightDown(event);
    +        return;
    +    }
    +
    +    m_is_left_down = true;
         detect_selected_slider(pos);
         Refresh();
         Update();
    @@ -1098,21 +1139,23 @@ void PrusaDoubleSlider::correct_higher_value()
     
     void PrusaDoubleSlider::OnMotion(wxMouseEvent& event)
     {
    -    if (!m_is_left_down || m_selection == ssUndef)
    +    if (m_selection == ssUndef)
             return;
    -
         wxClientDC dc(this);
         wxPoint pos = event.GetLogicalPosition(dc);
    -
    -    if (m_selection == ssLower) {
    -        m_lower_value = position_to_value(dc, pos.x, pos.y);
    -        correct_lower_value();
    +    if (!m_is_left_down){
    +        m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action);
         }
    -    else if (m_selection == ssHigher) {
    -        m_higher_value = position_to_value(dc, pos.x, pos.y);
    -        correct_higher_value();
    +    else {
    +        if (m_selection == ssLower) {
    +            m_lower_value = position_to_value(dc, pos.x, pos.y);
    +            correct_lower_value();
    +        }
    +        else if (m_selection == ssHigher) {
    +            m_higher_value = position_to_value(dc, pos.x, pos.y);
    +            correct_higher_value();
    +        }
         }
    -
         Refresh();
         Update();
         event.Skip();
    @@ -1141,7 +1184,6 @@ void PrusaDoubleSlider::OnEnterWin(wxMouseEvent& event)
     void PrusaDoubleSlider::OnLeaveWin(wxMouseEvent& event)
     {
         m_is_focused = false;
    -    m_selection = ssUndef;
         OnLeftUp(event);
     }
     
    @@ -1170,6 +1212,25 @@ void PrusaDoubleSlider::move_current_thumb(const bool condition)
         ProcessWindowEvent(e);
     }
     
    +void PrusaDoubleSlider::action_tick(const TicksAction action)
    +{
    +    if (m_selection == ssUndef)
    +        return;
    +
    +    const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
    +
    +    const auto it = m_ticks.find(tick);
    +    if (it == m_ticks.end() && action == taAdd)
    +        m_ticks.insert(tick);
    +    else if (it != m_ticks.end() && action == taDel)
    +        m_ticks.erase(tick);
    +    else
    +        return;
    +
    +    Refresh();
    +    Update();
    +}
    +
     void PrusaDoubleSlider::OnWheel(wxMouseEvent& event)
     {
         wxClientDC dc(this);
    @@ -1184,22 +1245,27 @@ void PrusaDoubleSlider::OnWheel(wxMouseEvent& event)
     
     void PrusaDoubleSlider::OnKeyDown(wxKeyEvent &event)
     {
    -    if (is_horizontal())
    +    const int key = event.GetKeyCode();
    +    if (key == '+' || key == WXK_NUMPAD_ADD)
    +        action_tick(taAdd);
    +    else if (key == '-' || key == 390 || key == WXK_DELETE || key == WXK_BACK)
    +        action_tick(taDel);
    +    else if (is_horizontal())
         {
    -        if (event.GetKeyCode() == WXK_LEFT || event.GetKeyCode() == WXK_RIGHT)
    -            move_current_thumb(event.GetKeyCode() == WXK_LEFT); 
    -        else if (event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_DOWN){
    -            m_selection = event.GetKeyCode() == WXK_UP ? ssHigher : ssLower;
    +        if (key == WXK_LEFT || key == WXK_RIGHT)
    +            move_current_thumb(key == WXK_LEFT); 
    +        else if (key == WXK_UP || key == WXK_DOWN){
    +            m_selection = key == WXK_UP ? ssHigher : ssLower;
                 Refresh();
             }
         }
         else {
    -        if (event.GetKeyCode() == WXK_LEFT || event.GetKeyCode() == WXK_RIGHT) {
    -            m_selection = event.GetKeyCode() == WXK_LEFT ? ssHigher : ssLower;
    +        if (key == WXK_LEFT || key == WXK_RIGHT) {
    +            m_selection = key == WXK_LEFT ? ssHigher : ssLower;
                 Refresh();
             }
    -        else if (event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_DOWN)
    -            move_current_thumb(event.GetKeyCode() == WXK_UP);
    +        else if (key == WXK_UP || key == WXK_DOWN)
    +            move_current_thumb(key == WXK_UP);
         }
     }
     
    @@ -1208,7 +1274,7 @@ void PrusaDoubleSlider::OnRightDown(wxMouseEvent& event)
         if (m_selection == ssUndef)
             return;
     
    -    int new_tick = m_selection == ssLower ? m_lower_value : m_higher_value;
    +    const int new_tick = m_selection == ssLower ? m_lower_value : m_higher_value;
     
         if (!m_ticks.insert(new_tick).second)
             m_ticks.erase(new_tick);
    diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
    index 36892fe47d..ed029d800d 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.hpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.hpp
    @@ -501,6 +501,10 @@ enum SelectedSlider {
         ssLower,
         ssHigher
     };
    +enum TicksAction{
    +    taAdd,
    +    taDel
    +};
     class PrusaDoubleSlider : public wxControl
     {
     public:
    @@ -541,12 +545,13 @@ public:
     protected:
      
         void    render();
    +    void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end);
         void    draw_focus_rect();
         void    draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
         void    draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection);
         void    draw_ticks(wxDC& dc);
         void    draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
    -    void    draw_info_line(wxDC& dc, const wxPoint& pos, SelectedSlider selection) const;
    +    void    draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection);
         void    draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
     
         void    update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection);
    @@ -554,6 +559,7 @@ protected:
         void    correct_lower_value();
         void    correct_higher_value();
         void    move_current_thumb(const bool condition);
    +    void    action_tick(const TicksAction action);
     
         bool    is_point_in_rect(const wxPoint& pt, const wxRect& rect);
         bool    is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
    @@ -571,13 +577,20 @@ private:
         int         m_higher_value;
         wxBitmap    m_thumb_higher;
         wxBitmap    m_thumb_lower;
    +    wxBitmap    m_add_tick_on;
    +    wxBitmap    m_add_tick_off;
    +    wxBitmap    m_del_tick_on;
    +    wxBitmap    m_del_tick_off;
         SelectedSlider  m_selection;
         bool        m_is_left_down = false;
         bool        m_is_focused = false;
    +    bool        m_is_action_icon_focesed = false;
     
         wxRect      m_rect_lower_thumb;
         wxRect      m_rect_higher_thumb;
    +    wxRect      m_rect_tick_action;
         wxSize      m_thumb_size;
    +    int         m_tick_icon_dim;
         long        m_style;
         float       m_label_koef = 1.0;
     
    
    From 2ec045a0fb7bafad12f3e512576215483efacd6b Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Thu, 23 Aug 2018 14:37:17 +0200
    Subject: [PATCH 169/185] Added SetSliderValues and GetActiveValue functions
    
    ---
     lib/Slic3r/GUI/Plater.pm           |  1 +
     xs/src/slic3r/GUI/wxExtensions.cpp | 20 ++++++++++++++++++--
     xs/src/slic3r/GUI/wxExtensions.hpp | 16 ++++++++++++----
     3 files changed, 31 insertions(+), 6 deletions(-)
    
    diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm
    index 062746e791..a0a6a560db 100644
    --- a/lib/Slic3r/GUI/Plater.pm
    +++ b/lib/Slic3r/GUI/Plater.pm
    @@ -1674,6 +1674,7 @@ sub print_info_box_show {
         $sizer->Show(1, $show);
     
         $self->Layout;
    +    Wx::GetTopLevelParent($self)->Refresh; # temporary workaround for DoubleSlider on right panel
         $panel->Refresh;
     }
     
    diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp
    index 10ba4efb53..43628cb472 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.cpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.cpp
    @@ -811,6 +811,13 @@ PrusaDoubleSlider::PrusaDoubleSlider(   wxWindow *parent,
         segm_pens = { &DARK_ORANGE_PEN, &ORANGE_PEN, &LIGHT_ORANGE_PEN };
     }
     
    +int PrusaDoubleSlider::GetActiveValue() const
    +{
    +    return m_selection == ssLower ?
    +    m_lower_value : m_selection == ssHigher ?
    +                m_higher_value : -1;
    +}
    +
     wxSize PrusaDoubleSlider::DoGetBestSize() const
     {
         const wxSize size = wxControl::DoGetBestSize();
    @@ -834,6 +841,13 @@ void PrusaDoubleSlider::SetHigherValue(const int higher_val)
         Update();
     }
     
    +void PrusaDoubleSlider::SetMaxValue(int max_value)
    +{
    +    m_max_value = max_value;
    +    Refresh();
    +    Update();
    +}
    +
     void PrusaDoubleSlider::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos)
     {
         int width;
    @@ -967,10 +981,12 @@ wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const
     {
         const int value = selection == ssLower ? m_lower_value : m_higher_value;
     
    -    if (m_label_koef == 1.0)
    +    if (m_label_koef == 1.0 && m_values.empty())
             return wxString::Format("%d", value);
     
    -    const wxString str = wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None);
    +    const wxString str = m_values.empty() ? 
    +                         wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None) :
    +                         wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None);
         return wxString::Format("%s\n(%d)", str, value);
     }
     
    diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp
    index ed029d800d..1887db2b35 100644
    --- a/xs/src/slic3r/GUI/wxExtensions.hpp
    +++ b/xs/src/slic3r/GUI/wxExtensions.hpp
    @@ -521,16 +521,23 @@ public:
             const wxValidator& val = wxDefaultValidator,
             const wxString& name = wxEmptyString);
     
    -    int GetLowerValue() {
    +    int GetLowerValue() const {
             return m_lower_value;
         }
    -    int GetHigherValue() {
    +    int GetHigherValue() const {
             return m_higher_value;
         }
    +    int GetActiveValue() const;
         wxSize DoGetBestSize() const override;
         void SetLowerValue(int lower_val);
         void SetHigherValue(int higher_val);
    -    void SetKoefForLabels(const double koef){ m_label_koef = koef;}
    +    void SetMaxValue(int max_value);
    +    void SetKoefForLabels(const double koef) {
    +        m_label_koef = koef;
    +    }
    +    void SetSliderValues(const std::vector& values) {
    +        m_values = values;
    +    }
     
         void OnPaint(wxPaintEvent& ){ render();}
         void OnLeftDown(wxMouseEvent& event);
    @@ -545,8 +552,8 @@ public:
     protected:
      
         void    render();
    -    void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end);
         void    draw_focus_rect();
    +    void    draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end);
         void    draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
         void    draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection);
         void    draw_ticks(wxDC& dc);
    @@ -608,6 +615,7 @@ private:
         std::vector line_pens;
         std::vector segm_pens;
         std::set       m_ticks;
    +    std::vector m_values;
     };
     // ******************************************************************************************
     
    
    From 7e228e66989028b18af8be6b9f9cae346586dffc Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 24 Aug 2018 08:38:28 +0200
    Subject: [PATCH 170/185] Fixed compile on Linux
    
    ---
     xs/src/slic3r/GUI/GLToolbar.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/GUI/GLToolbar.cpp b/xs/src/slic3r/GUI/GLToolbar.cpp
    index e82369db4c..c730afbe0a 100644
    --- a/xs/src/slic3r/GUI/GLToolbar.cpp
    +++ b/xs/src/slic3r/GUI/GLToolbar.cpp
    @@ -1,4 +1,4 @@
    -#include "../../libslic3r/point.hpp"
    +#include "../../libslic3r/Point.hpp"
     #include "GLToolbar.hpp"
     
     #include "../../libslic3r/libslic3r.h"
    
    From 16259e6f2693e16112728ddc844ae3ca29c49dd9 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 24 Aug 2018 08:56:53 +0200
    Subject: [PATCH 171/185] Fixed out of print volume detection
    
    ---
     xs/src/slic3r/GUI/3DScene.cpp | 5 ++++-
     1 file changed, 4 insertions(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index a3f7e0a4cf..0fa31ee508 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -818,7 +818,10 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
             return false;
     
         BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
    -    BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), unscale(config->opt_float("max_print_height"))));
    +//############################################################################################################################################
    +    BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), config->opt_float("max_print_height")));
    +//    BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), unscale(config->opt_float("max_print_height"))));
    +//############################################################################################################################################
         // Allow the objects to protrude below the print bed
         print_volume.min(2) = -1e10;
     
    
    From 727a5fd99744d579b9590a402be2180494f95fdf Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 24 Aug 2018 10:03:34 +0200
    Subject: [PATCH 172/185] Better fix for out of print volume detection
    
    ---
     xs/src/slic3r/GUI/3DScene.cpp    | 21 +++++++++------------
     xs/src/slic3r/GUI/3DScene.hpp    |  2 +-
     xs/src/slic3r/GUI/GLCanvas3D.cpp |  2 +-
     3 files changed, 11 insertions(+), 14 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp
    index 0fa31ee508..d836866e23 100644
    --- a/xs/src/slic3r/GUI/3DScene.cpp
    +++ b/xs/src/slic3r/GUI/3DScene.cpp
    @@ -293,12 +293,12 @@ void GLVolume::set_convex_hull(const TriangleMesh& convex_hull)
         m_convex_hull = &convex_hull;
     }
     
    -Transform3d GLVolume::world_matrix() const
    +Transform3f GLVolume::world_matrix() const
     {
    -    Transform3d matrix = Transform3d::Identity();
    -    matrix.translate(m_origin);
    -    matrix.rotate(Eigen::AngleAxisd((double)m_angle_z, Vec3d::UnitZ()));
    -    matrix.scale((double)m_scale_factor);
    +    Transform3f matrix = Transform3f::Identity();
    +    matrix.translate(Vec3f((float)m_origin(0), (float)m_origin(1), (float)m_origin(2)));
    +    matrix.rotate(Eigen::AngleAxisf(m_angle_z, Vec3f::UnitZ()));
    +    matrix.scale(m_scale_factor);
         return matrix;
     }
     
    @@ -306,7 +306,7 @@ BoundingBoxf3 GLVolume::transformed_bounding_box() const
     {
         if (m_transformed_bounding_box_dirty)
         {
    -        m_transformed_bounding_box = bounding_box.transformed(world_matrix());
    +        m_transformed_bounding_box = bounding_box.transformed(world_matrix().cast());
             m_transformed_bounding_box_dirty = false;
         }
     
    @@ -318,9 +318,9 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box() const
         if (m_transformed_convex_hull_bounding_box_dirty)
         {
             if ((m_convex_hull != nullptr) && (m_convex_hull->stl.stats.number_of_facets > 0))
    -            m_transformed_convex_hull_bounding_box = m_convex_hull->transformed_bounding_box(world_matrix());
    +            m_transformed_convex_hull_bounding_box = m_convex_hull->transformed_bounding_box(world_matrix().cast());
             else
    -            m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix());
    +            m_transformed_convex_hull_bounding_box = bounding_box.transformed(world_matrix().cast());
     
             m_transformed_convex_hull_bounding_box_dirty = false;
         }
    @@ -818,10 +818,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
             return false;
     
         BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
    -//############################################################################################################################################
    -    BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), config->opt_float("max_print_height")));
    -//    BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), unscale(config->opt_float("max_print_height"))));
    -//############################################################################################################################################
    +    BoundingBoxf3 print_volume(Vec3d(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Vec3d(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height")));
         // Allow the objects to protrude below the print bed
         print_volume.min(2) = -1e10;
     
    diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp
    index ac3a53fe3b..1fbd958a84 100644
    --- a/xs/src/slic3r/GUI/3DScene.hpp
    +++ b/xs/src/slic3r/GUI/3DScene.hpp
    @@ -333,7 +333,7 @@ public:
         int                 volume_idx() const { return (this->composite_id / 1000) % 1000; }
         int                 instance_idx() const { return this->composite_id % 1000; }
     
    -    Transform3d         world_matrix() const;
    +    Transform3f         world_matrix() const;
         BoundingBoxf3       transformed_bounding_box() const;
         BoundingBoxf3       transformed_convex_hull_bounding_box() const;
     
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index a155769a57..d32c07b66d 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -3625,7 +3625,7 @@ BoundingBoxf3 GLCanvas3D::_selected_volumes_bounding_box() const
                 bb.merge(volume->bounding_box);
             }
     
    -        bb = bb.transformed(selected_volumes[0]->world_matrix());
    +        bb = bb.transformed(selected_volumes[0]->world_matrix().cast());
         }
         else
         {
    
    From acdbd987f5ea179dbaf820602b0a40632b5b25d7 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 24 Aug 2018 10:20:00 +0200
    Subject: [PATCH 173/185] Use double in place of coordf_t
    
    ---
     xs/src/slic3r/GUI/GLCanvas3D.cpp | 42 ++++++++++++++++----------------
     xs/src/slic3r/GUI/GLGizmo.cpp    |  8 +++---
     2 files changed, 25 insertions(+), 25 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index d32c07b66d..11a97b6f0f 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -1040,7 +1040,7 @@ void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object,
         // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
         layer_height_max *= 1.12;
     
    -    coordf_t max_z = unscale(print_object.size(2));
    +    double max_z = unscale(print_object.size(2));
         double layer_height = dynamic_cast(print_object.config.option("layer_height"))->value;
         float l = bar_rect.get_left();
         float w = bar_rect.get_right() - l;
    @@ -1062,7 +1062,7 @@ void GLCanvas3D::LayersEditing::_render_profile(const PrintObject& print_object,
         const ModelObject* model_object = print_object.model_object();
         if (model_object->layer_height_profile_valid)
         {
    -        const std::vector& profile = model_object->layer_height_profile;
    +        const std::vector& profile = model_object->layer_height_profile;
     
             ::glColor3f(0.0f, 0.0f, 1.0f);
             ::glBegin(GL_LINE_STRIP);
    @@ -2079,7 +2079,7 @@ void GLCanvas3D::set_bed_shape(const Pointfs& shape)
         bool new_shape = m_bed.set_shape(shape);
     
         // Set the origin and size for painting of the coordinate system axes.
    -    m_axes.origin = Vec3d(0.0, 0.0, (coordf_t)GROUND_Z);
    +    m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z);
         set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size());
     
         if (new_shape)
    @@ -2098,7 +2098,7 @@ void GLCanvas3D::set_auto_bed_shape()
     {
         // draw a default square bed around object center
         const BoundingBoxf3& bbox = volumes_bounding_box();
    -    coordf_t max_size = bbox.max_size();
    +    double max_size = bbox.max_size();
         const Vec3d center = bbox.center();
     
         Pointfs bed_shape;
    @@ -2111,7 +2111,7 @@ void GLCanvas3D::set_auto_bed_shape()
         set_bed_shape(bed_shape);
     
         // Set the origin for painting of the coordinate system axes.
    -    m_axes.origin = Vec3d(center(0), center(1), (coordf_t)GROUND_Z);
    +    m_axes.origin = Vec3d(center(0), center(1), (double)GROUND_Z);
     }
     
     void GLCanvas3D::set_axes_length(float length)
    @@ -2463,7 +2463,7 @@ void GLCanvas3D::reload_scene(bool force)
             if ((extruders_count > 1) && semm && wt && !co)
             {
                 // Height of a print (Show at least a slab)
    -            coordf_t height = std::max(m_model->bounding_box().max(2), 10.0);
    +            double height = std::max(m_model->bounding_box().max(2), 10.0);
     
                 float x = dynamic_cast(m_config->option("wipe_tower_x"))->value;
                 float y = dynamic_cast(m_config->option("wipe_tower_y"))->value;
    @@ -3208,7 +3208,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
     
                     m_dirty = true;
                 }
    -            m_mouse.drag.start_position_3D = Vec3d((coordf_t)pos(0), (coordf_t)pos(1), 0.0);
    +            m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
             }
             else if (evt.MiddleIsDown() || evt.RightIsDown())
             {
    @@ -3302,7 +3302,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
         }
         else if (evt.Moving())
         {
    -        m_mouse.position = Vec2d((coordf_t)pos(0), (coordf_t)pos(1));
    +        m_mouse.position = Vec2d((double)pos(0), (double)pos(1));
             // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
             if (m_picking_enabled)
                 m_dirty = true;
    @@ -3671,9 +3671,9 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co
         ::glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
     
         // camera axes
    -    Vec3d right((coordf_t)matrix[0], (coordf_t)matrix[4], (coordf_t)matrix[8]);
    -    Vec3d up((coordf_t)matrix[1], (coordf_t)matrix[5], (coordf_t)matrix[9]);
    -    Vec3d forward((coordf_t)matrix[2], (coordf_t)matrix[6], (coordf_t)matrix[10]);
    +    Vec3d right((double)matrix[0], (double)matrix[4], (double)matrix[8]);
    +    Vec3d up((double)matrix[1], (double)matrix[5], (double)matrix[9]);
    +    Vec3d forward((double)matrix[2], (double)matrix[6], (double)matrix[10]);
     
         Vec3d bb_min = bbox.min;
         Vec3d bb_max = bbox.max;
    @@ -3691,11 +3691,11 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co
         vertices.push_back(bb_max);
         vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2));
     
    -    coordf_t max_x = 0.0;
    -    coordf_t max_y = 0.0;
    +    double max_x = 0.0;
    +    double max_y = 0.0;
     
         // margin factor to give some empty space around the bbox
    -    coordf_t margin_factor = 1.25;
    +    double margin_factor = 1.25;
     
         for (const Vec3d v : vertices)
         {
    @@ -3704,8 +3704,8 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co
             Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
     
             // calculates vertex coordinate along camera xy axes
    -        coordf_t x_on_plane = proj_on_plane.dot(right);
    -        coordf_t y_on_plane = proj_on_plane.dot(up);
    +        double x_on_plane = proj_on_plane.dot(right);
    +        double y_on_plane = proj_on_plane.dot(up);
     
             max_x = std::max(max_x, margin_factor * std::abs(x_on_plane));
             max_y = std::max(max_y, margin_factor * std::abs(y_on_plane));
    @@ -3718,7 +3718,7 @@ float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) co
         max_y *= 2.0;
     
         const Size& cnv_size = get_canvas_size();
    -    return (float)std::min((coordf_t)cnv_size.get_width() / max_x, (coordf_t)cnv_size.get_height() / max_y);
    +    return (float)std::min((double)cnv_size.get_width() / max_x, (double)cnv_size.get_height() / max_y);
     }
     
     void GLCanvas3D::_deregister_callbacks()
    @@ -4155,7 +4155,7 @@ Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
     
         GLdouble out_x, out_y, out_z;
         ::gluUnProject((GLdouble)mouse_pos(0), (GLdouble)y, (GLdouble)mouse_z, modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z);
    -    return Vec3d((coordf_t)out_x, (coordf_t)out_y, (coordf_t)out_z);
    +    return Vec3d((double)out_x, (double)out_y, (double)out_z);
     }
     
     Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
    @@ -5110,7 +5110,7 @@ void GLCanvas3D::_load_shells()
         }
     
         // adds wipe tower's volume
    -    coordf_t max_z = m_print->objects[0]->model_object()->get_model()->bounding_box().max(2);
    +    double max_z = m_print->objects[0]->model_object()->get_model()->bounding_box().max(2);
         const PrintConfig& config = m_print->config;
         unsigned int extruders_count = config.nozzle_diameter.size();
         if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
    @@ -5183,8 +5183,8 @@ void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& previe
     void GLCanvas3D::_update_toolpath_volumes_outside_state()
     {
         // tolerance to avoid false detection at bed edges
    -    static const coordf_t tolerance_x = 0.05;
    -    static const coordf_t tolerance_y = 0.05;
    +    static const double tolerance_x = 0.05;
    +    static const double tolerance_y = 0.05;
     
         BoundingBoxf3 print_volume;
         if (m_config != nullptr)
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 6adad04d19..b348e83356 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -295,7 +295,7 @@ void GLGizmoRotate::on_update(const Linef3& mouse_ray)
     
         double theta = ::acos(clamp(-1.0, 1.0, new_dir.dot(orig_dir)));
         if (cross2(orig_dir, new_dir) < 0.0)
    -        theta = 2.0 * (coordf_t)PI - theta;
    +        theta = 2.0 * (double)PI - theta;
     
         // snap
         double len = mouse_pos.norm();
    @@ -303,11 +303,11 @@ void GLGizmoRotate::on_update(const Linef3& mouse_ray)
         double out_radius = 2.0 * (double)in_radius;
         if ((in_radius <= len) && (len <= out_radius))
         {
    -        coordf_t step = 2.0 * (coordf_t)PI / (coordf_t)SnapRegionsCount;
    -        theta = step * (coordf_t)std::round(theta / step);
    +        double step = 2.0 * (double)PI / (double)SnapRegionsCount;
    +        theta = step * (double)std::round(theta / step);
         }
     
    -    if (theta == 2.0 * (coordf_t)PI)
    +    if (theta == 2.0 * (double)PI)
             theta = 0.0;
     
         m_angle = (float)theta;
    
    From 95ae2d715b9ba3fc4f465a25af40d2f7de8d3cae Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 24 Aug 2018 10:32:05 +0200
    Subject: [PATCH 174/185] Fixed direction of rotate gizmo around y axis
    
    ---
     xs/src/slic3r/GUI/GLGizmo.cpp | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index b348e83356..34d1f2efc6 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -513,7 +513,7 @@ void GLGizmoRotate::transform_to_local() const
         }
         case Y:
         {
    -        ::glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
    +        ::glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
             ::glRotatef(180.0f, 0.0f, 0.0f, 1.0f);
             break;
         }
    @@ -543,7 +543,7 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray) cons
         case Y:
         {
             m.rotate(Eigen::AngleAxisd(-(double)PI, Vec3d::UnitZ()));
    -        m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitX()));
    +        m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitX()));
             break;
         }
         default:
    
    From 7f542a0f851590846625772e88934f9cc689809c Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 24 Aug 2018 11:17:53 +0200
    Subject: [PATCH 175/185] 3D gizmos grabbers always visible to picking pass
    
    ---
     xs/src/slic3r/GUI/GLCanvas3D.cpp | 15 +++++++++------
     xs/src/slic3r/GUI/GLGizmo.cpp    |  6 +-----
     2 files changed, 10 insertions(+), 11 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index 11a97b6f0f..40362ac87f 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -2352,13 +2352,14 @@ void GLCanvas3D::render()
         float theta = m_camera.get_theta();
         bool is_custom_bed = m_bed.is_custom();
     
    +    // picking pass
         _picking_pass();
    -    _render_background();
     
    +    // draw scene
    +    _render_background();
         _render_current_gizmo();
     
    -    // untextured bed needs to be rendered before objects
    -    if (is_custom_bed)
    +    if (is_custom_bed) // untextured bed needs to be rendered before objects
         {
             _render_bed(theta);
             // disable depth testing so that axes are not covered by ground
    @@ -2366,13 +2367,15 @@ void GLCanvas3D::render()
         }
         _render_objects();
     
    -    // textured bed needs to be rendered after objects
    -    if (!is_custom_bed)
    +    if (!is_custom_bed) // textured bed needs to be rendered after objects
         {
             _render_axes(true);
             _render_bed(theta);
         }
    +
         _render_cutting_plane();
    +
    +    // draw overlays
         _render_gizmos_overlay();
         _render_warning_texture();
         _render_legend_texture();
    @@ -3819,8 +3822,8 @@ void GLCanvas3D::_picking_pass() const
     
             ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     
    -        m_gizmos.render_current_gizmo_for_picking_pass(_selected_volumes_bounding_box());
             _render_volumes(true);
    +        m_gizmos.render_current_gizmo_for_picking_pass(_selected_volumes_bounding_box());
     
             if (m_multisample_allowed)
                 ::glEnable(GL_MULTISAMPLE);
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 34d1f2efc6..0ff5909a0f 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -373,11 +373,7 @@ void GLGizmoRotate::on_render(const BoundingBoxf3& box) const
     
     void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const
     {
    -#if ENABLE_GIZMOS_3D
    -    ::glEnable(GL_DEPTH_TEST);
    -#else
         ::glDisable(GL_DEPTH_TEST);
    -#endif // ENABLE_GIZMOS_3D
     
         ::glPushMatrix();
         transform_to_local();
    @@ -926,7 +922,7 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
     
     void GLGizmoScale3D::on_render_for_picking(const BoundingBoxf3& box) const
     {
    -    ::glEnable(GL_DEPTH_TEST);
    +    ::glDisable(GL_DEPTH_TEST);
     
         for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i)
         {
    
    From 5f6a8adf7cda863e07f9fbf441c9cd25cf935117 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 24 Aug 2018 12:06:05 +0200
    Subject: [PATCH 176/185] Modified render order of gizmos
    
    ---
     xs/src/slic3r/GUI/GLCanvas3D.cpp | 4 +++-
     xs/src/slic3r/GUI/GLGizmo.cpp    | 2 +-
     2 files changed, 4 insertions(+), 2 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    index 40362ac87f..0bb1df25d4 100644
    --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
    +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
    @@ -507,6 +507,7 @@ void GLCanvas3D::Bed::_render_prusa(float theta) const
         if (triangles_vcount > 0)
         {
             ::glEnable(GL_DEPTH_TEST);
    +        ::glDepthMask(GL_FALSE);
     
             ::glEnable(GL_BLEND);
             ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    @@ -535,6 +536,7 @@ void GLCanvas3D::Bed::_render_prusa(float theta) const
             ::glDisable(GL_TEXTURE_2D);
     
             ::glDisable(GL_BLEND);
    +        ::glDepthMask(GL_TRUE);
         }
     }
     
    @@ -2357,7 +2359,6 @@ void GLCanvas3D::render()
     
         // draw scene
         _render_background();
    -    _render_current_gizmo();
     
         if (is_custom_bed) // untextured bed needs to be rendered before objects
         {
    @@ -2373,6 +2374,7 @@ void GLCanvas3D::render()
             _render_bed(theta);
         }
     
    +    _render_current_gizmo();
         _render_cutting_plane();
     
         // draw overlays
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 0ff5909a0f..212bf51a9e 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -837,7 +837,7 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const
     {
         ::glEnable(GL_DEPTH_TEST);
     
    -    Vec3d offset_vec((double)Offset, (double)Offset, (double)Offset);
    +    Vec3d offset_vec = (double)Offset * Vec3d::Ones();
     
         m_box = BoundingBoxf3(box.min - offset_vec, box.max + offset_vec);
         const Vec3d& center = m_box.center();
    
    From 8a9d0023a7616e27f209448ecc2adab52e5b5188 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 24 Aug 2018 12:16:11 +0200
    Subject: [PATCH 177/185] Added snap to scale to rotate gizmo
    
    ---
     xs/src/slic3r/GUI/GLGizmo.cpp | 14 +++++++++++++-
     1 file changed, 13 insertions(+), 1 deletion(-)
    
    diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp
    index 212bf51a9e..ee98857e9b 100644
    --- a/xs/src/slic3r/GUI/GLGizmo.cpp
    +++ b/xs/src/slic3r/GUI/GLGizmo.cpp
    @@ -297,8 +297,9 @@ void GLGizmoRotate::on_update(const Linef3& mouse_ray)
         if (cross2(orig_dir, new_dir) < 0.0)
             theta = 2.0 * (double)PI - theta;
     
    -    // snap
         double len = mouse_pos.norm();
    +
    +    // snap to snap region
         double in_radius = (double)m_radius / 3.0;
         double out_radius = 2.0 * (double)in_radius;
         if ((in_radius <= len) && (len <= out_radius))
    @@ -306,6 +307,17 @@ void GLGizmoRotate::on_update(const Linef3& mouse_ray)
             double step = 2.0 * (double)PI / (double)SnapRegionsCount;
             theta = step * (double)std::round(theta / step);
         }
    +    else
    +    {
    +        // snap to scale
    +        in_radius = (double)m_radius;
    +        out_radius = in_radius + (double)ScaleLongTooth;
    +        if ((in_radius <= len) && (len <= out_radius))
    +        {
    +            double step = 2.0 * (double)PI / (double)ScaleStepsCount;
    +            theta = step * (double)std::round(theta / step);
    +        }
    +    }
     
         if (theta == 2.0 * (double)PI)
             theta = 0.0;
    
    From bfac36174f33f05d1042bc019393eb105e1f6dd2 Mon Sep 17 00:00:00 2001
    From: Enrico Turri 
    Date: Fri, 24 Aug 2018 12:32:14 +0200
    Subject: [PATCH 178/185] Renamed private member functions in GLToolbar
    
    ---
     xs/src/slic3r/GUI/GLToolbar.cpp | 50 ++++++++++++++++-----------------
     xs/src/slic3r/GUI/GLToolbar.hpp | 24 ++++++++--------
     2 files changed, 37 insertions(+), 37 deletions(-)
    
    diff --git a/xs/src/slic3r/GUI/GLToolbar.cpp b/xs/src/slic3r/GUI/GLToolbar.cpp
    index c730afbe0a..388868b12f 100644
    --- a/xs/src/slic3r/GUI/GLToolbar.cpp
    +++ b/xs/src/slic3r/GUI/GLToolbar.cpp
    @@ -82,10 +82,10 @@ bool GLToolbarItem::is_separator() const
     
     void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const
     {
    -    GLTexture::render_sub_texture(tex_id, left, right, bottom, top, _get_uvs(texture_size, border_size, icon_size, gap_size));
    +    GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(texture_size, border_size, icon_size, gap_size));
     }
     
    -GLTexture::Quad_UVs GLToolbarItem::_get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const
    +GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const
     {
         GLTexture::Quad_UVs uvs;
     
    @@ -209,11 +209,11 @@ float GLToolbar::get_width() const
         default:
         case Layout::Horizontal:
         {
    -        return _get_width_horizontal();
    +        return get_width_horizontal();
         }
         case Layout::Vertical:
         {
    -        return _get_width_vertical();
    +        return get_width_vertical();
         }
         }
     }
    @@ -225,11 +225,11 @@ float GLToolbar::get_height() const
         default:
         case Layout::Horizontal:
         {
    -        return _get_height_horizontal();
    +        return get_height_horizontal();
         }
         case Layout::Vertical:
         {
    -        return _get_height_vertical();
    +        return get_height_vertical();
         }
         }
     }
    @@ -279,12 +279,12 @@ void GLToolbar::update_hover_state(const Vec2d& mouse_pos)
         default:
         case Layout::Horizontal:
         {
    -        _update_hover_state_horizontal(mouse_pos);
    +        update_hover_state_horizontal(mouse_pos);
             break;
         }
         case Layout::Vertical:
         {
    -        _update_hover_state_vertical(mouse_pos);
    +        update_hover_state_vertical(mouse_pos);
             break;
         }
         }
    @@ -300,11 +300,11 @@ int GLToolbar::contains_mouse(const Vec2d& mouse_pos) const
         default:
         case Layout::Horizontal:
         {
    -        return _contains_mouse_horizontal(mouse_pos);
    +        return contains_mouse_horizontal(mouse_pos);
         }
         case Layout::Vertical:
         {
    -        return _contains_mouse_vertical(mouse_pos);
    +        return contains_mouse_vertical(mouse_pos);
         }
         }
     }
    @@ -358,12 +358,12 @@ void GLToolbar::render() const
         default:
         case Layout::Horizontal:
         {
    -        _render_horizontal();
    +        render_horizontal();
             break;
         }
         case Layout::Vertical:
         {
    -        _render_vertical();
    +        render_vertical();
             break;
         }
         }
    @@ -371,27 +371,27 @@ void GLToolbar::render() const
         ::glPopMatrix();
     }
     
    -float GLToolbar::_get_width_horizontal() const
    +float GLToolbar::get_width_horizontal() const
     {
    -    return _get_main_size();
    +    return get_main_size();
     }
     
    -float GLToolbar::_get_width_vertical() const
    +float GLToolbar::get_width_vertical() const
     {
         return m_icons_texture.items_icon_size;
     }
     
    -float GLToolbar::_get_height_horizontal() const
    +float GLToolbar::get_height_horizontal() const
     {
         return m_icons_texture.items_icon_size;
     }
     
    -float GLToolbar::_get_height_vertical() const
    +float GLToolbar::get_height_vertical() const
     {
    -    return _get_main_size();
    +    return get_main_size();
     }
     
    -float GLToolbar::_get_main_size() const
    +float GLToolbar::get_main_size() const
     {
         float size = 0.0f;
         for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
    @@ -408,7 +408,7 @@ float GLToolbar::_get_main_size() const
         return size;
     }
     
    -void GLToolbar::_update_hover_state_horizontal(const Vec2d& mouse_pos)
    +void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos)
     {
         float zoom = m_parent.get_camera_zoom();
         float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    @@ -488,7 +488,7 @@ void GLToolbar::_update_hover_state_horizontal(const Vec2d& mouse_pos)
         m_parent.set_tooltip(tooltip);
     }
     
    -void GLToolbar::_update_hover_state_vertical(const Vec2d& mouse_pos)
    +void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos)
     {
         float zoom = m_parent.get_camera_zoom();
         float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    @@ -568,7 +568,7 @@ void GLToolbar::_update_hover_state_vertical(const Vec2d& mouse_pos)
         m_parent.set_tooltip(tooltip);
     }
     
    -int GLToolbar::_contains_mouse_horizontal(const Vec2d& mouse_pos) const
    +int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos) const
     {
         float zoom = m_parent.get_camera_zoom();
         float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    @@ -609,7 +609,7 @@ int GLToolbar::_contains_mouse_horizontal(const Vec2d& mouse_pos) const
         return -1;
     }
     
    -int GLToolbar::_contains_mouse_vertical(const Vec2d& mouse_pos) const
    +int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos) const
     {
         float zoom = m_parent.get_camera_zoom();
         float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
    @@ -650,7 +650,7 @@ int GLToolbar::_contains_mouse_vertical(const Vec2d& mouse_pos) const
         return -1;
     }
     
    -void GLToolbar::_render_horizontal() const
    +void GLToolbar::render_horizontal() const
     {
         unsigned int tex_id = m_icons_texture.texture.get_id();
         int tex_size = m_icons_texture.texture.get_width();
    @@ -684,7 +684,7 @@ void GLToolbar::_render_horizontal() const
         }
     }
     
    -void GLToolbar::_render_vertical() const
    +void GLToolbar::render_vertical() const
     {
         unsigned int tex_id = m_icons_texture.texture.get_id();
         int tex_size = m_icons_texture.texture.get_width();
    diff --git a/xs/src/slic3r/GUI/GLToolbar.hpp b/xs/src/slic3r/GUI/GLToolbar.hpp
    index 18d3f130df..c1501b10c2 100644
    --- a/xs/src/slic3r/GUI/GLToolbar.hpp
    +++ b/xs/src/slic3r/GUI/GLToolbar.hpp
    @@ -69,7 +69,7 @@ public:
         void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const;
     
     private:
    -    GLTexture::Quad_UVs _get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const;
    +    GLTexture::Quad_UVs get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const;
     };
     
     class GLToolbar
    @@ -155,18 +155,18 @@ public:
         void render() const;
     
     private:
    -    float _get_width_horizontal() const;
    -    float _get_width_vertical() const;
    -    float _get_height_horizontal() const;
    -    float _get_height_vertical() const;
    -    float _get_main_size() const;
    -    void _update_hover_state_horizontal(const Vec2d& mouse_pos);
    -    void _update_hover_state_vertical(const Vec2d& mouse_pos);
    -    int _contains_mouse_horizontal(const Vec2d& mouse_pos) const;
    -    int _contains_mouse_vertical(const Vec2d& mouse_pos) const;
    +    float get_width_horizontal() const;
    +    float get_width_vertical() const;
    +    float get_height_horizontal() const;
    +    float get_height_vertical() const;
    +    float get_main_size() const;
    +    void update_hover_state_horizontal(const Vec2d& mouse_pos);
    +    void update_hover_state_vertical(const Vec2d& mouse_pos);
    +    int contains_mouse_horizontal(const Vec2d& mouse_pos) const;
    +    int contains_mouse_vertical(const Vec2d& mouse_pos) const;
     
    -    void _render_horizontal() const;
    -    void _render_vertical() const;
    +    void render_horizontal() const;
    +    void render_vertical() const;
     };
     
     } // namespace GUI
    
    From 774315fe07ad3cbb4908a0cce159d22483e9b738 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Fri, 24 Aug 2018 12:54:21 +0200
    Subject: [PATCH 179/185] Fixed after-merging compilation problems
    
    ---
     xs/src/libslic3r/PrintConfig.cpp | 2 +-
     xs/src/libslic3r/PrintConfig.hpp | 4 ++--
     2 files changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
    index a00e99ce53..3094dbe282 100644
    --- a/xs/src/libslic3r/PrintConfig.cpp
    +++ b/xs/src/libslic3r/PrintConfig.cpp
    @@ -39,7 +39,7 @@ void PrintConfigDef::init_common_params()
     
         def = this->add("bed_shape", coPoints);
         def->label = L("Bed shape");
    -    def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) };
    +    def->default_value = new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) };
         
         def = this->add("layer_height", coFloat);
         def->label = L("Layer height");
    diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
    index 5144ad4241..43e3af169a 100644
    --- a/xs/src/libslic3r/PrintConfig.hpp
    +++ b/xs/src/libslic3r/PrintConfig.hpp
    @@ -82,7 +82,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::get
         return keys_map;
     }
     
    -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() {
    +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() {
         static t_config_enum_values keys_map;
         if (keys_map.empty()) {
             keys_map["octoprint"]       = htOctoPrint;
    @@ -91,7 +91,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum::get_enu
         return keys_map;
     }
     
    -template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() {
    +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() {
         static t_config_enum_values keys_map;
         if (keys_map.empty()) {
             keys_map["rectilinear"]         = ipRectilinear;
    
    From d90f5fa591d35b930bb3668b9d86381c02ddfe82 Mon Sep 17 00:00:00 2001
    From: YuSanka 
    Date: Fri, 24 Aug 2018 13:34:38 +0200
    Subject: [PATCH 180/185] Added "one_layer" mode for PrusaDoubleSlider
    
    ---
     resources/icons/one_layer_lock_off.png   | Bin 0 -> 577 bytes
     resources/icons/one_layer_lock_on.png    | Bin 0 -> 528 bytes
     resources/icons/one_layer_unlock_off.png | Bin 0 -> 508 bytes
     resources/icons/one_layer_unlock_on.png  | Bin 0 -> 483 bytes
     xs/src/slic3r/GUI/GUI.cpp                |  13 --
     xs/src/slic3r/GUI/wxExtensions.cpp       | 232 ++++++++++++++++-------
     xs/src/slic3r/GUI/wxExtensions.hpp       |  41 ++--
     7 files changed, 197 insertions(+), 89 deletions(-)
     create mode 100644 resources/icons/one_layer_lock_off.png
     create mode 100644 resources/icons/one_layer_lock_on.png
     create mode 100644 resources/icons/one_layer_unlock_off.png
     create mode 100644 resources/icons/one_layer_unlock_on.png
    
    diff --git a/resources/icons/one_layer_lock_off.png b/resources/icons/one_layer_lock_off.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..7dc8b06118fbf0e2e3960e458d2ccaded8dc6398
    GIT binary patch
    literal 577
    zcmV-H0>1r;P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&0oF-GK~y+TeN$N|
    zrC}KU<5*H|WNDJ+#-(y)kUJSAS6nOSSW<>rkPD&?Y8ofhIVmJJgsI7vL?>IBL5)ap
    zA(DN`KGw55-uL|9`Tk$Y(|hqh-}}7Z|8AcGJudcPx54rWW#P{ze^kVxB?~hhxIPvF
    z54JO+8n8&(yEg+935s@4-j0fTkyD+)q)A+{E<^A9Vd*qq1CGJaR1I2a}a
    z#w~EWyfM@5F!^#!O+2=ju=ER!sVEI&I?}+w<EqW25`
    zP(5i#;R~Fa5D0(j=`F(oqUht-17aj6T6LWO4fLDCe|h}PziG)Y7^LO
    zIj~z{{-|l1(iN^xa!Ty#2nXw2`tB;%M$I_g=4MDuS%d6)`{J5Jn`Vvo8Gap
    zAG&=bI36+E`QBdt>4D3kYIQRq(EFKe#m_W)KTe|oUq8T-fvL7vg92Uvsz+lDLmI@D
    P00000NkvXXu0mjfoBjT?
    
    literal 0
    HcmV?d00001
    
    diff --git a/resources/icons/one_layer_lock_on.png b/resources/icons/one_layer_lock_on.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..41b7ff173f98d36fc3ccbafcfa9ac4cf20a83726
    GIT binary patch
    literal 528
    zcmV+r0`L8aP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&0i{VqK~y+Tl~PG4
    zg<%wx5|NDzSs2P%$-*298Il!iMaD8_+7R`;m*N?UEJ!viC}l_@31uuI#X?>$GDn_S
    z@s4}ncmMYu7IM0a@7#ON|J{4P-v$52FV8|RjH#&bN3j=bL(!FqrG6=CaPq7S!jOQW
    zLL6_vyp7#y9Ie8IJ|Cl7tQF&w5XY>lzG+>hy$p3ODEs1FmF!_yRjTo<&&
    z;CK@wl_>ILN}-SAZfipL+?u4cHqW#{6ja-rK~I)Yis~TlE#FNj`NgA;l~iX2_UABK
    z2VE53OwpT-g&ydlMbMt2c}lz`MUCBG;Ou}$KiT8iXc^ZgUWO7>1ZbW-S4r7nxni5;
    zQYb+Q3O(p()0O#3%4%gwnCs$(XN^K%A4K4|hnAyKpC3#KlRDlnEn4B_UIdPL?0d6*
    zG9|2!pg#vY6GGn}ML_!y4?(?;y*D-Y-Ko&`XRfv;D&{nIsSit^Zh3Vy{lhgMC?0Qc
    ze#lR!!O62cfaXZ);YS$au{JESZytsDZnP!yzgOUnsz4aiu-GRh4Zl2p0{8;H4zuib
    SoxZjJ0000hI
    
    literal 0
    HcmV?d00001
    
    diff --git a/resources/icons/one_layer_unlock_off.png b/resources/icons/one_layer_unlock_off.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..c3931f8b9d11ba5db566066820ac2afb40b87057
    GIT binary patch
    literal 508
    zcmVPx#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&0g*{WK~y+TeUr

    yC2SD3|l|&Ez;vJoVMgJM+xE&%E=Q`wI?- zL#NY8r_+Hzz-F_B!{KZ;+w1jCr;}z_P|o#w9SjB}k@_EzNMt-7OD1@5Ak=C#jemf{ z;b^s5x7$rQbhq2>`Fwu0T2XH@nP3zO1rC*A!Rd5LH?!OAd_EsX%jL3ME@O~dEEW+a zs$LNag^tIgX*3$*&*$@0DkXL-7Nf1l;}Q22)H3mST%6HpluRbM0Oc87sZ@$aqkg|1 z*X#944xp}ayWQfDK`fKW%w{w0$(=Ww4fb?8MWN&XN|-mHl}ZJb0M25uV5HcLOT!in z-PN?fxP()$*HJ|)?EQYPVd;}dB-FF;TJ^b1@rm+k@I<#MT3s~Y(X6#nj;(E9!Um%Sbi yhcb7d904-g?KayWn-&ZPS%_}8`{)0;-+urK-CfHDDVaL}0000Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&0eDG7K~y+Tm6FS= zK~WTk?;lSd$Ezge5D76ekrQU5L@1956VDlikpbIx?%K{l>UMvl_4@YSYpwmG{;yA_ z-|v@7rD!x72n6Esc%@Pq4u?ATl*^3A<4h*w`KEk6uZw==GRm0^3&CO;5Mw(K~vUnsf6hP$MW;2~mhtO)ZDi({9PNh<{TFo#FaUF4x zX0q8V{&YGm6be3{kK-9bqtQqvlaWXSHxvp9!=ai927~xikSmwV%jJ@H9*@WUeviFa zEa*@ec+I@_+G@3kAei-feY@ST@Atc8{eC~L&F}yTA;x7BO10ZN5Ua>Eii)7uoA@(EDYPCw;`F!SV5dbSD z^|l{TI2;!2tN44m?v)vhM(K1~m%NLHCzFY;du2quUXRT(AC?=7#aQlOFwnWDKAFFz Z)C1+OrOso1VzU4M002ovPDHLkV1i}d*xLXA literal 0 HcmV?d00001 diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 8701db511c..088cbf80bd 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1052,19 +1052,6 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl // Object List add_objects_list(parent, sizer); - // experiment with slider - PrusaDoubleSlider* slider_h = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 200, wxDefaultPosition, - wxSize(60, wxDefaultSize.y), wxSL_HORIZONTAL); - sizer->AddSpacer(5); - sizer->Add(slider_h, 0, wxEXPAND | wxLEFT, 20); - sizer->AddSpacer(5); - PrusaDoubleSlider* slider_v = new PrusaDoubleSlider(parent, wxID_ANY, 50, 70, 0, 100, wxDefaultPosition, - wxSize(120 ,200), wxSL_VERTICAL); - slider_v->SetKoefForLabels(2.25); - sizer->AddSpacer(5); - sizer->Add(slider_v, 0, wxLEFT, 20); - sizer->AddSpacer(5); - // Frequently Object Settings add_object_settings(parent, sizer); } diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index 43628cb472..7fcad9e656 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -771,17 +771,23 @@ PrusaDoubleSlider::PrusaDoubleSlider( wxWindow *parent, SetDoubleBuffered(true); #endif //__WXOSX__ - m_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("right_half_circle.png")) : - Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG); - m_thumb_lower = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) : - Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG); - m_thumb_size = m_thumb_lower.GetSize(); + m_bmp_thumb_higher = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("right_half_circle.png")) : + Slic3r::GUI::from_u8(Slic3r::var("up_half_circle.png")), wxBITMAP_TYPE_PNG); + m_bmp_thumb_lower = wxBitmap(style == wxSL_HORIZONTAL ? Slic3r::GUI::from_u8(Slic3r::var("left_half_circle.png")) : + Slic3r::GUI::from_u8(Slic3r::var("down_half_circle.png")), wxBITMAP_TYPE_PNG); + m_thumb_size = m_bmp_thumb_lower.GetSize(); - m_add_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_on.png")), wxBITMAP_TYPE_PNG); - m_add_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_off.png")), wxBITMAP_TYPE_PNG); - m_del_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG); - m_del_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_off.png")), wxBITMAP_TYPE_PNG); - m_tick_icon_dim = m_add_tick_on.GetSize().x; + m_bmp_add_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_add_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_add_off.png")), wxBITMAP_TYPE_PNG); + m_bmp_del_tick_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_del_tick_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("colorchange_delete_off.png")), wxBITMAP_TYPE_PNG); + m_tick_icon_dim = m_bmp_add_tick_on.GetSize().x; + + m_bmp_one_layer_lock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_lock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_lock_off.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_unlock_on = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_on.png")), wxBITMAP_TYPE_PNG); + m_bmp_one_layer_unlock_off = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("one_layer_unlock_off.png")), wxBITMAP_TYPE_PNG); + m_lock_icon_dim = m_bmp_one_layer_lock_on.GetSize().x; m_selection = ssUndef; @@ -794,10 +800,12 @@ PrusaDoubleSlider::PrusaDoubleSlider( wxWindow *parent, Bind(wxEVT_ENTER_WINDOW,&PrusaDoubleSlider::OnEnterWin, this); Bind(wxEVT_LEAVE_WINDOW,&PrusaDoubleSlider::OnLeaveWin, this); Bind(wxEVT_KEY_DOWN, &PrusaDoubleSlider::OnKeyDown, this); + Bind(wxEVT_KEY_UP, &PrusaDoubleSlider::OnKeyUp, this); Bind(wxEVT_RIGHT_DOWN, &PrusaDoubleSlider::OnRightDown,this); + Bind(wxEVT_RIGHT_UP, &PrusaDoubleSlider::OnRightUp, this); // control's view variables - SLIDER_MARGIN = 4 + (style == wxSL_HORIZONTAL ? m_thumb_higher.GetWidth() : m_thumb_higher.GetHeight()); + SLIDER_MARGIN = 4 + (style == wxSL_HORIZONTAL ? m_bmp_thumb_higher.GetWidth() : m_bmp_thumb_higher.GetHeight()); DARK_ORANGE_PEN = wxPen(wxColour(253, 84, 2)); ORANGE_PEN = wxPen(wxColour(253, 126, 66)); @@ -841,7 +849,7 @@ void PrusaDoubleSlider::SetHigherValue(const int higher_val) Update(); } -void PrusaDoubleSlider::SetMaxValue(int max_value) +void PrusaDoubleSlider::SetMaxValue(const int max_value) { m_max_value = max_value; Refresh(); @@ -852,7 +860,7 @@ void PrusaDoubleSlider::draw_scroll_line(wxDC& dc, const int lower_pos, const in { int width; int height; - GetSize(&width, &height); + get_size(&width, &height); wxCoord line_beg_x = is_horizontal() ? SLIDER_MARGIN : width*0.5 - 1; wxCoord line_beg_y = is_horizontal() ? height*0.5 - 1 : SLIDER_MARGIN; @@ -879,7 +887,7 @@ void PrusaDoubleSlider::draw_scroll_line(wxDC& dc, const int lower_pos, const in double PrusaDoubleSlider::get_scroll_step() { - const wxSize sz = GetSize(); + const wxSize sz = get_size(); const int& slider_len = m_style == wxSL_HORIZONTAL ? sz.x : sz.y; return double(slider_len - SLIDER_MARGIN * 2) / (m_max_value - m_min_value); } @@ -892,6 +900,19 @@ wxCoord PrusaDoubleSlider::get_position_from_value(const int value) return wxCoord(SLIDER_MARGIN + int(val*step + 0.5)); } +wxSize PrusaDoubleSlider::get_size() +{ + int w, h; + get_size(&w, &h); + return wxSize(w, h); +} + +void PrusaDoubleSlider::get_size(int *w, int *h) +{ + GetSize(w, h); + is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim; +} + void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos) { const double step = get_scroll_step(); @@ -914,7 +935,7 @@ void PrusaDoubleSlider::draw_focus_rect() const wxPen pen = wxPen(wxColour(128, 128, 10), 1, wxPENSTYLE_DOT); dc.SetPen(pen); dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); - dc.DrawRectangle(2, 2, sz.x - 4, sz.y - 4); + dc.DrawRectangle(1, 1, sz.x - 2, sz.y - 2); } void PrusaDoubleSlider::render() @@ -933,22 +954,27 @@ void PrusaDoubleSlider::render() // draw line draw_scroll_line(dc, lower_pos, higher_pos); - //lower slider: - draw_thumb(dc, lower_pos, ssLower); +// //lower slider: +// draw_thumb(dc, lower_pos, ssLower); +// //higher slider: +// draw_thumb(dc, higher_pos, ssHigher); - //higher slider: - draw_thumb(dc, higher_pos, ssHigher); + // draw both sliders + draw_thumbs(dc, lower_pos, higher_pos); //draw color print ticks draw_ticks(dc); + + //draw color print ticks + draw_one_layer_icon(dc); } void PrusaDoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end) { const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - wxBitmap* icon = m_is_action_icon_focesed ? &m_add_tick_off :&m_add_tick_on; + wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off : &m_bmp_add_tick_on; if (m_ticks.find(tick) != m_ticks.end()) - icon = m_is_action_icon_focesed ? &m_del_tick_off :&m_del_tick_on; + icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off : &m_bmp_del_tick_on; wxCoord x_draw, y_draw; is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim; @@ -992,7 +1018,8 @@ wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const void PrusaDoubleSlider::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const { - if (selection == ssUndef) return; + if (m_is_one_layer && selection != m_selection || !selection) + return; wxCoord text_width, text_height; const wxString label = get_label(selection); dc.GetMultiLineTextExtent(label, &text_width, &text_height); @@ -1029,7 +1056,7 @@ void PrusaDoubleSlider::draw_thumb_item(wxDC& dc, const wxPoint& pos, const Sele y_draw = pos.y - m_thumb_size.y; } } - dc.DrawBitmap(selection == ssLower ? m_thumb_lower : m_thumb_higher, x_draw, y_draw); + dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower : m_bmp_thumb_higher, x_draw, y_draw); // Update thumb rect update_thumb_rect(x_draw, y_draw, selection); @@ -1039,7 +1066,7 @@ void PrusaDoubleSlider::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const Sel { //calculate thumb position on slider line int width, height; - GetSize(&width, &height); + get_size(&width, &height); const wxPoint pos = is_horizontal() ? wxPoint(pos_coord, height*0.5) : wxPoint(0.5*width, pos_coord); // Draw thumb @@ -1052,11 +1079,35 @@ void PrusaDoubleSlider::draw_thumb(wxDC& dc, const wxCoord& pos_coord, const Sel draw_thumb_text(dc, pos, selection); } +void PrusaDoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos) +{ + //calculate thumb position on slider line + int width, height; + get_size(&width, &height); + const wxPoint pos_l = is_horizontal() ? wxPoint(lower_pos, height*0.5) : wxPoint(0.5*width, lower_pos); + const wxPoint pos_h = is_horizontal() ? wxPoint(higher_pos, height*0.5) : wxPoint(0.5*width, higher_pos); + + // Draw lower thumb + draw_thumb_item(dc, pos_l, ssLower); + // Draw lower info_line + draw_info_line_with_icon(dc, pos_l, ssLower); + + // Draw higher thumb + draw_thumb_item(dc, pos_h, ssHigher); + // Draw higher info_line + draw_info_line_with_icon(dc, pos_h, ssHigher); + // Draw higher thumb text + draw_thumb_text(dc, pos_h, ssHigher); + + // Draw lower thumb text + draw_thumb_text(dc, pos_l, ssLower); +} + void PrusaDoubleSlider::draw_ticks(wxDC& dc) { dc.SetPen(DARK_GREY_PEN); int height, width; - GetSize(&width, &height); + get_size(&width, &height); const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width; for (auto tick : m_ticks) { @@ -1069,6 +1120,25 @@ void PrusaDoubleSlider::draw_ticks(wxDC& dc) } } +void PrusaDoubleSlider::draw_one_layer_icon(wxDC& dc) +{ + wxBitmap* icon = m_is_one_layer ? + m_is_one_layer_icon_focesed ? &m_bmp_one_layer_lock_off : &m_bmp_one_layer_lock_on : + m_is_one_layer_icon_focesed ? &m_bmp_one_layer_unlock_off : &m_bmp_one_layer_unlock_on; + + int width, height; + get_size(&width, &height); + + wxCoord x_draw, y_draw; + is_horizontal() ? x_draw = width-2 : x_draw = 0.5*width - 0.5*m_lock_icon_dim; + is_horizontal() ? y_draw = 0.5*height - 0.5*m_lock_icon_dim : y_draw = height-2; + + dc.DrawBitmap(*icon, x_draw, y_draw); + + //update rect of the lock/unlock icon + m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim); +} + void PrusaDoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection) { const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y); @@ -1078,10 +1148,9 @@ void PrusaDoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& m_rect_higher_thumb = rect; } -int PrusaDoubleSlider::position_to_value(wxDC& dc, const wxCoord x, const wxCoord y) +int PrusaDoubleSlider::get_value_from_position(const wxCoord x, const wxCoord y) { - int width, height; - dc.GetSize(&width, &height); + const int height = get_size().y; const double step = get_scroll_step(); if (is_horizontal()) @@ -1119,15 +1188,23 @@ bool PrusaDoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect) void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event) { + this->CaptureMouse(); wxClientDC dc(this); wxPoint pos = event.GetLogicalPosition(dc); if (is_point_in_rect(pos, m_rect_tick_action)) { - OnRightDown(event); + action_tick(taOnIcon); return; } m_is_left_down = true; - detect_selected_slider(pos); + if (is_point_in_rect(pos, m_rect_one_layer_icon)){ + m_is_one_layer = !m_is_one_layer; + m_selection == ssLower ? correct_lower_value() : correct_higher_value(); + if (!m_selection) m_selection = ssHigher; + } + else + detect_selected_slider(pos); + Refresh(); Update(); event.Skip(); @@ -1137,38 +1214,39 @@ void PrusaDoubleSlider::correct_lower_value() { if (m_lower_value < m_min_value) m_lower_value = m_min_value; - else if (m_lower_value >= m_higher_value && m_lower_value <= m_max_value) - m_higher_value = m_lower_value; else if (m_lower_value > m_max_value) m_lower_value = m_max_value; + + if (m_lower_value >= m_higher_value && m_lower_value <= m_max_value || m_is_one_layer) + m_higher_value = m_lower_value; } void PrusaDoubleSlider::correct_higher_value() { if (m_higher_value > m_max_value) m_higher_value = m_max_value; - else if (m_higher_value <= m_lower_value && m_higher_value >= m_min_value) - m_lower_value = m_higher_value; else if (m_higher_value < m_min_value) m_higher_value = m_min_value; + + if (m_higher_value <= m_lower_value && m_higher_value >= m_min_value || m_is_one_layer) + m_lower_value = m_higher_value; } void PrusaDoubleSlider::OnMotion(wxMouseEvent& event) { - if (m_selection == ssUndef) - return; - wxClientDC dc(this); - wxPoint pos = event.GetLogicalPosition(dc); - if (!m_is_left_down){ + const wxClientDC dc(this); + const wxPoint pos = event.GetLogicalPosition(dc); + m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon); + if (!m_is_left_down && !m_is_one_layer){ m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action); } - else { + else if (m_is_left_down || m_is_right_down){ if (m_selection == ssLower) { - m_lower_value = position_to_value(dc, pos.x, pos.y); + m_lower_value = get_value_from_position(pos.x, pos.y); correct_lower_value(); } else if (m_selection == ssHigher) { - m_higher_value = position_to_value(dc, pos.x, pos.y); + m_higher_value = get_value_from_position(pos.x, pos.y); correct_higher_value(); } } @@ -1179,6 +1257,7 @@ void PrusaDoubleSlider::OnMotion(wxMouseEvent& event) void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event) { + this->ReleaseMouse(); m_is_left_down = false; Refresh(); Update(); @@ -1189,25 +1268,20 @@ void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event) ProcessWindowEvent(e); } -void PrusaDoubleSlider::OnEnterWin(wxMouseEvent& event) +void PrusaDoubleSlider::enter_window(wxMouseEvent& event, const bool enter) { - m_is_focused = true; + m_is_focused = enter; Refresh(); Update(); event.Skip(); } -void PrusaDoubleSlider::OnLeaveWin(wxMouseEvent& event) -{ - m_is_focused = false; - OnLeftUp(event); -} - // "condition" have to be true for: // - value increase (if wxSL_VERTICAL) // - value decrease (if wxSL_HORIZONTAL) void PrusaDoubleSlider::move_current_thumb(const bool condition) { + m_is_one_layer = wxGetKeyState(WXK_CONTROL); int delta = condition ? -1 : 1; if (is_horizontal()) delta *= -1; @@ -1235,13 +1309,17 @@ void PrusaDoubleSlider::action_tick(const TicksAction action) const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - const auto it = m_ticks.find(tick); - if (it == m_ticks.end() && action == taAdd) - m_ticks.insert(tick); - else if (it != m_ticks.end() && action == taDel) + if (action == taOnIcon && !m_ticks.insert(tick).second) m_ticks.erase(tick); - else - return; + else { + const auto it = m_ticks.find(tick); + if (it == m_ticks.end() && action == taAdd) + m_ticks.insert(tick); + else if (it != m_ticks.end() && action == taDel) + m_ticks.erase(tick); + else + return; + } Refresh(); Update(); @@ -1285,17 +1363,43 @@ void PrusaDoubleSlider::OnKeyDown(wxKeyEvent &event) } } -void PrusaDoubleSlider::OnRightDown(wxMouseEvent& event) +void PrusaDoubleSlider::OnKeyUp(wxKeyEvent &event) { - if (m_selection == ssUndef) - return; - - const int new_tick = m_selection == ssLower ? m_lower_value : m_higher_value; - - if (!m_ticks.insert(new_tick).second) - m_ticks.erase(new_tick); + if (event.GetKeyCode() == WXK_CONTROL) + m_is_one_layer = false; Refresh(); Update(); + event.Skip(); +} + +void PrusaDoubleSlider::OnRightDown(wxMouseEvent& event) +{ + this->CaptureMouse(); + const wxClientDC dc(this); + detect_selected_slider(event.GetLogicalPosition(dc)); + if (!m_selection) + return; + + if (m_selection == ssLower) + m_higher_value = m_lower_value; + else + m_lower_value = m_higher_value; + + m_is_right_down = m_is_one_layer = true; + + Refresh(); + Update(); + event.Skip(); +} + +void PrusaDoubleSlider::OnRightUp(wxMouseEvent& event) +{ + this->ReleaseMouse(); + m_is_right_down = m_is_one_layer = false; + + Refresh(); + Update(); + event.Skip(); } // ***************************************************************************** diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 1887db2b35..064ce10389 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -502,6 +502,7 @@ enum SelectedSlider { ssHigher }; enum TicksAction{ + taOnIcon, taAdd, taDel }; @@ -529,9 +530,9 @@ public: } int GetActiveValue() const; wxSize DoGetBestSize() const override; - void SetLowerValue(int lower_val); - void SetHigherValue(int higher_val); - void SetMaxValue(int max_value); + void SetLowerValue(const int lower_val); + void SetHigherValue(const int higher_val); + void SetMaxValue(const int max_value); void SetKoefForLabels(const double koef) { m_label_koef = koef; } @@ -543,11 +544,13 @@ public: void OnLeftDown(wxMouseEvent& event); void OnMotion(wxMouseEvent& event); void OnLeftUp(wxMouseEvent& event); - void OnEnterWin(wxMouseEvent& event); - void OnLeaveWin(wxMouseEvent& event); + void OnEnterWin(wxMouseEvent& event){ enter_window(event, true); } + void OnLeaveWin(wxMouseEvent& event){ enter_window(event, false); } void OnWheel(wxMouseEvent& event); void OnKeyDown(wxKeyEvent &event); + void OnKeyUp(wxKeyEvent &event); void OnRightDown(wxMouseEvent& event); + void OnRightUp(wxMouseEvent& event); protected: @@ -556,7 +559,9 @@ protected: void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end); void draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos); void draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection); + void draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos); void draw_ticks(wxDC& dc); + void draw_one_layer_icon(wxDC& dc); void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection); void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection); void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const; @@ -567,6 +572,7 @@ protected: void correct_higher_value(); void move_current_thumb(const bool condition); void action_tick(const TicksAction action); + void enter_window(wxMouseEvent& event, const bool enter); bool is_point_in_rect(const wxPoint& pt, const wxRect& rect); bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } @@ -574,30 +580,41 @@ protected: double get_scroll_step(); wxString get_label(const SelectedSlider& selection) const; void get_lower_and_higher_position(int& lower_pos, int& higher_pos); - int position_to_value(wxDC& dc, const wxCoord x, const wxCoord y); + int get_value_from_position(const wxCoord x, const wxCoord y); wxCoord get_position_from_value(const int value); + wxSize get_size(); + void get_size(int *w, int *h); private: int m_min_value; int m_max_value; int m_lower_value; int m_higher_value; - wxBitmap m_thumb_higher; - wxBitmap m_thumb_lower; - wxBitmap m_add_tick_on; - wxBitmap m_add_tick_off; - wxBitmap m_del_tick_on; - wxBitmap m_del_tick_off; + wxBitmap m_bmp_thumb_higher; + wxBitmap m_bmp_thumb_lower; + wxBitmap m_bmp_add_tick_on; + wxBitmap m_bmp_add_tick_off; + wxBitmap m_bmp_del_tick_on; + wxBitmap m_bmp_del_tick_off; + wxBitmap m_bmp_one_layer_lock_on; + wxBitmap m_bmp_one_layer_lock_off; + wxBitmap m_bmp_one_layer_unlock_on; + wxBitmap m_bmp_one_layer_unlock_off; SelectedSlider m_selection; bool m_is_left_down = false; + bool m_is_right_down = false; + bool m_is_one_layer = false; bool m_is_focused = false; bool m_is_action_icon_focesed = false; + bool m_is_one_layer_icon_focesed = false; wxRect m_rect_lower_thumb; wxRect m_rect_higher_thumb; wxRect m_rect_tick_action; + wxRect m_rect_one_layer_icon; wxSize m_thumb_size; int m_tick_icon_dim; + int m_lock_icon_dim = 16; long m_style; float m_label_koef = 1.0; From bbc465fdf38b01eda4be5c40d43cf40a65be19cf Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 24 Aug 2018 14:11:41 +0200 Subject: [PATCH 181/185] Added tooltip to gizmos --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 12 +++--- xs/src/slic3r/GUI/GLCanvas3D.hpp | 2 +- xs/src/slic3r/GUI/GLGizmo.cpp | 64 ++++++++++++++++++++++++-------- xs/src/slic3r/GUI/GLGizmo.hpp | 17 ++++++--- 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 0bb1df25d4..4ff947abc2 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1131,12 +1131,12 @@ GLCanvas3D::Gizmos::~Gizmos() _reset(); } -bool GLCanvas3D::Gizmos::init() +bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) { #if ENABLE_GIZMOS_3D - GLGizmoBase* gizmo = new GLGizmoScale3D; + GLGizmoBase* gizmo = new GLGizmoScale3D(parent); #else - GLGizmoBase* gizmo = new GLGizmoScale; + GLGizmoBase* gizmo = new GLGizmoScale(parent); #endif // ENABLE_GIZMOS_3D if (gizmo == nullptr) return false; @@ -1147,9 +1147,9 @@ bool GLCanvas3D::Gizmos::init() m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); #if ENABLE_GIZMOS_3D - gizmo = new GLGizmoRotate3D; + gizmo = new GLGizmoRotate3D(parent); #else - gizmo = new GLGizmoRotate(GLGizmoRotate::Z); + gizmo = new GLGizmoRotate(parent, GLGizmoRotate::Z); #endif // ENABLE_GIZMOS_3D if (gizmo == nullptr) { @@ -1938,7 +1938,7 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) if (!m_volumes.empty()) m_volumes.finalize_geometry(m_use_VBOs); - if (m_gizmos.is_enabled() && !m_gizmos.init()) + if (m_gizmos.is_enabled() && !m_gizmos.init(*this)) return false; if (!_init_toolbar()) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index 0cd2870eb2..cae8ddf0d2 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -352,7 +352,7 @@ public: Gizmos(); ~Gizmos(); - bool init(); + bool init(GLCanvas3D& parent); bool is_enabled() const; void set_enabled(bool enable); diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp index ee98857e9b..b742b642d1 100644 --- a/xs/src/slic3r/GUI/GLGizmo.cpp +++ b/xs/src/slic3r/GUI/GLGizmo.cpp @@ -1,6 +1,7 @@ #include "GLGizmo.hpp" #include "../../libslic3r/Utils.hpp" +#include "../../slic3r/GUI/GLCanvas3D.hpp" #include @@ -157,8 +158,9 @@ void GLGizmoBase::Grabber::render_face(float half_size) const } #endif // ENABLE_GIZMOS_3D -GLGizmoBase::GLGizmoBase() - : m_group_id(-1) +GLGizmoBase::GLGizmoBase(GLCanvas3D& parent) + : m_parent(parent) + , m_group_id(-1) , m_state(Off) , m_hover_id(-1) , m_is_container(false) @@ -234,6 +236,18 @@ void GLGizmoBase::render_grabbers_for_picking() const } } +void GLGizmoBase::set_tooltip(const std::string& tooltip) const +{ + m_parent.set_tooltip(tooltip); +} + +std::string GLGizmoBase::format(float value, unsigned int decimals) const +{ + char buf[1024]; + ::sprintf(buf, "%.*f", decimals, value); + return buf; +} + const float GLGizmoRotate::Offset = 5.0f; const unsigned int GLGizmoRotate::CircleResolution = 64; const unsigned int GLGizmoRotate::AngleResolution = 64; @@ -245,8 +259,8 @@ const float GLGizmoRotate::ScaleShortTooth = 1.0f; const unsigned int GLGizmoRotate::SnapRegionsCount = 8; const float GLGizmoRotate::GrabberOffset = 5.0f; -GLGizmoRotate::GLGizmoRotate(GLGizmoRotate::Axis axis) - : GLGizmoBase() +GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) + : GLGizmoBase(parent) , m_axis(axis) , m_angle(0.0f) , m_center(0.0, 0.0, 0.0) @@ -327,6 +341,9 @@ void GLGizmoRotate::on_update(const Linef3& mouse_ray) void GLGizmoRotate::on_render(const BoundingBoxf3& box) const { + if (m_grabbers[0].dragging) + set_tooltip(format(m_angle * 180.0f / (float)PI, 4)); + #if ENABLE_GIZMOS_3D ::glEnable(GL_DEPTH_TEST); #else @@ -577,11 +594,11 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray) cons return Linef3(Vec3d(local_ray(0, 0), local_ray(1, 0), local_ray(2, 0)), Vec3d(local_ray(0, 1), local_ray(1, 1), local_ray(2, 1))).intersect_plane(0.0); } -GLGizmoRotate3D::GLGizmoRotate3D() - : GLGizmoBase() - , m_x(GLGizmoRotate::X) - , m_y(GLGizmoRotate::Y) - , m_z(GLGizmoRotate::Z) +GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent) + : GLGizmoBase(parent) + , m_x(parent, GLGizmoRotate::X) + , m_y(parent, GLGizmoRotate::Y) + , m_z(parent, GLGizmoRotate::Z) { m_is_container = true; @@ -682,8 +699,8 @@ void GLGizmoRotate3D::on_render(const BoundingBoxf3& box) const const float GLGizmoScale::Offset = 5.0f; -GLGizmoScale::GLGizmoScale() - : GLGizmoBase() +GLGizmoScale::GLGizmoScale(GLCanvas3D& parent) + : GLGizmoBase(parent) , m_scale(1.0f) , m_starting_scale(1.0f) { @@ -733,6 +750,9 @@ void GLGizmoScale::on_update(const Linef3& mouse_ray) void GLGizmoScale::on_render(const BoundingBoxf3& box) const { + if (m_grabbers[0].dragging || m_grabbers[1].dragging || m_grabbers[2].dragging || m_grabbers[3].dragging) + set_tooltip(format(100.0f * m_scale, 4) + "%"); + ::glDisable(GL_DEPTH_TEST); double min_x = box.min(0) - (double)Offset; @@ -779,8 +799,8 @@ void GLGizmoScale::on_render_for_picking(const BoundingBoxf3& box) const const float GLGizmoScale3D::Offset = 5.0f; -GLGizmoScale3D::GLGizmoScale3D() - : GLGizmoBase() +GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent) + : GLGizmoBase(parent) , m_scale_x(1.0f) , m_scale_y(1.0f) , m_scale_z(1.0f) @@ -847,6 +867,20 @@ void GLGizmoScale3D::on_update(const Linef3& mouse_ray) void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const { + if (m_grabbers[0].dragging || m_grabbers[1].dragging) + set_tooltip("X: " + format(100.0f * m_scale_x, 4) + "%"); + else if (m_grabbers[2].dragging || m_grabbers[3].dragging) + set_tooltip("Y: " + format(100.0f * m_scale_y, 4) + "%"); + else if (m_grabbers[4].dragging || m_grabbers[5].dragging) + set_tooltip("Z: " + format(100.0f * m_scale_z, 4) + "%"); + else if (m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) + { + std::string tooltip = "X: " + format(100.0f * m_scale_x, 4) + "%\n"; + tooltip += "Y: " + format(100.0f * m_scale_y, 4) + "%\n"; + tooltip += "Z: " + format(100.0f * m_scale_z, 4) + "%"; + set_tooltip(tooltip); + } + ::glEnable(GL_DEPTH_TEST); Vec3d offset_vec = (double)Offset * Vec3d::Ones(); @@ -1013,7 +1047,7 @@ void GLGizmoScale3D::do_scale_y(const Linef3& mouse_ray) double ratio = calc_ratio(2, mouse_ray, m_starting_center); if (ratio > 0.0) - m_scale_x = m_starting_scale_y * (float)ratio; + m_scale_x = m_starting_scale_y * (float)ratio; // << this is temporary // m_scale_y = m_starting_scale_y * (float)ratio; } @@ -1022,7 +1056,7 @@ void GLGizmoScale3D::do_scale_z(const Linef3& mouse_ray) double ratio = calc_ratio(1, mouse_ray, m_starting_center); if (ratio > 0.0) - m_scale_x = m_starting_scale_z * (float)ratio; + m_scale_x = m_starting_scale_z * (float)ratio; // << this is temporary // m_scale_z = m_starting_scale_z * (float)ratio; } diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp index 0bef4bf63f..50bb333e5c 100644 --- a/xs/src/slic3r/GUI/GLGizmo.hpp +++ b/xs/src/slic3r/GUI/GLGizmo.hpp @@ -16,6 +16,8 @@ class Linef3; namespace GUI { +class GLCanvas3D; + class GLGizmoBase { protected: @@ -61,6 +63,8 @@ public: }; protected: + GLCanvas3D& m_parent; + int m_group_id; EState m_state; // textures are assumed to be square and all with the same size in pixels, no internal check is done @@ -73,7 +77,7 @@ protected: bool m_is_container; public: - GLGizmoBase(); + explicit GLGizmoBase(GLCanvas3D& parent); virtual ~GLGizmoBase() {} bool init() { return on_init(); } @@ -114,6 +118,9 @@ protected: float picking_color_component(unsigned int id) const; void render_grabbers() const; void render_grabbers_for_picking() const; + + void set_tooltip(const std::string& tooltip) const; + std::string format(float value, unsigned int decimals) const; }; class GLGizmoRotate : public GLGizmoBase @@ -146,7 +153,7 @@ private: mutable bool m_keep_initial_values; public: - explicit GLGizmoRotate(Axis axis); + GLGizmoRotate(GLCanvas3D& parent, Axis axis); float get_angle() const { return m_angle; } void set_angle(float angle); @@ -179,7 +186,7 @@ class GLGizmoRotate3D : public GLGizmoBase GLGizmoRotate m_z; public: - GLGizmoRotate3D(); + explicit GLGizmoRotate3D(GLCanvas3D& parent); float get_angle_x() const { return m_x.get_angle(); } void set_angle_x(float angle) { m_x.set_angle(angle); } @@ -237,7 +244,7 @@ class GLGizmoScale : public GLGizmoBase Vec2d m_starting_drag_position; public: - GLGizmoScale(); + explicit GLGizmoScale(GLCanvas3D& parent); float get_scale() const { return m_scale; } void set_scale(float scale) { m_starting_scale = scale; } @@ -268,7 +275,7 @@ class GLGizmoScale3D : public GLGizmoBase Vec3d m_starting_center; public: - GLGizmoScale3D(); + explicit GLGizmoScale3D(GLCanvas3D& parent); float get_scale_x() const { return m_scale_x; } void set_scale_x(float scale) { m_starting_scale_x = scale; } From cda135ada7ac2599d414b5ab19280135b651ebd2 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 24 Aug 2018 15:08:19 +0200 Subject: [PATCH 182/185] Removed scale and rotate actions from toolbar --- lib/Slic3r/GUI/Plater.pm | 32 +------------- resources/icons/toolbar.png | Bin 25416 -> 20451 bytes xs/src/slic3r/GUI/3DScene.cpp | 15 ------- xs/src/slic3r/GUI/3DScene.hpp | 3 -- xs/src/slic3r/GUI/GLCanvas3D.cpp | 53 ++---------------------- xs/src/slic3r/GUI/GLCanvas3D.hpp | 6 --- xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 21 ---------- xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 3 -- xs/xsp/GUI_3DScene.xsp | 21 ---------- 9 files changed, 6 insertions(+), 148 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 8c3b10b90d..67528096c4 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -185,18 +185,6 @@ sub new { $self->decrease; }; - my $on_action_ccw45 = sub { - $self->rotate(45, Z, 'relative'); - }; - - my $on_action_cw45 = sub { - $self->rotate(-45, Z, 'relative'); - }; - - my $on_action_scale = sub { - $self->changescale(undef); - }; - my $on_action_split = sub { $self->split_object; }; @@ -239,9 +227,6 @@ sub new { Slic3r::GUI::_3DScene::register_action_arrange_callback($self->{canvas3D}, $on_action_arrange); Slic3r::GUI::_3DScene::register_action_more_callback($self->{canvas3D}, $on_action_more); Slic3r::GUI::_3DScene::register_action_fewer_callback($self->{canvas3D}, $on_action_fewer); - Slic3r::GUI::_3DScene::register_action_ccw45_callback($self->{canvas3D}, $on_action_ccw45); - Slic3r::GUI::_3DScene::register_action_cw45_callback($self->{canvas3D}, $on_action_cw45); - Slic3r::GUI::_3DScene::register_action_scale_callback($self->{canvas3D}, $on_action_scale); Slic3r::GUI::_3DScene::register_action_split_callback($self->{canvas3D}, $on_action_split); Slic3r::GUI::_3DScene::register_action_cut_callback($self->{canvas3D}, $on_action_cut); Slic3r::GUI::_3DScene::register_action_settings_callback($self->{canvas3D}, $on_action_settings); @@ -403,24 +388,11 @@ sub new { $self->{btn_send_gcode}->Hide; my %icons = qw( - add brick_add.png - remove brick_delete.png - reset cross.png - arrange bricks.png export_gcode cog_go.png print arrow_up.png send_gcode arrow_up.png reslice reslice.png export_stl brick_go.png - - increase add.png - decrease delete.png - rotate45cw arrow_rotate_clockwise.png - rotate45ccw arrow_rotate_anticlockwise.png - changescale arrow_out.png - split shape_ungroup.png - cut package.png - settings cog.png ); for (grep $self->{"btn_$_"}, keys %icons) { $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new(Slic3r::var($icons{$_}), wxBITMAP_TYPE_PNG)); @@ -2248,8 +2220,8 @@ sub selection_changed { # $self->{"btn_decrease"}->Disable; # } # } - - for my $toolbar_item (qw(delete more fewer ccw45 cw45 scale split cut settings)) { + + for my $toolbar_item (qw(delete more fewer split cut settings)) { Slic3r::GUI::_3DScene::enable_toolbar_item($self->{canvas3D}, $toolbar_item, $have_sel); } diff --git a/resources/icons/toolbar.png b/resources/icons/toolbar.png index e45f4989f0ec1022cb90a3de19dc30feb80426a2..ce954143dbe98cc25d31ea8831332e402ee395cb 100644 GIT binary patch literal 20451 zcmeIa2RzpQ|2KMx2%%CTLWo3m_Gl<%6$;rq`?B{)Aqv@>%1T!D-dWis0Rn*_l9CixKp;-R zUr!;<;KCo9jz9L{4_sYo330?R=D!b>X`%26K2lQE27w?X#r(!X#Ke)qi+HwDvJdbk z&yb(xVs{fQ_yI3nHC9rwy>Dr5ZeU>x|3o0}TN~)u8tBtH7~8&}m5`Estj}hEhd|IG zq{QzkISwt3IJx%ixXq&b2ZK7gtEA6-GkIOZa*q9@-1DZurf6H6MPz7fgxr12oHAapxs367YClar*+v`z8?K(?OR8DzrKly8LMRK$BBv4+~$LMDYVhV z)WZBsp_N4CNtLg3KaX@M3Rwwz>)EH?>$hMGt?GLqewiQZjPE^LTidR|LDl!PvH<}V zR-3=$&~+cr#qyco9GGMcGhX?#_7rd~_OCvp&~p5*s_8 z!D7_UEEdVs`@SUArj5F4Onnv;Lw;|XFUj3{`R2`=D~R*w&p*MYrK6)WLBM*56M4d` zt{kOfD_ZX*8QzG~y{^|mn3$TTr>B2RP8QD)lIDum*VXm%^TS8nWMN6q%>1^pL&h3v z!bJB8SNqA?`tdq%LgAy`DVycv?c(b8`M!)bN8#raiCMihH3$-s&8|4Kmh0Ms=W1$J zPmUY)16kG|WaT1j@Uygbz1+3knNIlt`Dy$7xAKceS^-ySmP$$;H=koJFu{R!F+I2+7LI z4t6@tr9`TfS{rn5rQ6fd<$cE0*4M`=MzyCarsc)f!S`Ihem#!g>XhgFyoryG4_h@w zYiH^s_=3HSS=B<5r!NAou&Q?(N>C;PcMiz-y{4RCzEnF^jB36?)FYkDM{CG{K_Z!DqK_Q`h(&d0@ zB>(4}oV1T0U-tAUz)1W0`Zl+=4D#X0PZSkjp_Ul01cbEc-(Y9&927iR+~1@KR1wxH z`p{9B`cwA}>L6d3{;1RJ)$#S%ns)&K!7_~b)ft86LTDqKlqkmGXw$7d*OCzn-CULJ zF>cCdjG;Q#)**0H-7eo@>zBfuf}Ih>Kv95!YFJpZ_f~nMS4{Lz-UIG%P6O}?IAB0 z*FCOihIqMr{g#l$d>jj&J~?sm%OeP!Q&^fVOSgY!s@`{U5?FJLg9#sAGgH01a<=;6 zH#;A#l4I)UU;0bB5_w)XaLYaVfsw^^*__ZvGplGk&T zEM%8|{__U_HK(SIeO7H(dfYJ#;2i;Oh#@HLNNlt63F z1Qww<;~9(jMBdCbBRjKpx&@s1)5y5ECj{f=;GE6bI*;NPyDXYlp*zM_Nh}X2#`q5s z9e-`w3`)d|-Dq!b=Z}{Qq!H^F%s0Y1gZpaGF2?EHxc?=VyrwZk@vt>veSN)vD$U0OyS|- z1w}<6GK>PQyBuWfnhfX8Jl52dgA399;$mcL!~WI+mgD$Kyw4dKt%LZU5bx;TS|AMJ zz&?Ne{DrBhp1%HTqeQoemoKpxnV6#A1X@|Io-5bdCqmd)?^4Fmh`dCFqm(UK8u9y= z?BVLyzv>O_qgCJ6_ddpneU|+j(ejy!`!bY! zfBtNQMWdjinx6eBTo+90yE-{j+L1G5S0dnez;tl1uhq8XeR1ve!VX%s$czGlPFhY* z(Bm85APx)I%$$`=3ngWe8@DdyciZP#T3T{qu7H_X+mv6kywA7svj|aaBh**;ox61d7|iHWV(r=@r;hWU-^++0>^%c&29Fmv41sEU&> z;r`y*%FND=r@b^(Sh3bfS+PH#LGJSswPZL_W`_yFIa-J>@sN2uDQCLNtD}l#r<_SG z%*}5ZbtW|Mv?t1zu-lB8tE~%8u<38iv^$NhJ{3BDzC{)Lv5dcQ-6hPSAgYlSp74ip zE4*;N`VzaI>?sJA0P+GHdVAA$qpmcDx3wCtc|-my=tS6ZfSDOZOhAl)tZPc_2PB z73HS4vpizYaG?cFnF)JtVq!8-Xc{>+AH>|d#XX4nnrMcSy~ZtKWk?p7mhCEXNe|vw z^#j&>5;hmg({5VBd%Qy@H3DX!q=;^OPdP}p7SuMH_{gy;H+v5cv2NN#=;S82Lu(v4r zGuba!Fe)nQ>D}cbA|hdrGH#8yb-KlNb-Y#}deMB~>eAJV_m}JwhWw_Osq(ommDzJg zZ)UO4<<;Qd;lKDPDd|;5M~4@Vv5DDqK$67lFd4D;K!vel?Pa5nGHS&ZDdpwmw9z4c zI1`f7F*099wzwfr2@~C^O8jy=F@w9UYNW_Q<1W`#LDyXk?#n2M>xBKVSJ_?CSa07z zia+=7`}6|=bw)E@82a$&G~_HvhI{-}xF3p&B4T2$JbLt~pe&yuREm+2F)Atw@gXlS z=BS^0dWNz^nR7Z=y@_ebBE;p54- z>=5w$QA=!+jDL>Jr?f_Ye*(Eg(e|N2Q;5>NY4TG$XaT2n{o1te_vA_-K|C-2=G^SIS^UGR>^?PAqDz}L`)&~8%J86*2 z9a6h49NEDD;_;rPLTC9v+_4YAs%Vv9FI0zuo*r9~}g8ZSuho3T13!^4+~d zn!WPd_iz@Qh?A`K;5LGuo*purT)bfT$VI3tZZI=XeDfnkC@Cv*^YGa2wX3-EJ}n!u z=tvan{PHwoUGVm0lR6^b`Vw_E7^zB?tJAh<^_aY}G8uA@6ET5|^6|knL!}lMlR*$m zk&aX;vz;T-qWl|NW#r`Ybn0<+bIC2M4|&-l-$MAJcH8V0NZe`T?mTw}2kOXgx|~qy zZRGh~Y<(>xBGL};D#>sGqVR-uDf!i_zh2@|M{PKvcUGEKc0yb9B^g5@!*-92C5Gaj z)MJoLdJ$KN@uVYIKc=L3!w!7^{v8U0+2K+fl?ZPGRVX(CVU!h`>{C-y$-Ty~&`3cW z^t<{&(q)+l_~b{*bOz_tSt25x7)Bo*sE4EDk(La;^>^LeL?9)HMMPXUgY!8%J2fYV zJvIvh9vq@!3%>3bVKR)6DxErh{#o2aC<%x>$5xuA*3Jbygt`=_zuqCVf`}Z$F*GxK zr_U9AsF7uk8Wf9VY2caYP|xU`tzWVyecOhQ+-Q)RfEm7RfI|7yKbJ^K3CHpnjr;M^>h{ZI2PsW8;)nYFb*p;%=$U zY#!w|C={Vw`u+O`a?hcK&$%wXQm)8+YGd!kix;%PlG-RC@^Gt8A4mek^Em-3F|rFobm^X`0h12aQLw4KJ6PXndR2^S`w&y zuN#Au?QOG=Nmsh*VkY7e4{I$VA`&O&k+8OAONDx4+F9$QJv z&ku#01CNCZWh*FR6py*9-@f6H2(Kan0s_=akS`$%O)oNA{Jlvp05HP(zrrKw86Iwf z%Ly5T=}LeudE>A2_3`7T)UjQWgITSY*vBvLJhnD8dk)NYTtJ#5{23o0#m3h5O6E~Rn}eYM9gk9CjRcNyY(37J#z`{!&GR8^&n zjhTZbU)TGRHT4k61*ynq=fsYLYme82k}uSMd-*$zu>1=fREO_Kvr$W_P)WnBc_<+S)u(ed3F1?~mWN3BV1q8FSFN_(pSl-j^`};V#b||Efm1frK&{Any;+(yi>-EP z{Tva6I=S8X-Q6k7u}DB3x{E=cNCIGhevW{Opkkv#2*4DA8|K{GTx zJvWy&+Q-M2tgXPHHNs0@&~bGssM|D(N-{L;6zi-CjD1RCYhGW*ZS&Z~~& z&{7d}cX!`4GBUdB#dDi?y|FvVW|`3Up4n(Yy7t@r$EjIr#XTb<8t-LV#J!LV*0PL! z%lpyd;`#}=v}5vpF`a!BhGQ0L#o54MTopaYrg&=}uT<`B3gxi!?-b7mnEivy{+|#F zHHB0UR)24joT}&WXZy<+XeP`VyONR;x(`zZK3?0#KX4l*~-l+1P5#wOrZ5(Kf`coyD$B>>G0< zUKtn|CYr83qR;N39UXf9g>8FE40SrfPEodmJGQ!Ms&-+^dh#2-Ztg%_`*CwHJwR)y zc%!4Dv;k3}ifqc*PGZ_TPR$MG+DBacjrdzuk7cxW$6_SR?TJr`EK|aplrTtH*+xrY2z1hn;5Q5mW-!uOWUxVRXi~ z9+R`Je?6iK9IiC0JP9nKSYI{RAOi1r{yN^3eSW;WVigfF$;?9U zGQrl}& zO5DJZ3#(|=t+8W#OMYu7Y^Qk|WJI_j)9|-dq#TnvR3nu;qp{|vfad}1_2b76s5UUO zlaX0vsa|I(8CkB#4E`r>@`l({Z>S~HpKp#rPMq4@_A(A3r~c@%QkkzU7BMY<>NcG zydl88Bz52LCB9m>u%)Yd8N)_Bdj?MfW@I)IlVR5?vj1cN6NE74VXb_~NE}$SU~($` z#c!zBW6p0?#vUB>J>C8C<@53PW50!-lvw+^fQLy3n`vSV3tI{b3ilW;e1wR^qD!HXl6VO6{EO zBvR0-s+F+ZRfTC7!&9tCGSoo{5G1lqZc$7LG+EB!P!1H$yziZuhP!^p$y#jYRLQmH9OK#x1~8Gg$(1}?$cvj!hmv*FWhJGk$bK|! zzjVZ+YBPnMlZPj4qh0HSak4wbLGRXjYvt&LSq>juv0S+lsCHKNg*}Q<@x}k~MUUJ3 z?fwGUJEDxk4vL=m%8(UrE`;}313A*!K(K4e`~zu^<96~o@e(^Fq@@W_#g^4Gctu_{{Z|y6*S$)Wt4h0U6&laMbOqc@GkZm1GzlYlOdy326G@oKZOgw%Q7)T6^B;keR z^xR;+KzCV)#ALl%ty+?mzS5l^dB)^cugN{3dpj*6GP(jAokq9{B+Ik z;6U4>%Ip9^|6ZMNXjd^?NFKVS4i}*G9{Xw5fB1&~JljpbsylQ)`f7e9YD$k>F_cGS z$8B*AA3;@XjVpF&Ec`3Ht%ZCpx(hjDXwUonw6b=z+*s~!u1B~ zs=`3wq$FZ>*F$%csQwgt9sfvo@N(59(^?M60B@DOq(lA+&Hb1wtHKBDmh%sm7Z>%N zodwauyN!(x0UKNi=;`b8ZkoDw?hLZ&s>5^+Gv*rnK(IcWQ?s1Z81ymP5=@n=lG?mS zh8X>B@(5)?&&_pb^|#LCj(M$yW^_LxODcXbs357Sj4?4WC5~%!K+Z+>=M(_wx(opW zFt8pJ%{Z@WD^9B1Pq^bAF*+3Q9yCQgl35=bkl$6{^4kFJuF@SxqA__piR6^}=wrbk z9m5MaRC2mL!8?R3)31eWrV}L@$f>CrLtDBA1|)$mPc`lt9VM=5cuO|7uu#t<8z;vE zOskm)rhZhl#a^AtXAdTLC}q&YuTD%Q$CTi2LbbHuSG~cTo4`H(l{0mUUqmjBn2eub zgTEf2JtzQxs?TBq|D>%TKJF_Zyu14Qo0^-?AX@Y<6bp$dW@3^}eq~zXYNekIP6gI8 zzRbF%&2glLd+2RSrt1q94I_CM^~}zuHTmhy5Dl2Iu#@Sk;3kD8*^(qq#ikt*jz;6} zm>JpE#YbBw#W%enQl}k9>dtCo;y9$&`T6-gA_Or07^+=EqaFC>QC<&B=pI$ZeqW?G zJen8Wyh-k39%5e>y+3h`pc;rcoeC%~GEIV(G?C6Q72jx>+|YtAlqS*5ub)wC!f~-s zfYd???Mx*~0<-?+EUzci#L4X!Cs&ecoTuM%0bqTRIX3cOcg>7cTlLxYQFJJxoJCa zH8FVuwDT!L8rRtg#si;d90S`4OA1)IW_IIzP?G=A=D>gM(RCWV5r9`SSI z2RDpd?lCA*sB3!2vFmTYE>G6=qB%CXzCf(5J@=CA%(I`0c}LIGE!gMuz4UcM-aaKd zn2uL4b*_=J->#~k7A^vlR;ZGc2_S8up%^O;u_twkhRTJPQTF$whib=H8C-$b(9 zm!wJM4Uj@q{3C2}0!QIX2c#C{$Ea9G{na$qy*YQ8R>P9y*EA#RQf$20Mk8OG->s21 zIddI+Cu+;HUwST=AneyWmzx^P~?rx7n%kSfnu_zVP@I$WnZ%fws zkvU~(67NBf^DjIeebNj=VLx^)T&CVDfJLoZw zo2U4Nwv4;BlwR{yMtJT&0mp-N&>sfBo_yv$58&%&m652 z-tOr1hZE`3acoHWXKz(r)eW{xP_{A(AufSzrlfTSv;0>)Ny34wjr6Ko`@e90&H zri(Gu_iz1M0&4ax$7=2~?W!hIHhRoxP!vx@PfWJ@_kuZ znZOQ^_?#U4Dqm%QvrFXbeTj`HIlFraeR*C-8VjooM#>#f zKy=73hW4KWVeY@KOu$FhE#!Rp#|!YMWbo)13)wdLpQ`Q3Vz?g$xG>~16GGnJcECL` z=&F$YMTX|lN*AeA1>szq+1t@ES11WXj9)l9*2<_AnK4VI0=3l90&M#7iXdNcQa7KczUteJjBen>3mXVdUg1QqK2((+%-Le&#TW?yOx61d$VcQT|2pjD~5H=w|ru)>jD~d z-@SYH)pgGrNVn*?IPeUdYRy}9uFTY`P5=;Nvpg&Sv~*DsFZ*8@UW)aG^2um4|FK-kZ!j^AGEBVNfcf`yDRpj$bRz8+8dzVqo?LU3>p#(zft*Y=KG!PlmzvgGGJ2@-+pM?lrEDY5x-}>8 z?aw0~Fos@($d2UCMwcRdf>CyuGp*m~;(qQ!(}a)Qxftj)Z5x#qsW#H2VbLUW?c_DX%3sr8ejTR^ZqcSyD*j* z+wpx)-_?~0oEhM+NQuUN}~}hevULk-I3?i(BSXjw`0D zgqbxR$;c?&zNfXNq*=yO1yroJv}(nwD-+wLm)lz#fV)%!FAEf|Kw>xLv4S{|nYkF# zGFD^`M3DeUKp=#)%2cCU7}W7$ndx%v(-PvCvF-7bi}s z^s6345>-ERGrq6MmKw#Vp4i!xXV3~#4`_g~msp-bWO#K#g!OcFy&pMX2XY@gDsslg z#{X<{A_9?_ZZbP@DQMkwhx?$tMr$qCPEAGG1#bXpd_XrQx|(REU*WozJ*I2ZD&kZg zr5lShH8D}iHzWcX*Z`P^+onGsD=XvG00k`(NL5>8&TBsOttUl#Bk$qgvIOxWqcpti z4yh$kb%$xZFNJi4moY_{zG{q8LLE!NgZ>? z{k;{X!DsbHrv4Y$xQD!rpbknK*U9eqAoJap9;2&X8Z^TC(bt!2)JYw|!$-!@Tr;k{ z{dC>X=n6R%t4iu2dZ#mXuI43nvSPZI{`$V_hT%>BA#eY$_Sf7(-@a)x>Y(fc0F4S1 zrvpi=Lf+)|?c0k(g{@XwO=16+95zw;2QA4Bvw{=6kFkU?AwaQ?2Ud4jc&6gwYLy#$ z5lUADCwI?SP$59kTS66UZD6ZKXD7Cd`WzMppl|3i#x8C51!3JNk^!4hCw@)n)vHBg zv!GUAJ&m8|u%a1~eo=mL*hw}uwXv>ZueED+)hinEbel^zjj$t94(xUI5-xZRkIUoJ z>?a)wnigfevqxkY-7?<3VMU%gN9_Ik_wNt+`T4YWA=5ie{q7dn@xFhJ*K$bdYlG^I zUSP21mY3NiQ}yZY@?R)r3^j2$#Ee89`~6CqN&D>l*?i05EH&l#GQPw#?)~o?isZhG z)L}>*mQ974f7coQPw86p#_&ppOH@pq%`#O-ewvOo0U=@eHwi-xq3t32&zU_0*icFb z2cHM=7D%m{Uk;gYv_GY%`)PAT9e2o|lyYCm6B0Bl@U&9$3Zuu|eW?Q4~-;i1Tmym4`IaWJ2Ump9M2mk|hHju-z1OCYdAqboO9XXqPeu`K)hLiPQoF z(Tt3@g{J)g%)YmTGD)Q1Ncf&%E|T_nl2f)0 zc2AzG-fPKPio$dEjDDn@GU+&}s6u+aEuAw{Kdvo<{mIUTSdbVMo!jJAg#|5mInv8_ z=#`G%N+1~-kyOXbfuz1`4XH1V7L8tHX4g3$p`whecZT*LfWmDD8boD9jSWGf?JV0N%V(9;(?kY94A^2dxGIJr|VX5t0Per1#s%i_YG>dZfG&m&- zii>Bzp18;KgNZzEi}(qh^UP+GO!@3IKj$Q7xoK1VhAF1DdX4&>gJyeYLz~b9<7yhI zO1{0oh7qa7bb2)-OghdC2^2<7e=Cp~K02!@>~6-RohUGcz+A#vlxGgE;+_z^0AU@G9^Ab?`0VMnx>*s_p%dBV;GCfXI=lK4){J? zX~|8E4-6`SPMmeWXWgxiKT!D!y*VxtV)mFOE8Xm|QKwq@co6A+)xqvh5*^m&XfCr`fZDKP>!;eV zJ<1$P?C~vUFu{WD%o@`5`cE}WPEpa0nf6#_7M9kfQ-7S1xCmsCdCe9(jZt&d%%<2O zQTiNC0*Y?}b#Q*Nx3^-iEzyQkfd*0KMhk{uSrWUKNKCl9k5p!0|Xd) ztnOe!`iI+kZ!&uvliQJSV1c*gp-IIp7B=}3MK>k)?P43z2{zTd*B~xH!H!vfxCrDv zcWKWxSBWE?oZN%*FvrzNYX$fBm+bnjHTj9--8BDM$s}`ZHfp2ac*o+qiAO#WMJ{Wj zRrsE};|~NA_iRG2JT#ex0Xbs;_WoAG#(Lpj06q5L($s&*$u@uFWEK8Goc|=u2a_mc zkFPlrP|wVt{hlnt#|09sdN7zuH8ifeY%l6a+1T9m0?U|!vNGRn)&H`lY}Hnk>#}1Z zbiwa^i~hQ3df$&9Urqbjn)+xI{|LWggGI6T44o(2*r?<+_MC4vLbA%cgH21L4m`Y< z`?dD`?#mcQ8Ri2ru^hto6fK?gM%0Ev4G75jXf#^t*B+L|e+6zP+?Zj%UtdpXIFx4~ z4SufwEzD_C>Eb3%h2Xv*H(4T4MxUa%W1EK|xJTgsM0-fo9m{g8G*u;sNJ zb!~47q7t}w<09R;GhmY@nZEufhl*kWf^?l%OvFHfO~1Pn)J>mDc>^UA-RP@UT?L!9)#+O)#>t z!(|6%=A1Oa@o%Th2w5r3D8HLVuJwBy_W$VbpFPwCFAEXQsfzXQERdHV)g!>vgQ_|> z;s=C}k@^6t|1$q4BqNUxcY4EB9>}JXwi4?SzUb~x>MS@ zhS{gMii?eNvU;(XeNK12zWIB3d1mYw?a`U7QEu@azn@3e48?fCZ*{evWFvmX)fbHC z!qg-usx4T@)XQxB*xlFOE~1LrcZmG{a;j%3WhjNOhr1pwXf^#JQqY=dRGzFYFk|g) zEHLwW+I~<=bAWa0)-9*<4?r!tiPFf69RexqtIhGIbG9`B0jUPWR%qpDZ_U9zK~hTC ztCcHmK@$@?Uf}_*2WuJ?y?hUC!pU0mIAsZYC)cZGW95$*L~Mz^?e(jLV4fR|)NlxY2tDvtpfH4Q#kg z?-I?aBKgjNZ2gtvEMd24Uvh-bw#ZiL#Y(s)SK+mH$`}qUGhmSU=!u6 zfqhwRrVru}7H*rK*0;~+lO^rtnH?^|Fw)jlyeA-PALm0wMVuRY`w1j!k*!KsCIvs^ zHZum^R0ACfULU)?=ARtv#eRTjDbM2W{VcA+WMOdegGS#rxQTVPzGS9K{RE-|dq7sT z>RqZBtD|?Yh?l>YKSraLe*|9a3Jn>cKdX-xsynm}8noJ?k+*v5cxnqW1t@F;CfxUX z6BBG)whP3@#P(jCn1g^2=0HVO9~k)f`Z8x&%;Z-e7pu(tn$l}66P7(H&T$V;P?X?b z4@kkZa_}v^M)&2ohl76$vt&j|G(CEwWC-#^FQmma%R?sB>pds^?#)TJgvvx zRDTPmuD!iRR}}m$(+S5_x*%@@Ot><#vS3ZQbm@{~<*3(Rs<*;nh9~>C4wa(sXN@&> z4pJRDH?8_?FiFkpji+}JV4o1W&@=%M5$v0wuBH|ggaIEnY~g@)4^v&5AVyhG-zbE9 zOy`-x_{=^{(4&#h8GG+bq??%;6`(IMmQ@oJRwHijOdu~?^xT?$ZHnRrZ2N-e5%~2N z`g59!%;|#90J#9M9jHWk1B2_oe*NN)ciIhjcM^S#5mYS}!=dbo7c$}DT8&zTxQjfE zy8=Ez$I1~!9$}4_b{;>i_77g>^YaNDH*+R8>buI<_d51b3ec5}D?2s|S;Y#^pA+2S zxsn3#5xA$W;b9pdzp%jtA|nGbsw~)hmwA;=95B6zlBVicHv6*k&nz3X+*sE9IPT99 z>!zl)eDhfi=hd00y&~jSY)g^lAlmlhF&X_xX9$(RyR;=~zJ}LA0s>NW!P;{@DPVpC zb1-O2bRcL#laG`X{+*(`7-|Ys5FkFl9&T)IUb*J|48u+45_!_^UoLi?G}Zs4lj^Hk z`$&G`t?wIoB2GJF&+?+>dc_N`2Tm`l@CBx`nZ#HoeYz5OI368mNe8x-MqSS3(phRy zX~D7t-BmZk=`O$J6cqfTF~9{ibsR01J?E!DuI7A@5^zU*H~ z%5ta6jG+gOf_~Uz2A!N0i_pA{YBHxfyH*R|0-;EUWH+Qjix%Pl}VyGwp2pfp_ zKpbCy?*u(m?NmcNdJuSfFcb??O69@>_2-+F^oCMG$TcE+Nu!O)0Z>iiKXl z`J$=w#UMhewT?cU3|(n3QAgoGAP<-9^t19CRI}SDENl>e)G? z{d9MwIo%9Y)_BNypAFUU+LUARktu3B?$};19$fLH0S^JZ!~1~X49+6pbZ>^&<*|aP zW%&x?NZ&B6@NUUX(vNe`40{>Q!!bp{T3;92GOa_(oPggS zr^-~5m$~S7hxS}e9xFH;VC=vQoU}Dv>E*F;Ifc>fphFKq@7+6v^{D2JXZR@=d_#hR zc5>bUW>OX%-EUmvYdl9jLRLfClr6&+bPsqYVp9D;Yvnj=hUz%di(%BvAwv~$o~1{R z*C^e5U}v{=yS<)Bzjfj+W!O~sRiIx}?%WxgZIYyjYT+>ymSRk(`(FEcVPlWq{eU0(mx7vJ z_0E0cX3DP_OxpIf-# zC=Wh=<8YtLC;GcK#og}_VsC;at>>1KWSa0+vko43Ws%I-lQMt8F9wba^bIiZJOp%} zp_~KdB{Wn?rS+Z&_B^+52e;@$Ne7%y&yOGCdU|>mf7;Dp_leUeC6+EzT`K0}-M#S9 z@))a~lKRtLpagRF`57L@uSBsQ6`B0bQq|{gl7@|LSt28TOdmaR=bhj!7Lk#9N;vT9 zJ$0;6-EAu;HGHOY=-faT1zzcX~0C=CXvY>+m&?kn}26r{))r~#t zBH@#=s0^#wf-%8*I4fu}voiK0;n-Bq0!n2ompz1h$!K*G^?2~!>Zn-b(^4+We;nGm zxxoNup<=<<5Wwk-2QUC*a|hJ#LqwU!P8vt@&!gqaYA;tTc#s*@U+$nmvqc#TQ&xGV zS=o%TCr2?2gUj<-rpjFiahQH6C}x)K{c-G699JiA+Yc8OlQo64eg|D-eh ziD!K>ik>`Ik z#{J(&8y5XA%s0snCVpE%Xl8{bp9A;mXZA1czUy;Adn;V(*^NsNPFyuih~r*NcnOtZ zJU-rD7AbD_Pv@8{1{=<;aZaih@FM{R#f9A9+G zGgpJ&YG|J8`ThHaVZ!%APEl(-#0`_vH9OhQcbO0f5_Zhb1#ojpVCuKK^tNHTESt48 zWCiWtpt~+C|LWDNM=1)=i_D^jhK4R1_{JtMb#7b?c*vc*Ta_cpK-am!!^K6{ninR? z@I&1vey1SDk`i6$Sw8!$X zP!TVHo%<~iPHtM@kSLqR9jtme8s*O-3|jJ7iHM261Gh^Xl3;Ds`44P|Ioe|qC$541Ab{_eVFXK#NKRxsAG>WPt2+P#nO zVVUgUbyy=~06jw$P8*+oBNfw?W(tqNLgIbznykFMDH8b@T22VO?+My3s?5v+p$5w} zzd6@S;9EcR*o(dUnghb~6Ct9>%C_&RvazeHI>c4~he?8V^I_SgGgof&@JNP0yO{0# z59Zr^e5UXPe4~y@P{C|2=66OMNHN^Fn!QklnK^XXK%tr<$?%EWsN+T7m#45w;hW_v znKU#s%5H9zkm(D+2LU@j*Qcy?OA71GG6E5)gh(E%nQ=;>3x2~^zR2}}ZkDEzoW9@A z;9X0_TcKzf6KChLWNdr|--WVls;1ysC$2^qP)0@}v~Cm5v}{xmiV{S*ud|%SEM6Ic zoQ9@9Qp-IZTE*svOmq6+5CXojoo+r43$j<01KmWX;HHFYGJ#Y9Gro@*>KR-ZDZC5G z#17Wh3KBuoPw9fYpqUqrBdjpYKP);8I-fGKvSb6NZom%IlZb^)@n1)OrYyxqT+~2- zo#jh@K6HiZ#Uvy|ER?OgX@REM1;q9*rvzvvr|;a*SB(g1NzSf5avrhmmAy_Q`Cf2% zd)vOSxY$%zmu|xyF}Bk;Je&zqQOkxfTp|O}o`^>&KL!WWp?y4Cf8#!^)B`d^2#8dv zpnGL$Rcn%xlER&ZUnZ88k(>{^U+`>nXMt6)5@V774)*YHoWS|V`^k3zlsW@54%ej6Vx%mj{rsOPIVPmRqn;`&FT7>1>M9;{XBpeE zYrR$6OrF%Q=_1?xLJ7K-9^b$J3a%IrH@D*YOPs9wEn#_h99*US{e>JmxEiqBaBJZ6 z&Zvugu%tKu@u%kIS!~q@)~XRtdj#m<=oO5iWjtX6u3Wj&W78>;CNH`tUOeiY@u~as zn0ja>_*j)sE<^@;d@0nOjwpQ()tIO-ZTDcSJbJEkFxVV%*%T`Ypr~BwA zoc-m?O*>oL7uwo)4i68n`=wM>X{O01{se@}!@=<(fRfKp*I%3tHmP{TJ{FuJg&+Y_ z=>33>oSwat%b<*4NG~r}%XpTh00Wz$+JQRZDOWVyas@aU*d)RqpoR4|FK>i7$K3^F zd083eCc_nJD?JWvNj5Yzgx!V%Y6t5I!RisLi9s~|pVRq1`B!VCk-KK4bK$7Rv710( z610afUnF3r4vO5uXBe5F7Iu6LeWFD$YmuGdq^c^>hN-o-ymibHfT&w}w7>AY+TE>1 z2&(KzLw5^PQ;fL`g1AM=1TLKL2Ho5|k-bnMM9p-rMHm@XpT7Sx|E#rFJghWj)E@fh<1WiUUu=IaMS&lfz;*R-wmXc=|) zuj!pa#6>!hYQvU;g*_#LB_~somdVGLd+Z?MYOmkKgnt8U(NP5_a91sCYsM~wehpJj zdIaLj53!@lpByz~(I!6L4hJgJ10qP^R9K~AMox~hyuAF49bVlsN13B%d-4ujUqU#S zcE3n`jORgR*vZGStW+|EHSZt$8%|9W|Wjy`x=QT zZ}JLw>z2V?w%uKw@c$MT6Wu+qyX&A!=Rg*Ok9ePkxUnNqxZVJL%g0}L0ptUQptlWZTI`)h5+gnTtj-RIg2~Im>9v<3w z7n6y10)>{(3?ICQlh%E@W_hHX(B5E=&9Z9Ga1a;%w*&YP$!zoX%7zx7dKx7-rT)hg z!uLn__I^%`0)6)~mgdimaa?aFinhjY>V~P9L1{|5njyFx@_GW*(r+7G{YuP@J0Vnl-DTO=oGy_4pHTi zsMw#cejLb`aqBhlPPYUN(Ld9_^Zs-x=6)dvJRb~9cnxFSdAw>@^Pg>FjVn9iQ}>I8 zS1kcAqu$KZ{s#JyrDcwos3=>L)%Vf#zW)AW%Ui>}hcVO78AMYDZ%t`zIKmOlc0S>Lb zkWO;8!!?4D*&Vk`@Ao9{)ezOBY_=S$$ypmqhzaQ`_PvLB0h`s?n^Sv^vBz?l>tiS_b&K^sRxgZbETqh_07#Xt& z+wkC{5lO{|`!o_|R@-tOmdV|=kj7 zPdCPtI^c&8Wv}NgJELQ*6l&>fd)xHVWkZtnO({2}u0(tZ;4za@CiCa!E8bwOa;i)` zrQ*qg$j1w--b$`+T{`#f$BGn6UK(G1$Rb(GKxE?h7rRkprcsUlN&N zI&PS=*dDvf5Ao9VIreNzFU3eCU!j+s9Zg_QEuG;MVx-@i6@J#z7_wrWd)O>#>D2Qo zA3bhQf4HD-H8aEe`|qZ(BNE=lk*d~I#!)qhzs$$uP=9`G`ToJoW|`;n~`*2SA~t*skBIzQZ73z2t%fbNn0LY(@N^7Y?nNqlM+PqF_7v&GcRJKQ0a41v1@P!3 z>&WjnSzkmjr`JoakQ;NA-*1}_dnhuOdk;l0BDPT2mmKWOK4EZ{Wh@Ob<8PDg6z4{A zhYKx&m_tbmM3fd?8VX`)33s2Du3|ZyMo(v6Jbfp9Qtb?y`Ty26{(9y>Om5v}Qt6RQ Q80H(L9>|Gj-_!Q`Z`x)?$N&HU literal 25416 zcmeFZ2T)bpwk^5{f}ntaBtcMEOuxlq5My5D+D2$%ueR z&RKAgb6oJo#J%@D`_z5!-hc1;zh2e9Se8QRSaZ%XX79bV-dYcQq99Fxe+?f7gAvHe zNGii%nBcFNu!}h0j~$o(Bk%`?y|VOuSYbEKFYpbv$-T$-V6ft_ODE4Sfba2aWHjwz zuuG)SZw%PGL`v{O9DO-yN!S_mE4B806!;0QgY4snxU;y_gja4Bs_~_RA6++7ed_SQ z+S1a<$^m=}gFUb_dhTFkc*EJu!Ssf-?BgefTt>Jsn0}b7+m z+ThWbk{8GV!eKtI<+!ep*wuF%o-Vq2_89sZR>@B%3K@A#DJm|06R~F^THM{fyKet8 zk?#gQefrYekFXu*Zf5}xH-38UFhaC-M<5R|aj*YNoc13-l&2yXq`!}i95Vx(F|2h)WHp=wM)&Tt%`?^ zc9(k%;6b>TF8MdVO^VLaT2Y(&euX#Ns59mlxx3|LjSvfyY{{Cdm0;RBzB5JMYidfB z%;WA5Spibwh@W(nm+8gMx!!=|8J6jqoC;YHBIbZ^2hp zQv<6FsA|l)s6>e~$(H%fU7CJfXD>j0%nT!mB0Yi@BtBnIqeypEtK3RQGO9i16Ytx% zZ?RiS96Cr!lM`a!)2^01Z}PjzQ=nbRn@$SLwd#t4kK7HfyQZ*I?a0Y^is#)t|GJLP z;KyT0$;_4|X?lA4Ao@hu6=LFtHx*z30Rib(m|>EVl1c%jFz|3$97ajK*Ve2YKgX_) zmFGVk%zE>3cV{OxEe#_&I@&zK(JUCgcb!;iN4NmbyKU~?VDuP;AFI2%6~mjWZf=g5MhB56B2UZ=J3+?cm-uMRMgWnG@P za~2+vicB6zRGy&~_o68-E@qoaIF=oqw_cy9*5LfaWoi7v4lYq9)#bf|_8d#+@i_L+gGKz{C9o^;%xi%ug z!ZzJPi#dodQHI=jJHy2W54thJ@3>m_B#W6^STyA*W%QqZf*xT2Cb(*wO6|DYW|2p^ z13U(;Ij{MjH)5}hmG>ajz8-;PBf`T|Nn{WRy4KL^!EtdP>n$$^N3m&-D1Jg1if3kJ z<<^T>jSEJ9{5EVT#=!6Zjo#aHax`_{S?Y~hWd>VId;#CWaR1N)r9{~BNI(3+;O6FL zUe()2kI8cl6A}^zZa#B;Og_>*KFFq!XVZnuPnf_- zop^joXB`7QJx@BGx9Fp2CbH4DtSYJxSvwTiG^ygUs@^U}8opMwRA(k;R;%zimL(sd z*m71;QBhM~MWK|BALTs@Y#CQ1$xu6Hy~PAwmoLBD*E9_(8>5H^gS;0zGcHf_%5*MA zisrp@p8iGT6^gL-yh#1k%Ugggx%6bkl22ABtuZ>&Y>jz@`6vCc>}kvgIr^xl^H26f z)@xpU`o!zVY8r@?MvYTqd5m1YCLkqwk5sZPAivfQ-FTsf~u=!$UYeTd3zsHqWk zbe1@L)PN5iH~g!E*m|QSW+>b3pIsu7Z~ z#JR+*S-*xw=k7mkktMTIPzViruot1ad!O(OoW9wJD45B&xU|`*rm6dDmu^JC*gviZ z1h71LN(!S&<>TX1OOK2i*7ewY=op`%YHSx%O;aZ}>2!6o@U{n>8)3OUV1e|DC++rc zvsjf_&BWK(i%LvolGVub5UN2geZNT-3)dMzC=Wae*&UmK4$YgJUx0NF!{v)fDxmUKDM;>WUCWZ%U!!ea*di=Exn8cI^oS`M%1~3#$E5S zA_HkqG3%w=DYZ1#tK%=Rn`cJ0q{F9!ntrY#zYJ`PrUjtPt>=zDG=BB6qbLgVaxj%nC^O0xa=fW@b;}_!_d1ri74i+t6?C$_>$k2h>3|g3Z)Txshgl^BPTDfDvlYIk|J){U@ZUY zehWNsZ^kz(z)*g_K7sD7O%f$3F)_gwQK-d)6(W+yrtn$mqw--3^wI7T4@t`_43qH$ z9wQSYC}j%VejQ>q^2rF?-d4h)507lRV~>3ko~H(-4VgF9zbF;YZ#4((Csh{@i(`bx zC4PsVRbW8C?X!jow_Wq8D#vB|camjqqth}o;Vju%axpo(GTL%ncU}zNf@epwYe+d@ z-WVvLih_^f{?Gu|R$iFZ{P&e6@#DZw}bQcUui&Qfe)kSeaETB5!x0;m6I|hTX*^6pkGAFxWa@TCU3{ukl{L+I9N&)iEAHy>BJ&#w*2O2n*d7sE_-NJpY+2-D( z@32a)y%m3K)78{7`$^>PtGBO^Ew34@^FgKzxkt+wDx+Qz**-5~E=2WY(0@$P&R0TC z?owgx-m`J!8M@*KmEQ}Z++G10^xj-$`GD6@V%FC`_p>WuAnU2J*jMNAEp%x(My31q zI?r*Q;tjJiY8$aVvrHNn{J!_0502`F`rSXwX5tGlGfTJJJeKL+ps+ib0#!;_;(*te z3lur_FkK|t(a}OrXoa(bO~@XhTh%ZzNrd)jQD$E7WQzTWr_S`3_I+BJ?v^2 zBD_)fnZz`2C`F$D)I^_B=CS!@tL*f(G&SRe$GL4~e?+4hrEv|B9Sbk)BF9$CrwS+8 z2MT}RjH#r_laBo&yGC^@tHP?+%en89l}$`LVUw|UaCF8}uDW+bex#i#oO}6;gwvoZUHTwK4$h9CT~}9JZ|5k z=4?*wJ}1s#Jac_mXCf@g`q(Cw@sg~DCvisMagA4bY2wqFcbR!fV(xc-b|(e(+& z=3x(#mRFB81a=TeQ`|Jn;$^afC|c4#T)rxgyRtSs@{isY)FkQFUv+ zzP?SZZ!P)=>@Y3=OIez^UWOU#eG**B(9lq?>mC0C9P`%OEUNO6HbXzja}lH=s`5nY zg?Nuj6hc6{!m+gxm6K^4$S3B}{++r3(vwsQCi|iJQX1wrOAMn=|NalK*5fBsmFlA#Ph*}_P7{Mt!%9o#`<_V$|(4=eNh3BOMNjkFyeR~R;_=FbKoN; zHaqnxPEBoCbgEX;)hz^QTT{uWV5}^yqC$`*yCs$N z^I@`)mNN&Km7+Yeo{Lr-dx|hP6Z1|IRg@K|MY_aq73$WGmRn6!1u#W$8X2*$vf>4q z{fcBX7b6*p3?`tyb2-p#;cGN;5=_R?q7~k;=d&|lbAPJ9f_3w6E4?Yl1SG*fW#+#_ zX2W0D4My8x4JWl*E~0K-Lgy!?oLCVyD5Vyta z&2%E!2wOrCHf`C{YNyr4wTWu~By5>2WZSSfE)lJyqa*+5*w`-n;B)W>L|k;o)ML5= zXTp?~joaP%EMsalScJmf8n(WY5EJzJn|z3v1GcRD9)RIT*9YzI`&uv|4=s;gzs6n} zuOtPC?r4B0>P*YJ)@Y>>9zrX2jZ>`Th`Od|HqFj;^zJSc$sw={>TkF@8UPt6IEwgubusT7)+j`;q&_H>K70utkFzb<&O5UxBo0WvmTXl0$2a=G%dN) z@ysB^#qh9+?liTV=;Jw_lz*-W(-#q*rru~g5NGh|&sdI&kf61r9%0nwY&%^hE};(0 zv^>Me&=2(cHKFL^wh8t;JJQyYF*uF6MEh*t-4yv z`Z>X$ufWc?I3EV~yS~Y-b8J5S=$wNlZ2ds53fKlEG-uk{MSYCjvNudPMr9QXLY zBn{}0fiu&dB^BUF3Kldb-Fp7Ail`yUc~WR4%j?Hp7Pn_d%)B*}_aZ2Ju_bo8;`ux< zd8h)LyRqp(x->R2B1kxSy!B3}V+uO)=^#8Me$6H0LQg>-w)ZYn~o z`zw^%h|!jR10;Why>@YofHN&--I4iuO3J&dQZC0u%}*{9 zKhe_gKRxxjN={BltzxB{Y*pIR`iA;JYd?ieK0<^M&VuMFfLO;7+nsdBy0mE1Vm589+}O@Bkk*=d)U@Yneo z4Ehz4%gq2|L0{?Q?3tgj!(f+N7y#Lsc7Y%~D=VvxK{^y09aDRqgp`!Wb<1#magm7X zBUNAO696eaU7oYjQ}vfv=sNj={BT>}($$$L?IEpulY@ap|Q;bMX2Fo3#Xy<>cjo&&ta!Y~9ReLdr+J zxYS0`z7R;fF5+xZjKsXbBa{Z^QGlL79<|*?4b(X<4?esd_=1)Ffvl|T6M4B<4yEOY ziWuz|!yli^$g^t{hAd%ALN=hER{duEiYu4X7 zRNv0?v^z;eNl7U!*~G}``N_eix##+1?Qp3@?CQ|Q=ReEIACpK5ybLJ@%Tr&8`=X;3 zd+F$QjMC&7_q!y@(l;Hos6;I}-7Xyq<~Bwj&oDDH8vq_DAUIe>8WZqQ16AXnb=P!| z8`Jk3oSaacAKJic6J!Odc~LA{8VfPMJU`Ea)wMIJ`-(*RKfm2uOtz~ys%@`XaVrhj z+uHxGsrBlr#$^xMW87tcN`Brv%a9J!ZwtM?w7#yPhSbqXas!=@tTiG30#q2M2i(F;PpzipLtq zZ_6~RKdVMhe0Ruue??RdLsZabZuiO`Z;%%EnL*UmSBBg9-iGBJ;q z0Ig%Ww6izkf}|1EwTObs>s%D`<9C^LeX;195V=9SP)UOl81tpU+o0_DP*l|Oi2?JE zD`vT7ak(Z})+{d8o|fy~rz=ICvmUCgdvgZ}grs5%S2N@L8+W)c(6zNoredMBxds~t zv(YZaqy8r4G~a&%Ddo-1K+mn(desw%(Obl5A33$VpGtE*;LRH~Swtvyg*KO=v2l8K zHVz={lK+v&uL#NOockr6@}44aGf*ah_d<_uvL69oQQPSxVWNvYL)B=4Eu7HBqx+4^ zNexn>N~t9a-ZEF)gg#RFFaxMnCRpTn*1OoIlP$FdK+O+iLUo@GAq!|{2npN}6T9A* z{aA$*tCHLMFGMQw&HGZ@XjuA?480h7Vrh5JpdqK(sp3=bhA*$)S{PoV&W>E&WRm8-~eD)gYd9(#;fw0MBP*@119K)5FX1#bMIrt z@ms{4Dc_5Sx6e!Hmg7Uk4Cr2zDAFYOM#ql7VsAtEuab!r8%-scuD0QQhKnemYiI0{cNms{N=dl`a}w-pEy_m)#&uL&zejk zl{{NoT4yHNnNn8@_BXd;*lttdHL=h}T%mi9^J}hcG-7<}HL~iCoV5`Fvqr^F%`-by ziM2y+XWZM?$Lt7#c1Z~)96JZSUq7<)Cd|Pmq%S#1q24EM8rYmk_q4T|fl&^z2( zr~uW6>W})>vawtCKjqQphEPi(y;JjyAn)I>u#!D~^q=1?eG7hfULp|uJx2h>I)CjR zU1*eno_v#m{QFiso-4#c1OCn`Jb#o1c6IAO)UK`Ybi@hxQ@v8R!yJ;Dcg41k5`+&l}WW#_|zsB795}fL(uw)Z<`b z*{Gbb+I8QJc=GRpVL(|(sHuTwwZ>yd{!jt_LxV^|Ex|eoF2xX+sY>c#G$|~w1JBc> zH8TOQHrqrY`5Hxg0RC{L%NojQQayU~=%xO%D#O5MVa=uU`b~ZW5A>f^+8IeQ!;~l9 z@yLGiyy3|Yfco;YM_@z$VWqhOZ$K;jQ_J`d8szT>LBRs^`+`|p-QMc((=+jdzO=Je zItwp9@#`K}MMCR+%j!svbl~-V&a=&U{r&xVQ+1v>A3iis*1Fs7EGdnxN1B{dI1q}y z=0>RBxw-f~Y243faUviLVMlb=uHz9$va?X$QRim=DIzV;sNBnW2eq;yX=%x+udg2- z8|xp;RVBmu*`!Cx-M#wV<#xrNqk&M$VgQXk%n`3yZo&1$mZx`?nMw#s45T~nth{ig znBdW=c>ZNIE=~-i=lTKG-fd)R`KQ=(Jfpmzm?TWbXVU$;ZSKqFT$@@W9VKPM$>H|E z`e3ILhFLil6yGF=K@Tob+;rsSwy|~y)!g>S;x*TmyH%vpT4Cw=^`wqTr7p+AOo;-! z`ty{5^S)@sj7pIsHf^Dmso~+ANTjhb6Ctf=04S77N=wseV8yr9#@XTubj*K6-#P}!@sPCj#*SE>gPPpjJ2yA2>o(08BlA2msN@${~D;&B9 zmK>5AjtSP63JMnAf=IglJ~K0Co(qA|{#qgwrtS7C3&rl8oJ`A46^`hP%D~bsq5#F2 zn}r1w1lq6eVNXA4)0I!SMG;*Ejd8Gh+mz9;}&2#%78}WXhm?+5eih{exUzHi)CiT($Y@FNS9eoaRzjUD#>C|fdz24IL*Uk;WqcK?R*Z3|x*4gu@hgz21Yhhtw z8hE;S?uUod)K(Su>hZ_Dj#n!-Td4;sVRpU^OT8JKXZYUGhG&0dhUpgJCZShN*DW3( zU({P&Pwo+cQFtr~loh`M@l19_d8Tt8%{4T2$|LMr6kc@kHz%MnOzQ2TL1L~*Bl%4G zn%i;JMgu}T40$>wvYX$4#qBA@$x}!MeJIVWIk?M6~m_kdEsoMhuG zd(kxVY##~!IcPh+Cy%EAOsiDVt+=;uGZCdY(1C%#X?E6vzWeT2+&f;Zx>MbX5PaM5 zVO2EBAE)MS^W}kSyl6^}NYX9|9TyN^6dd*I)n=9#-;M8(f082>1tXHjK+ zq}E-43Di`G;=fil4()jG$>j26p&OQ*UEzb55ged&RTC=oI^}DfUU_g|o}*B7(YU4p z-D9)c_Pk@>2<$oFMz0H4CAx{)OnX=LE6Oy+=ADh0q^K?1y_G109ZbI>>UYJo>qz}; z18)gG=ZIMt_Z}U^c`>rhRv70*Y8;jjrevFOi|sC%D`|I|+9F3=ut;)?yZioh-{Pb% zo*C)x;2_982_U$V92Fb1JyBEhrQ$QrNjPl}^ufc!ixad>7eDOed`ZFa)VrkQ_CK8a z8^&Qt{t3q8p+`r?Cnu$6_nkE;ws^T4YBnj-N^1$u4j1V%wi<{%*3!B4Hs|I3By12? zhwRRWVPOy5yapV)?19}J-QhKfwLNB1pZb#pZj=CQ zKBqxVL&r#KO90}wO`Az!5fO=j%f=jSL)>RXq7F@GO;rh}m&7afZhFA?N#o*TvZjo< znbj-oq$MhQ!{EnSR+SfkRw|(ix1q*$n?rns4l1C2!=zk*=YQlv1mDQq2T+-V|K?y0 zy-$N87Ux=?tJnS}>G=nmn$R)A$?kH9nM=s8e!-hK^z`qy&J+s9R`)u*g;<%T%ZKyJ z3Kh^ER{y$ga`@QKLh#DBOZ*|gS{ogwm*maHWgo(3eD)hx3koJP!e>we0hC<76Ez@k z({ozze{pK6wdWbD>xax6OT61!8aue&0!L_SwK#xVl z&G8U!P<1wOlKT1__nU?zZM2u2jw3^S><987=6HFiNIEQnh4!-G+++x=2s`gSvdG%> zB30ifqliP!qyJ=9ej6LROzy6xuBx|OX34_EMHpnZnf~xd)9^AyUl18PILnY?OhxC- z+~dcmK#WBm${pTG$}s7?)~bJoEun9BAL2Iv1@PkpYYsJ#@3}l!5Nt{^qO9&085x-x z7sI8QKFDCpSqb2JQ+S+up*B@7p0{ebF~I(GG6+yHOjN~?{Cy=Cc0KUSSpMBi{I}L} zTHiZZb4lsV%?am-lj^A(hs?51E*S|sdWK8 zy&f*~+S0)AaDek0$>mL#kEXr!<2FqM6+hou*i5=Erq$KaYL&gX2e7bLxfpbSZv(9m zJUsIaHIG+Ei%h+%kbafc^yn#*wn%6A@Q7ccq9=*wTx;X@_*sJ%ReVC9AW-*#GD<$-EZBc=BpH`*FK=6=m*X z&#V2?_utiuu_oR~^L(H1@yYO?cHLGBsa#Q(rx7Epkz9;~M zYynNfj}}8*(Fa;uTD^UJ8MF(IAhuuQ#}{#0kpS>`a`YO6tayRkEr2gl{h1qYWDg%n zI|AWdUQUfws}rzp-XtMMT4UKox%BJ)S+>TlQ&fJ0Lk+Q~$W*VERu3zg)q@9E$|@>6 zjwscYkB+(|B>kchw2>%~bDy5wKD1dG z=1H{g7D7qhJMZ>Esb4QOgUptdRmzVqZ(vf_Q1od2quhR)!BYNc7@Psm?Zvg6B-NR( z&rd zFYxfl#PJdo3=G(0g$!zGLA6`+2_EbPc6-q0XS62KN7c5%o zY6==D2X@9(vY8Z{8MC|WuWF?Dmv>v}y^c6&Mt6lHxeoqFwNRfoX+hj+{jlw|{O#IJ;aTgd3s|*0HZ84ru{AbdmZKwt z2s5m!$Z!Sj`g3LEJ+51_+pQV^C6cj;2?Hl50iDMNMz{ExhhrQf&-14LG2`Z?O7Vuh z)iGj=l?Ot^-<6@K@S`yWmKo-|97Nswee!i{tnR(7H|00ICJ()ND640cBOFeBDQiy{ z1W&$x7Ic_}Ng;RWhy|?DCx@RwoB+wW7EM28d`X%KBZ^s3of)UKr6t!LOWS3}7gb8i zte^)ZODHc`%sGEdeu~|2CCZ&k-@2A`Hl3dEdLB7^OB7N`qG7Sz+HLreC8t(> z9eT^*QnxX#*j6qw{G!|Du^HgZ5mgV|pE|*sM51j~Z+mzTTUa+8aC?nSV$;(+ol$sq z8&5;Wkqoe@?-lUhHlfGLELE7%T`A&p0+TKomjA|QORiLG%J4?ESEx<39axLL+?+xS zF?lUWQ+jqWUx|(QGsALu!;cp_W9LFe)*9Z6qs=^aGC+^}QV8w!`1RruaDBb2Kv1Kw z(2gIgfCrLpJFUqAwuE4DW0uOfa%)mk1QP3{fB#!x{XNn|C%)5d;q)K2g8>mJ z{jbZj|8mtwzxLSSGZs(fpEd)xo=@NYF(@75aM(r)nIb8r#}q0T*oqP;tECz+==MLk?NX+^eFYNkG^)j!IPi6K|j(8&1o zi3CIeAGa4TUi249LFGG(8))Et=l|~WUdziLESdF96Q$cS3>1S&Cdm8G! zQ|whw%cF_j&biI$jhUt;khVd%16qsFcs>*T+2%mNueSi6+_GNdE>zb_iDJ0qu2N;f zv-H%DUQ}ooZ@7qU5V}Czvk>Xx4Ixgv`ht@zFK0uLRv|s~{ri|eLu|3aOabEF^sd{$ z(ZS|iL}KEH(2K)=V(Assq))}cQ+XF|cMGft!@7X@`9UC7H>F-yKetLX#^wFe; zmh6GHHjUHjs4s+TiJ_On2Mc#ceell)S!(XSAc;ZL6@$y)`5*IF7_RVY^K3rTj`qA> z*j-6WU0xSta(cv}>we7NU)ukxU-J+&x`g|khBw&RFR5h9f9cOv8K9p!s|P}o z;X2Q1VY&uFC|HvKj=|;l&6nXtrB@|GlyOF>cf`uqsjlj%z4R~Se%R3^{-ufTFy5<$ z7E(|Ha+t;c%!sF>*Bc;NjLul@Hp}t}zc9}ggCnwYX$+*gxrP7Bbk+Q!34)m^+hlI{ zLDB5yS494hsMO2D*u}b>L544~WQ8=S*bEpNji%%?zHDx8ej+}?9`({B*;T4w?0moU zB}u(6NpVYdZC-1>hE>{mLEH9AwCT>BX?AW;s*!l1_T=za+@h{wUgfnQKZQ~YC|o}h z05*nPxzqE0u$s5W{@Hw#B1&WR%Xxo09k1abRcvZe*DFuQeaate2u)*va&m%2x>~Mtxp&%FG@afXn`*xAC3=RsXnv#*@qMU_9VjoY?ee_E`60SI78BIfA=zyeom z6##L=D-^Npe0H@G^ktvT;6xT-(8r(nLzyLr*2DvIqZ(PZ|6#8lQcZW5bs)}(*fy#Z z*guMe$<#<;fNQu)3m#Z+JoFuaOhb2-9wSP|L2>u2o;s6Yl5rr!24cQ*8i+ax*7RjrD3<&9c%BTOam77-*xf~a9@^U z%RZj!B6?KuTkv#<+g55-&fP&Ns8R3?;IL9JwA&74;OrM~zO}BnPoF;l^7+5O#Qq;J zU(`u5^8aos_K!N{y%@&u#AhtU+h`K;T;xR}I^E#5(37j{f?@64hYoHa9oxbvFyiD9ANhvmJJdNM z8i;2eN?SAES#u?$LTk3~7|#u?QumGw`G(dh$X^quLgsos{+;Ezy1HZ@KUNFWCEQ*I z;H~8f1%*E_IOX2avFfHFvt}HX`=we2l1urh@{9X7g5- zZAO~em2{k8#jVpxZ{NPIiV?1j76Wnl>;5Uw&MF5Y0cYOc(eVY?YHk6X2$}_;_g04= z0GQE5tYIH0U_k-@n3-Z0k&Prk#P2@7`qPuTh}P>~Jtn{DKJ_cdGl_boTGyFh+e;cy zhI0npB*FEXZJQZi3VnbGa&mKfizts}Q!d?*#+@&&uSeADbN-IeDcGh*`_A=#UP&xo z<;y%Z+Pj!`Oor50j<^aXNfJ`Z*=}823I#R-mZO<@Pr=p^t^mi>^`qD#n6QY*K!>-J z0YFXVw<^#&?Y7(`OVx@XPq~ZLOa%z z3{b<=`nec%$51&owXWn}vcO^4)8HS5mfBkSh5E9$m9!*0`f5M}sa8Mc1*}{HcX`RT zMUeIp88A?vqUP&?#96t1j25a3u5DsZyUWqt?~1tgcKsN(;C{DZyQt24QP+*oy?njx z(>L~w39b6D;@r8N>Q#W!_fh#pt-uX8fam>JT1t7!s4zcte-=ptq%fdpi&Y?iU=yUB1!k52zrQzzKtt(JcJx1?mOVZAS7xPa z&i}fs1e$~Qin^F97@3^(AdwS@2_4u;yr2)0i@kXOE;5Yv#C;jCp{W~B6{>1~e zMFw-%4|8WTy?g$forB8guTO~RYz@$9r7XFJH&5-)h&I00t^d;K_JRUk=a*NwN-8QD zF;74}pVd7%RcX%*G$WyqaJ1ge7&^i%PllyoJhQI3t5m)4QZa*g+d)4b zWeP9GV;$y=y|nV_(zGB4Ad>Ca1Nmubd0C}HhXi1`0x!oWMn+j~V*LDMK%9%Rz$zNb zM~Hof!oazi!8>C3ZhRGq<8{ZJ)jYLV*7fCxdSMp!yVPRn+8ndOd!r2?!k#*F&_Sf# zBaSVD+n~Q_9??U`RSeWm`Lg%z?fF3W)TVEwP;;E8n{Q-b9j|ROjc(5P3x+PMoyWvj^1k8GfSPI=ZG}mI#fx+ z_4Zaq;&_dMtf(F-VBU_Z|BFAep$Ec>opNaNSDXjsVe%ks{pyFJ(u)1yxki8K?9MD+ zLkjAQLXq~c!0S%G_1@i}b!#-?=<%qdoI_q=p%f_FyzaamzhDC~+{qYDP)kJ#+%O#- zhjLh?#Iu^NB}&t_C|uuH&)4gH!9=4{0g5F8USC1m+1GAd_1ABVJv*-rfAxtFCfde8ZZ; zi)c!dQaDi5;W3_{+s-MfT2(L;n zqnrfmI!#UpfOD3QzZ|owe*9N>0b9pxZ|5tAq=qt^mq{0PLkG~#T0&{>IEnupe+fJO zO)LwTz#Bs!q(?h=Ze`E#WUI`*rVI#3{EA$p`e*Z|aoxv{Hp6jt+glF#o_DOqDS5N+ zQX^}apWN5ie7sNTW#!&Bp9^_4>D8DXJpnY}+`q@1RD9Bgb`!26f$)29&0 z0xHapz&m>ri~o09tir-R_)5orLm-lxo?YGH+{^I}Nct9X=*f?4) znmb%fX+xZDQB07m3}_%)48p|xpfU^CL24oTsKku4%xWS}>t*fkpw_uUwr6nkT+RmR z7sSNGpjisvM@NJ5CgX?4_(|PEveG@7oT#AWXuFu?oL*O_YfYRqzgKQo-T+#44vvm# z85s{aKTR1%pzS9dhti6RmHwz@p=L~GcJ}PA_bF08ayBVrNnY7zgfQ+lwS|;WhIA(e zL6Zt7fe2u-@h)JfRFsuBTB)sb+UDmCOQ*g`YyRl#+XS}n=ilOZ+%7FcA^2P*62x98 zF-d^<(zv|V=uJdP39D(%sML1r^sLyS8=UJ)IW76U9tD?nKua)1e#_$am*w3&X&$|m zk}SE{S@4wFx&)>^jINxUC;9>uFyD$YbgkJvtquImDsgR^sfpf|_XyMuuuFJHQoY21VLvs3Hc z@kh{03vqOpUBu_H+X9i zy;{#Y&BzKhcLn(QXZ)zGB!KfEPj4B8dZMKj3}`6e$ejfoYy~Jvlyr0o%1a2LxC|)? zXvJ9CnBUJuNYi5IN8Ng^O2d@Z#^CM=9P7 zjEkdG`xOcrihwFr4^G6o(X+tp2(10PRz+PvoqZUu_2 zF8FmOk5r3@9>-mm5a}=zSUK7rudr%84E5f8br#5f6wKWZ7h(rGEQWO5Il1aj>>RI3 zFk#s(Awmh`I@|8<@pu3aOB)uEXli-cUrG|l#f?1hM@pd6YI7|1JmLgHl(cBYXfqmb zNBXz1@QF$BM!fzpWgW6~$L^6Y7$%{_0VZBRn4|fQ?2%8P_(r9uo8h8n2!u+Z6KywK zRZ^m=)9Uo=wfT}h%)Yam<#qnT?NvAQ(gYm_?{)!&6 zq^QdH1FWcep_{i5pRYk%BhMg=_k9N(4KgHK6R`a% zIWKX3Sm?*!mli*YsOkq9>$LRIj&Y+v)R{H%SnFh#qF&f z+;@DsU$foar#0LrOST5qRnpe}pzy4EK2ok<2e#mhSqMbH+Mc@u?Ok0h0`-TFza@(S z_X-4Sl^jIC7#Et+7(@v>D{C+37q6qf6iB3w+9;m@kI-v@9q}a$n3}@<59&AHSWgEs zCpXle9oH|DpTGC#rp?4}f2G3Zq*9P5|18WtI1iprm|?m7?+u#y-}93hCm)0U(KiVi zWw90u|D+UMULD}vdn6-&Ij93n0&3YFXBz>ALVPER#$6U|=C-RHfA*6u5QIE@_^>WJ zn~C3MRF>o+aBBhkDxYEM?r64I5 zi*V8;cEgy@aE!*4u?-bNO;AN7gXOiyQ()&hjB7zZe$}))aJD}T$`CP8^$KQ{Z_*;R^7w-=ajz*uDfMASoxQ4 ziSyX#S5J;g5f|DlobChlA$%IVz)e8uF|gh*cML?Zy%Q5}5))}a`KD33YYy?8gay0Hm!<)=~rY%9F5;jt-P3fQ66` zCJHP5a(l!}z=q}xb^n3aEHJ0dk#zQjo|I;DcD*WRI5_XhZxmZ_e?Q?G%`4OY+K9aW zwLc+(*Nl~9*6qsF9v82jv0it}r5T~4nA5Avf?18LD=Vu0$I@Z61(8+q7c;kF10{fJ z*UsLa$97iI@vgTA!3_jyyPK|Vvaq?{Uw)YB?UqJd3X(_?9`kWkcysFw&lkdW*-5WD zR9Z2`{6juDiGQ~gb)u8fXc`kNn|r^6ddye$26EEB2b#%xF4m9&B(irpRu-t>LW^Pif zZ5eO*D;xN~lYjYs2+LZLu0{&; z*E))Sa@6_(iLHV!dvuK~NmVmb7%M9)rHIo9;cwhgn0&uFGvqNj#4yjt8Ng_A+^t|`x(kf1*-84_DEutu*IRw?)*Bg8qN^2mI5=LpZPr3d%91_~j z+sPnEYT*L*5qm%{RLUg)i)^;`x_$#e7WWTe^M)%9;Ylg>1T!H{mpdSNs+EBw@8$bxqCOI{J^;=gGt(mVyr@!ay?*T* zx_OTnQg(p#CMrm&&srd_+}_ru?N0y7#O0w9BbnNrvETk40TB@`wKLElqy?0ZY69Fz zi0ww^QLLk8hHgOyX;3r=MO87R+gk4ibl4L+=$eh7RiY zMlIMnItjcD>2D03bS`+?QBQAe*$pr`UwS)+-6`v%4ylurQa9=_ziV%15DTn2HFF;b zqlkuPS5qe5%;A=$m+oC;n{uHo@YK!ZIwXRpxD)9|@_{b7oxU$SS-ti#cloiH(+Xv> z>rblwfq~ovtPTxqJHm#_#pGZ}u3JR#yHtlpe6cb)@{;^SQsGgE2B6n1r6#^$1&BK{ zDwC7Z-$By=<)C0?WAs-<1O00S%@s ze=9kFV>MiBQ0kwtcoyA^Q?)0rp2F9TUXVcx@>TZRxH&jz?qrYGh)$TNyrCBG3&|}i z*C-!mzsbYH!ZbCpLDplRZ%Fa0j(u3?;!uW75{(M{&(VM6w{MY?ksShiReysSv+PwW zDypZTEfMbXj)wrBEDAneTKN#nJNmE9XGl4bw1hb44(^%9$)Z&MJf9)YOIJ{tXX4Dt z>q+D98BOitf`yLNp0ceCmF5F%z;v>^Je1KUbCn1u(bl^4Kb&ibRfu`ZAjsiQEA9!F z!M%GvJ*!R`H8t7*#(zr3)GD`pU~6mpL_U~;^G6T8K}8(!VEg3dL7(*wWlm#lzSRcr zD6{eK(N3n^{C_mqkT%ewqs5Pa+ON69ZsBWy0xYwNA2?i&C63I&%*Dadw+DY4k=Uw* z0qn2;{yf7!*k@q|QdrSYFqmuz%$(`(-%FSO=m3*wP9r>x_PcEFL(Jd9_n<53aW@4S z(R|ZcH-Xm(-`v7NZLFaIc64;KQ=OqP?G=fJa#Sg(QI7VbChB7-D9_ES!f#A5Z^+(f zlg`yQnwZy)g5f|Qff9VsZE#=*4?t0P|+=|r9Zo*UBJmZr?t9jZshLCERcYp=#MfC?mK zR;7<>8sU{l*xK5<<-Yf#)C<^LPXN|^2;4GmR=Bbxpz*q}P9yH6jUHos4qgO>RrWrh zd4xKRrMd`27{tCmCi%-L0TXiItS@64i09zs-T#K)xruST?b;vNBG@x$rM3 zEf8S(JSB;TUzBCyij@_>=T`9H0_?qD!jJX|n$c>bg1vuE$F)qrVpUzfL7 z)+3sxlnFW;EExgsxwRha$%vHOdlgQKxnSzuF)sdz$Vg|>no0cMQb{iU*~y}K{(3QE zn(X~}Fc4DMft?IY{M+KIR91Sl75-?egE>Xg%#2l8S$XIE(g^CHgAZuK}3I-S!K@dn<1PKu%OHqMvi3GwXA%PIcI``7) zbbIFMOy`$5f57*g^F8OD@0|C0-}g>t?$y(?!u1wH?eCx5i*|Z}f_Cc<9jXhRD@ytf zUAH(CoSW=%&@`~%dygH1(`@$S*7IvurRu?ulDbo*;T3(yTIkSE1MIk4YG-(8RAiYot`UFELmb^w$ZnC z^CPxHMRwilBW+{=KApA!9YM72EROF~#uW&_;|{xHnMJ`h+_Uu{j|+=gr=F=&YwX}x zV=GTKg-1Tog20SIh+WXh!O7zIQ6Ct2Eo540`(r#oXT>=VhS#$<2tzG==dwXf1G&UW zSr+>NXW+^!&#I!lUR%46Tu#%}F)W-!Uh6WdQvO0U-Lu4n+1r<}j|#-jSI=>*;(6_l zJ{-umc6pm8`2>)hk78W;nePr#$M%#v8{S zp*G7=IbO3rvFAF@-`{^O027{byOC<1=NGVH2MTt+cUm4~&=Xf19<$5h3@~^J?(3j$ z^g$y{-W2V}so;8g2_&>qFMZLYNC^bv{Ysmn2ok$(n9%9!#wHOV*V%siKcM6V=T7~3 zWk$v8{Zg5ATBgxXwh12qO{lzUPoLht6(7}k7EQ0t@hlhBct`1aWd3j^+%|jRpNh(J zFDb50PORLxLE1)P4xb<)s+7$PP)UtkoKjU7a7-_8S|CO5x#B%dWrX zp2Qko$^LuH^7QRn`}$kiEzD~j`r95WQ}B8N8`{q68`@4KB_;FbQ3CjpfR(p5T{(o& zw7sjV>u-k*y&5esH}FiK9Dq~%e{Cc2Tk4w0T?9-rjUt0@Y&o=cCN3L@M-_B*n9J@N z?BBmXmiF>RHv)ECCSDH0fX{!K&*DSoDuXPYC(jw4JjOu4+}=dB?b^AkAIJo`ge1x_ zSlRCJElWd?)uQ?c2+2YpQmRu%vhyUBW`tX}Y4^YQRgnCp`}$*pg>q=g%Am9gDaNjDRsTGizAg&AP0=Pnbh_ufj{uuo0V@|WfydX;jh1xRaHe`W9d<`PXu zy0rN>A;nSwS*6M^?O{+~WQ@U~L&NT=kx(?zV#LRCE-PA)TB{S$uSih_;{`=4PW&=e z%-Z)VsntF58V%vO&%id78x}rNSR_|6t1O5(AXi@vztK|S!7QRraY2B&|38l#JWPT& zeT39#NJkccol0nxj%rCrPiEN*#S;fdm>q*VNJ51I$D@-{XpXClSvxt>%rWRzor(gl z`|m@uTzZdEIk|y`fq7nh!ZBZ2UESe+v(U!dUnCWA=6@Y@A`K(H@Dj*LxL{snGod9v zRh2BGK|vRu43^5d_D4a-!gef}MtvwOb61k%Gf-gu<;AZUnJ}wwL@o4$9XaI~e&rda z8gWfAtex*N7ClMW&=P@ow(s?9kR9%Y-)humk3D4vJyq-JZYWrm%`7YR2wBRn1r1!! zOn_MCUMW~LCo2lPeh98@PyM3U?9THCE}&6ZtaVy5)f~ceL`&>HRXMMN3Mei&)-e2c z-mFqY`R{e^48~77cU4sGo5x@K_S_Q2#({<}s;X+BJ52GS`Y#WP8S*ZB?>`({!9hU| zKU==_RC}lWx4x_(HT2((yT)rnGHFNYEUwei+-S&*ndtJE^Bq}d!Ts(Ga-kO1>ph{g zIDFZUyv zI<7uq#JK4Jm`at#=}(-t-5EJ~RE?t|K$6re*PfHP0x_VsOCmw6zL#XY>ApxUi72HT zb7mDj&`cRV);$ntfJM;hHzVN|c=kM@>!NU}CA+O75RJKDaGJnB1J49Z^8A%uKEO`%!H}jwlBwxY#ub(k0Nvu zv$^2QN5g8=(hugzJyBR^Wd2p2STGa7=VU!&`l!~2HLASHRJ~DWX*;EQFU*A`ZNBCc zFU^u=;9aM8&tFMP-WQXZ4bPowX-rFZlod-7;_kmz2LS#?hPG(hYa8d+Zfe@%OL-mC z$ktJCnCg#~HZ{`X@a(9tJ+nt-d*qGrNUh0;J}YJdK)=sR zbYpQeXs0v1GD z@pRJA8A)y0w@6mvHazv>SvBS520Xh->~7)hKEd8Bjq-D zCnq32S(3C;IkF(6Igqo)YVp!~%MzptZ6{?0GEUyNckL&<_po7_DP8B~NAYZjsNN@ND#X1%)9ukG~q zwfhukN4*OIdM1L<mHLF&ngTZw}V0@MmgB(3P1}DUSYr!^lrMLq*+lp z1c{NcWEt1$r1$GWO!E6$_54aYC8dss$0CzMyMcy%9l6BKpRGT#CkCbuZEUK~VvLP9 zefvaV6koOSIs62^!bY3G-dZkUy|d%DaMyt&=HGR>&f)nu@{KpuT2wt$A i1kCLJFlI=b#kd#tRnbGpfKuq66;`M0ENVXYzWNXSMIht= diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index d836866e23..2d38d78b37 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -2046,21 +2046,6 @@ void _3DScene::register_action_fewer_callback(wxGLCanvas* canvas, void* callback s_canvas_mgr.register_action_fewer_callback(canvas, callback); } -void _3DScene::register_action_ccw45_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_ccw45_callback(canvas, callback); -} - -void _3DScene::register_action_cw45_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_cw45_callback(canvas, callback); -} - -void _3DScene::register_action_scale_callback(wxGLCanvas* canvas, void* callback) -{ - s_canvas_mgr.register_action_scale_callback(canvas, callback); -} - void _3DScene::register_action_split_callback(wxGLCanvas* canvas, void* callback) { s_canvas_mgr.register_action_split_callback(canvas, callback); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 1fbd958a84..da86c458bc 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -550,9 +550,6 @@ public: static void register_action_arrange_callback(wxGLCanvas* canvas, void* callback); static void register_action_more_callback(wxGLCanvas* canvas, void* callback); static void register_action_fewer_callback(wxGLCanvas* canvas, void* callback); - static void register_action_ccw45_callback(wxGLCanvas* canvas, void* callback); - static void register_action_cw45_callback(wxGLCanvas* canvas, void* callback); - static void register_action_scale_callback(wxGLCanvas* canvas, void* callback); static void register_action_split_callback(wxGLCanvas* canvas, void* callback); static void register_action_cut_callback(wxGLCanvas* canvas, void* callback); static void register_action_settings_callback(wxGLCanvas* canvas, void* callback); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 4ff947abc2..04acc2136b 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2720,24 +2720,6 @@ void GLCanvas3D::register_action_fewer_callback(void* callback) m_action_fewer_callback.register_callback(callback); } -void GLCanvas3D::register_action_ccw45_callback(void* callback) -{ - if (callback != nullptr) - m_action_ccw45_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_cw45_callback(void* callback) -{ - if (callback != nullptr) - m_action_cw45_callback.register_callback(callback); -} - -void GLCanvas3D::register_action_scale_callback(void* callback) -{ - if (callback != nullptr) - m_action_scale_callback.register_callback(callback); -} - void GLCanvas3D::register_action_split_callback(void* callback) { if (callback != nullptr) @@ -3459,33 +3441,9 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.add_separator()) return false; - item.name = "ccw45"; - item.tooltip = GUI::L_str("Rotate CCW 45 degrees"); - item.sprite_id = 6; - item.is_toggable = false; - item.action_callback = &m_action_ccw45_callback; - if (!m_toolbar.add_item(item)) - return false; - - item.name = "cw45"; - item.tooltip = GUI::L_str("Rotate CW 45 degrees"); - item.sprite_id = 7; - item.is_toggable = false; - item.action_callback = &m_action_cw45_callback; - if (!m_toolbar.add_item(item)) - return false; - - item.name = "scale"; - item.tooltip = GUI::L_str("Scale..."); - item.sprite_id = 8; - item.is_toggable = false; - item.action_callback = &m_action_scale_callback; - if (!m_toolbar.add_item(item)) - return false; - item.name = "split"; item.tooltip = GUI::L_str("Split"); - item.sprite_id = 9; + item.sprite_id = 6; item.is_toggable = false; item.action_callback = &m_action_split_callback; if (!m_toolbar.add_item(item)) @@ -3493,7 +3451,7 @@ bool GLCanvas3D::_init_toolbar() item.name = "cut"; item.tooltip = GUI::L_str("Cut..."); - item.sprite_id = 10; + item.sprite_id = 7; item.is_toggable = false; item.action_callback = &m_action_cut_callback; if (!m_toolbar.add_item(item)) @@ -3504,7 +3462,7 @@ bool GLCanvas3D::_init_toolbar() item.name = "settings"; item.tooltip = GUI::L_str("Settings..."); - item.sprite_id = 11; + item.sprite_id = 8; item.is_toggable = false; item.action_callback = &m_action_settings_callback; if (!m_toolbar.add_item(item)) @@ -3512,7 +3470,7 @@ bool GLCanvas3D::_init_toolbar() item.name = "layersediting"; item.tooltip = GUI::L_str("Layers editing"); - item.sprite_id = 12; + item.sprite_id = 9; item.is_toggable = true; item.action_callback = &m_action_layersediting_callback; if (!m_toolbar.add_item(item)) @@ -3753,9 +3711,6 @@ void GLCanvas3D::_deregister_callbacks() m_action_arrange_callback.deregister_callback(); m_action_more_callback.deregister_callback(); m_action_fewer_callback.deregister_callback(); - m_action_ccw45_callback.deregister_callback(); - m_action_cw45_callback.deregister_callback(); - m_action_scale_callback.deregister_callback(); m_action_split_callback.deregister_callback(); m_action_cut_callback.deregister_callback(); m_action_settings_callback.deregister_callback(); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index cae8ddf0d2..2334fe0927 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -505,9 +505,6 @@ private: PerlCallback m_action_arrange_callback; PerlCallback m_action_more_callback; PerlCallback m_action_fewer_callback; - PerlCallback m_action_ccw45_callback; - PerlCallback m_action_cw45_callback; - PerlCallback m_action_scale_callback; PerlCallback m_action_split_callback; PerlCallback m_action_cut_callback; PerlCallback m_action_settings_callback; @@ -625,9 +622,6 @@ public: void register_action_arrange_callback(void* callback); void register_action_more_callback(void* callback); void register_action_fewer_callback(void* callback); - void register_action_ccw45_callback(void* callback); - void register_action_cw45_callback(void* callback); - void register_action_scale_callback(void* callback); void register_action_split_callback(void* callback); void register_action_cut_callback(void* callback); void register_action_settings_callback(void* callback); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 0fe24ed43f..57a49d7ab4 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -737,27 +737,6 @@ void GLCanvas3DManager::register_action_fewer_callback(wxGLCanvas* canvas, void* it->second->register_action_fewer_callback(callback); } -void GLCanvas3DManager::register_action_ccw45_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_ccw45_callback(callback); -} - -void GLCanvas3DManager::register_action_cw45_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_cw45_callback(callback); -} - -void GLCanvas3DManager::register_action_scale_callback(wxGLCanvas* canvas, void* callback) -{ - CanvasesMap::iterator it = _get_canvas(canvas); - if (it != m_canvases.end()) - it->second->register_action_scale_callback(callback); -} - void GLCanvas3DManager::register_action_split_callback(wxGLCanvas* canvas, void* callback) { CanvasesMap::iterator it = _get_canvas(canvas); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index 58ee1c9d98..f7705a56c6 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -167,9 +167,6 @@ public: void register_action_arrange_callback(wxGLCanvas* canvas, void* callback); void register_action_more_callback(wxGLCanvas* canvas, void* callback); void register_action_fewer_callback(wxGLCanvas* canvas, void* callback); - void register_action_ccw45_callback(wxGLCanvas* canvas, void* callback); - void register_action_cw45_callback(wxGLCanvas* canvas, void* callback); - void register_action_scale_callback(wxGLCanvas* canvas, void* callback); void register_action_split_callback(wxGLCanvas* canvas, void* callback); void register_action_cut_callback(wxGLCanvas* canvas, void* callback); void register_action_settings_callback(wxGLCanvas* canvas, void* callback); diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 5f1b55cf3e..c15fdc196f 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -692,27 +692,6 @@ register_action_fewer_callback(canvas, callback) CODE: _3DScene::register_action_fewer_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); -void -register_action_ccw45_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_ccw45_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_cw45_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_cw45_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - -void -register_action_scale_callback(canvas, callback) - SV *canvas; - SV *callback; - CODE: - _3DScene::register_action_scale_callback((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), (void*)callback); - void register_action_split_callback(canvas, callback) SV *canvas; From 220d430956f353d78abbbed44b33104df07bb737 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 24 Aug 2018 15:49:57 +0200 Subject: [PATCH 183/185] Mouse capture when dragging gizmos --- xs/src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ xs/src/slic3r/GUI/GLGizmo.cpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 04acc2136b..571ac00297 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -3111,6 +3111,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.Dragging() && m_gizmos.is_dragging()) { + m_canvas->CaptureMouse(); + m_mouse.dragging = true; m_gizmos.update(mouse_ray(pos)); @@ -3286,6 +3288,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.dragging = false; m_toolbar_action_running = false; m_dirty = true; + + if (m_canvas->HasCapture()) + m_canvas->ReleaseMouse(); } else if (evt.Moving()) { diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp index b742b642d1..39ba440c3c 100644 --- a/xs/src/slic3r/GUI/GLGizmo.cpp +++ b/xs/src/slic3r/GUI/GLGizmo.cpp @@ -197,6 +197,8 @@ void GLGizmoBase::start_dragging() void GLGizmoBase::stop_dragging() { + set_tooltip(""); + for (int i = 0; i < (int)m_grabbers.size(); ++i) { m_grabbers[i].dragging = false; From 90fcdd4e5feabfe6c998e8c203982d960225bcda Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 24 Aug 2018 16:20:19 +0200 Subject: [PATCH 184/185] Fixed post-commit compilation problems --- lib/Slic3r/GUI/Plater.pm | 10 +++--- xs/src/slic3r/GUI/Field.cpp | 2 +- xs/src/slic3r/GUI/GLCanvas3D.cpp | 6 ---- xs/src/slic3r/GUI/GUI.cpp | 6 +--- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 46 +++++++++++++-------------- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 2 +- 6 files changed, 32 insertions(+), 40 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 2c2cf086ce..0de933c373 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -386,12 +386,12 @@ sub new { $self->{btn_print}->Hide; $self->{btn_send_gcode}->Hide; +# export_gcode cog_go.png my %icons = qw( add brick_add.png remove brick_delete.png reset cross.png arrange bricks.png - export_gcode cog_go.png print arrow_up.png send_gcode arrow_up.png reslice reslice.png @@ -648,6 +648,8 @@ sub new { $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 10) if defined $expert_mode_part_sizer; $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, 10); $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); + # Show the box initially, let it be shown after the slicing is finished. + $self->print_info_box_show(0); $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -1727,7 +1729,7 @@ sub print_info_box_show { # return if (!$show && ($scrolled_window_sizer->IsShown(2) == $show)); my $panel = $self->{right_panel}; my $sizer = $self->{info_sizer}; - return if (!$show && ($sizer->IsShown(2) == $show)); + return if (!$sizer || !$show && ($sizer->IsShown(1) == $show)); Slic3r::GUI::set_show_print_info($show); return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); @@ -1772,8 +1774,8 @@ sub print_info_box_show { # $scrolled_window_panel->Layout; $sizer->Show(1, $show); -#? $self->Layout; -#? $panel->Refresh; + $self->Layout; + $panel->Refresh; } sub do_print { diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index b9753fe93e..4043a44572 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -718,7 +718,7 @@ void SliderCtrl::BUILD() auto temp = new wxBoxSizer(wxHORIZONTAL); - auto def_val = static_cast(m_opt.default_value)->value; + auto def_val = static_cast(m_opt.default_value)->value; auto min = m_opt.min == INT_MIN ? 0 : m_opt.min; auto max = m_opt.max == INT_MAX ? 100 : m_opt.max; diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index 2b548896d8..dc867b767a 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -2952,12 +2952,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #endif } else if (evt.Leaving()) - { - // to remove hover when mouse goes out of this canvas - m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y); - render(); - } - else if (evt.Leaving()) { // to remove hover on objects when the mouse goes out of this canvas m_mouse.position = Vec2d(-1.0, -1.0); diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index ed160b421c..161da5267f 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -803,10 +803,6 @@ PresetBundle* get_preset_bundle() return g_PresetBundle; } -wxNotebook* get_tab_panel() { - return g_wxTabPanel; -} - const wxColour& get_label_clr_modified() { return g_color_label_modified; } @@ -1116,7 +1112,7 @@ void show_buttons(bool show) if (!tab) continue; g_btn_print->Show(show && !tab->m_config->opt_string("serial_port").empty()); - g_btn_send_gcode->Show(show && !tab->m_config->opt_string("octoprint_host").empty()); + g_btn_send_gcode->Show(show && !tab->m_config->opt_string("print_host").empty()); break; } } diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index e9d08ede52..bb5a414238 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -35,8 +35,8 @@ wxSlider* m_mover_y = nullptr; wxSlider* m_mover_z = nullptr; wxButton* m_btn_move_up = nullptr; wxButton* m_btn_move_down = nullptr; -Point3 m_move_options; -Point3 m_last_coords; +Vec3d m_move_options; +Vec3d m_last_coords; int m_selected_object_id = -1; bool g_prevent_list_events = false; // We use this flag to avoid circular event handling Select() @@ -387,12 +387,12 @@ void update_after_moving() if (volume_id < 0) return; - Point3 m = m_move_options; - Point3 l = m_last_coords; + Vec3d m = m_move_options; + Vec3d l = m_last_coords; - auto d = Pointf3(m.x - l.x, m.y - l.y, m.z - l.z); + auto d = Vec3d(m(0) - l(0), m(1) - l(1), m(2) - l(2)); auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; - volume->mesh.translate(d.x,d.y,d.z); + volume->mesh.translate(d(0), d(1), d(2)); m_last_coords = m; m_parts_changed = true; @@ -407,17 +407,17 @@ wxSizer* object_movers(wxWindow *win) optgroup->m_on_change = [](t_config_option_key opt_key, boost::any value){ int val = boost::any_cast(value); bool update = false; - if (opt_key == "x" && m_move_options.x != val){ + if (opt_key == "x" && m_move_options(0) != val){ update = true; - m_move_options.x = val; + m_move_options(0) = val; } - else if (opt_key == "y" && m_move_options.y != val){ + else if (opt_key == "y" && m_move_options(1) != val){ update = true; - m_move_options.y = val; + m_move_options(1) = val; } - else if (opt_key == "z" && m_move_options.z != val){ + else if (opt_key == "z" && m_move_options(2) != val){ update = true; - m_move_options.z = val; + m_move_options(2) = val; } if (update) update_after_moving(); }; @@ -448,8 +448,8 @@ wxSizer* object_movers(wxWindow *win) m_sizer_object_movers = optgroup->sizer; m_sizer_object_movers->Show(false); - m_move_options = Point3(0, 0, 0); - m_last_coords = Point3(0, 0, 0); + m_move_options = Vec3d(0, 0, 0); + m_last_coords = Vec3d(0, 0, 0); return optgroup->sizer; } @@ -1122,9 +1122,9 @@ void load_part( wxWindow* parent, ModelObject* model_object, part_names.Add(new_volume->name); // apply the same translation we applied to the object - new_volume->mesh.translate( model_object->origin_translation.x, - model_object->origin_translation.y, - model_object->origin_translation.y ); + new_volume->mesh.translate( model_object->origin_translation(0), + model_object->origin_translation(1), + model_object->origin_translation(2) ); // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); @@ -1162,9 +1162,9 @@ void load_lambda( wxWindow* parent, ModelObject* model_object, break;} case LambdaTypeSlab:{ const auto& size = model_object->bounding_box().size(); - mesh = make_cube(size.x*1.5, size.y*1.5, params.slab_h); + mesh = make_cube(size(0)*1.5, size(1)*1.5, params.slab_h); // box sets the base coordinate at 0, 0, move to center of plate and move it up to initial_z - mesh.translate(-size.x*1.5 / 2.0, -size.y*1.5 / 2.0, params.slab_z); + mesh.translate(-size(0)*1.5 / 2.0, -size(1)*1.5 / 2.0, params.slab_z); name += "Slab"; break; } default: @@ -1514,7 +1514,7 @@ void update_scale_values() (*m_objects)[m_selected_object_id]->instances[0]->scaling_factor); } -void update_scale_values(const Pointf3& size, float scaling_factor) +void update_scale_values(const Vec3d& size, float scaling_factor) { auto og = get_optgroup(ogFrequentlyObjectSettings); @@ -1525,9 +1525,9 @@ void update_scale_values(const Pointf3& size, float scaling_factor) og->set_value("scale_z", int(scale)); } else { - og->set_value("scale_x", int(size.x + 0.5)); - og->set_value("scale_y", int(size.y + 0.5)); - og->set_value("scale_z", int(size.z + 0.5)); + og->set_value("scale_x", int(size(0) + 0.5)); + og->set_value("scale_y", int(size(1) + 0.5)); + og->set_value("scale_z", int(size(2) + 0.5)); } } diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 15be90bbfa..8c6bd2ed05 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -108,7 +108,7 @@ void set_extruder_column_hidden(bool hide); void update_extruder_in_config(const wxString& selection); // update scale values after scale unit changing or "gizmos" void update_scale_values(); -void update_scale_values(const Pointf3& size, float scale); +void update_scale_values(const Vec3d& size, float scale); // update rotation values object selection changing void update_rotation_values(); // update rotation value after "gizmos" From d4c8bc072054ed6604c3f64c03301defddda55bb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 24 Aug 2018 16:56:42 +0200 Subject: [PATCH 185/185] Fixed post-merge compilation problem --- xs/src/slic3r/GUI/GUI.cpp | 6 ++++-- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 11 ++++++++--- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 161da5267f..9e28de04a5 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1111,8 +1111,10 @@ void show_buttons(bool show) TabPrinter *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); if (!tab) continue; - g_btn_print->Show(show && !tab->m_config->opt_string("serial_port").empty()); - g_btn_send_gcode->Show(show && !tab->m_config->opt_string("print_host").empty()); + if (g_PresetBundle->printers.get_selected_preset().printer_technology() == ptFFF) { + g_btn_print->Show(show && !tab->m_config->opt_string("serial_port").empty()); + g_btn_send_gcode->Show(show && !tab->m_config->opt_string("print_host").empty()); + } break; } } diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index bb5a414238..50b1a1f5a3 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -101,7 +101,8 @@ void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) { auto options = get_options(is_part); - auto extruders_cnt = get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + auto extruders_cnt = get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : + get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); DynamicPrintConfig config; for (auto& option : options) @@ -855,7 +856,8 @@ void update_settings_list() if (opt_keys.size() == 1 && opt_keys[0] == "extruder") return; - auto extruders_cnt = get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + auto extruders_cnt = get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : + get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); for (auto& opt_key : opt_keys) { auto category = (*m_config)->def()->get(opt_key)->category; @@ -1625,8 +1627,11 @@ void on_drop(wxDataViewEvent &event) g_prevent_list_events = false; } -void update_objects_list_extruder_column(const int extruders_count) +void update_objects_list_extruder_column(int extruders_count) { + if (get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA) + extruders_count = 1; + // delete old 3rd column m_objects_ctrl->DeleteColumn(m_objects_ctrl->GetColumn(3)); // insert new created 3rd column diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 8c6bd2ed05..40dfb92797 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -119,7 +119,7 @@ void on_drop_possible(wxDataViewEvent &event); void on_drop(wxDataViewEvent &event); // update extruder column for objects_ctrl according to extruders count -void update_objects_list_extruder_column(const int extruders_count); +void update_objects_list_extruder_column(int extruders_count); } //namespace GUI } //namespace Slic3r

    Up: Home page for Qhull
    +Up:News about Qhull
    +Up: FAQ about Qhull
    +To: Qhull manual: Table of Contents +(please wait while loading)
    +To: Programs +• Options +• Output +• Formats +• Geomview +• Print +• Qhull +• Precision +• Trace +• Functions
    + +