From 741682f874638d52470fa0be1f8eb3e7ef129625 Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Tue, 1 Sep 2026 04:49:09 +0200 Subject: [PATCH] The Design tab gets a grid a modeller can read, centred on the origin In CAD the centre IS the sketch origin -- GLCanvas3D already moves the axis triad there for exactly that reason. The grid under it did not agree: it comes from PartPlate::calc_gridlines, generated from m_origin, the plate's front-left corner, with an adaptive step meant for a print bed. Measured on a screenshot from the user's machine: the nearest grid line was 10 px from the origin in a 23 px pitch. The origin floated mid-cell, in both axes. Corner-origin is CORRECT for Prepare -- a print bed starts at a corner -- and the plate list is SHARED with the plater, so re-centring it there would change the bed for every user of the app to fix one tab. The seam used instead already existed: _render_platelist takes show_grid, and m_axes_at_bed_center is already the "this is the Design canvas" flag. The Design canvas suppresses the plate's grid and draws its own. Minor every 10 mm, major every 50 mm, both generated from bed_center() so a line passes exactly THROUGH the origin in each axis. Two GLModels, rebuilt only when the bed shape changes, not per frame. White majors, grey minors, the SAME in both themes. There is no white bed to vanish against: the plate is dark grey either way (DEFAULT_MODEL_COLOR {0.326,0.337,0.337} light, DEFAULT_MODEL_COLOR_DARK {0.255,0.255,0.283} dark), a difference of 0.07. An earlier draft inverted the palette on the light theme; that was a branch buying nothing. For contrast with what this replaces: the plate's grid draws BOTH its thin and bold families in one 0.43 grey, which is most of why the stock grid reads as a flat mesh with no scale to it. z = -0.26, the same value as PartPlate::GROUND_Z_GRIDLINE -- below the bed fill at -0.03, above the bed model at -0.41, so no z-fighting. Matched by construction, since that constant is file-static in another TU. Known limit, commented: the grid is clipped to the bed's BOUNDING BOX, not its polygon. Identical on a rectangular bed; on a circular one it would spill past the round edge. The target printers are rectangular. Verified on the rig, not just compiled: white majors over a fine grey mesh, and a white line through the origin in both axes. snaporca-kha0 --- src/slic3r/GUI/GLCanvas3D.cpp | 101 ++++++++++++++++++++++++++++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 14 +++++ 2 files changed, 115 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1ee3959c68..4b8ac55cc8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2081,6 +2081,11 @@ void GLCanvas3D::render(bool only_init) no_partplate = true; else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward()) show_grid = false; + if (m_axes_at_bed_center) + // Design tab: the plate grid is generated from the plate's front-left corner, so it + // floats mid-cell under the modeling-origin triad. Suppress it here; a CAD grid centred + // on the origin is rendered in its place (see _render_cad_grid). + show_grid = false; /* view3D render*/ int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1; @@ -2091,6 +2096,9 @@ void GLCanvas3D::render(bool only_init) _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes); if (!no_partplate && m_show_bed) //BBS: add outline logic _render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid); + if (m_axes_at_bed_center && m_show_bed && !no_partplate) + // Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid. + _render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix()); //BBS: add outline logic // Depth pass for object-on-object and self shadows; consumed by the gouraud shader below. @@ -8084,6 +8092,99 @@ void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transfo 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); } +// Design tab: CAD grid on the bed plane, drawn in place of the plate's corner-origin grid. +// Generated from the bed centre (= modeling origin) so a grid line passes exactly through the +// triad in both axes. Minor lines every 10 mm, major every 50 mm; the two GLModels are built +// once and rebuilt only when the bed shape changes, not per frame. +void GLCanvas3D::_render_cad_grid(const Transform3d& view_matrix, const Transform3d& projection_matrix) +{ + const BuildVolume& build_volume = m_bed.build_volume(); + if (!build_volume.valid()) + return; + + const Vec2d center = build_volume.bed_center(); + const BoundingBoxf bb = build_volume.bounding_volume2d(); + if (!m_cad_grid_valid || m_cad_grid_center != center || m_cad_grid_bb != bb) { + m_cad_grid_center = center; + m_cad_grid_bb = bb; + m_cad_grid_valid = true; + + // Same z as PartPlate::GROUND_Z_GRIDLINE (-0.26f): just below the bed fill (GROUND_Z = + // -0.03f, which is drawn with the depth mask disabled) and above the physical bed model + // (offset z = -0.41), so the grid never z-fights the bed quad. Chosen by construction, + // not by magic number: it is the exact z the plate grid already uses on the shared bed. + const float z = -0.26f; + + auto build_grid = [&z, ¢er, &bb](double step, GLModel& model) { + std::vector> segs; + // Constant-x (vertical on screen) lines, both directions from the centre so the + // centre column itself is always present. Clipped to the bed bounding box so nothing + // spills past the bed quad. + for (double x = center.x(); x >= bb.min.x(); x -= step) + segs.emplace_back(Vec2d(x, bb.min.y()), Vec2d(x, bb.max.y())); + for (double x = center.x() + step; x <= bb.max.x(); x += step) + segs.emplace_back(Vec2d(x, bb.min.y()), Vec2d(x, bb.max.y())); + // Constant-y (horizontal on screen) lines, same centre-first convention. + for (double y = center.y(); y >= bb.min.y(); y -= step) + segs.emplace_back(Vec2d(bb.min.x(), y), Vec2d(bb.max.x(), y)); + for (double y = center.y() + step; y <= bb.max.y(); y += step) + segs.emplace_back(Vec2d(bb.min.x(), y), Vec2d(bb.max.x(), y)); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + data.reserve_vertices(2 * segs.size()); + data.reserve_indices(2 * segs.size()); + for (const auto& s : segs) { + data.add_vertex(Vec3f(float(s.first.x()), float(s.first.y()), z)); + data.add_vertex(Vec3f(float(s.second.x()), float(s.second.y()), z)); + const unsigned int vc = static_cast(data.vertices_count()); + data.add_line(vc - 2, vc - 1); + } + model.init_from(std::move(data)); + }; + + m_cad_grid_minor.reset(); + m_cad_grid_major.reset(); + build_grid(10.0, m_cad_grid_minor); + build_grid(50.0, m_cad_grid_major); + } + + if (!m_cad_grid_minor.is_initialized() || !m_cad_grid_major.is_initialized()) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + shader->set_uniform("view_model_matrix", view_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + + // White every 5 cm, grey every 1 cm — the SAME in both themes, deliberately. There is no + // "white bed" to vanish against: the plate is dark grey either way, DEFAULT_MODEL_COLOR + // {0.326,0.337,0.337} on light and DEFAULT_MODEL_COLOR_DARK {0.255,0.255,0.283} on dark + // (3DBed.cpp:185-186), a difference of 0.07. A per-theme palette here would be a branch + // that buys nothing and one more thing to keep in step. + // + // For contrast with what this replaces: the plate's own grid uses LINE_TOP_DARK_COLOR, a + // 0.43 grey, for BOTH its thin and its bold family — which is most of why the stock grid + // reads as a flat mesh with no scale to it. + const ColorRGBA minor_color(0.40f, 0.40f, 0.42f, 1.0f); + const ColorRGBA major_color(0.90f, 0.90f, 0.90f, 1.0f); + + glsafe(::glLineWidth(1.0f)); + m_cad_grid_minor.set_color(minor_color); + m_cad_grid_minor.render(); + + glsafe(::glLineWidth(2.0f)); + m_cad_grid_major.set_color(major_color); + m_cad_grid_major.render(); + + glsafe(::glDisable(GL_BLEND)); +} + void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform3d& projection_matrix) { if (wxGetApp().app_config == nullptr) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 11c538defe..6440632aee 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -555,6 +555,16 @@ private: // Design tab: draw the printer bed and its plate grid at all. Default true, so the // main editor is untouched; the Design tab lets the user hide it to model without a bed. bool m_show_bed{true}; + // Design tab: CAD grid drawn on the bed plane in place of the plate's corner-origin grid. + // Two GLModels (10 mm minor / 50 mm major) generated from the bed centre so a line passes + // exactly through the modeling origin; built once and rebuilt only when the bed shape changes. + GLModel m_cad_grid_minor; + GLModel m_cad_grid_major; + // Geometry the CAD grid models were last built from, so they are rebuilt on bed-shape change + // rather than every frame. + BoundingBoxf m_cad_grid_bb; + Vec2d m_cad_grid_center; + bool m_cad_grid_valid{false}; #ifdef SLIC3R_CAD DesignSketchTool* m_design_sketch_tool{nullptr}; #endif @@ -1297,6 +1307,10 @@ 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); + // 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. + void _render_cad_grid(const Transform3d& view_matrix, const Transform3d& projection_matrix); //BBS: add outline drawing logic void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true); void _render_wireframe_overlay();