mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-08 00:01:09 +00:00
Belt printer: supports reach the belt under a leading overhang
The slicing frame of a belt object started at its lowest vertex, but the belt under the leading end of an overhang lies below that, by the overhang's length times the tilt's shear. Every support generator works in layers at z >= 0, so none of them could reach it: normal supports stopped at the object's own lowest layer, and the two tree generators each carried a stack of hacks to extend themselves below it (a post-hoc copy of the lowest base area in TreeSupport, "virtual belt raft layers" in TreeSupport3D/TreeModelVolumes), sized from the pre-rotation bbox and capped at global z = 0, which is only right for the trailing half of the belt. Start the frame at the lowest belt-floor point under the footprint instead, less a 10 mm margin along the belt for the base of a support column (BeltSliceStrategy::apply_preslice_transforms and BeltTransformPipeline::compute_belt_height_and_floor agree on it). The layers between it and the first vertex come out empty, which belt slicing already tolerates, and the generators need no extension at all: - normal supports: the generator anchors its layer grid at the frame origin, so run it in the object frame and shift the global belt Z offset onto the result afterwards, as organic supports already did. With the offset on the object layers a top contact at negative z turned the intermediate-layer count negative and the generator allocated layers until the kernel killed it (any overhang in the leading half of the belt). Drop the first-layer flange expansion on a belt: the first support layer is the leading tip of the support, not a flange, and inflating it put lines in the air ahead of the belt. - classic tree: a node now keeps dropping until its whole circle is in the belt, so the branch tapers to a tip on the belt instead of stopping, a radius above it, when its centre crosses. - organic: the belt is no longer a support blocker. A blocker is a collision, and a branch descending onto one slides off it, down the tilted belt and ahead of the part; the belt is where branches end, which the per-layer m_belt_floor clipping already does. The belt brim is generated after the parallel support step instead of inside it: belt_brim_obstacles() reads every object's layers and support layers, which another object's support step rebuilds (and, now, shifts) at the same time. This is the race behind the Windows arm64 segfault in "Belt brim of each object precedes its perimeters on its own filament". Also: the belt tilt axis moves to Developer mode as its own row (a shared line is shown by its first option's mode), first_layer_plane band thickness, belt_support_floor_mode, belt_preslice_global and gcode_back_transform are retired and presumed on, the gravity arrow is a plain line along the up direction, and the "Show raw G-code (belt only)" preview toggle is gone. Regression test: "Belt supports reach the belt under a leading overhang" slices a cube with a fin whose underside is parallel to the layers, 20 mm ahead of the cube and up to 41 mm of slicing Z above the belt, for normal, organic and classic tree supports, and checks that the lowest support layer sits on the belt beneath its own lines. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
8039d4d2ac
commit
dda58b07cd
@@ -1318,13 +1318,11 @@ std::vector<int> GCodeViewer::get_plater_extruder()
|
||||
// Belt printers: compute the full machine->model back-transform from the print
|
||||
// config, so the "designed" (upright) G-code preview maps each toolpath vertex
|
||||
// back to Cartesian space. The G-code forward pipeline is (BeltKinematics::
|
||||
// to_machine_coords): gcode = MachineFrame( AxisRemap( X ) ), with X = model if
|
||||
// gcode_back_transform (write already un-rotated to Cartesian) else BeltForward(
|
||||
// model). So the inverse is:
|
||||
// model = [BeltForward^-1 if !gcode_back_transform] . AxisRemap^-1 . MachineFrame^-1
|
||||
// All parts are config-driven affines -> handles any rotation/shear/scale/axis-
|
||||
// remap combination. (origin-snap is a per-instance translation that only shifts
|
||||
// position, not orientation, so it is intentionally omitted.)
|
||||
// to_machine_coords): gcode = MachineFrame( AxisRemap( X ) ), with X the model
|
||||
// already un-rotated to Cartesian by the back-transform. So the inverse is:
|
||||
// model = AxisRemap^-1 . MachineFrame^-1
|
||||
// (origin-snap is a per-instance translation that only shifts position, not
|
||||
// orientation, so it is intentionally omitted.)
|
||||
static Transform3d compute_belt_back_transform(const PrintConfig& cfg)
|
||||
{
|
||||
if (!cfg.belt_printer.value)
|
||||
@@ -1360,11 +1358,7 @@ static Transform3d compute_belt_back_transform(const PrintConfig& cfg)
|
||||
}
|
||||
const Transform3d ar_inv = ar.inverse();
|
||||
|
||||
Transform3d bf_inv = Transform3d::Identity();
|
||||
if (!cfg.gcode_back_transform.value)
|
||||
bf_inv = BeltTransformPipeline::build_forward_transform(cfg).inverse();
|
||||
|
||||
return bf_inv * ar_inv * mf_inv;
|
||||
return ar_inv * mf_inv;
|
||||
}
|
||||
|
||||
//BBS: always load shell at preview
|
||||
@@ -1393,11 +1387,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
|
||||
|
||||
// avoid processing if called with the same gcode_result.
|
||||
// On a belt printer the toolpath geometry fed to libvgcode also depends on the
|
||||
// designed/raw view state (the back-transform is applied in convert), so the
|
||||
// same result is converted again only when that view has been toggled.
|
||||
const bool same_belt_view = !m_belt_view_enabled || m_last_belt_show_designed == m_belt_show_designed;
|
||||
if (m_last_result_id == gcode_result.id && wxGetApp().is_editor() && same_belt_view) {
|
||||
if (m_last_result_id == gcode_result.id && wxGetApp().is_editor()) {
|
||||
//BBS: add logs
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": the same id %1%, return directly, result %2% ") % m_last_result_id % (&gcode_result);
|
||||
|
||||
@@ -1439,22 +1429,20 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
}
|
||||
|
||||
// convert data from PrusaSlicer format to libvgcode format.
|
||||
// Belt printers: when the "designed (upright) view" is active, back-transform
|
||||
// the toolpath geometry into model/Cartesian space using the general belt
|
||||
// inverse (handles any mesh rotation + shear + axis remap). When off, the raw
|
||||
// machine-frame G-code is shown (useful for debugging the transform itself).
|
||||
// Belt printers: back-transform the toolpath geometry into model/Cartesian
|
||||
// space using the general belt inverse (handles the mesh rotation, shear and
|
||||
// axis remap), so the part is shown upright, the way it was designed.
|
||||
const bool is_belt = m_belt_view_enabled && print.config().belt_printer.value;
|
||||
Transform3d belt_inv = (is_belt && m_belt_show_designed)
|
||||
? compute_belt_back_transform(print.config()) : Transform3d::Identity();
|
||||
Transform3d belt_inv = is_belt ? compute_belt_back_transform(print.config()) : Transform3d::Identity();
|
||||
// Belt: move positions are stored as gcode_Z + belt_z_origin (the start G-code's
|
||||
// purge-blob advance baked into the machine-Z origin by its G92 Z0 resets). Subtract
|
||||
// that constant before the linear back-transform so every toolpath maps to the model's
|
||||
// belt coordinate. Without it the back-transform mixes the offset with the gantry-Y
|
||||
// term, leaving a per-move designed-Y error that min-corner anchoring cannot remove
|
||||
// when a bridge/keel move happens to cancel it at the bbox minimum.
|
||||
if (is_belt && m_belt_show_designed && gcode_result.belt_z_origin != 0.0f)
|
||||
if (is_belt && gcode_result.belt_z_origin != 0.0f)
|
||||
belt_inv = belt_inv * Transform3d(Eigen::Translation3d(Vec3d(0.0, 0.0, -double(gcode_result.belt_z_origin))));
|
||||
const bool apply_belt = is_belt && m_belt_show_designed
|
||||
const bool apply_belt = is_belt
|
||||
&& !belt_inv.matrix().isApprox(Transform3d::Identity().matrix());
|
||||
if (apply_belt) {
|
||||
// The linear belt back-transform recovers the print's shape and orientation but not
|
||||
@@ -1691,7 +1679,6 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
|
||||
//BBS: move the id to the end of reset
|
||||
m_last_result_id = gcode_result.id;
|
||||
m_last_belt_show_designed = m_belt_show_designed;
|
||||
m_gcode_result = &gcode_result;
|
||||
m_move_type_counts.fill(0);
|
||||
for (auto& move_type_times : m_move_type_times)
|
||||
@@ -5146,33 +5133,6 @@ void GCodeViewer::render_legend(float &legend_height, int canvas_width, int canv
|
||||
if (m_nozzle_nums > 1 && (m_viewer.get_view_type() == libvgcode::EViewType::Summary || m_viewer.get_view_type() == libvgcode::EViewType::ColorPrint)) // ORCA show only on summary and filament tab
|
||||
render_legend_color_arr_recommen(window_padding);
|
||||
|
||||
// Belt printer: toggle for viewing designed (upright) vs. machine-frame G-code.
|
||||
// Rendered with a separator and hint text so users can find it easily.
|
||||
if (m_belt_view_enabled) {
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
ImGui::Dummy({ window_padding, 0 });
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.f, 0.59f, 0.53f, 1.f), "%s", _u8L("Belt printer").c_str());
|
||||
ImGui::Dummy({ window_padding, 0 });
|
||||
ImGui::SameLine();
|
||||
// Checked = show the raw machine-frame G-code (designed/upright view off). Worded to
|
||||
// match the canvas-toolbar menu item "Show raw G-code (belt only)". m_belt_show_designed
|
||||
// is the inverse of this checkbox, so bind a temporary and flip it on change.
|
||||
bool show_raw = !m_belt_show_designed;
|
||||
const std::string key = wxGetApp().shortcuts().display(Shortcut::ToggleBeltRawGcode);
|
||||
const std::string label = _u8L("Show raw G-code (belt only)") + (key.empty() ? std::string() : " [" + key + "]");
|
||||
if (ImGui::Checkbox(label.c_str(), &show_raw)) {
|
||||
m_belt_show_designed = !show_raw;
|
||||
// The designed-view back-transform is baked into the toolpath geometry at load
|
||||
// time, so the toggle only takes effect once the preview is re-converted. Defer
|
||||
// the refresh to the next event-loop tick (CallAfter) to avoid re-entering the
|
||||
// preview load from inside legend rendering.
|
||||
if (Plater* plater = wxGetApp().plater())
|
||||
plater->CallAfter([plater]() { plater->refresh_belt_view(); });
|
||||
}
|
||||
}
|
||||
|
||||
legend_height = ImGui::GetCurrentWindow()->Size.y;
|
||||
imgui.end();
|
||||
|
||||
Reference in New Issue
Block a user