Merge upstream/main into belt/rebase/may-18

Reconciles the belt-printer branch with upstream PRs through #13723. Six
files had conflicts; three additional files needed manual follow-up fixes
where the auto-merge produced code that referenced upstream-renamed fields
or changed function signatures.

Notable reconciliations:
- TreeSupport.cpp: kept belt-floor early-exit branches around HEAD's
  drop-down logic, folded upstream's `(distance_to_top > 0 ? 1 : 0)`
  formula into the non-belt-floor path (upstream PR #11812). Dropped dead
  `roof_enabled`/`force_tip_to_roof` locals.
- TreeSupport3D.cpp: combined upstream's safety-offset + remove_small
  changes with HEAD's belt-floor clip in the per-slice trim loop. Dropped
  HEAD's `else` block (superseded by upstream's rewritten bottom-contact
  propagation) and re-added the belt-floor clip into the new propagation
  loop. Gated the propagation on belt printers to prevent OOM when
  belt-floor clipping produces empty initial slices.
- TriangleSelector.{cpp,hpp}: merged both new `select_patch` parameters
  (HEAD's `up_direction` and upstream's `select_partially`); body uses
  `dot(up_direction)` for the overhang angle check and forwards
  `select_partially` to `select_triangle`.
- SupportMaterial.cpp: `slicing_params.soluble_interface` →
  `zero_gap_interface_bottom` in HEAD's `detect_belt_floor_bottom_contacts`,
  matching upstream's same-purpose rename at line 2495.
- Custom.json, GCodeWriter.cpp: simple additive merges (kept entries /
  includes from both sides).

Verified by building OrcaSlicer (RelWithDebInfo) after a full deps
rebuild (Eigen v5.0.1, libigl v2.6.0 are now managed deps) and slicing
a scaled Benchy on the NORMALIZER belt-printer profile without OOM.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
harrierpigeon
2026-05-18 21:53:24 -05:00
2143 changed files with 130163 additions and 174147 deletions

View File

