From b8d45c6bb1b1ea2fc2730a53e94e3d230f22bd68 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 16:52:27 +0000 Subject: [PATCH] Design tab: studio lighting and body edges in the 3D view Reported: once extruded, a part is hard to read; the lighting says little about its shape. Both lights of the object shaders sit near the camera, so the sides of a part come out in almost the same tone, and nothing marks where one face ends and the next begins. - The phong shader gains a studio lighting model, chosen by a new lighting_model uniform: a sky/ground hemisphere in world space (up faces cool and bright, down faces warm and dark), a key light from the upper left and a weak fill from the right, a plastic-like highlight, and a darker base with a faint sheen toward the silhouette so curved faces read as round. GLCanvas3D::set_studio_lighting() makes a canvas draw its objects with it whatever the realistic-view preferences; only the Design canvas turns it on. Every canvas sets the uniform on each use, 0 for the slicer's, so they render as before. - Every B-rep edge of a body is drawn as a thin dark line over it, depth tested and pulled a few pixels toward the eye so it wins against the faces meeting at it and hides behind the faces in front. The seam of a closed surface and degenerate edges are left out (GeometryEngine::display_edges). Edges are sampled once per shape and kept across recomputes that leave a body unchanged; bodies faded by body focus get fainter edges, and a dress-up previewing its result alone hides them with the bodies. Tests: display_edges gives a box its 12 edges at their lengths, a cylinder its two round rims without the seam, a cone its base rim without the seam or the apex. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9 --- docs/CAD/design_tab.md | 7 ++ resources/shaders/110/phong.fs | 31 ++++++++- resources/shaders/140/phong.fs | 31 ++++++++- src/libslic3r/CAD/GeometryEngine.cpp | 22 ++++++ src/libslic3r/CAD/GeometryEngine.hpp | 4 ++ src/slic3r/GUI/CAD/DesignCanvas.cpp | 2 + src/slic3r/GUI/CAD/DesignSketchTool.cpp | 91 +++++++++++++++++++++++++ src/slic3r/GUI/CAD/DesignSketchTool.hpp | 12 ++++ src/slic3r/GUI/GLCanvas3D.cpp | 10 ++- src/slic3r/GUI/GLCanvas3D.hpp | 4 ++ tests/libslic3r/test_caddocument.cpp | 28 ++++++++ 11 files changed, 237 insertions(+), 5 deletions(-) diff --git a/docs/CAD/design_tab.md b/docs/CAD/design_tab.md index 675c94bb4c..3904df1fff 100644 --- a/docs/CAD/design_tab.md +++ b/docs/CAD/design_tab.md @@ -362,6 +362,13 @@ the plane, `F` flips which half is kept. **Place on Face** (`F`, when section is picked face flat on the bed. Origin planes (`P`) and world axes (`A`) can be toggled on while you orient yourself. +Bodies are lit as in a studio, whatever the realistic-view preferences of the slicer: faces +facing up read cooler and brighter than faces facing down, a key light from the upper left +separates the sides of a part, and curved faces darken toward their outline. Every edge of a +body is drawn as a thin dark line, except the seam OCCT puts down the side of a cylinder or +cone; the edges of bodies faded by body focus are fainter, and a dress-up previewing its +result alone hides them with the bodies. + --- ## Known limitations diff --git a/resources/shaders/110/phong.fs b/resources/shaders/110/phong.fs index 5fedf7edff..c3a5436731 100644 --- a/resources/shaders/110/phong.fs +++ b/resources/shaders/110/phong.fs @@ -64,6 +64,10 @@ uniform PrintVolumeDetection print_volume; uniform float z_far; uniform float z_near; uniform bool enable_ssao; +// 1 = the Design tab's studio lighting (studio_shade below); 0 = the two-light model above, which +// every other canvas keeps. world_up_eye is world +Z in eye space, for the hemisphere ambient. +uniform int lighting_model; +uniform vec3 world_up_eye; // Depth-based shadow map (object-on-object and self shadows). shadow_intensity == 0 disables it. uniform sampler2D shadow_map; @@ -206,6 +210,27 @@ float shadow_shade() return 1.0 - shadow_intensity * (sum / 25.0); } +// Studio lighting for the Design tab. The default model lights every face from near the camera, +// so the sides of a part come out in nearly the same tone and its form is hard to read. This one +// separates faces by their orientation in the WORLD (a sky/ground hemisphere: up-facing faces +// cool and bright, down-facing ones warm and dark), keeps a strong key light from the upper left +// and a weak fill from the right, gives a plastic-like highlight, and darkens the base colour +// toward the silhouette while adding a faint sheen there, so curved faces read as round. +vec3 studio_shade(vec3 base, vec3 n, vec3 v) +{ + vec3 key = normalize(vec3(-0.45, 0.60, 0.66)); + vec3 fill = normalize(vec3(0.70, -0.15, 0.70)); + float hemi = 0.5 + 0.5 * dot(n, normalize(world_up_eye)); + vec3 ambient = mix(vec3(0.16, 0.15, 0.14), vec3(0.40, 0.42, 0.46), hemi); + float kd = max(dot(n, key), 0.0); + float fd = max(dot(n, fill), 0.0); + vec3 diffuse = ambient + vec3(0.60) * kd + vec3(0.20) * fd; + float spec = 0.28 * pow(max(dot(n, normalize(key + v)), 0.0), 48.0) + + 0.06 * pow(max(dot(n, normalize(fill + v)), 0.0), 24.0); + float rim = pow(1.0 - clamp(dot(n, v), 0.0, 1.0), 3.0); + return base * diffuse * (1.0 - 0.30 * rim) + vec3(spec + 0.08 * rim); +} + void main() { if (any(lessThan(clipping_planes_dots, ZERO))) @@ -262,9 +287,11 @@ void main() // SSAO is applied in post-process pass. Keep base lighting unchanged here. float shade = shadow_shade(); + vec3 lit = (lighting_model == 1) ? studio_shade(color.rgb, normal, view_dir) + : (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS; if (is_outline) { - vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade; + vec3 shaded_rgb = lit * shade; vec4 shaded_color = vec4(clamp(shaded_rgb, vec3(0.0), vec3(1.0)), color.a); vec2 fragCoord = gl_FragCoord.xy; float s = DetectSilho(fragCoord); @@ -282,5 +309,5 @@ void main() gl_FragColor = vec4(clamp((0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + window_reflection + 0.8 * color.rgb * diffuse) * PHONG_BRIGHTNESS * shade, vec3(0.0), vec3(1.0)), color.a); #endif else - gl_FragColor = vec4(clamp((vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade, vec3(0.0), vec3(1.0)), color.a); + gl_FragColor = vec4(clamp(lit * shade, vec3(0.0), vec3(1.0)), color.a); } \ No newline at end of file diff --git a/resources/shaders/140/phong.fs b/resources/shaders/140/phong.fs index f847763658..a7d29a3e41 100644 --- a/resources/shaders/140/phong.fs +++ b/resources/shaders/140/phong.fs @@ -74,6 +74,10 @@ uniform PrintVolumeDetection print_volume; uniform float z_far; uniform float z_near; uniform bool enable_ssao; +// 1 = the Design tab's studio lighting (studio_shade below); 0 = the two-light model above, which +// every other canvas keeps. world_up_eye is world +Z in eye space, for the hemisphere ambient. +uniform int lighting_model; +uniform vec3 world_up_eye; // Depth-based shadow map (object-on-object and self shadows). shadow_intensity == 0 disables it. uniform sampler2D shadow_map; @@ -263,6 +267,27 @@ float shadow_shade() return 1.0 - shadow_intensity * (sum / 25.0); } +// Studio lighting for the Design tab. The default model lights every face from near the camera, +// so the sides of a part come out in nearly the same tone and its form is hard to read. This one +// separates faces by their orientation in the WORLD (a sky/ground hemisphere: up-facing faces +// cool and bright, down-facing ones warm and dark), keeps a strong key light from the upper left +// and a weak fill from the right, gives a plastic-like highlight, and darkens the base colour +// toward the silhouette while adding a faint sheen there, so curved faces read as round. +vec3 studio_shade(vec3 base, vec3 n, vec3 v) +{ + vec3 key = normalize(vec3(-0.45, 0.60, 0.66)); + vec3 fill = normalize(vec3(0.70, -0.15, 0.70)); + float hemi = 0.5 + 0.5 * dot(n, normalize(world_up_eye)); + vec3 ambient = mix(vec3(0.16, 0.15, 0.14), vec3(0.40, 0.42, 0.46), hemi); + float kd = max(dot(n, key), 0.0); + float fd = max(dot(n, fill), 0.0); + vec3 diffuse = ambient + vec3(0.60) * kd + vec3(0.20) * fd; + float spec = 0.28 * pow(max(dot(n, normalize(key + v)), 0.0), 48.0) + + 0.06 * pow(max(dot(n, normalize(fill + v)), 0.0), 24.0); + float rim = pow(1.0 - clamp(dot(n, v), 0.0, 1.0), 3.0); + return base * diffuse * (1.0 - 0.30 * rim) + vec3(spec + 0.08 * rim); +} + void main() { if (any(lessThan(clipping_planes_dots, ZERO))) @@ -319,9 +344,11 @@ void main() // SSAO is applied in post-process pass. Keep base lighting unchanged here. float shade = shadow_shade(); + vec3 lit = (lighting_model == 1) ? studio_shade(color.rgb, normal, view_dir) + : (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS; if (is_outline) { - vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade; + vec3 shaded_rgb = lit * shade; vec4 shaded_color = vec4(clamp(shaded_rgb, vec3(0.0), vec3(1.0)), color.a); float s = DetectSilho(gl_FragCoord.xy); if (s < 0.01) @@ -333,5 +360,5 @@ void main() out_color = vec4(clamp((0.45 * texture(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + window_reflection + 0.8 * color.rgb * diffuse) * PHONG_BRIGHTNESS * shade, vec3(0.0), vec3(1.0)), color.a); #endif else - out_color = vec4(clamp((vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade, vec3(0.0), vec3(1.0)), color.a); + out_color = vec4(clamp(lit * shade, vec3(0.0), vec3(1.0)), color.a); } \ No newline at end of file diff --git a/src/libslic3r/CAD/GeometryEngine.cpp b/src/libslic3r/CAD/GeometryEngine.cpp index 5795058150..1410f9c8e3 100644 --- a/src/libslic3r/CAD/GeometryEngine.cpp +++ b/src/libslic3r/CAD/GeometryEngine.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -611,6 +612,27 @@ std::vector GeometryEngine::sample_edge_world(const TopoDS_Edge& edge, do return pts; } +std::vector> GeometryEngine::display_edges(const TopoDS_Shape& shape, double chord_tol) +{ + TopTools_IndexedDataMapOfShapeListOfShape faces_of_edge; + TopExp::MapShapesAndAncestors(shape, TopAbs_EDGE, TopAbs_FACE, faces_of_edge); + std::vector> out; + for (int i = 1; i <= faces_of_edge.Extent(); ++i) { + const TopoDS_Edge& edge = TopoDS::Edge(faces_of_edge.FindKey(i)); + if (BRep_Tool::Degenerated(edge)) + continue; + bool seam = false; + for (TopTools_ListIteratorOfListOfShape it(faces_of_edge.FindFromIndex(i)); it.More() && !seam; it.Next()) + seam = BRep_Tool::IsClosed(edge, TopoDS::Face(it.Value())); + if (seam) + continue; + std::vector pts = sample_edge_world(edge, chord_tol); + if (pts.size() >= 2) + out.push_back(std::move(pts)); + } + return out; +} + Vec3d GeometryEngine::face_centroid_world(const TopoDS_Face& face) { GProp_GProps props; diff --git a/src/libslic3r/CAD/GeometryEngine.hpp b/src/libslic3r/CAD/GeometryEngine.hpp index 16040eee5e..c37478bb87 100644 --- a/src/libslic3r/CAD/GeometryEngine.hpp +++ b/src/libslic3r/CAD/GeometryEngine.hpp @@ -149,6 +149,10 @@ public: static Vec3d face_normal_world(const TopoDS_Face& face); // Sample an edge into a world-space polyline (>=2 pts) for pick-distance + highlight. static std::vector sample_edge_world(const TopoDS_Edge& edge, double chord_tol = 0.05); + // The edges a viewer draws over a body, each as a polyline: every edge of the shape once, + // without degenerate edges (a cone apex) and without the seam of a closed surface (the line + // down a cylinder's side), which is where OCCT closes the parameter space, not a real edge. + static std::vector> display_edges(const TopoDS_Shape& shape, double chord_tol); // 0-based edge index into TopExp::MapShapes(shape, TopAbs_EDGE, map). static int edge_count(const TopoDS_Shape& shape); static TopoDS_Edge edge_by_index(const TopoDS_Shape& shape, int index); diff --git a/src/slic3r/GUI/CAD/DesignCanvas.cpp b/src/slic3r/GUI/CAD/DesignCanvas.cpp index 2f00b04e61..7feda3e9bd 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.cpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.cpp @@ -46,6 +46,7 @@ DesignCanvas::DesignCanvas(wxWindow* parent) // cannot be null. Borrow the plater's, as the editor canvases do. m_canvas->set_process(&wxGetApp().plater()->background_process()); m_canvas->set_type(GLCanvas3D::ECanvasType::CanvasView3D); + m_canvas->set_studio_lighting(true); // see GLCanvas3D::m_studio_lighting and phong.fs // CAD navigation, this canvas only: left-drag sweeps a selection rubber band, so orbit // moves to middle-drag and pan to right-drag. Design is a different modality from @@ -1347,6 +1348,7 @@ void DesignCanvas::set_body_hidden(bool on) { if (m_body_hidden == on) return; m_body_hidden = on; + m_sketch_tool.set_body_edges_hidden(on); reload(true); // hides/show base bodies + flips the ghost opaque/faint for preview-only mode } diff --git a/src/slic3r/GUI/CAD/DesignSketchTool.cpp b/src/slic3r/GUI/CAD/DesignSketchTool.cpp index 5d3a8df075..f06fcb1a81 100644 --- a/src/slic3r/GUI/CAD/DesignSketchTool.cpp +++ b/src/slic3r/GUI/CAD/DesignSketchTool.cpp @@ -14,6 +14,10 @@ #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/GLShader.hpp" #include "libslic3r/CAD/GeometryEngine.hpp" + +#include +#include +#include #include "libslic3r/TriangleMesh.hpp" #include @@ -3424,9 +3428,94 @@ void DesignSketchTool::set_solid_pick(const std::vector* bodies, const m_solid_bodies = bodies; m_solid_mesh = mesh; m_solid_tri_face = tri_face; m_solid_tri_body = tri_body; m_solid_visible = visible; m_solid_xform = xform; } + refresh_body_edges(); clear_solid_selection(); } +void DesignSketchTool::refresh_body_edges() +{ + const size_t n = m_solid_bodies != nullptr ? m_solid_bodies->size() : 0; + m_body_edges.resize(n); + m_body_edges_key.resize(n, nullptr); + for (size_t b = 0; b < n; ++b) { + const TopoDS_Shape& shape = (*m_solid_bodies)[b].shape; + const void* key = shape.IsNull() ? nullptr : shape.TShape().get(); + if (key == m_body_edges_key[b] && key != nullptr) + continue; + m_body_edges_key[b] = key; + m_body_edges[b].clear(); + if (key == nullptr) + continue; + // A thousandth of the body's size: round edges stay round at any zoom that shows the + // whole body, without sampling a large import into millions of segments. + Bnd_Box box; + BRepBndLib::Add(shape, box); + const double diag = box.IsVoid() ? 1.0 : std::sqrt(box.SquareExtent()); + try { + m_body_edges[b] = GeometryEngine::display_edges(shape, std::max(1e-3 * diag, 0.005)); + } catch (const Standard_Failure&) { + m_body_edges[b].clear(); // an unsampleable edge costs its body the lines, nothing else + } + } +} + +void DesignSketchTool::render_body_edges() +{ + if (m_body_edges_hidden || m_solid_bodies == nullptr) + return; + using EPT = GLModel::Geometry::EPrimitiveType; + using EVL = GLModel::Geometry::EVertexLayout; + const Camera& cam = wxGetApp().plater()->get_camera(); + const Vec3d vd = cam.get_dir_forward(); + const double px = 1.0 / std::max(cam.get_zoom(), 1e-6); + const double hw = 0.75 * px; // ~1.5 px wide + // Pulled toward the eye by a few pixels, so the line wins the depth test against the two + // faces meeting at the edge while a face in front of it still hides it. + const Vec3d pull = -vd * (3.0 * px); + // Two passes: the edges of a body faded by body focus are fainter, like the body itself. + for (int pass = 0; pass < 2; ++pass) { + GLModel::Geometry g; g.format = { EPT::Triangles, EVL::P3 }; + unsigned int base = 0; + for (int b = 0; b < int(m_body_edges.size()); ++b) { + if (m_solid_visible != nullptr && b < int(m_solid_visible->size()) && !(*m_solid_visible)[b]) + continue; + const bool faded = m_pick_only_body >= 0 && m_pick_only_body < int(m_body_edges.size()) + && b != m_pick_only_body; + if (faded != (pass == 1)) + continue; + for (const std::vector& pl : m_body_edges[b]) + for (size_t s = 1; s < pl.size(); ++s) { + const Vec3d a = body_xform_pt(b, pl[s - 1]) + pull, c = body_xform_pt(b, pl[s]) + pull; + Vec3d dir = c - a; if (dir.norm() < 1e-9) continue; dir.normalize(); + Vec3d off = dir.cross(vd); + if (off.norm() < 1e-9) continue; // edge seen end-on: a point, nothing to draw + off = off.normalized() * hw; + g.add_vertex((Vec3f)(a + off).cast()); + g.add_vertex((Vec3f)(c + off).cast()); + g.add_vertex((Vec3f)(c - off).cast()); + g.add_vertex((Vec3f)(a - off).cast()); + g.add_triangle(base, base + 1, base + 2); + g.add_triangle(base, base + 2, base + 3); base += 4; + } + } + if (base == 0) + continue; + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthFunc(GL_LEQUAL)); + glsafe(::glDepthMask(GL_FALSE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + m_body_edges_model.reset(); + m_body_edges_model.init_from(std::move(g)); + m_body_edges_model.set_color(ColorRGBA(0.08f, 0.09f, 0.11f, pass == 0 ? 0.85f : 0.25f)); + m_body_edges_model.render(); + glsafe(::glDepthMask(GL_TRUE)); + glsafe(::glDepthFunc(GL_LESS)); + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDisable(GL_DEPTH_TEST)); + } +} + // Map a point sampled from the (untransformed) OCCT body shape through the body's display // transform, so edge picking/highlight track a moved body. The pick MESH is already // transformed by the host; only OCCT-sampled edges need this. @@ -8929,6 +9018,8 @@ void DesignSketchTool::render(GLCanvas3D& canvas) shader->set_uniform("view_model_matrix", camera.get_view_matrix()); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + render_body_edges(); + // Persistent committed sketches (e.g. an un-consumed sketch left visible after its // extrude is removed): faces translucent, outlines orange. Each uses its own plane. if (!m_display_sketches.empty()) { diff --git a/src/slic3r/GUI/CAD/DesignSketchTool.hpp b/src/slic3r/GUI/CAD/DesignSketchTool.hpp index 57a4d087e8..7228e3b362 100644 --- a/src/slic3r/GUI/CAD/DesignSketchTool.hpp +++ b/src/slic3r/GUI/CAD/DesignSketchTool.hpp @@ -198,6 +198,9 @@ public: // another solid is reachable without hiding anything. -1 = no restriction. // Survives set_solid_pick() — it is owned by the panel, not by the mesh feed. void set_pick_only_body(int b) { m_pick_only_body = b; } + // Off while the bodies themselves are hidden (a dress-up previewing its result alone), so + // their edges do not float over the preview. + void set_body_edges_hidden(bool h) { m_body_edges_hidden = h; } void clear_solid_selection(); bool has_solid_selection() const { return m_solid_sel != SolidSel::None; } // Every picked edge when the selection is an edge set (Shift/Ctrl+click adds and removes @@ -1204,6 +1207,14 @@ private: int m_pick_only_body{-1}; // >=0: only this body catches clicks (body-focus x-ray for CoordSys picking) const std::vector* m_solid_xform{nullptr}; // per-body display transform (for edge sampling) Vec3d body_xform_pt(int body, const Vec3d& p) const; // map an OCCT-shape point through the body xform + // The bodies' B-rep edges, drawn as dark lines over the solids so faces and features read + // apart. One polyline set per body in its own shape coordinates, resampled only for a body + // whose shape changed (keyed by the TShape), since set_solid_pick runs on every recompute. + std::vector>> m_body_edges; + std::vector m_body_edges_key; + bool m_body_edges_hidden{false}; + void refresh_body_edges(); + void render_body_edges(); bool body_pickable(int b) const; // false when the body is explicitly hidden SolidSel m_solid_sel{SolidSel::None}; int m_sel_body{-1}; // which body the face/edge selection is on @@ -1269,6 +1280,7 @@ private: GLModel m_mc_fill_model; // the face treatment's shaded facets GLModel m_solid_face_model; GLModel m_solid_edge_model; + GLModel m_body_edges_model; GLModel m_solid_vertex_model; int m_display_pick_region{-1}; // selected closed-region index within that feature (-1 none) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c4d9173268..ca6e037eb7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -8489,10 +8489,11 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with const bool realistic_mode = _is_realistic_view_enabled(); const bool realistic_phong = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_PHONG); - const std::string shader_name = (realistic_mode && realistic_phong) ? "phong" : "gouraud"; + const std::string shader_name = (m_studio_lighting || (realistic_mode && realistic_phong)) ? "phong" : "gouraud"; GLShaderProgram* shader = wxGetApp().get_shader(shader_name); if (shader == nullptr && shader_name != "gouraud") shader = wxGetApp().get_shader("gouraud"); + const bool studio = m_studio_lighting && shader != nullptr && shader->get_name() == "phong"; ECanvasType canvas_type = this->m_canvas_type; bool partly_inside_enable = canvas_type == ECanvasType::CanvasAssembleView ? false : true; // The edited printer's per-extruder printable heights feed the object shader's @@ -8505,6 +8506,13 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with const bool phong_ssao = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SSAO); shader->set_uniform("enable_ssao", phong_ssao); + // Set on every use: the program is shared, so a canvas that leaves it unset would inherit + // the last canvas's choice. + shader->set_uniform("lighting_model", studio ? 1 : 0); + if (studio) { + const Transform3d& view = wxGetApp().plater()->get_camera().get_view_matrix(); + shader->set_uniform("world_up_eye", Vec3f((view.matrix().block<3, 3>(0, 0) * Vec3d::UnitZ()).cast())); + } // Object-on-object and self shadows: sample the depth map built in _render_shadow_map_pass(). // shadow_intensity == 0 disables the effect entirely (unchanged behavior when off / unsupported). diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 36a6c7b944..04224930d2 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -590,6 +590,9 @@ private: //BBS: add canvas type for assemble view usage ECanvasType m_canvas_type; + // Objects drawn with the phong shader's studio lighting whatever the realistic-view settings + // (the Design tab's canvas). Off for every canvas of the slicer, which render as before. + bool m_studio_lighting{false}; std::array m_clipping_planes; ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; @@ -815,6 +818,7 @@ public: void set_context(wxGLContext* context) { m_context = context; } void set_type(ECanvasType type) { m_canvas_type = type; } + void set_studio_lighting(bool on) { m_studio_lighting = on; } ECanvasType get_canvas_type() { return m_canvas_type; } wxGLCanvas* get_wxglcanvas() { return m_canvas; } diff --git a/tests/libslic3r/test_caddocument.cpp b/tests/libslic3r/test_caddocument.cpp index ce695079f7..fe788d0ffa 100644 --- a/tests/libslic3r/test_caddocument.cpp +++ b/tests/libslic3r/test_caddocument.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include #include @@ -8920,3 +8922,29 @@ TEST_CASE("CadDocument: a profile on a body face touches it, one in free space d CHECK(doc.body_touching_sketch(-1) == -1); CHECK(doc.body_touching_sketch(base + 1) == -1); // the extrude: not a sketch } + +TEST_CASE("display edges: every real edge once, no seams, no degenerate apex", "[CadDocument][display]") +{ + const auto box = GeometryEngine::display_edges(BRepPrimAPI_MakeBox(10., 20., 30.).Shape(), 0.01); + CHECK(box.size() == 12); + for (const auto& pl : box) { + REQUIRE(pl.size() >= 2); + const double len = (pl.back() - pl.front()).norm(); + CHECK((std::abs(len - 10.) < 1e-6 || std::abs(len - 20.) < 1e-6 || std::abs(len - 30.) < 1e-6)); + } + + // A cylinder has three edges in OCCT: the two rims and the seam down its side. Only the rims + // are drawn, each sampled finely enough to look round and closed. + const auto cyl = GeometryEngine::display_edges(BRepPrimAPI_MakeCylinder(5., 8.).Shape(), 0.01); + REQUIRE(cyl.size() == 2); + for (const auto& pl : cyl) { + CHECK(pl.size() > 16); + CHECK((pl.front() - pl.back()).norm() < 1e-6); + for (const Vec3d& p : pl) + CHECK(std::abs(std::hypot(p.x(), p.y()) - 5.) < 0.02); + } + + // A cone keeps its base rim; the apex is a degenerate edge and the side has a seam. + const auto cone = GeometryEngine::display_edges(BRepPrimAPI_MakeCone(5., 0., 8.).Shape(), 0.01); + CHECK(cone.size() == 1); +}