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
Expand shared filament and process values to their declared variant widths while preserving existing tuning. Bump the Snapmaker bundle version to 02.04.00.22.
Validation: Snapmaker profile check passed with zero errors and warnings; verified all expanded entries repeat their original values.
Co-authored-by: Codex <codex@openai.com>
Use the shared unsigned variant index for adaptive pressure advance and volumetric speed controls, resolving missing identifiers and the ambiguous string accessor.
Co-authored-by: Codex <codex@openai.com>
Refinement now tapers off away from the paint, and the unpainted surface is kept out of
simplification, so what one bake leaves unpainted is still the model's own triangles when a later
bake paints there. A second bake used to refine the slivers and long triangles the first one left
around its stroke, and came out many times denser, with walls off the texture's lines.
Once the budget is met the progress fraction stops moving, and a cancel was only checked when it
moved, so Cancel did nothing until the flat-face merging finished. It is now also checked every 16k
steps.
Meeting the triangle budget by merging flat faces alone no longer raises the warning, and the
warning now quotes the budget instead of the triangle count left after the flat faces were merged.
A vertex pinned on flat ground could collect a fan of thousands of slivers, and validating each
collapse next to it walked the whole fan, turning a second of simplification into minutes. Collapses
that would leave more than 64 faces around a vertex are now skipped.
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
* Fill Settings Missing From a CLI Project From Its System Presets
A project saved before a printer or process option existed has no value
for it. The GUI takes such keys from the project's system preset; the
CLI left them at the option default, so e.g. extruder_clearance_dist_to_rod
sliced as 40 instead of the P1S's 33.
The CLI now resolves the project's system printer and process presets by
name and copies the keys the project lacks, skipping preset bookkeeping,
print-host keys, the extruder variant layout and keys the legacy handler
drops. PresetBundle::resolve_system_preset finds the vendor through its
manifest or preset cache, so it also works in release builds, which ship
vendors as caches only.
* Load a CLI Project's Printer and Process Settings as the GUI Does
The CLI filled only the keys a project lacked from its system preset.
The GUI builds a project preset differently: the project's values go
over the default preset, without the print-host keys, and every key
the project does not list in different_settings_to_system is refreshed
to its base system preset's current value. After a profile update the
two sliced the same project differently.
That step now lives in Preset::load_external_config, which takes plain
configs and gets the base preset from a callback, so the collection
lookup stays in PresetCollection. PresetBundle::project_different_keys
builds the kept-key set from a project's escaped entry, adding the
preset bookkeeping keys, and is used by both the GUI and the CLI.
PresetCollection::load_external_preset calls the shared step with no
change in behaviour.
The CLI now builds the project's printer and process configs with the
same step, passing the system preset from resolve_system_preset. That
drops the hand-kept skip list for print-host and variant-layout keys
and the legacy-key check: the shared step already excludes print-host
keys and maps per-variant values onto the base preset's variant layout.
The --uptodate path and a printer or process given on the command line
are left as they were.
Because the GUI's kept-key set always holds the bookkeeping keys, the
refresh runs for every project that names a system preset, so the CLI
now loads that preset's vendor on every such run.
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
Twelve defects found by the 2D design pass.
- ONE erase path repairs the dimensions' cached constraint indices. Removing a
constraint renumbered m_constraints and left every later DimAnnot.con pointing
one slot short, and set_dimension_value checked only the range, not the slot's
identity — so editing a dimension's value could overwrite an unrelated
constraint. All five mutators (badge delete, fillet/chamfer, Move/Rotate/Scale,
the two drop paths) now route through erase_constraints().
- apply_dimension validates BEFORE moving or recording. A value outside a case's
threshold moved nothing and then recorded the constraint anyway, handing the
solver a number it could never satisfy; the socket guarded against this, the
tool did not.
- A Distance and its zero case are one dimension slot: typing 0 and then 5 used
to leave a Coincident AND a Distance on the same operands.
- A dimension whose constraint the solver rejected is named: the label renders in
the refusal colour and the commit says the sketch is over-constrained, instead
of showing a number the geometry does not have.
- Six silent refusals now speak: fillet/chamfer on a non-corner and on an
overrunning radius, offset on an ellipse or spline (the kernel's own reason),
a rejected array binding ladder, a dimension pick on an unsupported entity.
- set_tool commits a ready transform instead of dropping it, the rule the ready
edit-op already followed.
- Constraint releases are reported, and roles_of is one function again (the two
copies had already diverged on EllipseArc).
- Labels no longer collide: every label is its own centred ImGui window at an
anchor whose offsets are multiples of the text height, so on a feature smaller
than one text height a line's Length and Angle labels landed on the same spot.
draw_text, the one function all of them pass through, now pushes a colliding
label clear of the ones already drawn this frame.
Verified: build and LTO link on behemoth (exit 0, new binary); on the rig,
badge-delete took constraints 3->2 with dof 9->10 and the geometry untouched;
the draw-then-edit chain committed a typed 70 to exactly 70.0 mm; and a 5.4 mm
selected line renders "5.4 mm" and "21.8°" as separate readable labels.
NOT yet exercised: the stale-index corruption itself (needs a middle delete with
a labelled dimension after it), the poison-value guard through the field, the
transform commit, and the red refusal label.
The design pass over the rest of the 2D tool suite: Select/Measure,
Dimensioning, Constraints, Modification (Trim/Extend/Split/Offset/Mirror),
Dress-up (Fillet/Chamfer) and Transform, each with its FSM, its event routing,
its C++20 blueprint and the defects it exposed.
Thirty-two findings, every one verified against the tree with file:line, plus
nineteen open scope calls and a closing note that orders the six cross-cutting
changes by what unblocks what. Batches 1-8 were delivered in-session; only
their cross-cutting results (six archetypes, two defect classes) survive here.
Merged by /bot merge on behalf of @peachismomo (id 52488812).
Grants: resources/profiles/OrcaFilamentLibrary/filament/Elegoo, resources/profiles/OrcaFilamentLibrary.json, resources/profiles/Elegoo, resources/profiles/Elegoo.json
Head: ff6a2f2056
Since the CLI can open an OpenGL context (#15745) it renders plate
thumbnails on export, and the viewport restore at the end of
render_thumbnail_internal (#15674) then reads the plater through the wx
application. The CLI has neither, so every --export-3mf on a machine
with a display died with a segmentation fault after the first
thumbnail. Skip the restore when there is no application or no plater;
the GUI path is unchanged.
Include variant-aware validation, temperature and pressure controls, named nozzle selection, and filament sidebar refresh.
Co-authored-by: Codex <codex@openai.com>
Upstream moved pressure advance onto the extruder variant: enable_pressure_advance,
pressure_advance and the four adaptive keys joined filament_options_with_variant,
the repeated inline blocks in set_extruder() became a helper, and the lookups
moved from the filament id to get_filament_config_index().
All three conflicts were the same collision, because this branch had modified two
of those same inline blocks to pass a tool qualifier so each carriage is addressed
explicitly in parallel modes. Taking either side whole would have lost something:
upstream's drops the qualifier and leaves parallel carriages with no pressure
advance, ours drops the per-variant indexing and reads the wrong column on a
multi-variant printer. The helper now takes an optional tool, defaulting to -1,
which omits the qualifier. imex_pem_tool_for() already returns -1 off IMEX and in
primary mode, so non-IMEX output is unchanged, and the three call sites that never
passed a tool keep upstream's behavior exactly.
The third conflict was two test cases appended at the same place. Both are kept.
Separately, one defect that merged cleanly and so was not flagged: the loop that
emits pressure advance for secondary carriages at the start of a print still
bounded and indexed those vectors with a raw filament id. They are variant
expanded now, so their length is columns rather than filament slots -- the value
read was the wrong column, and the bound no longer sat in slot space, letting an
out-of-slot filament through. It now bounds on filament_diameter and translates
with get_filament_config_index(), which is what the sibling second-layer
temperature loop already does.
Verified: both changed translation units compile clean under -Werror. The merge
was resolved independently twice and the two resolutions agree on every line of
code. Not yet run: the Release test suite and a parallel-mode slice sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every extruder on every model carried the same yellow. The key exists so the
plater, the preview and the filament mapping can tell toolheads apart, which
matters most on the machine this bundle exists to demonstrate: four independent
heads that were indistinguishable at a glance.
Yellow, blue, plum and orange, assigned in tool order, so the two-tool models
take the first two. Red and green are avoided as a pair because they are the
hardest to separate for the commonest color vision deficiency.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- IQEX (0.4/0.6/0.8): retract_restart_extra_toolchange 1 -> 0 on all four
tools. With the silicone ooze blockers the parked nozzle stays primed,
so an extra 1 mm after each toolchange can over-extrude at the restart.
- Add 0.04mm Ultra Fine and 0.08mm Extra Fine @Xplorer 0.4 on the existing
fdm_process_xplorer_common ladder for all four 0.4 printers. Same speeds,
accelerations and line widths as the 0.12mm Fine tier; only the layer
height, shell layers, bottom_shell_thickness 0.6 and (0.04) top solid
infill flow differ. Support stays off, as on the other tiers.
- Xplorer_buildplate_model.stl: decimated from 82,508 to 10,000 triangles
(4.1 MB -> 500 KB). Same bounding box and origin, so bed_model is unchanged.
- Covers: one distinct 240x240 image per model instead of the same image
copied four times (39 KB each -> about 11 KB each).
load_obj emits the second triangle of a quad from corners 0, 2 and 3,
but read its texture coordinates from corners 0, 1 and 2, so half of
every textured quad sampled the wrong part of the texture. The corner
indices are now passed down to where the coordinates are read.
A mesh with inward-facing triangles is flipped after loading, which
swaps corners 1 and 2 of every face. The texture coordinates were left
as they were. They are now swapped along with the corners.
* Escape Project Metadata in the Project Page and Restrict Accessory Opening
The Project page rendered the model and profile name, author, description
and accessory file names from the 3MF as live HTML. Names, authors and file
names are now set as text, and the file list is built from DOM nodes with
bound click handlers instead of concatenated markup. Descriptions can
legitimately carry rich-text HTML, so they are rebuilt from an inert
DOMParser document, keeping only plain formatting tags, http(s) links and
http(s) images, with every other attribute dropped.
Opening an accessory from the page now only launches regular files that
lie inside the project's extracted auxiliary directory. The containment
check is a new libslic3r helper, is_absolute_path_within_root, built on
is_path_within_root so symlinks leading out of the root are rejected too.
* Tighten Project Page Description Rendering and Keep More Formatting
Link and image URLs in descriptions must now start with an http or https
scheme as written and parse as such with the URL parser. Preview images are
built as DOM nodes like the file list, and accessory names show their full
text as a tooltip.
Descriptions keep more plain formatting: del, ins, figure, figcaption, dl,
dt, dd, caption, q, abbr, kbd and wbr, plus alt, title, width and height on
images, colspan and rowspan on table cells and start on ordered lists.
Numeric attributes must be plain integers. Embedded YouTube players become
a link to the video.
* Confirm Before Opening Program Attachments and Load Only HTTPS Images
Opening a project attachment whose type runs as a program or script
(executables, installers, shortcuts, shell and PowerShell scripts, macOS
command files and apps, Linux desktop entries) now asks for confirmation
first. The check lives in libslic3r as is_executable_file_name and ignores
the trailing dots and spaces Windows strips from file names.
Images in project descriptions are kept only when they load over https,
so opening the Project tab no longer issues plain-http requests.
* Open Project Attachments Through One Guarded Helper
The Edit Project Info view launched attachments directly, without the
checks the project page has. Both now call
desktop_open_project_attachment, which checks that the file is inside
the auxiliary directory, asks for confirmation where needed and then
opens it.
The auxiliary root was built through encode_path, which returns code
page bytes on Windows, while boost::filesystem reads a narrow string as
UTF-8. With a non-ASCII temporary directory the root never matched and
no attachment opened. It is now built from the UTF-8 path directly.
The list of program extensions could not be kept complete and let
unknown types open without a prompt. It is replaced by
is_safe_to_open_file_name, a list of plain document, image, model and
video types that open directly. Everything else asks first.
* Stop Malformed Network Responses from Crashing the App
Duet, MKS and UltiMaker parsed print host replies with boost read_json
inside the HTTP completion callback with no try, so an HTML or truncated
reply threw out of the Physical Printer Test button and terminated the app,
or killed the upload queue thread. The five identical copies of the parser
(ESP3D's and Flashforge's were unused) are replaced by one shared
PrintHost::get_err_code_from_body that reports a non-JSON reply as an error.
The upload queue now catches a failing job per job, so one bad upload no
longer leaves later jobs queued forever.
Flashforge read material station slots with nlohmann value(), which throws
on off-type fields or non-object entries. The parsing moves into
Flashforge::parse_material_slots, which reads fields leniently with the
existing try_parse_json_int and skips bad entries.
UserManager::parse_json parsed the payload before its try block; the parse
now happens inside it.
* Keep UploadFinished Paired with UploadStarted When an Upload Throws
The exception from a throwing upload was caught around perform_job, so
the UploadFinished lifecycle event was skipped and plugins saw an
upload start that never finished.
The catch now sits around the upload call. The error is reported
through the job's error callback and UploadFinished is fired with an
error code, as for any other failed upload. The worker keeps running
for the next job. The started, upload and finished sequence moved to
PrintHostJobQueue::upload_job so it can be tested without the dialog.