Make the connected-net layout affordable on a dense patch
Laying a patch out as a connected net cost ~175 ms on a 42k-triangle patch,
against ~21 ms for the unwrap it works from, and the gizmo asks for it on every
preview, overlay and bake. Measured on a real project the grid behind it ran
~19 million triangle-pair tests per net, nearly all of them misses: a cell
holds every triangle whose box touches it, and a candidate really meets a
couple of them.
Keep a bounding box with each stored triangle and answer those misses with four
comparisons instead of a full intersection. The net drops to ~53 ms with
identical output - the seam metrics on the test project did not move by one.
Two further attempts were measured and dropped, and are recorded in the comment
so they are not tried again: a free-space pre-check per chart came out slower,
because a folded chart lands against the net by construction and the cells
under it are occupied anyway, and splitting the boxes into their own array for
locality lost more to growing two vectors per bucket than it gained.
Also pick a pair's fold line from the longest boundary they share rather than
whichever edge came first, and grow the net strongest-adjacency-first rather
than breadth-first by area. Only the fold a chart is reached by comes out
matching, so a chart claimed across a short boundary leaves the long one it
shared with its true neighbour torn.
texture_unwrap_dump reports an unwrap from a saved project - charts, their
topology, folded triangles, where the texture is discontinuous and how long
those seams are. All of the above was found with it, and it is what keeps a
claim about this code honest; reading the 3D view and guessing had produced
three wrong diagnoses in a row.
Its toggle handler did not call Skip(), so CheckBox's own handler, which
redraws the tick, never ran. Each click still flipped and saved the
setting, but the box kept showing it ticked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Multimaterial page showed an IDEX/IQEX section in Simple mode on
every FFF printer. The pre-slice warnings row's hand-built option
definition left its mode at comSimple; it now uses comAdvanced, like the
IMEX options around it. The parallel modes grid had a group of its own
holding only a full-width widget line, which records no mode, so that
group showed in every mode. The grid now sits in the configuration
group, which follows its Advanced rows. That group shows or hides
everything in it, so the grid keeps itself hidden on non-IMEX printers
(IMEXModesCtrl::set_applicable).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Explain the texture displacement views that cannot be used, and show them
Checker and Distortion are views of a layer's unwrap, so they mean nothing on
any other mapping - but they were offered on all of them and simply drew
nothing when picked. Fade them out on anything but Unwrap (LSCM), with the
reason in the tooltip, and have a click bring the UV editor up on the view it
selected, since that pane is where the unwrap is actually worked on.
Neither view was visible even when it did apply. The overlay is built from the
base patch and drawn with a polygon offset, which biases depth values rather
than moving geometry, so it cannot win against the displaced preview standing
in front of it. Leave that preview out while a UV-check view is on and draw the
undisplaced surface instead, which is what the offset assumes and what the
mapping being inspected belongs to.
In the UV editor, a tool that cannot be used right now is faded rather than
disabled. A disabled window gets no mouse events on GTK or MSW, so every one of
those tools - Cut, Join, Unjoin, Clear seams, Clear UV edits, the select modes,
Snap, Frame - silently had no tooltip in the state where the user most needs to
know what is missing. Each now says what to do instead. The tile size, the
island statistics, the status line and the three select modes gained tooltips
of their own; the select modes now name the gestures they enable, which were
documented nowhere.
* UV editor: keep the mouse capture balanced
The canvas captured the mouse on every button press without checking whether
it already held one, released it in a single place, and handled no capture
loss at all. Two sequences leaked a capture: pressing a second button during a
drag nested a second one that the single release on button-up could not undo,
and a modal R/S skipped that release entirely while waiting for a confirming
click that may never come.
A leaked capture is not a local problem on macOS, where wxEVT_MOUSE_CAPTURE_LOST
is never sent and nothing recovers it. While any wx window holds a capture,
wxOSX routes every mouse event to that window and never calls through to
NSWindow, so the application stops seeing motion and enter/leave, and native
tooltips stop appearing anywhere in it.
Capture through grab_mouse()/drop_mouse() so there is at most one, give it back
on any button-up including a modal gesture (which tracks the pointer and needs
no capture), and cancel on wxEVT_MOUSE_CAPTURE_LOST: commit nothing, put back
what a modal rotate or scale already applied, and do not release a capture that
is already gone.
---------
Co-authored-by: ExPikaPaka <mrfsfyt@gmail.com>
* Fix a crash on loading a 3MF with empty project settings
opt_float() dereferences what option<>() returns without checking it, and
option<>() is called with create = false. Three CLI sites read printable_height
that way, so a 3mf whose Metadata/project_settings.config holds an empty object
takes the CLI down with a null dereference. Both models shipped in
resources/handy_models are such files, so `--info` on either of them segfaults.
Guard the three reads the way the neighbouring reads of
extruder_clearance_height_to_rod and friends already are. All three target
variables are initialised to 0 and the consumer tests for > 0, so an absent
setting already had a defined meaning and nothing changes for a project that
carries the setting.
* Add a CLI regression test for a project with empty settings
Runs --info over a copy of a shipped model whose Metadata/project_settings.config
has been rewritten to an empty object, so the test keeps covering the crash no
matter what settings the shipped models carry later.
Verified both ways: the test passes against this branch and fails with a
segmentation fault against a build without the guards.
---------
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
Every BambuStudio project opened with a "BambuStudio Project" info dialog
(or, from BambuStudio 2.8.2, one saying the file is newer than the
compatible version and to update the software), followed by the
configuration-substitution dialogs for the project and its embedded
presets. None of them asks anything and all of them fire for every
BambuStudio file.
For BambuStudio projects (untagged files newer than 2.3.2, the existing
test) log the version with the unrecognized settings, and each replaced
value, instead. The geometry-only, invalid-values and G-code safety
dialogs stay, and other 3MFs are unchanged.
The Slice-plate hover popup (FilamentGroupPopup, a wxPopupTransientWindow)
takes the mouse capture while it is shown, and on macOS its OnIdle handler
reacquires that capture whenever the cursor sits outside the popup. If the
popup is still shown when the modal filament grouping dialog opens, wx routes
every dialog mouse event to the now-hidden popup, because WX_filterSendEvent
short-circuits to the capture window while GetCapture() is non-null. The
dialog's filament blocks never receive a mouse-down, so they can't be dragged
and the whole app looks frozen even though its modal loop is healthy and the
keyboard still works.
Dismiss the popup synchronously before the dialog opens: Dismiss() hides it
and releases the capture, and hiding it stops OnIdle from reacquiring. This is
a no-op where the popup is never shown (Linux, where the hover popup is
disabled, and any non-dual-nozzle printer).
MoonrakerPrinterAgent filled physical_extruder_map from AFC's per-lane
extruder_index whenever the printer's filaments were synced. IMEX does
not need it: printers carry their map in the printer profile, and IMEX
reads it from there. The sync also wrote device state into the edited
printer preset, so a sync could mark the preset modified or replace a
hand-tuned map. The two Moonraker agent files go back to upstream's
version. The sync can return separately, writing the map somewhere other
than the printer preset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The comment said the lambda takes off "the instance part" of m_origin.
On a belt m_origin has been rotated by on_set_origin() by then, so
m_origin minus the plate offset is not the instance shift. Say what the
code does: undo what point_to_gcode() added and what the writer took
off. Comment only.
Reported by raistlin7447 in the review of #14394.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The by-layer collect_layers_to_print() dropped every merged layer group
in which nothing prints, and the per-object overload drops every belt
entry that prints nothing. The merged groups are built only from the
per-object entries, so after the second drop every group holds at least
one entry that prints and the first never removed anything. Remove it
and keep its explanation at the drop that does the work. No output
changes.
Reported by raistlin7447 in the review of #14394.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The leading-edge-only brim is the outer brim cut down to the part's first
contact with the belt. The cut was taken at the first layer with
geometry, but with an overhang on the leading side that layer is the
overhang's tip, which is sliced before the part reaches the belt and
does not touch it. The cut then lay ahead of the part: a 30 mm fin
left a sliver of brim well ahead of a 20 mm cube, and a 40 mm fin, or a
leading brim length of 0, left none at all.
The footprint loop already finds the layers that touch the belt (their
contact band is not empty); record the first of them and cut there.
The new test slices a cube with and without a 30 or 40 mm fin over its
leading end and checks that the leading-edge brim is the same. It fails
without the fix.
Reported by raistlin7447 in the review of #14394.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin enabled at startup loads before the main frame exists, so a
dock panel it opens from on_load was dropped by the one-shot CallAfter
that found no plater. Opened a moment later, before the frame was laid
out, the pane was sized against the unsized frame and track_docked_size
kept that width. Poll until the plater is shown on screen, then build
the pane; release the reserved id instead when the app is closing.
The bed-axes toggle added for the Design tab's reference planes reads
m_design_sketch_tool, which only exists under SLIC3R_CAD. Compute the
flag once and read the sketch tool inside the same guard as its other
uses.
The last headers with a using or namespace alias at namespace scope:
- TCPConsole.hpp imported boost::asio::ip::tcp into Slic3r::Utils for
two member declarations. The alias is now a private member of the
class.
- WebSocketClient.hpp declared four namespace aliases and a tcp alias
at global scope, each used only by the header. The names are spelled
out.
- Repair.hpp aliased CGAL::Polygon_mesh_processing as PMP in
Slic3r::tex2color. The three functions that use it declare the alias
themselves.
- PreciseSeam.hpp, Thumbnails.hpp and MarchingSquares.hpp used a
using-declaration or directive for one or two spots each; those spots
are qualified. Thumbnails.hpp's "PNG"sv default argument becomes
"PNG", which converts to the std::string_view parameter the same way.
- tests/sla_print/sla_test_utils.hpp had "using namespace Slic3r;" and
tests/filament_group/fg_test_serialization.hpp "using json =
nlohmann::json;" at global scope. The headers qualify their own names;
the two SLA test sources get the directive themselves.
Also removed: twelve type aliases in headers that nothing references
(ConflictObjName, CircleSqf, CircleSqd, TRawBuffer, DistanceFunction,
SamePair, ExtruderNozzleInfos, Vec2dEvent, Vec2dsEvent, Vec3dEvent,
t_option, t_optgroups, Plater::fs_path) and a duplicate
fn_ft_job_msg_destroy alias in FileTransferUtils.hpp.
46 files include the same header twice at file scope, outside any #if,
66 times in all:
Model.cpp included Model.hpp twice, Utils.hpp <algorithm> and
<string_view> twice, seven GUI headers <wx/dataview.h> and
<wx/artprov.h> twice. The second include of each is removed.
GCodeSender.cpp and GCodeSender.hpp have been commented out of
libslic3r/CMakeLists.txt since 2022 and their only two includes are
commented out as well. Both files go, with the commented lines, and
the CMake entry for SLA/SupportTreeIGL.cpp, a file that no longer
exists.
The mixed-colour metadata options are parallel per-slot arrays in the project
config. A project saved before they were sized per slot stores a single value
for the gradient ones, and one saved before they existed stores none. The GUI
sizes all seven to the filament count when it opens a project; the CLI kept
the stored arrays and exported one-element defaults for absent ones, so a
project it exported carried one-element arrays where the GUI writes one entry
per filament. Slicing is unaffected, every reader treats a missing entry as
not mixed / no gradient, but the GUI-vs-CLI comparison reported the four
gradient keys on every mixed-filament project.
The resize helper moves from PresetBundle.cpp, where it was file-local, to
PrintConfig.cpp next to set_filament_dev_options(). It creates an option the
config lacks before sizing it, a no-op for the bundle's project config where
all seven always exist. The CLI calls it with its filament count once the
project and loaded filaments are merged, after the check that every mixed
slot has a filament of its own.
The leading-edge-only brim is the outer brim cut down to the part's
first contact with the belt, and the cut was taken at layers().front().
Since the slicing frame starts at the belt below the footprint (#16236)
that is an empty lead-in layer whose contact lies ahead of the part, so
the cut removed the whole region and the brim type produced no brim at
all. Take the cut at the first layer with geometry.
The all-brim-types test now includes leading_edge_only, and a new test
checks that the brim starts no later than the part and covers fewer
layers than the outer brim.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Guard the layer count and the per-object layer collection against an
object that is left without a layer to print on a belt (the counting
loop stepped before begin() and front() was taken of an empty vector).
Check the belt temperature tower's embossed model before the current
project is replaced, not after. Only invalidate the support step of
objects that own a belt brim when an object is added or removed. The
empty-layers test now counts an extrusion only where material is laid
down along a move. The BeltBrim.cpp SEQUENCING note says exactly which
layers are read, and the machine-frame scale is 1/|sin|.
Remove more code that nothing calls: the kinematics inverse
(to_logical, apply_axis_remap_inverse, to_build_volume and the state
kept for them), the world_coordinates(), is_active() and
belt_brim_areas_by_layer() accessors, the PrintConfig overload of
physical_tilt() and the DynamicPrintConfig overload of
compute_belt_height_and_floor(). Comments in GCode.hpp,
BeltSliceStrategy.hpp/.cpp and PrintObjectSlice.cpp that described the
retired pre-slice remap and plane-evaluator still did; the purge-tower
width tooltip named the wrong switch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
BeltBrim.cpp still described the brim as running inside the parallel
support step; it runs sequentially after it (generate_belt_brim). The
GCodeWriter, calib.cpp and calib.hpp comments referred to an inheritance
layout and a dynamic_cast that no longer exist.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
belt_remapped_bbox() had no callers; belt_min_z() and
m_belt_global_xy_correction were written but never read;
LayerTools::has_belt_brim was set but never read; belt_surface_z was a
named zero.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The machine-frame transform is a shear of cot(tilt) and a scale of
1/sin(tilt), not tan and 1/cos; fix the tooltip and the matching comment
in BeltGCode.cpp. The gcode_remap_* labels and tooltips were passed
through L() as variables inside a lambda, which the string extraction
does not see; pass L("literal") at the call sites.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The calibration fell back to the 230-190 tower when no embossed model
existed for the requested range, so the printed numbers did not match
the temperatures. Show an error naming the range and stop instead.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
update_fff() derives build_plate_tilt_x/y from the belt tilt on a belt
printer, so a value typed into those fields was silently overwritten.
Disable the two fields while belt_printer is on.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A belt brim is clipped against the other objects on the plate and is
built with its object's support step. When an object was added or
deleted only the print-level skirt/brim and export steps were
invalidated, so the remaining objects kept brims clipped against objects
that were no longer there, or overlapping ones that had arrived.
Invalidate posSupportMaterial on every object in that case on a belt
printer. Also reword the comments that still described the global Z
offset as a minimum across all objects.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The first-layer rule that prints the outer wall first when a brim is
attached to it, and the brim_type change rule that regenerates the
perimeters for it, only knew btOuterOnly. btLeadingEdgeOnly, the belt
brim at the part's first contact, is an outer brim too.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
posSlice's invalidation list did not include posDetectOverhangsForLift.
A re-slice starts the layers over with empty overhang regions while the
step stayed done, so GCode::needs_retraction() had no overhangs to test
against until something else invalidated it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
collect_layers_to_print() drops the belt layers that print nothing (an
object's empty lead-in), but m_layer_count still counted every object
and support layer, so "total layers count", the total_layer_count
placeholder and the M73 progress disagreed with the layer changes in the
file. Count with the same predicate, shared through
belt_object_layer_prints_something(). The by-object overload of
collect_layers_to_print() now drops those layers as well, so both print
sequences write the same layer changes.
Dropping the empty entries per object has one more effect on multi-
filament belt prints: a layer no longer selects a filament that it then
prints nothing with. On belt_project.3mf (two filaments, belt purge
tower) the T commands go from 472 to 106 while the extruded length per
filament is unchanged; each of the removed tool changes was followed by
no extrusion.
The empty-layers test now runs for both print sequences and also checks
"total layers count" against the layer changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
In by-object printing the cooling buffer is reset for every object, but
m_belt_in_band, which tracks whether the extrusion is inside the band
along the belt where the part fan stays off, kept the previous object's
value. If the previous object ended inside the band the next one never
emitted its band start marker. Reset it with the cooling buffer.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>