Felix14-v2's review of OrcaSlicer#16019 found the tab behaving as a world of its own.
- Icons: the design_* glyphs were drawn in a fixed light grey, made for the dark ribbon, and the
toolbar re-tinted some of them by rebuilding the bitmap from a wxImage, which drops the HiDPI
scale factor Orca sets on Windows: at 150 % the icons came out half again too large for buttons
that were sized in raw pixels, overlapping and clipped. The glyphs now use Orca's sidebar icon
grey (#949494), which the icon cache maps per theme, nothing is re-tinted, toolbar glyphs drawn
for Prepare's light toolbar use their "_dark" twin, and every size is in DIP.
- Theme: the chrome colours were read once, at construction, and nothing in the tab answered a
theme switch, so switching left light surfaces and unreadable text in a dark tab and the other
way round. The colours are now {light, dark} token pairs; MainFrame::on_sys_color_changed
reaches DesignPanel::on_sys_color_changed, which moves every token colour onto the other
theme's, runs the app's dark pass and re-rasterises the icons. Card borders are StateColors,
resolved at paint time.
- Scale: MainFrame::on_dpi_changed reaches DesignPanel::msw_rescale, which re-rasterises every
icon (buttons, flyout rows, card headers, the tree's image list, now sized from its bitmaps)
and re-measures the Orca widgets.
- Mouse: the canvas no longer forces middle-drag to orbit and right-drag to pan; it reads the
drag actions in Preferences > Control like Prepare. Left-drag is shared with picking, so the
whole-body rubber band takes Shift+left-drag while left-drag is given to the camera.
- FPS counter: ImGui's display size is shared and only refreshed when a canvas sees its own
size change; the Design canvas now re-announces its size when the tab is entered (and the
editor canvas when it is left), as Plater does between Prepare and Preview.
- Viewport text: the status line and the tool readout were top-level popups over GL. A popup
does not follow its frame, so the empty-canvas hint floated over other applications, and the
readout was never taken down with the tab. Both are drawn by the canvas in the tool's ImGui
pass now, with the theme's overlay style.
- Dialogs: messages use MessageDialog/RichMessageDialog; Add/Edit Variable is one Orca dialog
with Name and Expression fields instead of two native text prompts; the Text dialog uses
TextInput, ComboBox and CheckBox (its height is a TextInput: SpinInput is integer-only), and
enumerates the installed fonts once per session. The ribbon's Confirm/Cancel, the reference
pick buttons and the expression buttons are Orca Buttons; the variable actions are icon
buttons like the other cards'.
- Undo: the tab's own Undo/Redo buttons are gone. The top bar's Undo/Redo drive the Design
history while the tab is shown, greyed to what an undo would do, as Ctrl+Z and Edit already
did.
- The first build of the tab logs how long each phase took ("Design tab build: ..."): it is
under a second here but was reported at about fifteen on Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
main moved the CAD docs into docs/HLSD/design-tab.md and the offer
generator and atlas into scripts/CAD/ (#15803).
- The four docs this branch had edited are deleted as on main. What the
branch changes about the design goes into the HLSD doc: the right-click
is judged by drift over the whole press, with no time budget; a Text
feature stores its outlines as well as its string, font and height; a
new section on how the Design canvas renders bodies (studio lighting
through the shared phong shader, B-rep edge ribbons).
- gen_offer_table.py keeps both main's atlas validation and this
branch's L() markers on user-facing strings.
- The Text verb's hint is now changed in scripts/CAD/tool_atlas.json as
well, so `gen_offer_table.py --check` passes.
- DesignPanel.cpp keeps the DesignTextDialog include and main's new
path in the offer comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
OrcaSlicer#16019's Windows unit-test jobs failed on one test, "Hole
standards: ... 82° countersink": CTest passes the test name to Catch on
the command line, the ° arrives in the ANSI code page, the filter
matches no test and the run counts as failed ("No test cases matched",
"No tests ran"). The name now says "82 degree"; it was the only test
name in the suite outside ASCII.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
The Windows builds of OrcaSlicer#16019 failed in DesignSketchTool.cpp:
windows.h defines near (and far) as empty macros, so
`Vec2d near = m_op_anchor;` reads as `Vec2d = m_op_anchor;` under
clang-cl. The variable is now called closest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
Reported: the Revolve axis could only be the sketch plane's X or Y axis
through its origin, so a half-profile drawn beside a centerline, the
usual way, could not be revolved about that centerline.
- CadFeature::revolve_axis_entity names a Line of the profile sketch to
revolve about (a construction centerline, or an edge of the profile
itself); -1 keeps revolve_axis. Appended at the end of the framed
recipe, so existing projects load and rebuild unchanged. Revolve and
Surface Revolve resolve their axis in one place (revolve_axis_of); an
index that no longer names a line fails with a reason.
- SketchEngine::make_revolve takes the world axis. A profile with points
on both sides of it is refused with "the profile crosses the revolve
axis"; MakeRevol failed there with no reason.
- The Axis list of both cards reads Plane X, Plane Y, then every line of
the sketch, named as the constraint list names them (Centerline E4,
Line E3). A fresh revolve preselects the sketch's centerline when it
has exactly one. The gizmo turns about the chosen axis and draws it
dashed; construction lines no longer pull its centre.
Tests: a rectangle beside a construction centerline revolves into the
tube of the expected volume along that line; about its own edge, into a
cylinder; an axis through it is refused with the reason; a stale axis
index fails with a reason; the axis survives save and load. The
truncated-recipe test accounts for the new tail field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
Reported: once extruded, a part is hard to read; the lighting says
little about its shape. Both lights of the object shaders sit near the
camera, so the sides of a part come out in almost the same tone, and
nothing marks where one face ends and the next begins.
- The phong shader gains a studio lighting model, chosen by a new
lighting_model uniform: a sky/ground hemisphere in world space (up
faces cool and bright, down faces warm and dark), a key light from the
upper left and a weak fill from the right, a plastic-like highlight,
and a darker base with a faint sheen toward the silhouette so curved
faces read as round. GLCanvas3D::set_studio_lighting() makes a canvas
draw its objects with it whatever the realistic-view preferences; only
the Design canvas turns it on. Every canvas sets the uniform on each
use, 0 for the slicer's, so they render as before.
- Every B-rep edge of a body is drawn as a thin dark line over it, depth
tested and pulled a few pixels toward the eye so it wins against the
faces meeting at it and hides behind the faces in front. The seam of a
closed surface and degenerate edges are left out
(GeometryEngine::display_edges). Edges are sampled once per shape and
kept across recomputes that leave a body unchanged; bodies faded by
body focus get fainter edges, and a dress-up previewing its result
alone hides them with the bodies.
Tests: display_edges gives a box its 12 edges at their lengths, a
cylinder its two round rims without the seam, a cone its base rim
without the seam or the apex.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
The context menu still said the text outline is added to the sketch as
editable lines, which is what it did before Text became its own
feature. Only SVG goes into the open sketch now; the comments on that
path say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
Reported on the rig: Text showed no preview where it would go, its
dialog was pinned over the middle of the window (GNOME attaches a modal
dialog to its parent and it cannot be moved), and once confirmed the
text could not be edited and did not appear in the feature tree. Inside
an open sketch it became loose lines of that sketch.
- Text is always a feature, "Text N" in the tree. A new text goes on the
plane of the open sketch (committed first when it holds anything,
closed when it is empty), else centred on the picked face, else on the
reference plane.
- The dialog is modeless and opens at the top right of the window. The
feature is created at the first character and redrawn on every change,
so the text appears in the view where it will be as it is typed. Enter
inserts it, then the usual move/scale gizmo and Confirm; Esc, Cancel or
closing the dialog takes it out again (undo to the checkpoint taken
when it appeared).
- CadFeature keeps text_string, text_font (the WxFontUtils descriptor)
and text_height, appended at the end of the framed recipe. Editing a
Text feature reopens the dialog with them and redraws the outline in
place, keeping its placement. The outlines are still saved, so the
project opens the same on a machine without that font.
- The MCP control refuses writes while the Text dialog is open.
Tests: the text parameters survive a save and load along with the
outline; the truncated-recipe test accounts for the new tail fields.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
Offset worked on the single entity under the pointer. On an outline made
of many short entities (a text glyph, an imported shape) that is a
segment a few tenths of a millimetre long, and the starting distance was
a tenth of that: the ghost was too small to see, and a typed distance
moved one invisible segment. It read as "Offset does nothing, no
preview".
- The pick takes the chain the entity belongs to (connected_loop, same
construction state), highlights all of it, and offsets it as one
outline through offset_entities, which already joins and trims chains
at their seams.
- The starting distance is a twentieth of the outline's size, so the
preview is visible at once.
- The arrow reads its side off the ghost, since the engine may walk the
picked entity backwards in the chain; it keeps pointing at the offset
copy, flipped for a negative distance.
- A single entity still gets its Parallel/Concentric constraint. A chain's
offset is placed as geometry, since its joined entities no longer map
one to one onto the originals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
A sketch drawn on the rig extruded to walls with no caps. Every joint of
its loop met, so the loop analysis called it closed and MakeFace
accepted it, but the loop crossed itself: an arc left the top line's end
heading back over it and crossed it again 2.5 mm on. A second arc left a
0.28 mm line tangent to it but the other way, a cusp. The prism of that
face is an invalid solid, and it was shipped as a body.
- SketchEngine::wires_to_face checks the face it builds and, when OCCT
calls it invalid, fails with "the profile crosses or folds back on
itself, so it does not bound one region". The extrude reports that
instead of producing the broken body.
- sketch_loop_defect() judges a closed loop of lines and arcs exactly:
any contact between two of its entities away from the joints they
share, or a joint where the curve turns straight back (a cusp; OCCT
still builds that one, but it is never what was meant). It returns the
point.
- The sketch uses it on every region: the loop is tinted red, the point
gets a marker, and the status line says what the red means the first
time one appears. The MCP loop report lists the defects and no longer
calls such a profile buildable.
Tests: the rig's profile, with each defect and with both, from a
recording of the real entities. The analysis names the cusp at its joint
and the crossing on the top line, in either traversal order, and
passes ordinary tangent and collinear joints. The extrude refuses every
crossing variant with the reason, and the same arcs swept the other way
round extrude to a valid solid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
A solid edge could only be picked one at a time, and Fillet/Chamfer took
either that one edge or a whole face group. Rounding three chosen edges
meant three features, whose edge ids each resolve against a body the
previous one had already changed.
- Shift+click (or Ctrl+click) on an edge of the body already picked adds
it to the selection, or removes it; the same modifiers that extend a
sketch selection. The whole set is highlighted. A plain click replaces
it, as before.
- Fillet/Chamfer dresses every picked edge in ONE feature at one size,
all ids resolved against the same body. The card says "3 edges", the
status line and the offer header name the count.
- CadFeature gains dressup_edges, appended at the end of the framed
recipe, so existing projects load and rebuild unchanged. dressup_edge
keeps the first edge, so an older build opening a newer project still
dresses that edge instead of falling back to the face group.
- The MCP fillet/chamfer verbs take `edge` as one id or an array.
Tests: a fillet on the four picked top edges equals the Top face group
exactly; the list survives save/load; two opposite chamfers remove
exactly twice one; a missing id fails with a reason. The truncated-
recipe test accounts for the new tail field.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
Drawing a line or a rounded rectangle crashed the Linux AppImage: the
constraint list labels were formatted from narrow literals holding "—",
"·" and "°". wx converts a bare char* with the current locale, and the
AppImage's AppRun sets LC_ALL=C, so the conversion failed, the format
string came out NULL and wxString::Format dereferenced it
(wxFormatConverterBase<wchar_t>::Convert, from constraint_label via
rebuild_constraint_list). A build started under a UTF-8 locale never
showed it.
These literals, and the "…" of the interference report, now go through
wxString::FromUTF8, as the panel's other non-ASCII literals already do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
- Text opens a dialog instead of a bare text entry: any installed font
(bold, italic), the height in mm, and a live outline of exactly what
will be inserted with its size. Enter inserts, Esc cancels; the last
font and height are remembered. text_to_regions gains an overload
taking a loaded font.
- The offer menu opens with a greyed title naming what the rows act on
("Flat face 4 of Body 2", "Sketch line", "Nothing selected").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
- Offer table: user-facing strings carry the L() marker so xgettext
extracts them; DesignOffer.hpp, DesignSketchTool.cpp and
SketchInlineEditor.cpp are listed in localization/i18n/list.txt.
- Offer: model-mode Constrain sits in the same row as the sketch one;
Interference is wired; Rib shows its R key; a verb that accepts the
selection but is blocked by the document stays greyed with its reason
instead of vanishing from the submenu; one refusal wording per verb.
- Extrude infers Join when the profile touches a solid (new
CadDocument::body_touching_sketch) and on face push/pull; New body in
free space. Revolve/Sweep/Loft/Boolean use the same result words.
- Interference and volume/area reports go to the status line in mm3/mm2
instead of modal dialogs; Delete Body no longer asks (it is undoable).
- New feature names match the card header ("Extrude 3"), translated;
"Coordinate system", "Angle (°)", center/color spelling, translated
face and length readouts, slot hints say width.
- CAD gizmos in Prepare are selectable only with the CAD feature on; the
sketch auto-close setting is stored per design.
- Docs: confirm/cancel rules, enabling the feature and MCP in
design_tab.md; drift-only right-click in interaction-model.md; the
portability note rewritten to describe the integration as it is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
Keyboard and mouse
- Esc drops what is pending (picks, a dimension's first point, an edit-op or transform) and
never applies it; Enter applies a ready edit-op/transform, ends a polyline/spline chain,
ends an armed tool, and confirms a feature card exactly when its ✓ is enabled.
- Right-click only abandons the gesture in progress; with nothing pending it opens the offer
in every tool (Trim, edit-ops, transforms, Dimension, Constrain, TransformArt, move gizmo).
Clicking empty space no longer commits. The offer needs no timing, only a still press.
- Delete removes only an explicit selection. Undo/redo inside a sketch go through the same
route as the buttons (whole shapes, with redo); Edit > Undo follows the shown tab.
- The canvas no longer handles Delete/Esc/Ctrl+Z itself (Backspace in a value field deleted
the geometry it measured); F is in the panel's key map.
- Value fields: a refused value keeps the field open with the reason; click outside and Tab
commit; an untouched field commits the exact value; any decimal separator is accepted;
lengths are always mm; validation is the same for every editor.
- Snapping: the marker shows only where the click will actually snap; pick tolerances are
one set of pixel budgets (Constrain picks within reach; no mm floor on labels).
Messages and consistency
- set_status(kind, text) gives every status line its own colour and glyph; kernel errors are
translated into sentences and formatted, not concatenated; sketch refusals go to the status
line instead of the per-frame HUD that erased them.
- Hints describe the gestures that now work; Dimension shows its second step; Constrain uses
the sketch palette (red means conflict only); the straight slot's value is its width.
- Hole/Thread/Project keep the user's pick or refuse up front; circular pattern opens with its
own preview; thread fields use the nominal diameter and the ISO internal depth.
Integration
- MCP loads the project's recipe before touching the document, refuses to mutate it while the
tab is busy, never runs a request that already timed out, only replaces a socket at its
path, caps line length and removes the socket at exit; not started in the G-code viewer.
- Hiding a feature keeps later body references on their bodies; a design keeps its modeling
origin across printer changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
Sketch layer
- Trim/extend of an arc (and a circle opened into an arc) rewrites p0/p1 from the new angles;
the wire builder, the solver and snapping read them.
- Tangency binds the arc end that touches the line (or the other arc) instead of always the
start, so a fillet tangent to both legs solves; circle-circle / circle-arc tangency uses the
centre distance instead of CURVE_CURVE_TANGENT, which aborts on a circle.
- Point-on-line distances and circle-line tangency keep the side the geometry is on; a point
on an arc's rim uses PT_ON_CIRCLE.
- The partitioned solve keeps constraints onto the origin/axes and counts free entities' DOF.
- Zero-radius circles get no solver primitive and build no wire; constraints the solver cannot
apply are reported in SketchSolveResult::skipped.
- EllipseArc: mirror no longer yields the complement; after a solve its angles and ends are
re-derived; on the XZ plane it is no longer built mirrored.
- Offset: a circle follows the "+d = left of travel" rule (it shrinks, like a CCW arc chain);
chains are joined at the weld tolerance. Bridge end pole fixed (G1, no cusp). Negative-scale
transforms keep arcs and ellipses on their ends. Inference tolerances aligned with the weld.
Model layer
- Body references follow the body across delete / reorder / hide (resolved by the feature
that made it); datum-plane ordinals are re-pointed; an index past the end is an error, not
"the last body". New set_feature_enabled(). A move that puts a consumer above its input is
refused.
- Threads made from now on read thread_radius as the nominal major radius (internal: bore to
minor, groove to major; external: groove cut into the rod); older recipes build as before.
Bad thread parameters say why. Circular patterns span their angle end to end (new ones);
add_pattern pivots on the modeling origin.
- clear() drops variables; expression fields the GUI offers are bindable (thread_diameter,
helix_*, thicken_thickness, ...); deg()/rad() in expressions; the recipe saves the modeling
origin and body colours; names/colours follow bodies by identity.
- Boolean and dress-up failures throw instead of returning the input; one produces_body();
hole standards corrected (82° inch countersinks, UNC names, #10-24); legacy profile solve
validates indices and writes back only on success; v4 recipes read with a frozen field list.
Tests cover each of the above.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9