Files
OrcaSlicer/src/slic3r/GUI/DesignPanel.hpp
T
Tommaso BianchiandClaude Opus 5 d5c5d5675e Design: a body tool acts on the body you picked, not on the first one
Mirror of snaporca e5e223a794 (DesignPanel.cpp applied as a patch; parity 30, hpp
byte-identical).

Every body combo opened on index 0, so picking a body and pressing Mirror acted on a
different solid while the card showed that other body as the target. Nine sites now read
the viewport selection; Boolean takes the picked body as target and a different one as
tool, since defaulting both to the same body is a no-op.

Verified functionally on the rig: picked the 20x20 body, mirrored, and the new body
measures 20x20 — not the 80x50 one it would have used before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
2026-08-01 12:15:46 +02:00

792 lines
47 KiB
C++

#ifndef slic3r_DesignPanel_hpp_
#define slic3r_DesignPanel_hpp_
#include <wx/panel.h>
#include <wx/scrolwin.h>
#include <wx/treebase.h> // wxTreeItemId
#include <vector>
#include <memory>
#include <functional>
#include <map>
#include "libslic3r/CadDocument.hpp"
class ComboBox; // Orca dropdown (Widgets/ComboBox.hpp) — replaces wxChoice everywhere here
class StaticBox; // Orca rounded card frame (Widgets/StaticBox.hpp)
class wxCheckBox;
class wxCheckListBox;
class wxSpinCtrl;
class wxSpinCtrlDouble;
class wxTreeCtrl;
class wxImageList;
class wxStaticText;
class wxStaticLine;
class Button; // Orca-styled button (Widgets/Button.hpp)
class CheckBox; // Orca teal checkbox (Widgets/CheckBox.hpp)
class wxSizer;
// wxBoxSizer, wxTextCtrl and wxListCtrl are used here as pointers only, so a forward
// declaration is enough — but they must be declared. Every ordinary build happened to pull
// them in transitively through the wx/panel.h + wx/scrolwin.h chain. The Snapmaker fork's
// Flatpak build does not, and it failed to compile this header with "'wxTextCtrl' does not
// name a type; did you mean 'wxTreeCtrl'?". Declaring them keeps the header self-contained
// instead of relying on whatever a particular wx configuration happens to include.
class wxBoxSizer;
class wxTextCtrl;
class wxListCtrl;
class wxButton;
class wxPanel;
class ScalableButton;
namespace Slic3r { namespace GUI {
class DesignCanvas;
// Design (CAD) tab: a sketch-first, Onshape-style form-driven CAD panel.
// Sketch and Extrude are independent tools: the user creates a Sketch first,
// then selects it and Extrudes to produce a solid.
class DesignPanel : public wxPanel
{
public:
explicit DesignPanel(wxWindow* parent);
void on_tab_shown(); // re-sync bed to the active printer when the Design tab is activated
// Rebuild off the UI thread (progress dialog only if it turns out to be slow), so a feature
// op on a heavy imported solid does not freeze the window. Returns m_doc.recompute()'s result.
bool recompute_guarded(const wxString& message);
// MCP control hooks: let the external control server (McpControl.cpp) drive and
// perceive the SAME kernel the GUI uses. Called only on the wx main thread.
CadDocument& mcp_doc() { return m_doc; } // live document (read + mutate)
void mcp_after_change() { after_tree_edit(true); } // refresh tree + viewport + status
private:
enum class Tool { None, Sketch, Extrude, Dressup, Hole, Thread, Shell, Revolve, Sweep, Pattern, Plane, Loft, Draft, Boolean, Cut, Insert, Axis, CoordSys, SurfaceExtrude, SurfaceRevolve, SurfaceLoft, SurfaceFill, SurfaceOffset, ThickenSurface, Transform, Mirror, Thicken, Rib, Project, DeleteFace, Helix, Mate };
// Which numeric fields an expression can be bound to, per feature type. A member rather
// than a file-static helper so Tool — 32 values of purely internal card state — does not
// have to become part of this panel's public API just to be named in a signature.
static std::vector<std::string> fields_for_tool(Tool t);
// Plane tool: which datum reference the next solid pick fills (declared early so the
// method decls + card lambdas below can name it).
enum class PlanePick { None, FaceA, FaceB, EdgeA, EdgeB };
enum class AxisPick { None, Face, Edge };
enum class CoordSysPick { None, Face, Edge };
// Onshape-style contextual top toolbar: only the active mode's tool group is
// shown (Feature = sketch/extrude/dress/hole/thread; Sketch = entity tools;
// Constrain = constraints + edit ops). Replaces the old always-visible wall.
enum class UiMode { Feature, Sketch, Constrain };
void set_ui_mode(UiMode m);
// Unified action-bar dispatch: one Confirm / one Cancel for every tool and mode.
void tool_confirm(); // ✓ : commit the active feature / sketch / constrain session
void tool_cancel(); // ✗ / Esc : cancel the active feature / discard / exit
void update_action_bar(); // show the ✓/✗ bar iff a tool or mode is active
void on_shape_changed();
void on_add_sketch();
void on_add_extrude();
void on_add_dressup();
void on_add_hole();
void on_add_thread();
void apply_thread_standard(); // fill pitch/depth/radius from m_thread_std selection
void infer_thread_spec(double diameter); // nearest M-standard from a picked cylinder diameter
void on_add_revolve();
void on_add_sweep();
void on_add_loft();
void on_add_pattern();
void on_add_plane();
void arm_plane_pick(PlanePick target); // Plane tool: next solid pick fills this reference
void apply_plane_refs(CadFeature& f) const; // copy type + face/edge refs + sizes from the card
void refresh_plane_labels(); // update the 4 pick labels from the captured refs
void reset_plane_refs(); // clear captured refs (fresh Plane add)
void on_add_shell();
void on_add_draft();
void on_add_boolean();
void on_add_cut(); // commit a plane Cut (split-by-plane)
void on_add_axis();
void arm_axis_pick(AxisPick target);
void apply_axis_refs(CadFeature& f) const;
void refresh_axis_labels();
void reset_axis_refs();
void on_add_coordsys();
void arm_coordsys_pick(CoordSysPick target);
void apply_coordsys_refs(CadFeature& f) const;
void refresh_coordsys_labels();
void reset_coordsys_refs();
void on_add_surface_extrude();
void on_add_surface_revolve();
void on_add_surface_loft();
void on_add_surface_fill();
void on_add_surface_offset();
void on_add_thicken_surface();
void on_add_transform();
void on_add_mirror();
void on_add_thicken();
void on_add_rib();
void on_add_project();
void on_add_delete_face();
void on_add_helix();
void on_add_mate();
void on_check_interference();
void on_mass_properties(); // read-only report on the selected solid; edits nothing
// Fill m_bool_target / m_bool_tool / m_cut_target. as_of_feature < 0 = current bodies (add);
// >= 0 = the bodies as they existed just before that feature index (Boolean re-edit, so a
// consumed tool body still appears and its saved selection round-trips).
// Which body a tool should act on when it opens: the one picked in the VIEWPORT, else
// the first. Selection comes first and the tool consumes it — every body combo used to
// default to index 0, so picking body 3 and opening Mirror silently mirrored body 1.
// Clamped to the list, so it is safe to hand straight to SetSelection. snaporca-e1p.
int selected_body_default() const;
void populate_body_choices(int as_of_feature = -1);
// Fill `c` with the bodies as they existed just before `as_of_feature` and select
// `want`. Re-editing any feature that stores a body index needs this: the index was
// recorded against the body list at that point in the timeline, not the final one.
void fill_body_choice(ComboBox* c, int as_of_feature, int want);
void populate_sheet_body_choices(ComboBox* c) const; // bodies where is_sheet_shape() is true
// Rows of a sheet-filtered picker are not body indices; go through these two, never
// GetSelection()/SetSelection() directly.
static int sheet_choice_body(ComboBox* c); // real body index of the current row, or -1
static void select_sheet_choice(ComboBox* c, int body);// select the row holding this body index
// Import rigid 2D art (Text / SVG) as a new Sketch feature carrying
// imported_regions (no solver entities). on_add_text/on_import_svg gather
// input; add_imported_sketch builds the feature, refreshes tree + display.
void on_add_text();
void on_import_svg();
void on_import_step(); // STEP -> editable B-rep body (keeps the OCCT solid, not a mesh)
void on_import_mesh(); // STL/OBJ -> B-rep body via GeometryEngine::mesh_to_brep
bool place_on_face(); // Prepare's Place on Face (F): lay the selected body face on the bed
void add_imported_sketch(const std::vector<std::vector<std::vector<Vec2d>>>& regions,
const wxString& base_name);
// Imported Text/SVG art is placed/sized in-canvas then explicitly committed via a
// small Confirm/Cancel card (Onshape Button->Dialog->Preview->Confirm). The feature
// is added provisionally by add_imported_sketch; Confirm keeps it, Cancel undoes it.
void open_insert_card(const wxString& base_name);
void finalize_insert(); // Confirm: keep the placed art, leave the placement gizmo
void cancel_insert(); // Cancel: undo the provisional insert
// Move / enlarge / stretch (independent X/Y) an imported Text/SVG sketch:
// a modal dialog editing the feature's placement transform in place.
void on_transform_imported(int feat_idx);
void on_commit();
void on_export_step(); // write all bodies to a .step file (native B-rep)
// Rehydrate the parametric model from a project's saved recipe (3MF
// Metadata/SnapOrca_cad.bin): deserialize -> recompute -> refresh viewport + tree.
void load_recipe(const std::string& blob);
void refresh_tree();
void set_status_ok();
// Feature-tree editing (Onshape-style): act on the selected tree row.
void on_delete_feature();
void on_new_design();
void on_move_feature(int delta); // -1 = up, +1 = down
void on_toggle_visibility(); // show/hide the selected feature (CadFeature::enabled)
// Constrain mode: enter on the tree-selected sketch, then apply a geometric
// constraint to the in-canvas picked segment and re-solve in the kernel.
void on_begin_constrain(int sel_override = -1);
// Sketch-toolbar Constrain entry: commit the live sketch in place, then enter Constrain
// mode on it (so the constraint palette + Trim/Extend are reachable without leaving the
// sketch flow). Returns true if constrain mode was entered.
bool enter_constrain_inline();
void apply_constraint(SketchConstraintType type);
void apply_entity_constraint(SketchConstraintType type); // Fase 4.2 entity path
enum class EditOp { Mirror, Offset, Fillet, Trim, Extend, Array, Move, Chamfer, Rotate, Scale, PolarArray }; // Fase 4.4/4.5/4.6 sketch edit ops
void apply_edit_op(EditOp op); // mutate selected sketch entities
// Onshape-style docked value entry (replaces wxGetTextFromUser popups for
// Angle/Radius/Diameter constraints + Offset/Fillet edit ops). request_value
// shows the card and stows a continuation run by confirm_value().
void request_value(const wxString& label, double def, double mn, double mx,
std::function<void(double)> cont,
std::function<void()> on_cancel = nullptr);
void confirm_value();
void cancel_value();
void commit_entity_constraint(const SketchEntityConstraintDef& def); // shared solve/refresh tail
void commit_entity_constraints(const std::vector<SketchEntityConstraintDef>& defs); // multi-def (Symmetric)
// Constraint manager (C3.4): a docked list of the constrained sketch's
// entity-constraints with per-row select (highlight the referenced entities in
// the viewport) and delete (drop the constraint + re-solve). Shown in Constrain
// mode only; operates on m_doc.features[m_constrain_feat].entity_constraints.
void rebuild_constraint_list(); // refill m_constraint_rows
void delete_constraint(int idx); // erase + re-solve + refresh
void highlight_constraint_entities(int idx); // push referenced entities to viewport
void refresh_constrain_dof(); // re-solve feature, mirror DoF readout
wxString constraint_label(const SketchEntityConstraintDef& d) const; // human-readable row text
void after_edit_op(); // shared edit-op refresh tail
void on_edit_feature(); // reopen the selected feature's dialog populated
void after_tree_edit(bool ok); // shared post-op refresh of tree/viewport/status
void load_feature_into_dialog(const CadFeature& f);
void reset_edit_state(); // back to add-mode (m_edit_index = -1)
// Onshape loop: Button -> open_tool (show dialog) -> refresh_preview (ghost) ->
// confirm_tool (commit) / cancel_tool (abort).
void open_tool(Tool t);
void close_tool();
void refresh_preview();
void confirm_tool();
void cancel_tool();
// Ctrl+Z / Ctrl+Shift+Z (Ctrl+Y) from the viewport. With a tool/dialog open it
// cancels that (Esc-like); otherwise it undoes/redoes the committed feature history.
void do_undo_redo(bool redo);
// The plane the Hole tool drills on: a picked face (inward, centred) or the dropdown.
SketchPlane hole_plane() const;
// The plane the Thread tool builds on: a picked cylindrical face (axis) or the dropdown.
SketchPlane thread_plane() const;
CadFeature build_candidate(Tool t) const;
int resolve_extrude_sketch() const;
// Plane pickers: fill a choice with XY/XZ/YZ + the document's datum planes, and
// map a choice row back to the actual SketchPlane (rows 0-2 base, 3+ datum).
void populate_plane_choices(ComboBox* c) const;
wxString ref_plane_name(int row) const; // "XY" / a datum's name, for the on-geometry hint
SketchPlane plane_from_choice(int row) const;
// Where a new sketch goes, resolved from what is SELECTED IN THE VIEWPORT rather than from a
// list: a picked planar face wins, otherwise the reference plane last clicked in 3D. `what`
// comes back as something to show the user, so the choice is visible without a combo.
SketchPlane sketch_plane_from_selection(wxString& what) const;
// Whether that resolution has anything the USER picked behind it, rather than the default
// reference plane. Lets a caller say "sketching on XZ" only when it is actually true.
bool sketch_plane_target(wxString& what) const;
// True when Extrude should build only the click-selected loop (a region of the
// resolved sketch is selected and it carries entities).
bool extrude_uses_loop() const;
void sync_sketch_display(); // push un-consumed committed sketches to the viewport
// Feed the viewport's visual Extrude depth-arrow gizmo (C5b) with the current profile
// plane + centroid + live depths while the Extrude card is open (self-gates on m_active).
void update_extrude_gizmo();
void update_fillet_gizmo(); // edge-anchored radius arrow (Dressup card)
void sync_dressup_target(); // Dressup card: show picked edge vs group, gate the combo
void update_hole_gizmo(); // footprint circle + diameter/depth arrows (Hole card)
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
void update_reference_planes(); // persistent XY/XZ/YZ reference planes (fallback when no object)
CadDocument m_doc;
Tool m_active{Tool::None};
// Keyboard shortcuts (Onshape-style, three scoped layers). Keys are encoded as the
// upper-cased letter, OR'd with 0x10000 when Shift is required. m_keys_sketch fires only
// while a sketch is open (single letters = sketch tools); m_keys_feature fires only when
// no sketch is open (Shift+letter = feature tools; single letters = view toggles/section).
static constexpr int SC_SHIFT = 0x10000;
std::map<int, std::function<void()>> m_keys_sketch;
std::map<int, std::function<void()>> m_keys_feature;
StaticBox* m_tree_box{nullptr}; // framed feature-tree section
StaticBox* m_parts_box{nullptr}; // framed bodies section (hidden while empty)
StaticBox* m_cards{nullptr}; // one framed panel holding every tool dialog (one visible at a time)
void update_cards_frame(); // show that frame iff some card inside it is visible
void show_move_card(bool show);
void apply_move_card(); // numeric move/rotate -> same xform the gizmo builds
void push_polygon_params();
wxSizer* m_tb_commit{nullptr}; // far-right Commit to Plate, beside Confirm/Cancel
wxSizer* m_tb_doc{nullptr}; // toolbar document/view actions (new, commit, export, section, place)
CheckBox* m_show_bed{nullptr}; // view option: draw the printer bed + plate grid, or not
wxSizer* m_box_move{nullptr}; // Move/Rotate numeric options (distance, axis, angle)
wxSizer* m_box_sketch{nullptr};
wxSizer* m_box_extrude{nullptr};
wxSizer* m_box_dressup{nullptr};
wxSizer* m_box_hole{nullptr};
wxSizer* m_box_thread{nullptr};
wxSizer* m_box_shell{nullptr};
wxSizer* m_box_revolve{nullptr};
wxSizer* m_box_sweep{nullptr};
wxSizer* m_box_pattern{nullptr};
wxSizer* m_box_plane{nullptr};
wxSizer* m_box_loft{nullptr};
wxSizer* m_box_draft{nullptr};
wxSizer* m_box_boolean{nullptr};
wxSizer* m_box_cut{nullptr};
wxSizer* m_box_axis{nullptr};
wxSizer* m_box_coordsys{nullptr};
wxSizer* m_box_surf_extrude{nullptr};
wxSizer* m_box_surf_revolve{nullptr};
wxSizer* m_box_surf_loft{nullptr};
wxSizer* m_box_surf_fill{nullptr};
wxSizer* m_box_surf_offset{nullptr};
wxSizer* m_box_surf_thicken{nullptr};
wxSizer* m_box_transform{nullptr};
wxSizer* m_box_mirror{nullptr};
wxSizer* m_box_thicken{nullptr};
wxSizer* m_box_rib{nullptr};
wxSizer* m_box_project{nullptr};
wxSizer* m_box_delete_face{nullptr};
wxSizer* m_box_helix{nullptr};
wxSizer* m_box_mate{nullptr};
wxSizer* m_box_insert{nullptr}; // Confirm/Cancel card for placing Text/SVG art
wxSizer* m_box_expr{nullptr}; // expression binding card (visible during edit only)
int m_insert_feat{-1}; // provisional imported-art feature awaiting Confirm
// Move-body gizmo runs through the unified action bar too: Confirm keeps the placement,
// Cancel reverts to the pose captured when the move started.
int m_move_body{-1};
Transform3d m_move_prev{Transform3d::Identity()};
// Onshape-style dialog-card title rows (icon + bold feature name), retitled
// per tool in open_tool() (edit-mode shows the feature's actual name).
wxStaticText* m_hdr_move{nullptr};
wxStaticText* m_hdr_sketch{nullptr};
// Onshape sketch-entry card (plane/orientation) that opens on "New sketch" and
// persists until Finish (Phase 3).
wxSizer* m_box_sketch_session{nullptr};
wxStaticText* m_hdr_sketch_session{nullptr};
wxStaticText* m_sketch_hint{nullptr}; // "click a plane" / "drawing on X" — must match the status
wxStaticText* m_hdr_extrude{nullptr};
wxStaticText* m_hdr_dressup{nullptr};
wxStaticText* m_hdr_hole{nullptr};
wxStaticText* m_hdr_thread{nullptr};
wxStaticText* m_hdr_shell{nullptr};
wxStaticText* m_hdr_revolve{nullptr};
wxStaticText* m_hdr_sweep{nullptr};
wxStaticText* m_hdr_pattern{nullptr};
wxStaticText* m_hdr_plane{nullptr};
wxStaticText* m_hdr_loft{nullptr};
wxStaticText* m_hdr_draft{nullptr};
wxStaticText* m_hdr_boolean{nullptr};
wxStaticText* m_hdr_cut{nullptr};
wxStaticText* m_hdr_axis{nullptr};
wxStaticText* m_hdr_coordsys{nullptr};
wxStaticText* m_hdr_surf_extrude{nullptr};
wxStaticText* m_hdr_surf_revolve{nullptr};
wxStaticText* m_hdr_surf_loft{nullptr};
wxStaticText* m_hdr_surf_fill{nullptr};
wxStaticText* m_hdr_surf_offset{nullptr};
wxStaticText* m_hdr_surf_thicken{nullptr};
wxStaticText* m_hdr_transform{nullptr};
wxStaticText* m_hdr_mirror{nullptr};
wxStaticText* m_hdr_thicken{nullptr};
wxStaticText* m_hdr_rib{nullptr};
wxStaticText* m_hdr_project{nullptr};
wxStaticText* m_hdr_delete_face{nullptr};
wxStaticText* m_hdr_helix{nullptr};
wxStaticText* m_hdr_mate{nullptr};
wxStaticText* m_hdr_insert{nullptr};
wxScrolledWindow* m_form{nullptr};
DesignCanvas* m_viewport{nullptr};
// Top contextual toolbar (parented to the panel, above the form/viewport row).
UiMode m_ui_mode{UiMode::Feature};
wxScrolledWindow* m_toolbar{nullptr}; // horizontally scrollable so the action bar stays reachable on narrow windows
wxSizer* m_tb_feature{nullptr};
wxSizer* m_tb_sketch{nullptr};
wxSizer* m_tb_constrain{nullptr};
// Unified Confirm/Cancel action bar (right end of the ribbon). Shown whenever any
// tool or mode is active; the single confirm/cancel surface for the whole tab.
wxSizer* m_tb_action{nullptr};
// Persistent Undo/Redo group at the left of the ribbon — always visible, independent
// of the mode-gated tool groups. The buttons are greyed per the document history and
// the do_undo_redo gate (see update_undo_redo_buttons).
wxSizer* m_tb_history{nullptr};
ScalableButton* m_btn_undo{nullptr};
ScalableButton* m_btn_redo{nullptr};
void update_undo_redo_buttons(); // enable/disable Undo/Redo from can_undo/can_redo + gate
// All tool buttons, for the active-tool teal highlight (Onshape-style).
std::vector<ScalableButton*> m_tool_btns;
ScalableButton* m_active_tool_btn{nullptr};
void set_active_tool_btn(ScalableButton* b); // nullptr clears the highlight
// Owns the themed DropDown flyouts (and the item vectors they hold by ref).
std::vector<std::shared_ptr<void>> m_flyout_keepalive;
wxCheckBox* m_construction{nullptr}; // sketch-mode construction toggle
wxSpinCtrlDouble* m_move_dx{nullptr}; // Move/Rotate card: world translation
wxSpinCtrlDouble* m_move_dy{nullptr};
wxSpinCtrlDouble* m_move_dz{nullptr};
ComboBox* m_move_axis{nullptr}; // rotation axis: X/Y/Z
wxSpinCtrlDouble* m_move_angle{nullptr}; // rotation angle (deg)
// Polygon's two parameters are chosen FROM THE TOOL, in the offer's Polygon submenu, not
// from a card on the left: the side count cannot be edited after drawing (the inline editor
// offers Side and Angle only), so it has to be settled at the moment the tool is armed —
// which is exactly where the offer already is. snaporca-e1p.
int m_poly_sides{6}; // 3..64; the submenu names the common ones
bool m_poly_circumscribed{false};
// Which reference plane a sketch falls back to when no face is picked: 0/1/2 = XY/XZ/YZ,
// >=3 indexes resolve_datum_planes(). Set by CLICKING a ghost plane in the viewport — there is
// deliberately no dropdown for it. snaporca-e1p.
int m_ref_plane{0};
// m_ref_plane is always a VALID plane, so it cannot itself distinguish "the user chose XY"
// from "nobody has chosen anything yet". This does.
bool m_plane_picked{false};
ComboBox* m_shape{nullptr};
ComboBox* m_mode{nullptr};
wxSpinCtrlDouble* m_width{nullptr};
wxSpinCtrlDouble* m_height{nullptr};
wxSpinCtrlDouble* m_radius{nullptr};
wxSpinCtrlDouble* m_distance{nullptr};
ComboBox* m_extrude_end{nullptr}; // Blind/Symmetric/TwoSided/ThroughAll/UpTo*
wxSpinCtrlDouble* m_distance2{nullptr}; // second-side depth (Two-sided)
wxSpinCtrlDouble* m_taper{nullptr}; // draft angle (deg)
CheckBox* m_flip{nullptr}; // reverse extrude direction
wxStaticText* m_extrude_sketch_label{nullptr};
int m_extrude_sketch_ref{-1};
// Revolve controls (sweep a sketch profile about an in-plane axis).
wxStaticText* m_revolve_sketch_label{nullptr};
wxSpinCtrlDouble* m_revolve_angle{nullptr};
ComboBox* m_revolve_axis{nullptr}; // 0 = plane X, 1 = plane Y
ComboBox* m_revolve_mode{nullptr}; // New/Add/Cut/Intersect
CheckBox* m_revolve_flip{nullptr};
int m_revolve_sketch_ref{-1};
// Sweep controls (sweep a profile sketch along a path sketch).
wxStaticText* m_sweep_profile_label{nullptr};
ComboBox* m_sweep_path{nullptr}; // path Sketch picker (feature index in client data)
ComboBox* m_sweep_mode{nullptr}; // New/Add/Cut/Intersect
int m_sweep_profile_ref{-1};
int m_sweep_path_ref{-1}; // path Sketch feature index (for re-edit pre-select)
// Loft controls (skin a solid through 2+ ordered profile Sketches).
wxCheckListBox* m_loft_list{nullptr}; // every Sketch; check 2+ in list order = profiles
CheckBox* m_loft_ruled{nullptr}; // ruled (straight) vs smooth sections
ComboBox* m_loft_mode{nullptr}; // New/Add/Cut/Intersect
std::vector<int> m_loft_sketch_idx; // feature index for each row in m_loft_list
std::vector<int> m_loft_refs; // chosen profile refs (for re-edit pre-check)
// Surface Extrude controls (sheet from sketch profile).
wxStaticText* m_surf_extrude_sketch_label{nullptr};
wxSpinCtrlDouble* m_surf_extrude_distance{nullptr};
int m_surf_extrude_sketch_ref{-1};
// Surface Revolve controls (sheet from sketch about axis).
wxStaticText* m_surf_revolve_sketch_label{nullptr};
wxSpinCtrlDouble* m_surf_revolve_angle{nullptr};
ComboBox* m_surf_revolve_axis{nullptr}; // 0 = plane X, 1 = plane Y
CheckBox* m_surf_revolve_flip{nullptr};
int m_surf_revolve_sketch_ref{-1};
// Surface Loft controls (skin a sheet through 2+ ordered profile sketches).
wxCheckListBox* m_surf_loft_list{nullptr}; // every Sketch; check 2+ in list order = profiles
CheckBox* m_surf_loft_ruled{nullptr}; // ruled (straight) vs smooth sections
std::vector<int> m_surf_loft_sketch_idx; // feature index for each row
std::vector<int> m_surf_loft_refs; // chosen profile refs (for re-edit pre-check)
// Surface Fill controls (one-face sheet from a sketch boundary).
wxStaticText* m_surf_fill_sketch_label{nullptr};
int m_surf_fill_sketch_ref{-1};
// Surface Offset controls (offset a SHEET body).
ComboBox* m_surf_offset_body{nullptr}; // sheet-body picker
wxSpinCtrlDouble* m_surf_offset_distance{nullptr};
// Thicken Surface controls (thicken a SHEET body into a solid).
ComboBox* m_surf_thicken_body{nullptr}; // sheet-body picker
wxSpinCtrlDouble* m_surf_thicken_thickness{nullptr};
CheckBox* m_surf_thicken_flip{nullptr};
// Transform controls (rigid move/rotate of a body).
ComboBox* m_xf_body{nullptr}; // body to transform
wxSpinCtrlDouble* m_xf_dx{nullptr}; // translate X
wxSpinCtrlDouble* m_xf_dy{nullptr}; // translate Y
wxSpinCtrlDouble* m_xf_dz{nullptr}; // translate Z
ComboBox* m_xf_axis{nullptr}; // rotation axis: X/Y/Z
wxSpinCtrlDouble* m_xf_angle{nullptr}; // rotation angle (deg)
wxSpinCtrlDouble* m_xf_pivot_x{nullptr}; // pivot X
wxSpinCtrlDouble* m_xf_pivot_y{nullptr}; // pivot Y
wxSpinCtrlDouble* m_xf_pivot_z{nullptr}; // pivot Z
CheckBox* m_xf_copy{nullptr}; // keep original (make a copy)
// Mirror controls (reflect a body about a plane).
ComboBox* m_mirror_body{nullptr}; // body to mirror
ComboBox* m_mirror_plane{nullptr}; // mirror plane (XY/XZ/YZ + datums)
CheckBox* m_mirror_keep{nullptr}; // keep original body
// Thicken controls (offset one solid face into a thin plate).
ComboBox* m_thicken_body{nullptr}; // source body
wxStaticText* m_thicken_face_label{nullptr}; // picked face
wxSpinCtrlDouble* m_thicken_thickness{nullptr};
CheckBox* m_thicken_flip{nullptr}; // flip direction
// Rib controls (thin wall from an open sketch line).
ComboBox* m_rib_body{nullptr}; // target body
ComboBox* m_rib_sketch{nullptr}; // sketch holding the open line (feature index in client data)
wxSpinCtrl* m_rib_entity{nullptr}; // entity index within the sketch
wxSpinCtrlDouble* m_rib_thickness{nullptr};
wxSpinCtrlDouble* m_rib_depth{nullptr};
// Project controls (project body edges onto a plane as sketch entities).
ComboBox* m_proj_source_body{nullptr}; // source body
wxStaticText* m_proj_face_label{nullptr}; // picked face (or "all edges")
ComboBox* m_proj_plane{nullptr}; // target plane
// Delete Face controls (remove faces, heal the solid).
ComboBox* m_del_face_body{nullptr}; // target body
wxButton* m_del_face_add_btn{nullptr}; // "Add picked face" button
wxStaticText* m_del_face_list{nullptr}; // shows the accumulated face ids
std::vector<int> m_del_faces; // accumulated face list
// Helix controls (helical curve).
ComboBox* m_helix_plane{nullptr}; // axis plane (XY/XZ/YZ + datums)
wxSpinCtrlDouble* m_helix_radius{nullptr};
wxSpinCtrlDouble* m_helix_pitch{nullptr};
wxSpinCtrlDouble* m_helix_height{nullptr};
CheckBox* m_helix_left_handed{nullptr};
wxSpinCtrlDouble* m_helix_taper{nullptr};
// Mate (assembly) controls
ComboBox* m_mate_kind{nullptr};
ComboBox* m_mate_cs_a{nullptr};
ComboBox* m_mate_cs_b{nullptr};
wxSpinCtrlDouble* m_mate_offset{nullptr};
wxSpinCtrlDouble* m_mate_angle{nullptr};
CheckBox* m_mate_flip{nullptr};
wxStaticText* m_offset_label{nullptr};
wxStaticText* m_angle_label{nullptr};
// Expression binding (per-feature, visible during edit only)
ComboBox* m_expr_field{nullptr}; // field-name picker (editable)
wxTextCtrl* m_expr_text{nullptr}; // expression string
wxButton* m_expr_set_btn{nullptr}; // Apply / bind
wxButton* m_expr_clear_btn{nullptr}; // Remove binding
wxStaticText* m_expr_status{nullptr}; // shows current bindings for the edited feature
void populate_expr_fields(Tool t); // fill m_expr_field from feature-type fields
void on_set_expr(); // checkpoint + write -> recompute -> undo on fail
void on_clear_expr(); // remove selected binding
// Document variables panel (below the feature tree / parts)
StaticBox* m_var_box{nullptr};
wxListCtrl* m_var_list{nullptr};
wxButton* m_btn_add_var{nullptr};
wxButton* m_btn_edit_var{nullptr};
wxButton* m_btn_del_var{nullptr};
void refresh_variables(); // rebuild m_var_list from m_doc.variables
void on_add_variable();
void on_edit_variable();
void on_remove_variable();
// Feature-tree button
ScalableButton* m_btn_interfere{nullptr};
// Pattern controls (replicate the target body: linear or circular).
ComboBox* m_pattern_type{nullptr}; // 0 = Linear, 1 = Circular
wxSpinCtrlDouble* m_pattern_count{nullptr}; // total instances incl. seed
wxSpinCtrlDouble* m_pattern_spacing{nullptr}; // linear step (mm)
ComboBox* m_pattern_dir{nullptr}; // linear direction: 0 = plane X, 1 = plane Y
wxSpinCtrlDouble* m_pattern_angle{nullptr}; // circular total angle (deg)
// Boolean controls (combine two existing bodies).
ComboBox* m_bool_op{nullptr}; // 0 = Union, 1 = Subtract, 2 = Intersect
ComboBox* m_bool_target{nullptr}; // body that survives (selection == body index)
ComboBox* m_bool_tool{nullptr}; // body consumed (selection == body index)
CheckBox* m_bool_keep{nullptr}; // keep the tool body after the op
wxSpinCtrlDouble* m_bool_tol{nullptr}; // OCCT fuzzy tolerance (mm); robust cut on near-coincident faces
// Plane Cut (split-by-plane): a reference plane + offset splits the target body into
// two separate bodies (both pieces kept).
ComboBox* m_cut_plane{nullptr}; // XY/XZ/YZ + datum planes (cut plane)
ComboBox* m_cut_target{nullptr}; // body to cut (selection == body index)
wxSpinCtrlDouble* m_cut_offset{nullptr}; // offset along the plane normal (mm)
// Datum plane controls (derive a selectable sketch plane: offset + tilt from a base).
ComboBox* m_plane_base{nullptr}; // 0=XY,1=XZ,2=YZ, 3+N = Nth datum plane
wxSpinCtrlDouble* m_plane_offset{nullptr}; // offset along base normal (mm)
wxSpinCtrlDouble* m_plane_tilt{nullptr}; // tilt about a base axis (deg) / Angle / Tangent angle
ComboBox* m_plane_tilt_axis{nullptr}; // 0 = base X, 1 = base Y
// Plane construction method + contextual face/edge reference picks (Onshape/Fusion parity).
ComboBox* m_plane_type{nullptr}; // PlaneType: Offset/Angle/Midplane/Tangent/TwoEdges/Coincident
wxButton* m_plane_pick_faceA{nullptr}; wxStaticText* m_plane_faceA_lbl{nullptr};
wxButton* m_plane_pick_faceB{nullptr}; wxStaticText* m_plane_faceB_lbl{nullptr};
wxButton* m_plane_pick_edgeA{nullptr}; wxStaticText* m_plane_edgeA_lbl{nullptr};
wxButton* m_plane_pick_edgeB{nullptr}; wxStaticText* m_plane_edgeB_lbl{nullptr};
wxSpinCtrlDouble* m_plane_usize{nullptr}; // datum rectangle extent u (mm) — also driven by drag handles
wxSpinCtrlDouble* m_plane_vsize{nullptr}; // datum rectangle extent v (mm)
// Captured references for the candidate datum (body index + face/edge index, -1 = none).
int m_pl_faceA_body{-1}, m_pl_faceA{-1};
int m_pl_faceB_body{-1}, m_pl_faceB{-1};
int m_pl_edgeA_body{-1}, m_pl_edgeA{-1};
int m_pl_edgeB_body{-1}, m_pl_edgeB{-1};
PlanePick m_plane_pick{PlanePick::None}; // which ref the next solid pick fills
// Plate loop selection (click a committed sketch loop): the Sketch feature + the
// clicked closed-region index, so Extrude builds just that one loop. -1 = none.
int m_sel_sketch_feat{-1};
int m_sel_sketch_region{-1};
// Click-selected solid topology (whole/face/edge cycle): face id for up-to-face / dress-up.
int m_sel_solid_body{-1}; // which body the face/edge selection is on
int m_sel_solid_face{-1};
int m_sel_solid_edge{-1};
bool m_sel_solid_vertex{false}; // a corner is picked (body+point, no face/edge)
// The face actually under the last solid click, INDEPENDENT of the whole/face/edge cycle level.
// The first click on a solid selects the WHOLE body, but the ray has already resolved which face
// it hit and the callback passes it. "Sketch on the face I clicked" must not require discovering
// that a second click refines the selection, so keep it instead of throwing it away. snaporca-3a2.
int m_pick_face_body{-1};
int m_pick_face{-1};
// What the live sketch was actually opened on ("the picked face", "XY", a datum's name), so the
// hint can say it. Resolved from the selection at begin_sketch, not read back from a combo.
wxString m_sketch_on;
// --- the object-driven offer (charter 4.1) ---------------------------------------------
// Right-click the geometry -> a vertical list in ratified row order, verbs that do not
// apply disabled IN PLACE with their reason. The rows come from the generated table in
// DesignOffer.hpp; this map is how a row reaches the code that already implements it, for
// the verbs that have no keyboard shortcut to route through.
std::map<std::string, std::function<void()>> m_verb_actions;
void show_offer_menu(const wxPoint& screen_pos);
// Where the offer opens when no mouse press anchors it: the keyboard route, and the automatic
// open on entering Sketch. The pointer if it is over the viewport, else the viewport's centre.
// A raw wxGetMousePosition() can be sitting on the toolbar, on the card column or on another
// monitor, and the menu would map there — detached from the geometry it is about.
wxPoint offer_anchor() const;
int offer_selection_kind() const; // an OfferSel, as int to keep the header light
// Does the SKETCH half of the map apply? A mode question, not a session one: begin_sketch
// does not run until the first tool is armed, so between "press Sketch" and "pick a tool"
// is_sketching() is still false — precisely when the drawing tools must be on offer. The
// is_sketching() arm covers re-opening a committed sketch, which enters the session first.
bool sketch_map_applies() const;
void run_offer_action(const char* action);
// Face-as-profile extrude (Onshape): when Extrude is opened on a picked solid face with
// no sketch source, this carries that global face id so the kernel extrudes the face.
// -1 = ordinary sketch/loop extrude. Set when opening the Extrude card, consumed on add.
int m_extrude_face_src{-1};
ComboBox* m_dressup_type{nullptr};
ComboBox* m_face_group{nullptr};
wxSpinCtrlDouble* m_dressup_size{nullptr};
wxStaticText* m_dressup_edge_label{nullptr}; // shows the picked edge, or the group fallback
ComboBox* m_hole_plane{nullptr};
wxSpinCtrlDouble* m_hole_diameter{nullptr};
wxSpinCtrlDouble* m_hole_depth{nullptr};
CheckBox* m_hole_through{nullptr};
wxSpinCtrlDouble* m_hole_x{nullptr};
wxSpinCtrlDouble* m_hole_y{nullptr};
// #2: when the Hole tool is opened on a picked solid face, drill on that face centred
// on it (origin = face centroid, normal = inward). m_hole_x/y then read as the offset
// from the face centre. Falls back to the m_hole_plane dropdown when no face is picked.
bool m_hole_on_face{false};
SketchPlane m_hole_face_plane;
int m_hole_face_body{-1};
// #2 Part B: the picked face's (u,v) bounds in m_hole_face_plane, so the hole's construction
// dims read as distance from the face sides (umin/vmin edges) rather than from the centre.
bool m_hole_has_bounds{false};
double m_hole_umin{0}, m_hole_umax{0}, m_hole_vmin{0}, m_hole_vmax{0};
ComboBox* m_thread_plane{nullptr};
ComboBox* m_thread_std{nullptr}; // standard designation (M6, 1/4-20 UNC, ...)
wxSpinCtrlDouble* m_thread_radius{nullptr};
wxSpinCtrlDouble* m_thread_pitch{nullptr};
wxSpinCtrlDouble* m_thread_height{nullptr};
wxSpinCtrlDouble* m_thread_depth{nullptr};
CheckBox* m_thread_internal{nullptr};
wxSpinCtrlDouble* m_thread_x{nullptr};
wxSpinCtrlDouble* m_thread_y{nullptr};
// #3: when the Thread tool is opened on a picked cylindrical face (a hole bore or a
// cylinder), thread that surface — plane on its axis, radius/internal derived from it.
bool m_thread_on_face{false};
SketchPlane m_thread_face_plane;
int m_thread_face_body{-1};
wxSpinCtrlDouble* m_shell_thickness{nullptr};
wxStaticText* m_shell_face_label{nullptr}; // shows the picked face to remove
// Draft controls (taper a single picked solid face about the body bottom).
wxSpinCtrlDouble* m_draft_angle{nullptr};
wxStaticText* m_draft_face_label{nullptr}; // shows the picked face to draft
// Axis controls (datum axis: line through two points or derived from geometry).
ComboBox* m_axis_type{nullptr}; // AxisType: TwoPoints/FaceNormal/CylinderCenterline/PlaneIntersection/AlongEdge
wxButton* m_axis_pick_face{nullptr}; wxStaticText* m_axis_face_lbl{nullptr};
wxButton* m_axis_pick_edge{nullptr}; wxStaticText* m_axis_edge_lbl{nullptr};
ComboBox* m_axis_plane_a{nullptr};
ComboBox* m_axis_plane_b{nullptr};
wxSpinCtrlDouble* m_axis_p1x{nullptr}; wxSpinCtrlDouble* m_axis_p1y{nullptr}; wxSpinCtrlDouble* m_axis_p1z{nullptr};
wxSpinCtrlDouble* m_axis_p2x{nullptr}; wxSpinCtrlDouble* m_axis_p2y{nullptr}; wxSpinCtrlDouble* m_axis_p2z{nullptr};
int m_ax_face_body{-1}, m_ax_face{-1};
int m_ax_edge_body{-1}, m_ax_edge{-1};
AxisPick m_axis_pick{AxisPick::None};
// CoordSys controls (datum coordinate system: point + orthonormal frame).
ComboBox* m_coordsys_type{nullptr}; // CoordSysType: PointWorld/FaceAndDirection
wxSpinCtrlDouble* m_cs_x{nullptr}; wxSpinCtrlDouble* m_cs_y{nullptr}; wxSpinCtrlDouble* m_cs_z{nullptr};
wxButton* m_cs_pick_face{nullptr}; wxStaticText* m_cs_face_lbl{nullptr};
wxButton* m_cs_pick_edge{nullptr}; wxStaticText* m_cs_edge_lbl{nullptr};
wxSpinCtrlDouble* m_cs_hx{nullptr}; wxSpinCtrlDouble* m_cs_hy{nullptr}; wxSpinCtrlDouble* m_cs_hz{nullptr};
int m_cs_face_body{-1}, m_cs_face{-1};
int m_cs_edge_body{-1}, m_cs_edge{-1};
CoordSysPick m_coordsys_pick{CoordSysPick::None};
// Onshape-style docked value-entry card (Angle/Radius/Diameter/Offset/Fillet).
wxSizer* m_box_value{nullptr};
wxStaticText* m_value_label{nullptr};
wxTextCtrl* m_value_input{nullptr}; // plain text field: forces en ('.') decimals
double m_value_min{0.0}; // range for confirm-time clamping
double m_value_max{0.0};
std::function<void(double)> m_value_cont; // deferred apply, run on Confirm
std::function<void()> m_value_cancel; // optional action when the card is cancelled
// Feature tree: a wxTreeCtrl with per-feature-type icons. Callers keep using
// integer row indices via tree_selection()/set_tree_selection(); m_tree_items
// maps feature order -> tree node, rebuilt by refresh_tree().
wxTreeCtrl* m_tree{nullptr};
wxTreeCtrl* m_parts{nullptr}; // Bodies list under the feature tree
wxStaticText* m_parts_label{nullptr}; // its "Bodies" caption (hidden when empty)
wxBoxSizer* m_parts_hdr{nullptr}; // Bodies card header (icon + title)
wxStaticLine* m_parts_rule{nullptr}; // rule under that header
wxBoxSizer* m_hdr_tree_row{nullptr}; // Feature tree header: title + row actions
wxStaticText* m_hdr_tree{nullptr}; // its title label
wxImageList* m_tree_images{nullptr};
std::vector<wxTreeItemId> m_tree_items;
// Parts list: tree rows for each body (parallel to m_doc.bodies). Selecting one
// highlights that body and makes it the target for the next op.
std::vector<wxTreeItemId> m_tree_body_items;
// Section views (non-destructive): named "Section View N" entries listed in the tree, each a
// horizontal clip height. View-only — NOT bodies/features, never serialized. Key X adds one;
// clicking a row activates it (again = off); Delete removes it; Alt+Wheel moves the active one.
// Section view (single, non-destructive): ONE horizontal clip that hides half the model to
// inspect inside — solid, no ghost of the hidden half. Toggled on/off; Flip shows the other
// half. Never a body, no tree entry.
bool m_section_on{false};
double m_section_cut_z{0.0};
bool m_section_upper{false}; // false = keep lower half, true = upper
ScalableButton* m_section_flip_btn{nullptr}; // toolbar action; enabled only while the section is on
void toggle_section_view(); // Section View button / X: on <-> off
void flip_section_view(); // Flip button / F: opposite half
void update_section_flip_btn(); // enable the Flip button iff the section is on
// Per-body visibility (parallel to m_doc.bodies; index stable across recompute since
// bodies are appended in feature order). Empty/grown to all-visible by sync_body_visible().
std::vector<bool> m_body_visible;
void sync_body_visible(); // grow/shrink m_body_visible to bodies.size()
// Per-body display translation (Move-body, M5). Parallel to m_doc.bodies; default
// identity. Applied to the display/pick meshes only — the OCCT shape (and face/edge
// global ids) is never touched, so dress-up targeting stays stable across a move.
std::vector<Transform3d> m_body_xform;
std::vector<TriangleMesh> m_disp_body_meshes; // display_body_meshes with m_body_xform applied
TriangleMesh m_disp_pick_mesh; // combined pick mesh with m_body_xform applied
void sync_body_xform(); // grow m_body_xform to bodies.size() (identity)
void rebuild_disp_meshes(); // recompute m_disp_* from m_doc + m_body_xform
void feed_bodies(); // push m_disp_* + visibility/xform to the viewport
void on_move_body(); // start the move gizmo on the selected body
void on_set_body_color(); // Color tool: pick a per-body display colour override
int tree_selection() const; // selected feature row, or wxNOT_FOUND
int tree_body_selection() const; // selected Parts-list body index, or -1
void refresh_parts(); // rebuild the Bodies list under the feature tree
void sync_sidebar_width(); // keep the panel as wide as Prepare's sidebar
void set_tree_selection(int row);
static int tree_icon_for(CadFeatureType t);
wxStaticText* m_status{nullptr};
wxStaticText* m_dof_status{nullptr}; // DoF / constraint-state readout (P3)
int m_feature_counter{0};
std::vector<wxButton*> m_confirm_btns;
// Edit-in-place state: add-mode is m_edit_index == -1. Single-feature edit
// (Sketch or Extrude independently) uses only m_edit_index as the row to replace.
int m_edit_index{-1};
// Tree row of the sketch currently being constrained (-1 = not constraining).
int m_constrain_feat{-1};
// Constraint-manager card (C3.4): header + a rebuildable list of constraint rows.
wxSizer* m_box_constraints{nullptr};
wxStaticText* m_hdr_constraints{nullptr};
wxSizer* m_constraint_rows{nullptr};
int m_constraint_sel{-1}; // highlighted constraint row, or -1
};
}} // namespace Slic3r::GUI
#endif // slic3r_DesignPanel_hpp_