@@ -435,15 +435,25 @@ void ObjectList::create_objects_ctrl()
// column Extruder of the view control:
BitmapChoiceRenderer* bmp_choice_renderer = new BitmapChoiceRenderer();
bmp_choice_renderer->set_can_create_editor_ctrl_function([this]() {
return m_objects_model->GetItemType(GetSelection()) & (itVolume | itLayer | itObject);
const auto get_filament_context_item = [this]() {
#ifdef __WXOSX__
return m_filament_editor_item.IsOk() ? m_filament_editor_item : GetSelection();
#else
return GetSelection();
#endif
};
bmp_choice_renderer->set_can_create_editor_ctrl_function([this, get_filament_context_item]() {
const wxDataViewItem item = get_filament_context_item();
return m_objects_model->GetItemType(item) & (itVolume | itLayer | itObject);
});
bmp_choice_renderer->set_default_extruder_idx([this]() {
return m_objects_model->GetDefaultExtruderIdx(GetSelection());
bmp_choice_renderer->set_default_extruder_idx([this, get_filament_context_item]() {
const wxDataViewItem item = get_filament_context_item();
return m_objects_model->GetDefaultExtruderIdx(item);
});
bmp_choice_renderer->set_has_default_extruder([this]() {
return m_objects_model->GetVolumeType(GetSelection()) == ModelVolumeType::PARAMETER_MODIFIER ||
m_objects_model->GetItemType(GetSelection()) == itLayer;
bmp_choice_renderer->set_has_default_extruder([this, get_filament_context_item]() {
const wxDataViewItem item = get_filament_context_item();
return m_objects_model->GetVolumeType(item) == ModelVolumeType::PARAMETER_MODIFIER ||
m_objects_model->GetItemType(item) == itLayer;
});
AppendColumn(new wxDataViewColumn(_L("Fila."), bmp_choice_renderer,
colFilament, m_columns_width[colFilament] * em, wxALIGN_CENTER_HORIZONTAL, 0));
@@ -461,11 +471,16 @@ void ObjectList::create_objects_ctrl()
wxALIGN_CENTER_HORIZONTAL, 0);
// Open filament editor faster
// On macOS, bypass wxDataViewCtrl::EditItem() for this custom renderer to avoid
// native text editing of wxCustomRendererObject.
this->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, [this](wxDataViewEvent& event) {
if (event.GetColumn() == colFilament) {
#ifdef __WXOSX__
start_filament_editor(event.GetItem());
#else
// Trigger the editor opening manually
this->EditItem(event.GetItem(), GetColumn(colFilament));
#endif
}
});
@@ -1082,10 +1097,11 @@ void ObjectList::update_filament_in_config(const wxDataViewItem& item)
if (m_prevent_update_filament_in_config)
return;
ModelConfig* config = nullptr;
const ItemType item_type = m_objects_model->GetItemType(item);
if (item_type & itObject) {
const int obj_idx = m_objects_model->GetIdByItem(item);
m_config = &(*m_objects)[obj_idx]->config;
config = &(*m_objects)[obj_idx]->config;
}
else {
const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetObject(item));
@@ -1094,15 +1110,17 @@ void ObjectList::update_filament_in_config(const wxDataViewItem& item)
if (obj_idx < 0 || ui_volume_idx < 0)
return;
int volume_in3d_idx = m_objects_model->get_real_volume_index_in_3d(obj_idx,ui_volume_idx);
m_config = &(*m_objects)[obj_idx]->volumes[volume_in3d_idx]->config;
config = &(*m_objects)[obj_idx]->volumes[volume_in3d_idx]->config;
}
else if (item_type & itLayer)
m_config = &get_item_config(item);
config = &get_item_config(item);
}
if (!m_config)
if (!config)
return;
m_config = config;
take_snapshot("Change Filament");
const int extruder = m_objects_model->GetExtruderNumber(item);
@@ -2849,7 +2867,7 @@ void ObjectList::split()
take_snapshot("Split to parts");
volume->split(filament_cnt);
volume->split(filament_cnt, wxGetApp().app_config->get_bool("keep_painting"));
wxBusyCursor wait;
@@ -3041,7 +3059,6 @@ void ObjectList::merge(bool to_multipart_object)
auto opt = object->config.option("extruder");
if (opt) { new_volume->config.set_key_value("extruder", new ConfigOptionInt(opt->getInt())); }
}
new_volume->mmu_segmentation_facets.assign(std::move(volume->mmu_segmentation_facets));
}
new_object->sort_volumes(true);
@@ -3226,6 +3243,22 @@ void ObjectList::boolean()
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "boolean");
ModelObject* object = (*m_objects)[obj_idxs.front()];
const bool keep_painting = wxGetApp().app_config->get_bool("keep_painting");
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings;
if (keep_painting) {
// Save painting of all the positive parts
saved_paintings.reserve(object->volumes.size());
for (const ModelVolume* vol : object->volumes) {
if (vol && vol->mesh_ptr() && vol->is_model_part() && vol->is_any_painted()) {
saved_paintings.emplace_back(vol->save_painting());
if (saved_paintings.back()) {
saved_paintings.back()->mesh.transform(vol->get_matrix(), true);
}
}
}
}
TriangleMesh mesh = Plater::combine_mesh_fff(*object, -1, [this](const std::string& msg) {return wxGetApp().notification_manager()->push_plater_error_notification(msg); });
// add mesh to model as a new object, keep the original object's name and config
@@ -3237,6 +3270,29 @@ void ObjectList::boolean()
new_object->add_instance();
ModelVolume* new_volume = new_object->add_volume(mesh);
// Remap paint
if (keep_painting) {
for (auto& saved_painting : saved_paintings) {
if (saved_painting) {
// For each original painted volume, we need to apply to each instance
// because we merged all instances into one in `combine_mesh_fff`
// First we save the non-instance-translated mesh
TriangleMesh vols_mesh(std::move(saved_painting->mesh));
for (const ModelInstance* i : object->instances) {
// Then for each instance, we apply the paint at the given instance place
saved_painting->mesh = vols_mesh;
saved_painting->mesh.transform(i->get_matrix());
// Then paint it
new_volume->restore_painting(saved_painting, true);
}
}
}
}
// BBS: ensure on bed but no need to ensure locate in the center around origin
new_object->ensure_on_bed();
new_object->center_around_origin();
@@ -6048,10 +6104,13 @@ void ObjectList::fix_through_cgal()
msg += "\n";
}
plater->clear_before_change_mesh(obj_idx);
const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting");
if (!keep_painting) {
plater->clear_before_change_mesh(obj_idx);
}
const size_t volumes_before = object(obj_idx)->volumes.size();
std::string res;
if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res))
if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res, keep_painting))
return false;
//wxGetApp().plater()->changed_mesh(obj_idx);
object(obj_idx)->ensure_on_bed();
@@ -6169,6 +6228,7 @@ void GUI::ObjectList::smooth_mesh()
WarningDialog dlg(static_cast<wxWindow *>(wxGetApp().mainframe), content, wxEmptyString, wxOK);
dlg.ShowModal();
};
const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting");
bool has_show_smooth_mesh_error_dlg = false;
if (vol_idxs.empty()) {
obj = object(object_idx);
@@ -6180,8 +6240,11 @@ void GUI::ObjectList::smooth_mesh()
bool ok;
auto result_mesh = TriangleMeshDeal::smooth_triangle_mesh(mv->mesh(), ok);
if (ok) {
const std::optional<TriangleSelector::SavedPainting> saved_painting = keep_painting ?
mv->save_painting() :
std::optional<TriangleSelector::SavedPainting>{};
mv->set_mesh(result_mesh);
mv->reset_extra_facets(); // reset paint color
mv->restore_painting(saved_painting);
mv->calculate_convex_hull();
mv->invalidate_convex_hull_2d();
mv->set_new_unique_id();
@@ -6206,8 +6269,11 @@ void GUI::ObjectList::smooth_mesh()
bool ok;
auto result_mesh = TriangleMeshDeal::smooth_triangle_mesh(mv->mesh(),ok);
if (ok) {
const std::optional<TriangleSelector::SavedPainting> saved_painting = keep_painting ?
mv->save_painting() :
std::optional<TriangleSelector::SavedPainting>{};
mv->set_mesh(result_mesh);
mv->reset_extra_facets(); // reset paint color
mv->restore_painting(saved_painting);
mv->calculate_convex_hull();
mv->invalidate_convex_hull_2d();
mv->set_new_unique_id();
@@ -6279,10 +6345,71 @@ void ObjectList::ItemValueChanged(wxDataViewEvent &event)
}
}
#ifdef __WXOSX__
bool ObjectList::is_live_model_item(wxDataViewItem item) const
{
if (!item.IsOk() || !m_objects_model)
return false;
wxDataViewItemArray all_items;
m_objects_model->GetAllChildren(wxDataViewItem(nullptr), all_items);
for (const wxDataViewItem& live_item : all_items)
if (live_item.GetID() == item.GetID())
return true;
return false;
}
void ObjectList::start_filament_editor(wxDataViewItem item)
{
if (m_starting_filament_editor || !is_live_model_item(item))
return;
const ItemType type = m_objects_model->GetItemType(item);
if (!(type & (itVolume | itLayer | itObject)))
return;
auto* column = GetColumn(colFilament);
if (!column)
return;
auto* custom_renderer = dynamic_cast<wxDataViewCustomRenderer*>(column->GetRenderer());
if (!custom_renderer || custom_renderer->GetEditorCtrl())
return;
m_starting_filament_editor = true;
m_filament_editor_item = item;
const bool started = custom_renderer->StartEditing(item, GetItemRect(item, column));
m_filament_editor_item = wxDataViewItem(nullptr);
m_starting_filament_editor = false;
if (!started)
return;
SetCustomRendererPtr(custom_renderer);
SetCustomRendererItem(item);
}
#endif // __WXOSX__
void GUI::ObjectList::OnStartEditing(wxDataViewEvent &event)
{
auto col = event.GetColumn();
auto item = event.GetItem();
#ifdef __WXOSX__
if (col == colFilament) {
if (m_starting_filament_editor)
return;
event.Veto();
CallAfter([this, item] {
if (!is_live_model_item(item))
return;
start_filament_editor(item);
});
return;
}
#endif // __WXOSX__
if (col == colName) {
ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID();
if (node->GetType() & itPlate) {
@@ -6350,6 +6477,12 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event)
dynamic_cast<TabPrintModel*>(wxGetApp().get_model_tab(vol_idx >= 0))->reset_model_config();
return;
}
#ifdef __WXOSX__
if (col == colFilament) {
start_filament_editor(item);
return;
}
#endif // __WXOSX__
if (col != colFilament && col != colName)
return;
auto column = GetColumn(col);