diff --git a/src/slic3r/GUI/DesignCanvas.cpp b/src/slic3r/GUI/DesignCanvas.cpp index 126719862b..0b11a15ba8 100644 --- a/src/slic3r/GUI/DesignCanvas.cpp +++ b/src/slic3r/GUI/DesignCanvas.cpp @@ -216,6 +216,8 @@ void DesignCanvas::reload(bool keep_view) // Selection tint wins; otherwise the per-body override (Color tool) or the // auto palette via body_color(). ColorRGBA c = m_body_selected ? sel_gold : body_color(b); + if (b == m_hl_body_target) c = ColorRGBA(0.30f, 0.90f, 0.70f, 1.0f); // target = teal-green + else if (b == m_hl_body_tool) c = ColorRGBA(1.00f, 0.55f, 0.15f, 1.0f); // tool = orange if (m_body_translucent) c.a(0.30f); v->set_color(c); } @@ -610,6 +612,44 @@ void DesignCanvas::set_on_revolve_angle_changed(std::function cb) m_sketch_tool.on_revolve_angle_changed = std::move(cb); } +void DesignCanvas::set_draft_gizmo(const Vec3d& face_centroid, const Vec3d& face_normal, double angle) +{ + m_sketch_tool.set_draft_gizmo(face_centroid, face_normal, angle); + request_repaint(); +} + +void DesignCanvas::clear_draft_gizmo() +{ + m_sketch_tool.clear_draft_gizmo(); + request_repaint(); +} + +bool DesignCanvas::drafting() const { return m_sketch_tool.drafting(); } + +void DesignCanvas::set_on_draft_angle_changed(std::function cb) +{ + m_sketch_tool.set_on_draft_angle_changed(std::move(cb)); +} + +void DesignCanvas::set_cut_gizmo(const SketchPlane& plane, double offset, const Vec3d& body_center, double half_extent) +{ + m_sketch_tool.set_cut_gizmo(plane, offset, body_center, half_extent); + request_repaint(); +} + +void DesignCanvas::clear_cut_gizmo() +{ + m_sketch_tool.clear_cut_gizmo(); + request_repaint(); +} + +bool DesignCanvas::cutting() const { return m_sketch_tool.cutting(); } + +void DesignCanvas::set_on_cut_offset_changed(std::function cb) +{ + m_sketch_tool.set_on_cut_offset_changed(std::move(cb)); +} + void DesignCanvas::begin_pattern_gizmo(const SketchPlane& plane, const Vec3d& body_centroid, bool circular, int count, int dir, double spacing, double angle) { @@ -759,6 +799,20 @@ void DesignCanvas::set_body_highlight(bool on) reload(true); // recolours the body volume (selected = cyan tint) } +void DesignCanvas::set_operand_bodies(int target_body, int tool_body) +{ + if (m_hl_body_target == target_body && m_hl_body_tool == tool_body) return; + m_hl_body_target = target_body; + m_hl_body_tool = tool_body; + reload(true); // recolours the body volumes (same idiom set_body_highlight uses) +} + +void DesignCanvas::set_highlight_sketches(std::vector> hl) +{ + m_sketch_tool.set_highlight_sketches(std::move(hl)); + request_repaint(); +} + void DesignCanvas::set_body_translucent(bool on) { if (m_body_translucent == on) return; diff --git a/src/slic3r/GUI/DesignCanvas.hpp b/src/slic3r/GUI/DesignCanvas.hpp index 5ccc578fa3..18f758dc8f 100644 --- a/src/slic3r/GUI/DesignCanvas.hpp +++ b/src/slic3r/GUI/DesignCanvas.hpp @@ -130,6 +130,18 @@ public: void clear_revolve_gizmo(); bool revolving() const; void set_on_revolve_angle_changed(std::function cb); + // Visual Draft angle-arc gizmo: the panel feeds the face centroid + face normal + angle while + // its Draft card is open; drag/edit fire the angle callback. + void set_draft_gizmo(const Vec3d& face_centroid, const Vec3d& face_normal, double angle); + void clear_draft_gizmo(); + bool drafting() const; + void set_on_draft_angle_changed(std::function cb); + // Visual Cut gizmo: plane-rectangle preview + draggable normal offset arrow while + // the Cut card is open; drag fires the offset callback. + void set_cut_gizmo(const SketchPlane& plane, double offset, const Vec3d& body_center, double half_extent); + void clear_cut_gizmo(); + bool cutting() const; + void set_on_cut_offset_changed(std::function cb); // Visual Pattern gizmo: the panel feeds the (world XY) plane + target body centroid + mode + // count/dir/spacing/angle while its Pattern card is open; drag/edit fire the value callback. void begin_pattern_gizmo(const SketchPlane& plane, const Vec3d& body_centroid, bool circular, @@ -157,9 +169,11 @@ public: void set_on_undo_redo(std::function cb); // Ctrl+Z / Ctrl+Shift+Z // Persistently draw committed sketches (un-consumed ones stay visible). void set_display_sketches(std::vector ds); + void set_highlight_sketches(std::vector> hl); void set_datum_planes(std::vector planes, - std::vector sizes = {}); // draw datum/reference planes (u/v extents) + std::vector sizes = {}); // draw datum/reference planes (u/v extents) void set_body_highlight(bool on); // tint the solid when its feature is tree-selected + void set_operand_bodies(int target_body, int tool_body); // -1,-1 clears void set_body_translucent(bool on); // render the solid see-through (fillet/chamfer preview) void set_body_hidden(bool on); // preview-only: hide base bodies, show only the result ghost void set_on_move_exit(std::function cb); // right-click finished the move-body gizmo @@ -229,6 +243,8 @@ private: Model m_model; bool m_first_frame{true}; bool m_body_selected{false}; // tree selected a body feature → tint the solid + int m_hl_body_target{-1}; + int m_hl_body_tool{-1}; bool m_body_translucent{false};// fillet/chamfer preview → render the body see-through bool m_body_hidden{false}; // preview-only mode → hide base bodies, ghost = the result std::vector m_body_visible; // per-body visibility (empty => all visible) diff --git a/src/slic3r/GUI/DesignPanel.cpp b/src/slic3r/GUI/DesignPanel.cpp index 14f577828c..a0597bbacf 100644 --- a/src/slic3r/GUI/DesignPanel.cpp +++ b/src/slic3r/GUI/DesignPanel.cpp @@ -1907,6 +1907,16 @@ DesignPanel::DesignPanel(wxWindow* parent) refresh_preview(); }); + m_viewport->set_on_draft_angle_changed([this](double angle) { + if (m_draft_angle) m_draft_angle->SetValue(angle); + refresh_preview(); + }); + + m_viewport->set_on_cut_offset_changed([this](double v) { + if (m_cut_offset) m_cut_offset->SetValue(v); + refresh_preview(); + }); + m_viewport->set_on_pattern_changed([this](double value) { // Linear drag feeds spacing; circular drag feeds angle. The card knows which is live. if (m_pattern_type && m_pattern_type->GetSelection() == 1) { @@ -5171,6 +5181,60 @@ void DesignPanel::update_revolve_gizmo() m_revolve_angle->GetValue(), m_revolve_flip->GetValue()); } +void DesignPanel::update_draft_gizmo() +{ + if (!m_viewport) return; + if (m_active != Tool::Draft || m_sel_solid_face < 0 || m_sel_solid_body < 0 + || m_sel_solid_body >= int(m_doc.bodies.size())) { + m_viewport->clear_draft_gizmo(); + return; + } + const TopoDS_Face f = GeometryEngine::face_by_index(m_doc.bodies[m_sel_solid_body].shape, m_sel_solid_face); + const Vec3d c = GeometryEngine::face_centroid_world(f); + const Vec3d n = GeometryEngine::face_normal_world(f); + m_viewport->set_draft_gizmo(c, n, m_draft_angle->GetValue()); +} + +void DesignPanel::update_cut_gizmo() +{ + if (!m_viewport) return; + if (m_active != Tool::Cut) { m_viewport->clear_cut_gizmo(); return; } + const int bi = m_cut_target ? m_cut_target->GetSelection() : -1; + if (bi < 0 || bi >= int(m_doc.bodies.size()) || bi >= int(m_doc.display_body_meshes.size())) { + m_viewport->clear_cut_gizmo(); + return; + } + const SketchPlane plane = plane_from_choice(m_cut_plane->GetSelection()); + const BoundingBoxf3 bb = m_doc.display_body_meshes[bi].bounding_box(); + const Vec3d center = bb.center(); + const double half = std::max(0.5 * (bb.max - bb.min).norm(), 10.0); + m_viewport->set_cut_gizmo(plane, m_cut_offset->GetValue(), center, half); +} + +void DesignPanel::update_operand_highlight() +{ + if (!m_viewport) return; + // default: nothing highlighted + int bt = -1, bl = -1; + std::vector> sk; + if (m_active == Tool::Boolean) { + if (m_bool_target) bt = m_bool_target->GetSelection(); + if (m_bool_tool) bl = m_bool_tool->GetSelection(); + } else if (m_active == Tool::Sweep) { + if (m_sweep_profile_ref >= 0) sk.emplace_back(m_sweep_profile_ref, ColorRGBA(0.30f, 0.85f, 1.0f, 1.0f)); // profile = cyan + if (m_sweep_path_ref >= 0) sk.emplace_back(m_sweep_path_ref, ColorRGBA(1.00f, 0.40f, 0.90f, 1.0f)); // path = magenta + } else if (m_active == Tool::Loft) { + // checked rows of m_loft_list map to feature indices via m_loft_sketch_idx + // (exactly as build_candidate(Tool::Loft) reads them). + if (m_loft_list) + for (unsigned i = 0; i < m_loft_list->GetCount(); ++i) + if (m_loft_list->IsChecked(i) && i < m_loft_sketch_idx.size()) + sk.emplace_back(m_loft_sketch_idx[i], ColorRGBA(0.40f, 0.90f, 0.50f, 1.0f)); // profiles = green + } + m_viewport->set_operand_bodies(bt, bl); + m_viewport->set_highlight_sketches(std::move(sk)); +} + void DesignPanel::update_pattern_gizmo() { if (!m_viewport) return; @@ -5449,6 +5513,12 @@ void DesignPanel::refresh_preview() update_shell_gizmo(); // Same for the Revolve angle-arc around the axis (self-gates: only while the Revolve card is open). update_revolve_gizmo(); + // Same for the Draft angle-arc around the face centroid (self-gates: only while the Draft card is open). + update_draft_gizmo(); + // Same for the Cut plane offset arrow + rectangle (self-gates: only while the Cut card is open). + update_cut_gizmo(); + // Operand highlight for Boolean (body tints) / Sweep / Loft (sketch tints); self-gates by tool. + update_operand_highlight(); // Same for the Pattern spacing arrow / angle-arc (self-gates: only while the Pattern card is open). update_pattern_gizmo(); // Datum-plane resize handles (self-gates: only while the Plane card is open). @@ -5604,7 +5674,11 @@ void DesignPanel::close_tool() m_viewport->clear_thread_gizmo(); m_viewport->clear_shell_gizmo(); m_viewport->clear_revolve_gizmo(); + m_viewport->clear_draft_gizmo(); + m_viewport->clear_cut_gizmo(); m_viewport->clear_pattern_gizmo(); + m_viewport->set_operand_bodies(-1, -1); + m_viewport->set_highlight_sketches({}); m_viewport->clear_datum_gizmo(); update_reference_planes(); // back to no-tool: show the origin planes if there is no object yet m_form->Layout(); diff --git a/src/slic3r/GUI/DesignPanel.hpp b/src/slic3r/GUI/DesignPanel.hpp index 6508bb1ed7..154e510f21 100644 --- a/src/slic3r/GUI/DesignPanel.hpp +++ b/src/slic3r/GUI/DesignPanel.hpp @@ -184,6 +184,9 @@ private: void update_thread_gizmo(); // footprint circle + radius/length arrows (Thread card) void update_shell_gizmo(); // inward thickness arrow on the picked face (Shell card) void update_revolve_gizmo(); // angle-arc around the axis (Revolve card) + void update_draft_gizmo(); // angle-arc around the face centroid (Draft card) + void update_cut_gizmo(); // plane-rectangle + offset arrow (Cut card) + void update_operand_highlight(); // Boolean/Sweep/Loft operand tinting on the canvas void update_pattern_gizmo(); // linear spacing arrow / circular angle-arc (Pattern card) void update_datum_gizmo(); // resize handles on the datum plane being created/edited (C3) void refresh_datum_planes(); // push resolved datum frames + per-plane u/v extents to viewport diff --git a/src/slic3r/GUI/DesignSketchTool.cpp b/src/slic3r/GUI/DesignSketchTool.cpp index 5a80a26fce..1cae074c89 100644 --- a/src/slic3r/GUI/DesignSketchTool.cpp +++ b/src/slic3r/GUI/DesignSketchTool.cpp @@ -4211,6 +4211,238 @@ static Vec3d rv_yaxis(const Vec3d& axis, const Vec3d& ref, bool flip) return flip ? -y : y; } +// Arc in the draft plane (perpendicular to the world +Z axis through the face centroid). +// The arc sweeps from angle 0 to m_dr_angle, representing the taper amount. +static Vec3d dr_yaxis(const Vec3d& axis, const Vec3d& ref) +{ + Vec3d y = axis.cross(ref); + if (y.norm() < 1e-9) return ref; + y.normalize(); + return y; +} + +void DesignSketchTool::set_draft_gizmo(const Vec3d& face_centroid, const Vec3d& face_normal, double angle) +{ + m_dr_center = face_centroid; + m_dr_angle = std::min(89.0, std::max(-89.0, angle)); + m_dr_axis = Vec3d::UnitZ(); // pull direction is world +Z + Vec3d ref = face_normal - face_normal.dot(m_dr_axis) * m_dr_axis; // horizontal component + if (ref.norm() < 1e-6) ref = Vec3d::UnitX(); // fallback: face normal is parallel to Z + m_dr_ref = ref / ref.norm(); + m_dr_radius = 12.0; // fixed world-size manipulator + if (!m_dr_active) m_dr_drag = false; + m_dr_active = true; +} + +void DesignSketchTool::clear_draft_gizmo() +{ + m_dr_active = false; + m_dr_drag = false; +} + +void DesignSketchTool::render_draft_gizmo() +{ + if (!m_dr_active) return; + const Camera& cam = wxGetApp().plater()->get_camera(); + const double upp = 1.0 / std::max(cam.get_zoom(), 1e-6); + const double th = std::max(15.0 * upp, 1e-4); + const Vec3d yax = dr_yaxis(m_dr_axis, m_dr_ref); + + const SketchPlane saved = m_plane; + SketchPlane rp; rp.origin = m_dr_center; rp.x_axis = m_dr_ref; rp.y_axis = yax; rp.normal = m_dr_axis; + m_plane = rp; + const ColorRGBA arcc(0.15f, 0.92f, 1.0f, 1.0f); + const double r = m_dr_radius; + // Draft angle can be negative: sweep counter-clockwise (positive) or clockwise (negative) + const double a = m_dr_angle * M_PI / 180.0; + const int N = std::max(8, int(std::abs(a) / (M_PI / 32.0))); // ~ every 5.6° + + std::vector> segs; + Vec2d prev(r, 0.0); + for (int i = 1; i <= N; ++i) { + const double t = a * double(i) / double(N); + const Vec2d cur(r * std::cos(t), r * std::sin(t)); + segs.emplace_back(prev, cur); + prev = cur; + } + const Vec2d tip(r * std::cos(a), r * std::sin(a)); + segs.emplace_back(Vec2d(0, 0), Vec2d(r, 0)); // spoke at angle 0 + segs.emplace_back(Vec2d(0, 0), tip); // spoke at the swept angle (the handle) + const Vec2d tang(-std::sin(a), std::cos(a)); + const Vec2d radial = tip.normalized(); + const double as = std::max(r * 0.14, th); + segs.emplace_back(tip, tip - tang * as - radial * (as * 0.5)); + segs.emplace_back(tip, tip - tang * as + radial * (as * 0.5)); + const double hs = std::max(th * 0.8, r * 0.05); + const Vec2d du = radial * hs, dv = Vec2d(-radial.y(), radial.x()) * hs; + segs.emplace_back(tip + du, tip + dv); + segs.emplace_back(tip + dv, tip - du); + segs.emplace_back(tip - du, tip - dv); + segs.emplace_back(tip - dv, tip + du); + + glsafe(::glDisable(GL_DEPTH_TEST)); + draw_strokes(m_dr_stroke_model, segs, std::max(0.8 * upp, 1e-4), arcc); + DimAnnot da; da.kind = DimType::Angle; da.value = m_dr_angle; + draw_text(m_line_model, dim_text(da), tip * 1.14, th, arcc); + m_plane = saved; +} + +bool DesignSketchTool::hit_test_draft_handle(GLCanvas3D& canvas, const wxMouseEvent& evt) const +{ + if (!m_dr_active) return false; + const Linef3 ray = canvas.mouse_ray(Point(evt.GetX(), evt.GetY())); + const Vec3d ro = ray.a, rd = ray.b - ray.a; + const Camera& cam = wxGetApp().plater()->get_camera(); + const double upp = 1.0 / std::max(cam.get_zoom(), 1e-6); + const Vec3d yax = dr_yaxis(m_dr_axis, m_dr_ref); + const double a = m_dr_angle * M_PI / 180.0; + const double tol = 16.0 * upp; + auto ray_pt = [&](const Vec3d& p) { + const double t = (p - ro).dot(rd) / std::max(rd.dot(rd), 1e-12); + return (p - (ro + t * rd)).norm(); + }; + const Vec3d tip = m_dr_center + m_dr_radius * (std::cos(a) * m_dr_ref + std::sin(a) * yax); + const Vec3d ref = m_dr_center + m_dr_radius * m_dr_ref; // angle-0 spoke end + const int N = 48; + for (int i = 0; i <= N; ++i) { + const double f = double(i) / double(N); + const double th = a * f; + const Vec3d arc = m_dr_center + m_dr_radius * (std::cos(th) * m_dr_ref + std::sin(th) * yax); + if (ray_pt(arc) <= tol) return true; + if (ray_pt(m_dr_center + f * (tip - m_dr_center)) <= tol) return true; + if (ray_pt(m_dr_center + f * (ref - m_dr_center)) <= tol) return true; + } + return false; +} + +void DesignSketchTool::drag_draft_arc(GLCanvas3D& canvas, const wxMouseEvent& evt) +{ + const Linef3 ray = canvas.mouse_ray(Point(evt.GetX(), evt.GetY())); + const Vec3d ro = ray.a, rd = ray.b - ray.a; + const double denom = rd.dot(m_dr_axis); + if (std::abs(denom) < 1e-9) return; + const double t = (m_dr_center - ro).dot(m_dr_axis) / denom; + const Vec3d p = ro + t * rd; + const Vec3d yax = dr_yaxis(m_dr_axis, m_dr_ref); + const double u = (p - m_dr_center).dot(m_dr_ref); + const double v = (p - m_dr_center).dot(yax); + double deg = std::atan2(v, u) * 180.0 / M_PI; + deg = std::min(89.0, std::max(-89.0, deg)); + m_dr_angle = deg; + if (m_on_draft_angle_changed) m_on_draft_angle_changed(deg); +} + +// ---- Cut gizmo (plane normal arrow + wire rectangle preview) -------------------------------- +// Arrow: shaft + arrowhead billboarded along the cut-plane normal from the projected body +// centre, with a signed Distance label = offset. Drag is RELATIVE via hole_axis_proj. +// Rectangle: 4 segments in the cut plane at the current offset, sized to the target body. + +void DesignSketchTool::set_cut_gizmo(const SketchPlane& plane, double offset, const Vec3d& body_center, double half_extent) +{ + m_ct_n = plane.normal.normalized(); + m_ct_u = plane.x_axis.normalized(); + m_ct_v = plane.y_axis.normalized(); + Vec3d rel = body_center - plane.origin; + m_ct_base = plane.origin + (rel - rel.dot(m_ct_n) * m_ct_n); // body center projected into the cut plane + m_ct_offset = offset; + m_ct_half = std::max(half_extent, 10.0); + if (!m_ct_active) m_ct_drag = false; + m_ct_active = true; +} + +void DesignSketchTool::clear_cut_gizmo() +{ + m_ct_active = false; + m_ct_drag = false; +} + +void DesignSketchTool::render_cut_gizmo() +{ + if (!m_ct_active) return; + const Camera& cam = wxGetApp().plater()->get_camera(); + const Vec3d right = cam.get_dir_right().normalized(); + const Vec3d up = cam.get_dir_up().normalized(); + const double upp = 1.0 / std::max(cam.get_zoom(), 1e-6); + const double th = std::max(15.0 * upp, 1e-4); + const ColorRGBA teal(0.20f, 0.80f, 0.75f, 1.0f); + + // ---- Wire rectangle in the cut plane ---- + { + const Vec3d cutpos = m_ct_base + m_ct_offset * m_ct_n; + const SketchPlane saved = m_plane; + SketchPlane rp; rp.origin = cutpos; rp.x_axis = m_ct_u; rp.y_axis = m_ct_v; rp.normal = m_ct_n; + m_plane = rp; + const double h = m_ct_half; + std::vector> segs; + segs.emplace_back(Vec2d(-h, -h), Vec2d( h, -h)); + segs.emplace_back(Vec2d( h, -h), Vec2d( h, h)); + segs.emplace_back(Vec2d( h, h), Vec2d(-h, h)); + segs.emplace_back(Vec2d(-h, h), Vec2d(-h, -h)); + glsafe(::glDisable(GL_DEPTH_TEST)); + draw_strokes(m_ct_rect_model, segs, std::max(0.7 * upp, 1e-4), teal); + m_plane = saved; + } + + // ---- Offset arrow (billboarded, clone of render_shell_gizmo) ---- + { + const double L_sign = (std::abs(m_ct_offset) < 40.0 * upp) + ? std::copysign(40.0 * upp, (m_ct_offset == 0.0 ? 1.0 : m_ct_offset)) + : m_ct_offset; + const Vec3d tipw = m_ct_base + m_ct_n * L_sign; + + const SketchPlane saved = m_plane; + SketchPlane bb; bb.origin = m_ct_base; bb.x_axis = right; bb.y_axis = up; + bb.normal = cam.get_dir_forward().normalized(); + m_plane = bb; + + const Vec2d tip2((tipw - m_ct_base).dot(right), (tipw - m_ct_base).dot(up)); + if (tip2.norm() > 1e-6) { + const Vec2d u = tip2.normalized(); + const Vec2d nrm(-u.y(), u.x()); + std::vector> segs; + segs.emplace_back(Vec2d(0, 0), tip2); + const double as = std::max(tip2.norm() * 0.20, th * 0.9); + const Vec2d back = tip2 - u * as; + segs.emplace_back(tip2, back + nrm * (as * 0.5)); + segs.emplace_back(tip2, back - nrm * (as * 0.5)); + glsafe(::glDisable(GL_DEPTH_TEST)); + draw_strokes(m_ct_stroke_model, segs, std::max(0.7 * upp, 1e-4), teal); + DimAnnot da; da.kind = DimType::Distance; da.value = m_ct_offset; + draw_text(m_line_model, dim_text(da), tip2 + u * (th * 1.4), th, teal); + } + m_plane = saved; + } +} + +bool DesignSketchTool::hit_test_cut_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt) const +{ + if (!m_ct_active) return false; + const Linef3 r = canvas.mouse_ray(Point(evt.GetX(), evt.GetY())); + const Vec3d ro = r.a, rd = r.b - r.a; + const Camera& cam = wxGetApp().plater()->get_camera(); + const double upp = 1.0 / std::max(cam.get_zoom(), 1e-6); + const double L_sign = (std::abs(m_ct_offset) < 40.0 * upp) + ? std::copysign(40.0 * upp, (m_ct_offset == 0.0 ? 1.0 : m_ct_offset)) + : m_ct_offset; + return ray_segment_dist3(ro, rd, m_ct_base, m_ct_base + m_ct_n * L_sign) <= 12.0 * upp; +} + +void DesignSketchTool::start_cut_drag(GLCanvas3D& canvas, const wxMouseEvent& evt) +{ + m_ct_drag = true; + m_ct_grab_val = m_ct_offset; + const double p = hole_axis_proj(canvas, evt, m_ct_base, m_ct_n); + m_ct_grab_proj = std::isnan(p) ? 0.0 : p; +} + +void DesignSketchTool::drag_cut_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt) +{ + const double proj = hole_axis_proj(canvas, evt, m_ct_base, m_ct_n); + if (std::isnan(proj)) return; + m_ct_offset = m_ct_grab_val + (proj - m_ct_grab_proj); + if (m_on_cut_offset_changed) m_on_cut_offset_changed(m_ct_offset); +} + void DesignSketchTool::set_revolve_gizmo(const SketchPlane& plane, const Vec2d& centroid, int axis_sel, double angle, bool flip) { @@ -6282,6 +6514,12 @@ void DesignSketchTool::confirm_transform() if (on_selection_changed) on_selection_changed(0); } +const ColorRGBA* DesignSketchTool::sketch_hl_color(int feature) const +{ + for (const auto& h : m_hl_sketches) if (h.first == feature) return &h.second; + return nullptr; +} + void DesignSketchTool::render(GLCanvas3D& canvas) { m_dim_label_seq = 0; @@ -6338,7 +6576,10 @@ void DesignSketchTool::render(GLCanvas3D& canvas) const std::vector loops = region_loops(ds.entities); for (int r = 0; r < int(loops.size()); ++r) { const bool sel = (ds.feature == m_display_pick && r == m_display_pick_region); - draw_fill(m_fill_model, loops[r].poly, sel ? sface : dface); + const ColorRGBA* hlc = sketch_hl_color(ds.feature); + ColorRGBA fc = sel ? sface : dface; + if (hlc && !sel) { fc = *hlc; fc.a(0.20f); } + draw_fill(m_fill_model, loops[r].poly, fc); } } glsafe(::glDisable(GL_BLEND)); @@ -6357,7 +6598,9 @@ void DesignSketchTool::render(GLCanvas3D& canvas) if (e.type == SketchEntity::Type::Point) continue; bool closed = false; std::vector poly = entity_polyline(e, closed); - draw_quad_strip(m_line_model, poly, closed, sel_ent[i] ? swire : dwire); + const ColorRGBA* hlc = sketch_hl_color(ds.feature); + ColorRGBA wc = sel_ent[i] ? swire : (hlc ? *hlc : dwire); + draw_quad_strip(m_line_model, poly, closed, wc); } } m_plane = saved_plane; @@ -6377,6 +6620,8 @@ void DesignSketchTool::render(GLCanvas3D& canvas) if (m_th_active) render_thread_gizmo(); if (m_sh_active) render_shell_gizmo(); if (m_rv_active) render_revolve_gizmo(); + if (m_dr_active) render_draft_gizmo(); + if (m_ct_active) render_cut_gizmo(); if (m_pt_active) render_pattern_gizmo(); shader->stop_using(); glsafe(::glEnable(GL_CULL_FACE)); @@ -7186,6 +7431,37 @@ bool DesignSketchTool::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) return true; } } + // Draft angle-arc gizmo: drag the arc tip to sweep the draft angle; a stationary click + // edits it. A LeftDown that misses falls through to normal picking. + if (m_dr_active) { + if (m_dr_drag && evt.Dragging() && evt.LeftIsDown()) { + drag_draft_arc(canvas, evt); + return true; + } + if (evt.LeftUp() && m_dr_drag) { + m_dr_drag = false; + return true; + } + if (evt.LeftDown() && hit_test_draft_handle(canvas, evt)) { + m_dr_drag = true; m_dr_press_x = evt.GetX(); m_dr_press_y = evt.GetY(); + return true; + } + } + // Cut gizmo: drag the offset arrow along the cut-plane normal; no positive clamp (offset is signed). + if (m_ct_active) { + if (m_ct_drag && evt.Dragging() && evt.LeftIsDown()) { + drag_cut_arrow(canvas, evt); + return true; + } + if (evt.LeftUp() && m_ct_drag) { + m_ct_drag = false; + return true; + } + if (evt.LeftDown() && hit_test_cut_arrow(canvas, evt)) { + start_cut_drag(canvas, evt); + return true; + } + } // Pattern gizmo: drag the diamond/arc handle to set the spacing (linear) or angle (circular); // a stationary click opens the inline editor. A LeftDown that misses falls through to picking. if (m_pt_active) { diff --git a/src/slic3r/GUI/DesignSketchTool.hpp b/src/slic3r/GUI/DesignSketchTool.hpp index 58dd20ca57..c08f1359e1 100644 --- a/src/slic3r/GUI/DesignSketchTool.hpp +++ b/src/slic3r/GUI/DesignSketchTool.hpp @@ -93,12 +93,13 @@ public: // own plane. render() draws these as translucent faces + outlines. struct DisplaySketch { std::vector entities; SketchPlane plane; int feature{-1}; }; void set_display_sketches(std::vector ds) { m_display_sketches = std::move(ds); } + void set_highlight_sketches(std::vector> hl) { m_hl_sketches = std::move(hl); } bool has_display() const { return m_active || !m_display_sketches.empty() || (m_solid_bodies != nullptr && !m_solid_bodies->empty()) || !m_datum_planes.empty() || m_ex_active || m_mv_active || m_fl_active || m_hl_active || m_th_active || m_sh_active - || m_dz_active || m_dbp_active; } + || m_dr_active || m_ct_active || m_dz_active || m_dbp_active; } // Solid topology selection on the committed bodies: clicking a solid cycles // whole-solid -> face -> edge (Onshape-style) to target fillet/chamfer/extrude. With @@ -238,6 +239,18 @@ public: bool revolving() const { return m_rv_active; } std::function on_revolve_angle_changed; + // Visual Draft angle-arc gizmo (taper a picked face; axis = world +Z, arc in XY). + void set_draft_gizmo(const Vec3d& face_centroid, const Vec3d& face_normal, double angle); + void clear_draft_gizmo(); + bool drafting() const { return m_dr_active; } + void set_on_draft_angle_changed(std::function cb) { m_on_draft_angle_changed = std::move(cb); } + + // Visual Cut gizmo (plane normal arrow + plane rectangle preview). + void set_cut_gizmo(const SketchPlane& plane, double offset, const Vec3d& body_center, double half_extent); + void clear_cut_gizmo(); + bool cutting() const { return m_ct_active; } + void set_on_cut_offset_changed(std::function cb) { m_on_cut_offset_changed = std::move(cb); } + // Visual Pattern gizmo. Linear: a 3D arrow along the world axis (plane X/Y per `dir`) of length // spacing*(count-1) with a tick at each copy; dragging the end sets the spacing. Circular: a // revolve-style angle-arc about the plane normal through the plane origin sweeping `angle`. @@ -660,6 +673,7 @@ private: void draw_vertices(GLModel& model, const std::vector& pts, const ColorRGBA& color, double half_size = 1.3); void draw_fill(GLModel& model, const std::vector& poly, const ColorRGBA& color); + const ColorRGBA* sketch_hl_color(int feature) const; bool m_active{false}; SketchPlane m_plane; @@ -822,6 +836,7 @@ private: float m_render_scale{1.0f}; // canvas scale for Measure-style dim labels GLModel m_fill_model; // translucent face fill for closed regions std::vector m_display_sketches; // committed sketches drawn persistently + std::vector> m_hl_sketches; // feature index -> outline colour (Sweep/Loft operands) int m_display_pick{-1}; // FEATURE index of the click-selected display sketch (-1 none) // Solid (whole/face/edge) selection on the committed bodies. Pointers are non-owning, @@ -1011,6 +1026,40 @@ private: void open_revolve_editor(); GLModel m_rv_stroke_model; + // Draft gizmo state (arc center = picked face centroid; axis = world +Z, taper pull direction). + bool m_dr_active{false}; + Vec3d m_dr_center{Vec3d::Zero()}; // arc center = face centroid (world) + Vec3d m_dr_axis{Vec3d::UnitZ()}; // draft axis = world +Z (pull direction) + Vec3d m_dr_ref{Vec3d::UnitX()}; // angle-0 reference dir (perp to axis) + double m_dr_radius{10.0}; // arc radius (world) + double m_dr_angle{5.0}; // current sweep magnitude (deg, [-89, 89]) + bool m_dr_drag{false}; + int m_dr_press_x{0}, m_dr_press_y{0}; + void render_draft_gizmo(); + bool hit_test_draft_handle(GLCanvas3D& canvas, const wxMouseEvent& evt) const; + void drag_draft_arc(GLCanvas3D& canvas, const wxMouseEvent& evt); + GLModel m_dr_stroke_model; + std::function m_on_draft_angle_changed; + + // Cut gizmo state (plane normal arrow + wire rectangle at the current offset). + bool m_ct_active{false}; + Vec3d m_ct_base{Vec3d::Zero()}; // body centre projected into the cut plane + Vec3d m_ct_n{Vec3d::UnitZ()}; // cut plane normal (unit) + Vec3d m_ct_u{Vec3d::UnitX()}; // cut plane U axis (unit) + Vec3d m_ct_v{Vec3d::UnitY()}; // cut plane V axis (unit) + double m_ct_offset{0.0}; + double m_ct_half{10.0}; + bool m_ct_drag{false}; + double m_ct_grab_val{0.0}; + double m_ct_grab_proj{0.0}; + void render_cut_gizmo(); + bool hit_test_cut_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt) const; + void start_cut_drag(GLCanvas3D& canvas, const wxMouseEvent& evt); + void drag_cut_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt); + GLModel m_ct_stroke_model; + GLModel m_ct_rect_model; + std::function m_on_cut_offset_changed; + // Pattern gizmo state. Linear arrow along m_pt_dirw from m_pt_base; circular arc like Revolve // but axis = m_pt_normal through m_pt_origin (the world XY plane by default). bool m_pt_active{false};