diff --git a/docs/HLSD/design-tab.md b/docs/HLSD/design-tab.md index d73c245718..9a92647f61 100644 --- a/docs/HLSD/design-tab.md +++ b/docs/HLSD/design-tab.md @@ -246,6 +246,15 @@ The tab is a page of Orca's main window and answers to the same settings as Prep `DesignRowList` rather than a `wxTreeCtrl`, so each row carries its own actions — Edit, Show/hide and Delete on a feature, Move, Show/hide and Delete on a body — and the eye shows whether that row is hidden. +- **Plates.** This is the one thing the tab does not follow. The canvas has a bed of its own at + the printer bed's home position, whichever plate Prepare has current, and a new document's + modeling origin is that bed's centre. A bed that followed the current plate would slide out + from under a design: the origin is fixed once per document, baked into every sketch plane and + saved in the recipe, while the current plate can change between visits. Commit to Plate does + not need it either, since the committed object is placed on an empty spot of the current + plate. What the canvas does read from the plate is moved onto its bed: the exclude areas, the + plate box the camera orbits about when nothing is picked (`GLCanvas3D::_current_plate_box`), + and the first view, which starts as a copy of Prepare's camera. - **Viewport text.** The status line and the active tool's values are drawn by the canvas in its ImGui pass, so they go with the canvas: a top-level window over GL does not follow its frame and was left floating over other applications. diff --git a/src/slic3r/GUI/CAD/DesignCanvas.cpp b/src/slic3r/GUI/CAD/DesignCanvas.cpp index a108221e34..e1718f05f0 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.cpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/Camera.hpp" // N: look down the sketch plane normal #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/PartPlate.hpp" // the current plate's origin: the first view's offset #include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/GLToolbar.hpp" #include "slic3r/GUI/Event.hpp" @@ -190,8 +191,11 @@ DesignCanvas::DesignCanvas(wxWindow* parent) refresh_bed(); // The view this canvas opens on. Built lazily, on the way into the Design tab, so this - // is the view the user is looking at right now. + // is the view the user is looking at right now — moved off the current plate onto the + // Design bed, which stays at the printer bed's home whichever plate is current. m_parked_camera = wxGetApp().plater()->get_camera(); + if (PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate()) + m_parked_camera.translate_world(-plate->get_origin()); // Before any of this class's own Binds below: wx calls dynamically bound handlers in // reverse order of binding, and GLCanvas3D swallows several events without skipping them — @@ -554,6 +558,7 @@ void DesignCanvas::refresh_bed() double printable_height = 100.0; const auto* ph_opt = config->opt("printable_height"); if (ph_opt) printable_height = ph_opt->value; + // No position: the Design bed stays at the printer bed's home, whichever plate is current. m_bed.set_shape(bed_shape_opt->values, printable_height, {}, {}, "", false); // mainline added extruder_areas/heights params } diff --git a/src/slic3r/GUI/CAD/DesignCanvas.hpp b/src/slic3r/GUI/CAD/DesignCanvas.hpp index 9584550980..a80ab4bef2 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.hpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.hpp @@ -3,6 +3,7 @@ #include #include "libslic3r/Point.hpp" +#include "libslic3r/BuildVolume.hpp" #include "libslic3r/Color.hpp" #include #include @@ -83,6 +84,8 @@ public: void finish_sketch(); bool is_sketching() const; void refresh_bed(); // re-sync the bed to the current printer (call on tab activation) + // Centre of the Design bed: the printer bed at its home position, whichever plate is current. + Vec2d bed_center() const { return m_bed.build_volume().bed_center(); } // The Camera is Plater-owned and shared with Prepare/Preview/Assemble; GLCanvas3D has no // per-canvas camera, so every orbit here would otherwise overwrite what the editor tabs // show. Exactly one of the two views is live at a time, so entering and leaving are the diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp index 5ff0f4c8a8..e7f77cf290 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.cpp +++ b/src/slic3r/GUI/CAD/DesignPanel.cpp @@ -104,7 +104,6 @@ #include "libslic3r/Model.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" -#include "libslic3r/BuildVolume.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Shortcuts.hpp" @@ -7345,8 +7344,12 @@ void DesignPanel::hydrate_from_model() // that has none yet. A design in progress keeps its origin: its sketches have it baked into // their planes, so moving it under them (a printer change between visits) would slide the // datum planes off the geometry. A loaded project brings its own (load_recipe). + // The centre of the Design bed, not the plater's: the plater moves its bed onto the current + // plate, so on plate 2+ the origin would land one plate stride away from the bed drawn here. + // Re-synced first because the control socket calls this without on_tab_shown(). if (!m_doc.origin_from_recipe) { - const Vec2d bc = plater->build_volume().bed_center(); + m_viewport->refresh_bed(); + const Vec2d bc = m_viewport->bed_center(); m_doc.modeling_origin = Vec3d(bc.x(), bc.y(), 0.0); // A document started here takes the weld rule from the preference; a loaded one // brings its own (load_recipe). diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c1267e8d31..63ff5dcdbd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1875,7 +1875,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box(bool current_plate_only) const bool is_limit = m_canvas_type != ECanvasType::CanvasAssembleView; if (is_limit) { if (current_plate_only) { - expand_part_plate_list_box = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_bounding_box(); + expand_part_plate_list_box = _current_plate_box(); } else { auto plate_list_box = wxGetApp().plater()->get_partplate_list().get_bounding_box(); auto horizontal_radius = 0.5 * sqrt(std::pow(plate_list_box.min[0] - plate_list_box.max[0], 2) + std::pow(plate_list_box.min[1] - plate_list_box.max[1], 2)); @@ -8267,7 +8267,30 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid) { - wxGetApp().plater()->get_partplate_list().render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid, !m_plate_chrome_enabled); + PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list(); + // Design tab: its bed stays at the printer bed's home whichever plate is current, so the + // current plate's exclude areas are moved from that plate onto it. + PartPlate* curr_plate = plate_list.get_curr_plate(); + const Transform3d plate_view_matrix = m_axes_at_bed_center && curr_plate != nullptr ? + Transform3d(view_matrix * Geometry::translation_transform(-curr_plate->get_origin())) : view_matrix; + plate_list.render(plate_view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid, !m_plate_chrome_enabled); +} + +BoundingBoxf3 GLCanvas3D::_current_plate_box() const +{ + // Design tab: its own bed stands in for the current plate (see _render_platelist). + const BuildVolume& build_volume = m_bed.build_volume(); + if (m_axes_at_bed_center && build_volume.valid()) { + // Flat at z = 0 like the plate's own box. Merged, as PartPlate builds it: the min/max + // constructor leaves a flat box undefined. + const BoundingBoxf bb = build_volume.bounding_volume2d(); + BoundingBoxf3 box; + box.merge(Vec3d(bb.min.x(), bb.min.y(), 0.)); + box.merge(Vec3d(bb.max.x(), bb.max.y(), 0.)); + return box; + } + PartPlate* curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); + return curr_plate != nullptr ? curr_plate->get_bounding_box() : BoundingBoxf3(); } // Design tab: CAD grid on the bed plane, drawn in place of the plate's corner-origin grid. @@ -11061,10 +11084,9 @@ std::optional GLCanvas3D::get_camera_orbit_target(ECameraNavigationType n { // Orca: Centralize the pre-existing pivot rules so orbiting and pan fallback cannot // choose different reference depths for the same canvas and active tool. - PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate(); + const BoundingBoxf3 plate_box = _current_plate_box(); if (navigation_type == ECameraNavigationType::Gesture) - return current_plate == nullptr ? std::nullopt : - std::make_optional(current_plate->get_bounding_box().center()); + return plate_box.defined ? std::make_optional(plate_box.center()) : std::nullopt; const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type(); const bool use_scene_target = m_canvas_type == ECanvasType::CanvasAssembleView || @@ -11084,15 +11106,15 @@ std::optional GLCanvas3D::get_camera_orbit_target(ECameraNavigationType n Vec3d target = Vec3d::Zero(); if (m_canvas_type == ECanvasType::CanvasPreview) { - if (current_plate != nullptr) - target = current_plate->get_bounding_box().center(); + if (plate_box.defined) + target = plate_box.center(); } else if (!m_selection.is_empty()) { target = m_selection.get_bounding_box().center(); } else { // Orca: Match regular mouse orbit: objects on the active plate, then the plate itself. BoundingBoxf3 bbox = volumes_bounding_box(true); - if (!bbox.defined && current_plate != nullptr) - bbox = current_plate->get_bounding_box(); + if (!bbox.defined) + bbox = plate_box; if (bbox.defined) target = bbox.center(); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index e53a9d6aba..070db6771b 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1441,6 +1441,8 @@ private: void _render_shadows(const Transform3d& view_matrix, const Transform3d& projection_matrix); //BBS: add part plate related logic void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true); + // The current plate's box (XY, at z = 0); in the Design tab, its own bed's. + BoundingBoxf3 _current_plate_box() const; // Design tab: draw the CAD grid (minor 10 mm + major 50 mm) in place of the plate's // corner-origin grid when the axes sit at the bed centre (modeling origin). Rebuilds its // GLModels lazily, only when the bed shape changed.