#ifndef slic3r_DesignCanvas_hpp_ #define slic3r_DesignCanvas_hpp_ #include #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" #include "libslic3r/BuildVolume.hpp" #include "libslic3r/Color.hpp" #include #include #include #include #include #include #include #include #include "slic3r/GUI/3DBed.hpp" #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/GLToolbar.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/CAD/SketchEngine.hpp" #include "slic3r/GUI/CAD/DesignSketchTool.hpp" class wxActivateEvent; class wxEvent; class wxIconizeEvent; class wxPopupWindow; class wxWindow; namespace Slic3r { struct CadBody; } class wxGLCanvas; class wxStaticText; namespace Slic3r { class TriangleMesh; namespace GUI { class GLCanvas3D; class SketchInlineEditor; class DesignCanvas : public wxPanel { public: explicit DesignCanvas(wxWindow* parent); ~DesignCanvas() override; // Multi-body display: one GLVolume per body, each coloured distinctly (per-body colour). // `visible` (optional, indexed by body) hides bodies whose flag is false. `body_meshes` is kept // by address (a stable panel member) and read again whenever the selection changes. The // bodies' edge lines are refreshed with them (DesignSketchTool::refresh_body_edges). void set_bodies(const std::vector* body_meshes, const std::vector& visible = {}); void clear_mesh(); // no bodies: drops their volumes and the tool's edge lines, pick and selection void set_preview_mesh(const TriangleMesh& mesh); void clear_preview(); void fit_view(); void set_view(const std::string& view_name); // Frame `box` along the current view direction, as the canvas's Fit button frames a selection. // False, the camera left alone, when there is nothing to frame: an undefined box. bool zoom_to_box(BoundingBoxf3 box); // Boxes to hand zoom_to_box: the selection and body faces (see DesignSketchTool). BoundingBoxf3 selection_box() const { return m_sketch_tool.selection_box(); } BoundingBoxf3 faces_box(std::vector> faces) const { return m_sketch_tool.faces_box(std::move(faces)); } void begin_sketch(const SketchPlane& plane, DesignSketchTool::Mode mode); // Re-open a committed entity sketch for full in-canvas editing (load geometry + // constraints, re-detect feature groups). Re-commits via finish_sketch(). void edit_sketch(const std::vector& entities, const std::vector& constraints, const SketchPlane& plane); void set_sketch_tool(DesignSketchTool::Mode mode); void set_sketch_construction(bool c); // Flip the sketch selection between construction and real geometry; returns the // number of entities changed (0 = nothing selected, caller falls back to the mode). // Open the in-canvas value field on the sketch selection's defining number. bool edit_sketch_selection_value(); int toggle_sketch_construction_selection(); // Is the sketch tool on Select (as opposed to a draw/edit tool being armed)? The // Construction box needs it to tell "convert what I picked" from "arm what I draw next". bool sketch_is_selecting() const { return m_sketch_tool.mode() == DesignSketchTool::Mode::Select; } // SVG art into the LIVE sketch, as ordinary editable lines. False = no session. bool add_sketch_regions(const std::vector>>& regions); void set_sketch_polygon_sides(int n); void set_sketch_polygon_circumscribed(bool c); 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 // same operation: trade the live camera for the parked one. That also keeps this canvas's // own view across a tab switch. void enter_viewport(); void leave_viewport(); void unbind_canvas_event_handlers(); // app close / language switch, from the plater's teardown void reset_canvas_volumes(); void set_show_bed(bool b); // view option: draw the printer bed + plate grid, or not // The sidebar's collapse button, as Prepare's: drawn on the edge `side` reports, it runs // `toggle` on a click and on Shift+Tab. Set before the first paint. void set_sidebar_collapse(std::function side, std::function toggle); void set_sidebar_collapse_tooltip(const std::string& tooltip); // N: look straight down the sketch plane's normal, keeping the current zoom. A sketch drawn // at an angle is a sketch drawn wrong, and no amount of orbiting by hand lands exactly square. bool view_normal_to_sketch(); void cancel_sketch(); void set_on_sketch_commit(std::function cb); void set_on_sketch_entities_commit( std::function&, const std::vector&, const SketchPlane&)> cb); // Line tool: pending-segment length entry + live readout (Phase 2). void set_on_segment_drawn(std::function cb); void set_on_cursor_metrics(std::function cb); void set_on_solve_state(std::function cb); // dof, ok, has_constraints // Live per-step guidance from the armed sketch tool (mode, step, picks). 1c0c. void set_on_sketch_step(std::function cb); void apply_segment_length(double len); // exact length, then commit & repaint void keep_segment_as_drawn(); // commit as-drawn & repaint // Sketch selection (Mode::Select). void set_on_sketch_selection_changed(std::function cb); void set_on_sketch_face_selected(std::function cb); // closed loop clicked: region index passed void set_on_display_sketch_selected(std::function cb); // committed loop clicked: (feature, region, entity) void set_on_display_sketch_activated(std::function cb); // committed sketch DOUBLE-clicked: edit it std::vector selected_loop_entities() const; // entities of the click-selected loop std::vector> region_entity_indices(const std::vector& ents) const; // Like region_entity_indices, but each region's entry is its OWN entities followed by the // entities of each of its holes — the same order selected_loop_entities() hands the kernel. // A per-loop extrude of a region WITH holes stores exactly this, so this is the shape a // consumed loop must be compared against. std::vector> region_entity_indices_with_holes(const std::vector& ents) const; void clear_loop_pick(); // drop the click-selected loop highlight (e.g. after extrude) void clear_solid_pick(); // drop the solid pick and its highlight; no callback, no repaint void set_loop_pick(int feature, int region); // adopt a loop pick made before the commit // The picked committed sketch (feature index, -1 = none) and its closed region (-1 also when // the click hit a stroke on no closed loop). The panel reads the pick here and keeps no copy. int loop_pick_feature() const; int loop_pick_region() const; void set_escalate_on_repick(bool on); // off while a card has armed a face/edge pick // Solid whole/face/edge selection: point the tool at the bodies + concatenated // tessellation (with per-triangle face & body ids), and a callback fired on each // whole->face->edge cycle (level, body index, face id, edge id). void set_solid_pick(const std::vector* bodies, const TriangleMesh* mesh, const std::vector* tri_face, const std::vector* tri_body, const std::vector* visible = nullptr, const std::vector* xform = nullptr); void set_on_solid_selection_changed(std::function cb); void set_on_empty_pick(std::function cb); // a click or rubber band took nothing std::vector selected_solid_edges() const; // the Shift/Ctrl+click edge set, last-clicked at the end void select_body(int body); // Parts-list -> highlight a whole body by index // Effective display colour of a body: the per-body override (Color tool) when set, // otherwise the auto body-index palette. Single source of truth shared with reload(). ColorRGBA body_color(int body) const; // Move-body gizmo (M5): three world-axis drag arrows on a body; drag fires the move // callback with the body index + accumulated translation (display-only, host applies it). // body_radius = bounding-sphere radius of the body in world mm; the gizmo scales with it so // the rotation rings sit OUTSIDE the solid (Orca's Prepare gizmos do the same). void begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform, double body_radius); void clear_move_gizmo(); void set_on_body_move_changed(std::function cb); // Visual Fillet/Chamfer radius gizmo: when a solid edge is picked, anchor a radius arrow on // it; drag/edit fire the radius callback. Returns false if no edge is currently picked. bool begin_fillet_gizmo(const Vec3d& body_centroid, double radius); void clear_fillet_gizmo(); bool filleting() const; void set_on_fillet_radius_changed(std::function cb); // Visual Hole gizmo: the panel feeds the hole plane + position + diameter/depth/through while // its Hole card is open; drag/edit fire the hole callback (x, y, diameter, depth). void begin_hole_gizmo(const SketchPlane& plane, double x, double y, double diameter, double depth, bool through); void set_hole_face_bounds(bool has, double umin, double umax, double vmin, double vmax); void clear_hole_gizmo(); bool holing() const; void set_on_hole_changed(std::function cb); // Visual Thread gizmo: footprint circle + radius/length arrows + draggable centre. void begin_thread_gizmo(const SketchPlane& plane, double x, double y, double radius, double height); void clear_thread_gizmo(); bool threading() const; void set_on_thread_changed(std::function cb); // Visual Shell gizmo: inward thickness arrow at the picked open-face centroid. void begin_shell_gizmo(const Vec3d& face_centroid, const Vec3d& inward_dir, double thickness); void clear_shell_gizmo(); bool shelling() const; void set_on_shell_thickness_changed(std::function cb); // Visual Revolve angle-arc gizmo: the panel feeds the sketch plane + profile centroid + the // world axis (a point on it, unit direction) + angle + flip while its Revolve card is open; // drag/edit fire the angle callback. void begin_revolve_gizmo(const SketchPlane& plane, const Vec2d& centroid, const Vec3d& axis_origin, const Vec3d& axis_dir, double angle, bool flip); 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, int count, int dir, double spacing, double angle); void clear_pattern_gizmo(); bool patterning() const; void set_on_pattern_changed(std::function cb); // Visual Extrude depth-arrow gizmo (C5b): the panel feeds the profile plane + centroid + // live depths/flags while its Extrude card is open; drag/edit fire the depth callback. void set_extrude_gizmo(const SketchPlane& plane, const Vec2d& centroid, double depth, double depth2, bool two_sided, bool flip); void clear_extrude_gizmo(); void set_on_extrude_depth_changed(std::function cb); void set_datum_gizmo(const SketchPlane& plane, double usize, double vsize, const Vec3d& base_origin, const Vec3d& base_normal, double offset, bool offset_on); // C3 resize handles + offset arrow void clear_datum_gizmo(); void set_on_datum_size_changed(std::function cb); void set_on_datum_offset_changed(std::function cb); void set_helix_gizmo(const SketchPlane& plane, double radius, double pitch, double height, double taper, bool left_handed); // helix curve + 3 drag handles void clear_helix_gizmo(); void set_on_helix_changed(std::function cb); void set_rib_gizmo(const SketchPlane& plane, const Vec2d& p0, const Vec2d& p1, double thickness); // rib slab footprint + 2 thickness handles void clear_rib_gizmo(); void set_on_rib_thickness_changed(std::function cb); void set_base_pick(std::vector planes, std::vector bases, std::vector labels = {}); // clickable labelled reference planes void clear_base_pick(); void set_on_datum_base_picked(std::function cb); void set_selected_base(std::function cb); // the reference plane drawn selected, or -1 void set_on_sketch_exit(std::function cb); // Esc -> exit the tool void set_on_sketch_exit_refused(std::function cb); // Esc declined: sketch has work void set_on_sketch_notice(std::function cb); // tool refusals/side effects // 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) // Mate connectors, drawn as frames so their verse and polarity are visible (wgsc). void set_mate_connectors(std::vector g); void set_mate_links(std::vector> l); // Draw these (body, face id) faces as selected: the faces the Feature tree's selected feature // made (CadDocument::faces_made_by). Empty clears them. void set_highlight_faces(const std::vector>& faces); // The status line, shown along the BASE OF THE VIEWPORT rather than in the side panel: // the panel clips it at ~73 characters with no warning (8cc), the viewport's // bottom margin has the whole window width to spare. Empty text hides it. void set_status_text(const wxString& text, const wxColour& colour); 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_xray_focus(int body); // >=0: fade+lock out every other body (CoordSys picking) void set_body_hidden(bool on); // preview-only: hide base bodies, show only the result ghost // Right-click (or its platform equivalent) on the viewport with no tool running: open the // object-driven offer there. Fires with SCREEN coordinates. Deliberately NOT fired while a // tool is live — right-click already ends a polyline chain and finishes the move gizmo, and // taking those over would break two working interactions in order to add a third. void set_on_context_menu(std::function cb); bool delete_selected_sketch_entities(); // false when nothing was selected bool inline_busy() const; // a sketch value field is open (guard keys) bool inline_has_focus() const; // the field itself holds keyboard focus void inline_commit(); // accept the typed value (Enter/Tab) void inline_cancel(); // discard the typed value (Esc) // The layered Esc: abandon the points of the gesture in progress, else drop the armed tool // back to Select, else leave the sketch. Same call GLCanvas3D::on_char makes, exposed so the // panel can do it when focus is not on the canvas. void request_sketch_exit(); bool live_sketch_has_work() const; // the live sketch holds entities a cancel would destroy bool undo_last_sketch_entity(); // Ctrl+Z in a sketch: drop the last drawn shape bool redo_last_sketch_entity(); // Ctrl+Y in a sketch: bring it back bool can_undo_sketch_entity() const { return m_sketch_tool.can_undo_entity(); } bool can_redo_sketch_entity() const { return m_sketch_tool.can_redo_entity(); } void clear_sketch_selection(); // View toggles (keys P / A): origin planes, world axis triad. Each returns the new on/off // state so the caller can echo it in the status bar. bool toggle_planes(); bool toggle_axes(); // Section views (non-destructive): the panel owns the named "Section View N" list; the canvas // just applies/clears one horizontal clip at a time. model_mid_z() is the default cut height. void set_section_plane(bool on, double z, bool keep_upper = false); double model_mid_z() const; // Dimension tool: act on the current sketch selection. DesignSketchTool::DimType sketch_dimension_kind() const; double sketch_dimension_current() const; void apply_sketch_dimension(double v); // Open the in-canvas value editor at the cursor for a host-driven value (the // committed-feature Constrain path uses this instead of a docked numeric card). void open_inline_value(double current, std::function commit, std::function cancel = {}); // Dimension tool (Mode::Dimension): click-to-place quotes. The pick-complete // callback lets the panel pop the value card; set/cancel apply or keep the value. void set_on_dimension_pick_complete(std::function cb); DesignSketchTool::DimType pending_dimension_type() const; void set_sketch_dimension_value(double v); void cancel_sketch_dimension(); // Constrain mode: load a committed profile for picking + constraint editing. void begin_constrain(const SketchProfile& prof, const SketchPlane& plane); // Leave constrain mode and clear any picked-entity highlight from the overlay. void end_constrain(); bool is_constraining() const; bool selected_segment(int& a, int& b) const; void update_constrain_profile(const std::vector& pts); // Entity-aware Constrain (Fase 4.2): pick Line entities of a committed sketch. void begin_constrain_entities(const std::vector& ents, const SketchPlane& plane); bool is_constraining_entities() const; // Sketch selection, for the offer menu: how many entities are selected and what the first // one is. Returns 0 when nothing is selected. int sketch_selection_count() const; // Esc routing (DesignInteraction.hpp). The panel decides WHICH level one press belongs to; // these are the levels it can act on inside the canvas. Each returns whether it did anything, // so the panel can fall through to the next level without asking twice. bool sketch_abort_gesture(); // CadLevel::Gesture — drop the entity being drawn, or the tool's picks bool sketch_disarm_tool(); // CadLevel::Tool — armed sketch tool falls back to Select bool sketch_confirm_pending(); // Enter — apply a ready edit-op or transform bool drawing_in_progress() const;// clicks or picks are down but nothing is committed yet bool has_any_selection() const; // model pick, committed-loop pick or sketch pick bool clear_any_selection(); // CadLevel::Idle — drop all three; true if anything was dropped bool sketch_first_selected_type(SketchEntity::Type& out) const; // Live sketch session (Fase 4.2 live constraint path): the panel reads the in-session // selection and entities, and commits a planned constraint through the tool's // append->solve->keep-or-rollback, rather than reaching into mcp_sketch_tool(). const std::vector& sketch_selection() const; const std::vector& sketch_entities() const; // How many constraints the LIVE session holds. Only a count: the hint line needs to know // whether any badge is on screen to talk about, nothing more. int sketch_constraint_count() const; const std::vector& sketch_constraints() const; bool remove_sketch_constraint(int idx); void set_on_sketch_constraints_changed(std::function cb); bool try_add_sketch_constraints(const std::vector& defs); // In-canvas bbox transform of imported Text/SVG art (replaces the Move/Scale dialog). void begin_imported_transform(int feat, const std::vector>>& base_regions, const SketchPlane& plane, const Vec2d& offset, double scale_x, double scale_y); void set_on_imported_transform(std::function cb); bool selected_constrain_entities(int& e0, int& e1) const; int selected_constrain_axis() const; // third pick slot (Symmetric axis), -1 if unset bool pick0_point(Vec2d& out) const; // plane-coords of the slot-0 pick (trim/extend) void update_constrain_entities(const std::vector& ents); // Constraint manager (C3.4): highlight the entities referenced by a selected // constraint (yellow tint in Constrain mode); empty clears the highlight. void set_constraint_highlight(std::vector entities); // Constraint glyph badges (C3.4b): the feature's constraints, drawn as iconic // marks near their entities in Constrain mode; empty clears them. void set_constraint_glyphs(std::vector cons); // Repaint the embedded canvas the right way for the active GL backend: // hardware GL gets a scheduled wxEVT_PAINT (render() runs inside the paint // cycle); software GL (llvmpipe etc.) gets a direct render() because a // scheduled Refresh() is frequently dropped there. Backend cached on first use. // Public: DesignPanel calls it after a tree edit to force a frame on software GL. // Scripted (MCP) access to the live sketch. One accessor rather than a passthrough per // verb: the MCP layer drives the SAME tool the mouse drives, which is the whole point of // having it — a socket that talked to a private copy would prove nothing about the app. DesignSketchTool& mcp_sketch_tool() { return m_sketch_tool; } const DesignSketchTool& mcp_sketch_tool() const { return m_sketch_tool; } void request_repaint(); // Repaint synchronously, once the pending show/resize has settled. Needed when the // notebook re-shows the Design page: an invalidation issued while the page is still // being shown is dropped on hardware GL and no wxEVT_PAINT ever follows, leaving the // canvas blank until another tab switch forces an expose. void force_repaint(); // Repaint synchronously, for use while a modal popup (the offer menu) owns the event loop: // a queued Refresh() is not serviced until the popup closes, so a hover ghost drawn behind it // would never appear. Mirrors DesignPanel's m_status->Update() flush. void repaint_now(); private: void reload(); void swap_camera(); // enter_viewport / leave_viewport, in the one direction they share // Selected faces are filled by the canvas: each body's selected faces become a volume of their // own, drawn opaque in the selection colour with the body's shader and lighting, so a selection // is the same colour on every body. The faces are the sketch tool's selected_faces() (the // Feature tree row's and the committed face or body pick), which the tool outlines. void rebuild_bodies(); // object 0 from m_body_meshes, split by m_lit_faces void sync_selected_faces(); // re-split and reload, once queued, if the selection changed struct BodyVolume { int body; bool lit; }; // an object 0 volume: its body, and whether it holds selected faces const std::vector* m_body_meshes{nullptr}; // set_bodies const std::vector* m_tri_face{nullptr}; // per-triangle face id, all bodies in order std::vector> m_lit_faces; // what object 0 is split by now std::vector m_volumes; // object 0's volumes, in order bool m_split_pending{false}; std::function m_on_solid_selection_changed; wxGLCanvas* m_canvas_widget{nullptr}; GLCanvas3D* m_canvas{nullptr}; int m_sw_gl{-1}; // -1 unknown, 0 hardware GL, 1 software GL GLToolbar m_collapse_toolbar{GLToolbar::Normal, "Collapse"}; std::function m_on_context_menu; bool m_ctx_bound{false}; // bind the RIGHT_UP handler once, however often the cb is set wxPoint m_ctx_press{0, 0}; // right-press origin: a right-DRAG orbits, it must not offer bool m_ctx_travelled{false}; // the right press wandered past the budget at ANY point, // so an orbit that ends where it began is still an orbit Bed3D m_bed; // The half of the camera swap above that is NOT on screen: the editor tabs' view while // Design is up, this canvas's view while it is not. Seeded in the constructor so the first // entry inherits the view the user was already looking at. Camera m_parked_camera; bool m_camera_swapped{false}; // guards a leave without an enter, and the reverse Model m_model; 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 int m_xray_focus{-1}; // >=0: only this body is opaque+clickable (CoordSys picking) 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) // Live pointer to the document's bodies (stable address: m_doc.bodies), stashed by // set_solid_pick so reload()/body_color() can read each body's colour override. const std::vector* m_color_bodies{nullptr}; DesignSketchTool m_sketch_tool; // Section view: whether a horizontal clip is currently applied (guards Alt+Wheel). The cut // height and the named-view list live in DesignPanel; the canvas is a dumb applier. bool m_section_on{false}; std::unique_ptr m_inline_editor; // floating in-canvas value editor // The viewport chips, drawn in the tool's ImGui pass (render_hud): bottom-right the active // tool's current values (fed by the tool's on_readout), bottom-left the status line written by // DesignPanel. Empty text draws nothing. They were top-level popups once; a popup does not // follow its frame, so it floated over other applications and outlived the tab. std::string m_hud_last; wxString m_status_hud_last; wxColour m_status_hud_colour; // wxNullColour: the overlay's own text colour void set_readout(const std::string& text); void render_hud(); std::function m_on_sketch_commit; std::function&, const std::vector&, const SketchPlane&)> m_on_sketch_entities_commit; }; }} // namespace Slic3r::GUI #endif // slic3r_DesignCanvas_hpp_