Compare commits

..
Author SHA1 Message Date
ExPikaPaka 3085e7ae53 Say what to do about the running operation, not just that it blocks
Review feedback: "Stop it first" is the only way out the old text offered,
and stopping is rarely what the user wants. Waiting for the operation to
finish works just as well, so the notification now names both.
2026-10-09 09:00:43 +02:00
ExPikaPaka 9d2568853e Merge remote-tracking branch 'origin/main' into fix/no-undo-during-job
# Conflicts:
#	src/slic3r/GUI/Plater.cpp
2026-10-09 09:00:35 +02:00
ExPikaPaka 6d616c288a Texture displacement: speed up the connected-net layout 3x (#16149)
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.
2026-10-09 14:23:23 +08:00
ExPikaPakaandExPikaPaka bff19b07d2 Texture displacement: fix the Checker and Distortion views, add UV editor tooltips (#16147)
* 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>
2026-10-09 12:14:47 +08:00
yw4z 25ec403156 Unify wiki & video guide buttons (#16159)
* init

* fix build

* Update PrintOptionsDialog.cpp

* Update TroubleshootDialog.cpp
2026-10-09 12:13:00 +08:00
ExPikaPakaandRodrigo Faselli 018c4f49ee Fix a crash on loading a 3MF with empty project settings (#16016)
* 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>
2026-10-09 12:07:03 +08:00
ExPikaPaka 4c74d84b94 Fail cleanly on a truncated or corrupt PNG (#16014)
libpng reports a bad file by longjmp()ing back to the buffer set with
setjmp(), and the frame it lands in must own nothing that needs
destroying: with exceptions enabled MSVC unwinds the stack as part of
longjmp, and returning from a frame unwound that way crashes. It did on
Windows while working everywhere else.

The read callback also returned quietly on a short read, leaving libpng
to decode whatever happened to be in the output buffer.

The calls that can fail now sit in two helpers that own nothing but
pointers, so every C++ object the decoders need stays in their own
frames, and a short read is reported through png_error().
2026-10-09 11:52:55 +08:00
HanifKoh 65978fc87a Cap Recursion Depth in the Plugin JSON Converters (#16255)
* Cap Recursion Depth in the Plugin JSON Converters

* Add Missing Includes to the Plugin JSON Depth Test
2026-10-09 11:39:46 +08:00
HanifKoh 9a9285a894 Log Instead of Showing Info and Substitution Dialogs When Opening a BambuStudio Project (#16153)
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.
2026-10-09 11:33:53 +08:00
HanifKoh ae59656fa4 Dismiss the Slice-Plate Popup Before the Filament Grouping Dialog Opens (#16284)
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).
2026-10-09 11:33:24 +08:00
Misterff1 627ae12dd3 Fix regression: bring back some machine specific BBL P2S start gcode (#15967)
Fix removal of custom code for machine_start_gcode for Bambu Lab P2S
2026-10-09 11:21:35 +08:00
Harm Berntsen b3490a1cdc Prevent segfault when X509_get_default_cert_file_env() environment variable is not set (#16128) 2026-10-08 22:39:26 -03:00
TheLegendTubaGuy c7e7fa2a0b Multi Object Vase Settings from Plate Settings Change (#16091) 2026-10-08 20:40:52 -03:00
lodriguez 16380e8560 add fallback to link spnav dynamiclly (#14223) 2026-10-08 20:35:47 -03:00
Ian Bassi 785a1946a6 Fix Adaptive and Support Cubic infill density with modifiers (#16295) 2026-10-08 18:28:56 -03:00
Kris Austin 8790b07773 fix: missing chamfers on STEP import since the OCCT 8.0.1 update (#16290) 2026-10-08 18:10:37 -03:00
mosfet80 1ec9b315f5 Update CMake minimum version to 3.10 (#16097) 2026-10-08 17:43:38 -03:00
Fernando Marino` e6be22dd4c Assign lib_name from $1 in has_host_runtime_library (#15912) 2026-10-08 17:22:57 -03:00
Rodrigo Faselli ee2c40ea85 Fix Mesh Boolean negative (cut) text object (#16275)
* Fix Mesh Boolean negative (cut) text object

* Update test_meshboolean.cpp

* missing headers

* Update test_meshboolean.cpp

* Refactor mcut difference test for source splits

* Apply suggestion from @raistlin7447
2026-10-08 16:00:28 -03:00
Error404JoyNotFound 9d32c1c545 docs: correct layer_num index tooltip to 0-based (fixes #10340) (#15984) 2026-10-08 15:44:03 -03:00
Ian Chua 67a16dabca fix: prompt for permission when plugin tries to create a thread (#16248)
* fix: prompt for permission when plugin tries to create a thread

* fix: request permission on main thread
2026-10-08 23:49:33 +08:00
Ian Bassi 59fc97fb28 Extend Separated Infills (#16274) 2026-10-08 09:43:46 -03:00
LixNix 7d44b60ae4 H2D X2D Multi Nozzle Size Printing Support (#14125) 2026-10-08 20:28:32 +08:00
ExPikaPakaandExPikaPaka 776ca6a3e4 build_linux.sh: add -J to build several dependencies at once (#16282)
* build_linux.sh: add -J to build several dependencies at once

The top-level deps build is fixed at -j1, so one dependency compiles at a
time while the small ones leave most cores idle. -J N raises that level.

-j still applies in full to each dependency, so the worst case is -J times
-j compile jobs: ninja has no job server to share a pool across the nested
builds. Without -J nothing changes.

* Quote the job count for shellcheck (SC2086)

---------

Co-authored-by: ExPikaPaka <mrfsfyt@gmail.com>
2026-10-08 18:56:02 +08:00
SoftFeverandxxxsam ee26e94170 Fix H2D prints rejected for a missing Filament Track Switch
Fixes #15927

Co-authored-by: xxxsam <31843242+xxxsam92123@users.noreply.github.com>
2026-10-08 17:49:04 +08:00
Ian Chua d401d4f837 revert: profile changes made for OTA testing (#16280) 2026-10-08 16:13:12 +08:00
HanifKoh 18b70844d1 Defer Plugin Dock Panes Until the Plater Is Shown (#16257)
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.
2026-10-08 14:18:51 +08:00
HanifKoh c8f2498681 Keep GLCanvas3D Building With SLIC3R_CAD Off (#16256)
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.
2026-10-08 14:02:34 +08:00
HanifKoh 30902561c0 Stop Exporting Names Through Usings in the Remaining Headers (#16250)
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.
2026-10-08 13:59:12 +08:00
HanifKoh 3fc515f48d Prompt for Permission When a Plugin Calls os.exec (#16254)
* Prompt for Permission When a Plugin Calls os.exec

* Add a ProcessReplace Audit Category for os.exec
2026-10-08 13:52:09 +08:00
HanifKoh 6cd5feed79 Remove Duplicate Includes and the Dead GCodeSender Sources (#16249)
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.
2026-10-08 13:51:50 +08:00
HanifKoh 09530ef7c4 Size a Project's Mixed-Colour Metadata to the Filaments in the CLI (#16247)
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.
2026-10-08 13:40:36 +08:00
ExPikaPaka ea60911bd3 Block undo and redo while a background job runs
A job is queued against the model as it stands and hands its result back
when it finishes, so undoing underneath it leaves that result landing on
geometry it was never computed for. Undo and redo now wait for the job
and say so, and can_undo()/can_redo() report the same, so the toolbar
and the menu items stay in step.
2026-09-30 09:00:29 +02:00
171 changed files with 2808 additions and 1704 deletions
+2 -2
View File
@@ -1084,10 +1084,10 @@ endif ()
find_path(SPNAV_INCLUDE_DIR spnav.h)
if (SPNAV_INCLUDE_DIR)
find_library(SPNAV_LIB NAMES libspnav.a) # Force linking libspnav statically
find_library(SPNAV_LIB NAMES libspnav.a spnav)
if (SPNAV_LIB)
add_definitions(-DHAVE_SPNAV)
message(STATUS "SPNAV library found")
message(STATUS "SPNAV library found: ${SPNAV_LIB}")
else()
message(STATUS "SPNAV library NOT found, Spacenavd not supported")
endif()
+36 -3
View File
@@ -8,9 +8,10 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
pushd "${SCRIPT_PATH}" > /dev/null
function usage() {
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-F][-g][-h][-i][-j N][-p][-r][-s][-t][-u][-l][-L]"
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-F][-g][-h][-i][-j N][-J N][-p][-r][-s][-t][-u][-l][-L]"
echo " -1: limit builds to one core (where possible)"
echo " -j N: limit builds to N cores (where possible)"
echo " -J N: build up to N dependencies at a time, each still using -j jobs (default: 1)"
echo " -b: build in Debug mode"
echo " -c: force a clean build"
echo " -C: enable ANSI-colored compile output (GNU/Clang only)"
@@ -36,12 +37,13 @@ function usage() {
}
SLIC3R_PRECOMPILED_HEADERS="ON"
DEPS_PARALLEL=""
unset name
BUILD_DIR=build
BUILD_CONFIG=Release
FORWARDED_ARGS=()
while getopts ":1j:bcCdDeFghiprstulL" opt ; do
while getopts ":1j:J:bcCdDeFghiprstulL" opt ; do
case ${opt} in
1 )
export CMAKE_BUILD_PARALLEL_LEVEL=1
@@ -51,6 +53,10 @@ while getopts ":1j:bcCdDeFghiprstulL" opt ; do
export CMAKE_BUILD_PARALLEL_LEVEL=$OPTARG
FORWARDED_ARGS+=("-j" "$OPTARG")
;;
J )
DEPS_PARALLEL=$OPTARG
FORWARDED_ARGS+=("-J" "$OPTARG")
;;
b )
BUILD_DIR=build-dbg
BUILD_CONFIG=Debug
@@ -135,6 +141,11 @@ if [[ -n "${CLEAN_DOCKER_IMAGE}" ]] && [[ -z "${USE_DOCKER}" ]] ; then
exit 1
fi
if [[ -n "${DEPS_PARALLEL}" ]] && ! [[ "${DEPS_PARALLEL}" =~ ^[1-9][0-9]*$ ]] ; then
echo "Error: -J expects a positive integer."
exit 1
fi
function check_available_memory_and_disk() {
FREE_MEM_GB=$(free --gibi --total | grep 'Mem' | rev | cut --delimiter=" " --fields=1 | rev)
MIN_MEM_GB=10
@@ -537,7 +548,29 @@ if [[ -n "${BUILD_DEPS}" ]] ; then
fi
print_and_run cmake -S deps -B deps/$BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" "${CMAKE_CCACHE_ARGS[@]}" -G Ninja "${COLORED_OUTPUT}" "${BUILD_ARGS[@]}"
print_and_run cmake --build deps/$BUILD_DIR -j1
# The top-level build runs one dependency at a time by default, which keeps the console
# output readable and lets that dependency's own build use all of CMAKE_BUILD_PARALLEL_LEVEL.
# -J raises the top level instead, and -j still applies in full to every dependency, so the
# worst case is -J times -j compile jobs at once. Ninja has no job server to share a pool
# across the nested builds, so that ceiling is not enforced anywhere: pick -J to suit the RAM.
DEPS_JOBS=1
if [[ -n "${DEPS_PARALLEL}" ]] ; then
DEPS_JOBS=${DEPS_PARALLEL}
SAVED_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL-}
export CMAKE_BUILD_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL:-$(nproc)}
echo "Building up to ${DEPS_JOBS} dependencies at a time, ${CMAKE_BUILD_PARALLEL_LEVEL} jobs each: up to $(( DEPS_JOBS * CMAKE_BUILD_PARALLEL_LEVEL )) compile jobs at once."
fi
print_and_run cmake --build deps/$BUILD_DIR -j"${DEPS_JOBS}"
if [[ -n "${DEPS_PARALLEL}" ]] ; then
# Give the whole -j back to the OrcaSlicer build below.
if [[ -n "${SAVED_PARALLEL_LEVEL}" ]] ; then
export CMAKE_BUILD_PARALLEL_LEVEL=${SAVED_PARALLEL_LEVEL}
else
unset CMAKE_BUILD_PARALLEL_LEVEL
fi
fi
fi
if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
+2 -2
View File
@@ -104,8 +104,8 @@ fi
CMAKE_VERSION=$(cmake --version | head -1 | sed 's/[^0-9]*\([0-9]*\).*/\1/')
if [ "$CMAKE_VERSION" -ge 4 ] 2>/dev/null; then
export CMAKE_POLICY_VERSION_MINIMUM=3.5
export CMAKE_POLICY_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
export CMAKE_POLICY_VERSION_MINIMUM=3.10
export CMAKE_POLICY_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.10"
echo "Detected CMake 4.x, adding compatibility flag (env + cmake arg)"
else
export CMAKE_POLICY_COMPAT=""
+4 -4
View File
@@ -1,5 +1,5 @@
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "4.0")
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "" FORCE)
set(CMAKE_POLICY_VERSION_MINIMUM 3.10 CACHE STRING "" FORCE)
endif()
#
@@ -24,7 +24,7 @@ endif()
# therefore, unfortunately, the installation cannot be copied/moved elsewhere without re-installing wxWidgets.
#
cmake_minimum_required(VERSION 3.2)
cmake_minimum_required(VERSION 3.10)
if (APPLE)
# if CMAKE_OSX_DEPLOYMENT_TARGET is not set, set it to 12.0 (the lowest Xcode 27 accepts)
if (NOT CMAKE_OSX_DEPLOYMENT_TARGET)
@@ -224,7 +224,7 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
${_source_dir_arg}
${_gen}
CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
-DCMAKE_POLICY_VERSION_MINIMUM=3.10
-DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR}
-DCMAKE_MODULE_PATH:STRING=${PROJECT_SOURCE_DIR}/../cmake/modules
-DCMAKE_PREFIX_PATH:STRING=${DESTDIR}
@@ -279,7 +279,7 @@ else()
${_source_dir_arg}
${_gen}
CMAKE_ARGS
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
-DCMAKE_POLICY_VERSION_MINIMUM=3.10
-DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR}
-DCMAKE_PREFIX_PATH:STRING=${DESTDIR}
-DCMAKE_IGNORE_PREFIX_PATH:STRING=${CMAKE_IGNORE_PREFIX_PATH}
@@ -0,0 +1,28 @@
diff --git a/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx b/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx
index 6f63781..9b1c08e 100644
--- a/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx
+++ b/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx
@@ -210,10 +210,10 @@ private:
// Define two pcurves of the seam-edge.
occ::handle<Geom2d_Curve> aPC1, aPC2;
- double af, al;
+ double af, al, af1, al1;
aE.Orientation(TopAbs_FORWARD);
- aPC1 = BRep_Tool::CurveOnSurface(aE, aF, af, al);
+ aPC1 = BRep_Tool::CurveOnSurface(aE, aF, af1, al1);
aE.Orientation(TopAbs_REVERSED);
aPC2 = BRep_Tool::CurveOnSurface(aE, aF, af, al);
@@ -224,7 +224,9 @@ private:
}
// Select the correct pcurve of the seam-edge.
- const gp_Pnt2d& aFPntOfPC1 = aPC1->Value(aPC1->FirstParameter());
+ // Use the edge's first parameter. A Geom2d_Line's FirstParameter() is -Precision::Infinite(),
+ // where a direction of (2e-16, -1) from rounding error gives an X far outside the U range.
+ const gp_Pnt2d aFPntOfPC1 = aPC1->Value(af1);
if (std::abs(aLPntOfIPC1.X() - aFPntOfPC1.X()) > Precision::Confusion())
{
+7
View File
@@ -25,9 +25,16 @@ endif()
# shipped bytes. Windows ships only the DLLs libslic3r links, so the tab adds the TKFillet,
# TKOffset and TKBool DLLs. See docs/HLSD/design-tab.md.
if (IN_GIT_REPO)
set(OCCT_DIRECTORY_FLAG --directory ${BINARY_DIR_REL}/dep_OCCT-prefix/src/dep_OCCT)
endif ()
orcaslicer_add_cmake_project(OCCT
URL https://github.com/Open-Cascade-SAS/OCCT/archive/refs/tags/V8_0_1.zip
URL_HASH SHA256=7c033d917ee8f040c0512d289dcc5f02c148889d5bac17c3e25639accb44f0da
# Makes BRepMesh triangulate cone faces whose seam pcurve is slightly tilted
# (Open-Cascade-SAS/OCCT#572); remove the patch once an OCCT release includes the fix.
PATCH_COMMAND git apply ${OCCT_DIRECTORY_FLAG} --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-BRepMesh-seam-pcurve-at-edge-parameter.patch
#DEPENDS dep_Boost
DEPENDS ${FREETYPE_PKG}
CMAKE_ARGS
+97
View File
@@ -0,0 +1,97 @@
# Separated infills — High Level Design
## Purpose and scope
An object's infill patterns are laid out from one reference point, the center
of the object. When an object groups several parts that do not touch, every
part cuts the same object-wide pattern at a different place, so equal parts get
different infill. `separated_infills` lays the infill of every connected body
out from the center of that body instead, as if the body were sliced on its own.
The option covers sparse infill, internal solid infill and bridges. Top and
bottom surfaces are left to `center_of_surface_pattern`, which centers the
Archimedean Chords and Octagram Spiral surface patterns. The option is off by
default; with it off, or for an object made of a single body, no fill changes.
Adaptive Cubic and Support Cubic do not depend on the option: they always fill
each body on its own (see Octree infill).
## Bodies
`PrintObject::prepare_infill()` groups the islands of every layer (`lslices`)
into 3D connected bodies before bridges are detected, so bridge anchors and
printed infill share one origin. Islands on adjacent layers belong to one body
when their slices overlap. Parts that touch or overlap form one body. Separate
parts, disconnected islands of one mesh, and interleaved parts that never touch,
such as chain links, each form their own. Every island stores the index of its
body in `Layer::lslices_separated_component_ids`, and
`PrintObject::separated_body_bboxes()` holds the bounding box of each body over
all its layers.
The pass runs when a region uses separated infills, per-model surface centering
or an octree infill pattern. It is skipped when the object has one model part
that cannot be split, since a single body already shares the object center.
## Centering a fill
`infill_body()` matches each fill region to the island it overlaps most, among
the islands whose bounding boxes overlap it, and the filler takes the bounding
box of that island's body instead of the object's. The box covers every layer
of the body, which is the box the body gets when sliced alone, so patterns that
depend on its extent as well as its center come out the same too. Bridge
anchoring (`Layer::generate_sparse_infill_polylines_for_anchoring()`) makes the
same choice, so the anchors match the printed infill.
The patterns follow the body's box in one of two ways:
- Rectilinear and its variants, Line, Grid, Triangles, Tri-hexagon, Cubic,
Quarter Cubic, Lateral Lattice, Lateral Honeycomb and the plane-path patterns
(Hilbert Curve, Archimedean Chords, Octagram Spiral) are laid out from the
box: they phase their lines through its center, and Hilbert Curve and the Zig
Zag links start from its corner. `Fill::extended_object_bounding_box()`
extends the box about its center, so it also serves a box that is not
centered on the origin.
- Honeycomb, 3D Honeycomb, Cross Hatch, Gyroid, TPMS-D and TPMS-FK are laid out
from the coordinate origin, which is the object center. They return true from
`Fill::aligned_to_origin()`, and `Fill::fill_surface()` moves each region so
that the box center lands on the origin, fills it, and moves the paths back.
With the default box the center is the origin, so nothing moves.
`is_separable_infill_pattern()` lists these patterns. The settings show the
option only when the sparse infill pattern is one of them.
## Octree infill
Adaptive Cubic and Support Cubic take their lines from an octree, laid out from
the center of the mesh it is built from and refined near its surfaces. An
octree of the whole object would lay every part out from the object's center
and refine it near the other parts, so these patterns
(`is_octree_infill_pattern()`) always fill each body on its own, and the
settings hide the option for them.
For an object of several bodies, `PrintObject::prepare_adaptive_infill_data()`
builds one octree per body (`FillAdaptive::Octrees`) from the triangles of that
body only, which is the octree the body gets when sliced alone. Each connected
component of the mesh goes to the body that most of a few sampled triangles lie
on. A sample is taken a layer height inside the solid, behind the triangle, and
looked up in the islands of the nearest layer. Each internal bridge surface goes
to the body of its island. The fill takes the octree of the region's body, from
the same `infill_body()`. The octree of the whole object is built only for an
object of a single body, or when some body received no triangles, which then
uses it.
The line spacing of an octree comes from the density, line width and multiline
count of a region, so a modifier or a part with its own density needs octrees of
its own. `adaptive_fill_line_spacing()` gives the spacing of each region, and
`FillAdaptive::RegionOctrees` holds one set of octrees per distinct spacing,
shared by the regions that have it. A set is built only for the bodies its
regions fill. The fill takes the set of its region, then the octree of its body.
## Patterns left out
Lightning grows its trees over the whole object, so moving a reference point
cannot center it on one body. Concentric and Spiral Inset follow the outline of
each region and need no centering.
Solid infill at full density spaces its lines over the extent of each region,
so it is already independent of the other bodies. Only bridges, which keep
their line spacing, and the plane-path solid patterns depend on the center.
+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 25 25"><circle cx="9.5" cy="12.5" r="9.5" style="fill:#009688"/><path d="M6.5,10.5a3,3,0,0,1,6,0c0,3-3,2-3,4v1" style="fill:none;stroke:#e6e6e6;stroke-linecap:round;stroke-linejoin:round"/><rect x="9" y="17" width="1" height="1" style="fill:#e6e6e6"/></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><rect x="7" y="12" width="1" height="1" style="fill:#fefefe;"/><path d="M4.5,5.5c0-1.71,1.43-3.09,3.16-3,1.62.08,2.84,1.54,2.84,3.17h0c0,.61-.25,1.2-.69,1.62l-2.31,2.24v.97" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/></svg>

Before

Width:  |  Height:  |  Size: 333 B

After

Width:  |  Height:  |  Size: 366 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 25 25"><circle cx="9.5" cy="12.5" r="9.5" style="fill:#33aba1"/><path d="M6.5,10.5a3,3,0,0,1,6,0c0,3-3,2-3,4v1" style="fill:none;stroke:#e6e6e6;stroke-linecap:round;stroke-linejoin:round"/><rect x="9" y="17" width="1" height="1" style="fill:#e6e6e6"/></svg>

Before

Width:  |  Height:  |  Size: 333 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="M12.31,8l-8.14,4.16c-.31.16-.67-.07-.67-.41V3.27c0-.35.37-.57.68-.41l8.14,4.32c.33.17.33.65,0,.82Z" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 301 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="M7.5,3.5c2-2,5.1-1,6-1" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/><path d="M7.5,12.5c2-2,5.1-1,6-1" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/><path d="M7.5,3.5C5.5,1.5,2.4,2.5,1.5,2.5" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/><path d="M7.5,12.5c-2-2-5.1-1-6-1" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/><line x1="13.5" y1="11.5" x2="13.5" y2="2.5" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/><line x1="7.5" y1="12.5" x2="7.5" y2="3.5" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/><line x1="1.5" y1="11.5" x2="1.5" y2="2.5" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 954 B

File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Elegoo",
"version": "02.04.00.12",
"version": "02.04.00.11",
"force_update": "0",
"description": "Elegoo configurations",
"machine_model_list": [
@@ -15,6 +15,6 @@
"Elegoo"
],
"filament_start_gcode": [
"; Elegoo PLA filament start gcode\n"
"; filament start gcode\n"
]
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "Orca Arena Printer",
"url": "",
"version": "02.04.00.08",
"version": "02.04.00.07",
"force_update": "0",
"description": "Orca Arena configuration files",
"machine_model_list": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "OrcaFilamentLibrary",
"version": "02.04.00.19",
"version": "02.04.00.15",
"force_update": "0",
"description": "Orca Filament Library",
"filament_list": [
@@ -39,7 +39,7 @@
"60"
],
"filament_start_gcode": [
"; Elegoo PLA filament start gcode\n"
"; filament start gcode\n"
],
"filament_end_gcode": [
"; filament end gcode \n"
+1 -1
View File
@@ -46,7 +46,7 @@ RUN set -eux; \
tar -xzf /tmp/assimp.tar.gz -C /tmp/assimp-src --strip-components=1; \
DESTDIR=/OrcaSlicer/deps/build/destdir/usr/local; \
cmake -S /tmp/assimp-src -B /tmp/assimp-build -G Ninja \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DCMAKE_POLICY_VERSION_MINIMUM=3.10 \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$DESTDIR" \
-DCMAKE_PREFIX_PATH="$DESTDIR" \
+8 -2
View File
@@ -1820,6 +1820,8 @@ int CLI::run(int argc, char **argv)
old_printable_width = static_cast<int>(old_printable_bbox.size().x());
old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
}
// A 3mf can carry an empty project_settings.config - the models in
// resources/handy_models do - and opt_float() dereferences without checking.
if (config.option<ConfigOptionFloat>("printable_height"))
old_printable_height = (int)(config.opt_float("printable_height"));
@@ -2505,7 +2507,8 @@ int CLI::run(int argc, char **argv)
orig_printable_width = static_cast<int>(orig_printable_bbox.size().x());
orig_printable_depth = static_cast<int>(orig_printable_bbox.size().y());
}
orig_printable_height = (int)(config.opt_float("printable_height"));
if (config.option<ConfigOptionFloat>("printable_height"))
orig_printable_height = (int)(config.opt_float("printable_height"));
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(":%1%, check printable size: old_printable_width=%2%, orig_printable_width=%3%, old_printable_depth=%4%, orig_printable_depth=%5%, old_printable_height=%6%, orig_printable_height=%7%")
%__LINE__ %old_printable_width %orig_printable_width %old_printable_depth %orig_printable_depth %old_printable_height %orig_printable_height;
if ((orig_printable_width > 0) && (orig_printable_depth > 0) && (orig_printable_height > 0))
@@ -4094,6 +4097,8 @@ int CLI::run(int argc, char **argv)
flush_and_exit(CLI_MIXED_FILAMENT_INVALID);
}
}
if (filament_count > 0)
resize_mixed_filament_metadata(m_print_config, size_t(filament_count), size_t(filament_count));
m_print_config.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value = printer_technology;
@@ -4615,7 +4620,8 @@ int CLI::run(int argc, char **argv)
BoundingBoxf temp_printable_bbox(temp_printable_area);
printer_plate.printable_width = static_cast<int>(temp_printable_bbox.size().x());
printer_plate.printable_depth = static_cast<int>(temp_printable_bbox.size().y());
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
if (config.option<ConfigOptionFloat>("printable_height"))
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
}
if (temp_exclude_area.size() >= 4) {
printer_plate.exclude_width = (int)(temp_exclude_area[2].x() - temp_exclude_area[0].x());
+6
View File
@@ -28,6 +28,12 @@ if (ORCA_TOOLS)
target_link_libraries(generate_system_cache libslic3r boost_headeronly)
target_compile_definitions(generate_system_cache PRIVATE ${_DEV_DEFS})
# texture_unwrap_dump: reports the LSCM unwrap of a saved project's texture displacement layers,
# chart by chart, so a defect can be reproduced from the project file instead of from a screenshot.
add_executable(texture_unwrap_dump texture_unwrap_dump.cpp)
target_link_libraries(texture_unwrap_dump libslic3r boost_headeronly nanosvg)
target_compile_definitions(texture_unwrap_dump PRIVATE ${_DEV_DEFS})
# profile_include_dump: prints what included templates contribute to a vendor's presets,
# to diff against the same tool built in BambuStudio. Built only on request.
add_executable(profile_include_dump EXCLUDE_FROM_ALL profile_include_dump.cpp)
@@ -303,10 +303,11 @@ else
fi
has_host_runtime_library() {
local lib_name path
local lib_name="\$1"
local path
if command -v ldconfig >/dev/null 2>&1; then
if ldconfig -p 2>/dev/null | grep -Fq " \$lib_name"; then
if ldconfig -p 2>/dev/null | grep -Fq "\$lib_name ("; then
return 0
fi
fi
+292
View File
@@ -0,0 +1,292 @@
// Diagnostic for the LSCM unwrap of a texture displacement layer.
//
// It exists because the defect it hunts only shows up on a real painted patch: the paint mask is built
// by TriangleSelector splitting base triangles, so the patch topology cannot be written down by hand,
// and reasoning about it from a screenshot of the 3D view had already produced three wrong diagnoses.
// This loads a saved project, rebuilds exactly the patch the bake would act on, runs the same unwrap,
// and reports what came out - per chart, so a bad one can be pointed at rather than guessed at.
//
// texture_unwrap_dump <project.3mf>
// nanosvg is header-only and libslic3r's 3mf import references it without carrying the implementation,
// so every executable that links libslic3r has to supply it. Must precede any include that pulls the
// header in, or its include guard suppresses the implementation. Same pattern as the other dev tools.
#define NANOSVG_IMPLEMENTATION
#include "nanosvg/nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvg/nanosvgrast.h"
#include <chrono>
#include <cstdio>
#include <string>
#include <functional>
#include <unordered_map>
#include <vector>
#include "libslic3r/Model.hpp"
#include "libslic3r/TextureDisplacement.hpp"
#include "libslic3r/Format/bbs_3mf.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/filesystem.hpp>
using namespace Slic3r;
namespace {
uint64_t edge_key(int a, int b)
{
if (a > b)
std::swap(a, b);
return (uint64_t(uint32_t(a)) << 32) | uint32_t(b);
}
// Boundary loops and the Euler characteristic of a face set, which together say whether a chart is the
// topological disk LSCM needs (one loop, V - E + F == 1).
void chart_topology(const indexed_triangle_set &mesh, const std::vector<int> &faces, int &loops, int &euler)
{
std::unordered_map<uint64_t, int> edge_use;
std::unordered_map<int, int> local;
for (const int f : faces) {
const stl_triangle_vertex_indices &t = mesh.indices[size_t(f)];
for (int i = 0; i < 3; ++i) {
++edge_use[edge_key(t[i], t[(i + 1) % 3])];
local.emplace(t[i], int(local.size()));
}
}
euler = int(local.size()) - int(edge_use.size()) + int(faces.size());
std::unordered_map<int, int> parent;
const std::function<int(int)> find = [&](int x) {
while (parent[x] != x)
x = parent[x] = parent[parent[x]];
return x;
};
for (const auto &[key, uses] : edge_use)
if (uses == 1)
for (const int v : { int(key >> 32), int(uint32_t(key)) })
parent.emplace(v, v);
for (const auto &[key, uses] : edge_use)
if (uses == 1) {
const int a = find(int(key >> 32)), b = find(int(uint32_t(key)));
if (a != b)
parent[b] = a;
}
std::unordered_map<int, int> roots;
for (const auto &[v, p] : parent)
roots[find(v)] = 1;
loops = int(roots.size());
}
float signed_area_2d(const Vec2f &a, const Vec2f &b, const Vec2f &c)
{
return 0.5f * ((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y()));
}
} // namespace
int main(int argc, char **argv)
{
if (argc < 2) {
std::printf("usage: texture_unwrap_dump <project.3mf>\n");
return 2;
}
Model model;
DynamicPrintConfig config;
ConfigSubstitutionContext ctx(ForwardCompatibilitySubstitutionRule::Enable);
PlateDataPtrs plate_data;
std::vector<Preset *> project_presets;
bool is_bbl_3mf = false, is_orca_3mf = false;
Semver file_version;
// The importer writes a backup copy under the data dir and silently loses objects without one.
const boost::filesystem::path tmp = boost::filesystem::temp_directory_path() / "texture_unwrap_dump";
boost::filesystem::create_directories(tmp);
set_data_dir(tmp.string());
// LoadModel so the meshes come through; AddDefaultInstances because an object with no instance is
// dropped by the plate mapping, which is what "skip this object" in the log means.
if (!load_bbs_3mf(argv[1], &config, &ctx, &model, &plate_data, &project_presets, &is_bbl_3mf, &is_orca_3mf,
&file_version, nullptr,
LoadStrategy::LoadModel | LoadStrategy::LoadConfig | LoadStrategy::AddDefaultInstances |
LoadStrategy::Silence)) {
std::printf("failed to load %s\n", argv[1]);
return 1;
}
std::printf("loaded: %zu object(s)\n", model.objects.size());
for (const ModelObject *object : model.objects)
for (const ModelVolume *volume : object->volumes) {
if (volume->texture_displacement_layers.empty()) {
std::printf("volume \"%s\": no texture displacement layers; paint masks per slot:",
volume->name.c_str());
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
std::printf(" %zu", volume->texture_displacement_facet(i).get_data().triangles_to_split.size());
std::printf("\n");
continue;
}
std::printf("volume \"%s\": %zu base triangles, %zu layer(s)\n", volume->name.c_str(),
volume->mesh().its.indices.size(), volume->texture_displacement_layers.size());
for (const TextureDisplacementLayer &layer : volume->texture_displacement_layers) {
std::printf("\n layer %d \"%s\" mapping=%d seam_angle=%.1f connect=%d islands_stored=%zu\n",
layer.slot, layer.name.c_str(), int(layer.projection_method),
layer.lscm_seam_angle_deg, int(layer.auto_connect_islands), layer.islands.size());
if (layer.projection_method != TextureProjectionMethod::LSCM)
continue;
const indexed_triangle_set patch =
extract_painted_patch(volume->mesh().its, volume->texture_displacement_facet(layer.slot).get_data());
std::printf(" patch: %zu vertices, %zu triangles\n", patch.vertices.size(), patch.indices.size());
if (patch.indices.empty())
continue;
const auto t0 = std::chrono::steady_clock::now();
const PatchUnwrap unwrap = compute_patch_unwrap(patch, layer.lscm_seam_angle_deg, 0.f,
layer.lscm_seam_edges);
const auto t1 = std::chrono::steady_clock::now();
std::printf(" TIMING compute_patch_unwrap: %.0f ms\n",
std::chrono::duration<double, std::milli>(t1 - t0).count());
std::printf(" unwrap: %d charts, %zu unwrapped triangles\n", unwrap.chart_count,
unwrap.indices.size());
// Group the patch's faces by chart so each can be examined on its own.
std::vector<std::vector<int>> chart_faces(size_t(std::max(unwrap.chart_count, 0)));
for (size_t i = 0; i < unwrap.indices.size(); ++i) {
const int chart = unwrap.vertex_chart[size_t(unwrap.indices[i][0])];
if (chart >= 0 && size_t(chart) < chart_faces.size())
chart_faces[size_t(chart)].push_back(unwrap.source_face[i]);
}
int bad_charts = 0;
for (size_t c = 0; c < chart_faces.size(); ++c) {
int loops = 0, euler = 0;
chart_topology(patch, chart_faces[c], loops, euler);
// Flipped triangles: the unwrap folded over itself, which is what a planar fallback
// does to a chart that is not flat. Measured on the unwrap's own triangles.
int pos = 0, neg = 0;
for (size_t i = 0; i < unwrap.indices.size(); ++i) {
const stl_triangle_vertex_indices &t = unwrap.indices[i];
if (unwrap.vertex_chart[size_t(t[0])] != int(c))
continue;
const float a = signed_area_2d(unwrap.uvs[size_t(t[0])], unwrap.uvs[size_t(t[1])],
unwrap.uvs[size_t(t[2])]);
if (a > 0.f) ++pos; else if (a < 0.f) ++neg;
}
const int flipped = std::min(pos, neg);
const bool disk = loops == 1 && euler == 1;
if (!disk || flipped > 0) {
++bad_charts;
std::printf(" chart %2zu: %4zu faces loops=%d euler=%d%s flipped=%d/%d%s\n", c,
chart_faces[c].size(), loops, euler, disk ? "" : " NOT A DISK", flipped,
pos + neg, flipped ? " FOLDED" : "");
}
}
std::printf(" charts with a defect: %d / %d\n", bad_charts, unwrap.chart_count);
// What the eye actually sees. Every patch edge shared by two charts should carry the same
// UV on both sides once the islands are laid out as a connected net; where it does not,
// the texture jumps across that seam. Measured through compute_lscm_uvs(), i.e. the exact
// coordinates the bake and the checker overlay sample.
{
const auto n0 = std::chrono::steady_clock::now();
const std::vector<TextureIsland> net = compute_connected_net(unwrap);
const auto n1 = std::chrono::steady_clock::now();
std::printf(" TIMING compute_connected_net: %.0f ms (%zu islands)\n",
std::chrono::duration<double, std::milli>(n1 - n0).count(), net.size());
}
const auto t2 = std::chrono::steady_clock::now();
const std::vector<Vec2f> uv = compute_lscm_uvs(patch, layer);
const auto t3 = std::chrono::steady_clock::now();
std::printf(" TIMING compute_lscm_uvs: %.0f ms (called on every preview, overlay and bake)\n",
std::chrono::duration<double, std::milli>(t3 - t2).count());
if (uv.size() != patch.vertices.size()) {
std::printf(" compute_lscm_uvs returned %zu uvs for %zu vertices\n", uv.size(),
patch.vertices.size());
continue;
}
// Per-corner UVs carry each chart's own placement, so an edge shared by two charts shows
// the jump directly: the same mesh vertex lands at two different UVs. That is exactly what
// the eye reads as the texture breaking.
const auto t4 = std::chrono::steady_clock::now();
const std::vector<Vec2f> corner = compute_lscm_corner_uvs(patch, layer);
const auto t5 = std::chrono::steady_clock::now();
std::printf(" TIMING compute_lscm_corner_uvs: %.0f ms\n",
std::chrono::duration<double, std::milli>(t5 - t4).count());
// Keyed by edge, holding the UV each incident face gives to the edge's *lower-numbered*
// endpoint. Comparing that same vertex on both sides is the point: indexing by corner
// position instead compares opposite ends of the edge, because the two faces wind it in
// opposite directions.
std::unordered_map<uint64_t, std::vector<Vec2f>> edge_seen;
if (corner.size() == patch.indices.size() * 3)
for (size_t f = 0; f < patch.indices.size(); ++f) {
const stl_triangle_vertex_indices &t = patch.indices[f];
for (int k = 0; k < 3; ++k) {
const int a = t[k], b = t[(k + 1) % 3];
const int probe = std::min(a, b);
const int local = (a == probe) ? k : (k + 1) % 3;
edge_seen[edge_key(a, b)].push_back(corner[f * 3 + size_t(local)]);
}
}
// Which chart each patch face belongs to, so a broken edge can be attributed to a pair.
std::vector<int> chart_of_face(patch.indices.size(), -1);
for (size_t i = 0; i < unwrap.indices.size(); ++i)
chart_of_face[size_t(unwrap.source_face[i])] = unwrap.vertex_chart[size_t(unwrap.indices[i][0])];
std::unordered_map<uint64_t, std::vector<int>> edge_faces;
for (size_t f = 0; f < patch.indices.size(); ++f) {
const stl_triangle_vertex_indices &t = patch.indices[f];
for (int k = 0; k < 3; ++k)
edge_faces[edge_key(t[k], t[(k + 1) % 3])].push_back(int(f));
}
int adjacent = 0, broken = 0, broken_same_chart = 0;
float worst = 0.f;
std::map<std::pair<int, int>, std::pair<int, float>> by_pair;
for (const auto &[key, seen] : edge_seen) {
if (seen.size() != 2)
continue;
++adjacent;
const float d = (seen[0] - seen[1]).norm();
if (d <= 1e-4f)
continue;
++broken;
worst = std::max(worst, d);
const auto &faces_here = edge_faces[key];
int c1 = -1, c2 = -1;
if (faces_here.size() == 2) {
c1 = chart_of_face[size_t(faces_here[0])];
c2 = chart_of_face[size_t(faces_here[1])];
}
if (c1 == c2)
++broken_same_chart;
auto &slot = by_pair[{ std::min(c1, c2), std::max(c1, c2) }];
++slot.first;
slot.second = std::max(slot.second, d);
}
std::printf(" broken edges inside a single chart: %d\n", broken_same_chart);
std::printf(" broken by chart pair:");
for (const auto &[pk, v] : by_pair)
std::printf(" (%d,%d)x%d/%.1f", pk.first, pk.second, v.first, v.second);
std::printf("\n");
// Total length of the seams left broken, in mm: how much visibly torn edge the layout has,
// which is what the eye adds up. A count alone hides whether the breaks are hairlines or
// whole sides of an island.
float seam_mm = 0.f;
for (const auto &[key, seen] : edge_seen) {
if (seen.size() != 2 || (seen[0] - seen[1]).norm() <= 1e-4f)
continue;
seam_mm += (patch.vertices[size_t(key >> 32)] - patch.vertices[size_t(uint32_t(key))]).norm();
}
std::printf(" interior edges: %d, discontinuous: %d, total torn seam: %.2f mm (worst jump %.3f)\n",
adjacent, broken, seam_mm, worst);
std::printf(" stored islands %zu vs charts %d -> %s\n", layer.islands.size(),
unwrap.chart_count,
layer.islands.size() == size_t(unwrap.chart_count) ? "stored placements used"
: "net rebuilt");
}
}
return 0;
}
-1
View File
@@ -84,7 +84,6 @@
#include <cmath>
#include <cctype>
#include <cstdio>
#include <functional>
#include <gp_XY.hxx>
#include <initializer_list>
#include <math.h>
-3
View File
@@ -261,8 +261,6 @@ set(lisbslic3r_sources
GCode/PreciseSeam.cpp
GCode/PreciseSeam.hpp
GCode/PreciseSeamInternal.hpp
#GCodeSender.cpp
#GCodeSender.hpp
GCode/SmallAreaInfillFlowCompensator.cpp
GCode/SmallAreaInfillFlowCompensator.hpp
GCode/SpiralVase.cpp
@@ -447,7 +445,6 @@ set(lisbslic3r_sources
SLA/SupportTreeBuildsteps.hpp
SLA/SupportTree.cpp
SLA/SupportTree.hpp
#SLA/SupportTreeIGL.cpp
SLA/SupportTreeMesher.cpp
SLA/SupportTreeMesher.hpp
SlicesToTriangleMesh.cpp
-1
View File
@@ -2589,7 +2589,6 @@ void priv::create_face_types(FaceTypeMap &map,
}
#include <CGAL/Polygon_mesh_processing/clip.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper)
{
CutMesh& tm = cut.mesh;
-1
View File
@@ -15,7 +15,6 @@
#include "Emboss.hpp"
#include <optional>
#include <stdio.h>
#include <numeric>
#include <cstdlib>
#include <boost/nowide/convert.hpp>
#include <boost/log/trivial.hpp>
+31 -25
View File
@@ -29,6 +29,7 @@
#include "ExtrusionEntity.hpp"
#include "Fill.hpp"
#include "libslic3r/Fill/FillBase.hpp"
#include "FillAdaptive.hpp"
#include "FillRectilinear.hpp"
#include "FillLightning.hpp"
#include "FillConcentricInternal.hpp"
@@ -926,7 +927,6 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.lateral_lattice_angle_2 = region_config.lateral_lattice_angle_2;
params.infill_overhang_angle = region_config.infill_overhang_angle;
params.center_of_surface_pattern = region_config.center_of_surface_pattern;
params.separated_infills = region_config.separated_infills;
if (params.pattern == ipLockedZag) {
params.infill_lock_depth = scale_(region_config.infill_lock_depth);
params.skin_infill_depth = scale_(region_config.skin_infill_depth);
@@ -999,6 +999,9 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
// (which would unnecessarily split fill batching).
// Stored on SurfaceFillParams; copied to FillParams during conversion.
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized;
// Orca: Likewise separated_infills only where it can move the pattern.
params.separated_infills = region_config.separated_infills && is_separable_infill_pattern(params.pattern) &&
params.extrusion_role != erTopSolidInfill && params.extrusion_role != erBottomSurface;
if (params.extrusion_role == erInternalInfill) {
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
@@ -1073,7 +1076,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
//get locked region param
if (params.pattern == ipLockedZag){
const PrintObject *object = layerm.layer()->object();
auto nozzle_diameter = float(object->print()->config().nozzle_diameter.get_at(layerm.region().extruder(extrusion_role) - 1));
auto nozzle_diameter = float(nozzle_diameter_for_filament(object->print()->config(), layerm.region().extruder(extrusion_role), object->print()->is_BBL_printer()));
Flow skin_flow = params.bridge ? params.flow : Flow::new_from_config_width(extrusion_role, region_config.skin_infill_line_width, nozzle_diameter, float((surface.thickness == -1) ? layer.height : surface.thickness));
//add skin flow
append_flow_param(lock_param.skin_flow_params, skin_flow, surface.expolygon);
@@ -1271,29 +1274,28 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
// Orca: Anchors and printed infill must share the same body origin. Keep the choice
// here so per-model surface centering and separated sparse infill cannot drift apart.
static BoundingBox infill_bounding_box(const Layer &layer, const SurfaceFill &fill, const ExPolygon &expoly, BoundingBox bbox)
// Returns the connected body the fill region is laid out on, or -1 to keep the object's origin.
static int infill_body(const Layer &layer, const SurfaceFill &fill, const ExPolygon &expoly)
{
const auto &params = fill.params;
const auto &config = layer.regions()[fill.region_id]->region().config();
const bool external = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface;
const bool per_model = external && params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model &&
const bool per_model = (params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface) &&
params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model &&
(params.pattern == ipArchimedeanChords || params.pattern == ipOctagramSpiral);
const bool separate = !external && params.separated_infills &&
(is_separable_infill_pattern(params.pattern) || !config.solid_infill_rotate_template.value.empty() ||
!config.sparse_infill_rotate_template.value.empty());
if (per_model || separate) {
double best_overlap = 0.;
for (size_t i = 0; i < layer.lslices.size() && i < layer.lslices_separated_component_bboxes.size(); ++i) {
int body = -1;
if (per_model || params.separated_infills || is_octree_infill_pattern(params.pattern)) {
const BoundingBox box = get_extents(expoly);
double best_overlap = 0.;
for (size_t i = 0; i < layer.lslices.size() && i < layer.lslices_separated_component_ids.size(); ++i) {
if (! layer.lslices_bboxes[i].overlap(box))
continue;
const double overlap = area(intersection_ex(layer.lslices[i], expoly));
if (overlap > best_overlap) {
best_overlap = overlap;
const Point center = layer.lslices_separated_component_bboxes[i].center();
bbox = layer.object()->bounding_box();
bbox.translate(center.x(), center.y());
body = int(layer.lslices_separated_component_ids[i]);
}
}
}
return bbox;
return body;
}
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
@@ -1318,7 +1320,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
#endif
// friend to Layer
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator)
void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator)
{
for (LayerRegion *layerm : m_regions)
layerm->fills.clear();
@@ -1351,7 +1353,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipConcentricInternal) {
@@ -1443,8 +1445,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.can_reverse = false;
for (ExPolygon& expoly : surface_fill.expolygons) {
// Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface.
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
// Orca: Reuse the body box and octree used for bridge anchoring, resetting them for each surface.
const int body = infill_body(*this, surface_fill, expoly);
f->set_bounding_box(body >= 0 ? this->object()->separated_body_bboxes()[body] : bbox);
f->adapt_fill_octree = octrees ? octrees->get(body) : nullptr;
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
if (params.symmetric_infill_y_axis) {
@@ -1512,7 +1516,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
* - For lightning/adaptive patterns, the respective generators are wired so their
* polylines match the final infill layout.
*/
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator) const
{
LockRegionParam skin_inner_param;
std::vector<SurfaceFill> surface_fills = group_fills(*this, skin_inner_param);
@@ -1570,7 +1574,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
@@ -1617,8 +1621,10 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
params.extrusion_role = surface_fill.params.extrusion_role;
for (ExPolygon &expoly : surface_fill.expolygons) {
// Orca: Match the per-body origin of make_fills() before generating physical anchors.
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
// Orca: Match the per-body box and octree of make_fills() before generating physical anchors.
const int body = infill_body(*this, surface_fill, expoly);
f->set_bounding_box(body >= 0 ? this->object()->separated_body_bboxes()[body] : bbox);
f->adapt_fill_octree = octrees ? octrees->get(body) : nullptr;
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
f->spacing = surface_fill.params.spacing;
surface_fill.surface.expolygon = std::move(expoly);
@@ -1788,7 +1794,7 @@ void Layer::make_ironing()
// Create the ironing extrusions for regions <i, j)
ExPolygons ironing_areas;
double nozzle_dmr = this->object()->print()->config().nozzle_diameter.get_at(ironing_params.extruder - 1);
double nozzle_dmr = nozzle_diameter_for_filament(this->object()->print()->config(), ironing_params.extruder, this->object()->print()->is_BBL_printer());
if (ironing_params.just_infill) {
//TODO just_infill is currently not used.
// Just infill.
+1
View File
@@ -25,6 +25,7 @@ public:
// pattern is placed on top of previous layers
bool use_bridge_flow() const override { return false; }
bool is_self_crossing() override { return false; }
bool aligned_to_origin() const override { return true; }
protected:
void _fill_surface_single(
+14 -77
View File
@@ -301,88 +301,25 @@ void OctreeDeleter::operator()(Octree *p) {
delete p;
}
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object)
std::vector<double> adaptive_fill_line_spacing(const PrintObject &print_object)
{
// Output, spacing for icAdaptiveCubic and icSupportCubic
double adaptive_line_spacing = 0.;
double support_line_spacing = 0.;
enum class Tristate {
Yes,
No,
Maybe
};
struct RegionFillData {
Tristate has_adaptive_infill;
Tristate has_support_infill;
double density;
double extrusion_width;
};
std::vector<RegionFillData> region_fill_data;
region_fill_data.reserve(print_object.num_printing_regions());
bool build_octree = false;
std::vector<double> line_spacing(print_object.num_printing_regions(), 0.);
const std::vector<double> &nozzle_diameters = print_object.print()->config().nozzle_diameter.values;
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) {
const PrintRegionConfig &config = print_object.printing_region(region_id).config();
bool nonempty = config.sparse_infill_density > 0;
bool has_adaptive_infill = nonempty && config.sparse_infill_pattern == ipAdaptiveCubic;
bool has_support_infill = nonempty && config.sparse_infill_pattern == ipSupportCubic;
double sparse_infill_line_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter);
region_fill_data.push_back(RegionFillData({
has_adaptive_infill ? Tristate::Maybe : Tristate::No,
has_support_infill ? Tristate::Maybe : Tristate::No,
config.sparse_infill_density,
sparse_infill_line_width != 0. ? sparse_infill_line_width : default_infill_extrusion_width
}));
build_octree |= has_adaptive_infill || has_support_infill;
for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id) {
const PrintRegionConfig &config = print_object.printing_region(region_id).config();
if (config.sparse_infill_density <= 0 || ! is_octree_infill_pattern(config.sparse_infill_pattern) ||
std::none_of(print_object.layers().begin(), print_object.layers().end(), [region_id](const Layer *layer) {
return region_id < layer->regions().size() && ! layer->regions()[region_id]->fill_surfaces.empty();
}))
continue;
double extrusion_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter);
if (extrusion_width == 0.)
extrusion_width = default_infill_extrusion_width;
line_spacing[region_id] = extrusion_width / ((config.sparse_infill_density / 100.0f) * 0.333333333f) * config.fill_multiline.value;
}
if (build_octree) {
// Compute the average of above parameters over all layers
for (const Layer *layer : print_object.layers())
for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) {
RegionFillData &rd = region_fill_data[region_id];
if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
rd.has_adaptive_infill = Tristate::Yes;
if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
rd.has_support_infill = Tristate::Yes;
}
double adaptive_fill_density = 0.;
double adaptive_infill_extrusion_width = 0.;
int adaptive_cnt = 0;
double support_fill_density = 0.;
double support_infill_extrusion_width = 0.;
int support_cnt = 0;
for (const RegionFillData &rd : region_fill_data) {
if (rd.has_adaptive_infill == Tristate::Yes) {
adaptive_fill_density += rd.density;
adaptive_infill_extrusion_width += rd.extrusion_width;
++ adaptive_cnt;
} else if (rd.has_support_infill == Tristate::Yes) {
support_fill_density += rd.density;
support_infill_extrusion_width += rd.extrusion_width;
++ support_cnt;
}
}
auto to_line_spacing = [](int cnt, double density, double extrusion_width) {
if (cnt) {
density /= double(cnt);
extrusion_width /= double(cnt);
return extrusion_width / ((density / 100.0f) * 0.333333333f);
} else
return 0.;
};
const int n_multiline = print_object.printing_region(0).config().fill_multiline.value;
adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width) * n_multiline;
support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width) * n_multiline;
}
return std::make_pair(adaptive_line_spacing, support_line_spacing);
return line_spacing;
}
// Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion
+38 -5
View File
@@ -14,6 +14,7 @@
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "FillBase.hpp"
#include <cstddef>
#include <memory>
#include <utility>
#include <Eigen/Geometry>
@@ -37,11 +38,43 @@ struct Octree;
struct OctreeDeleter { void operator()(Octree *p); };
using OctreePtr = std::unique_ptr<Octree, OctreeDeleter>;
// Calculate line spacing for
// 1) adaptive cubic infill
// 2) adaptive internal support cubic infill
// Returns zero for a particular infill type if no such infill is to be generated.
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object);
// Orca: One octree per body (see Layer::lslices_separated_component_ids), and one of the whole object
// for objects of a single body or with a body that has none of its own.
struct Octrees
{
OctreePtr object;
std::vector<OctreePtr> bodies;
// A body without an octree, or body -1, uses the object's, or any body's when the object has none.
Octree *get(int body) const
{
if (body >= 0 && size_t(body) < bodies.size() && bodies[body])
return bodies[body].get();
if (object)
return object.get();
for (const OctreePtr &octree : bodies)
if (octree)
return octree.get();
return nullptr;
}
};
// Orca: The octrees of each line spacing the regions of an object fill with.
struct RegionOctrees
{
std::vector<Octrees> sets;
// Index into sets for each region, -1 for a region without adaptive or support cubic infill.
std::vector<int> region_set;
const Octrees *region(size_t region_id) const
{
return region_id < region_set.size() && region_set[region_id] >= 0 ? &sets[region_set[region_id]] : nullptr;
}
};
// Line spacing of the adaptive or support cubic infill of each region of the object,
// zero for a region that generates no such infill.
std::vector<double> adaptive_fill_line_spacing(const PrintObject &print_object);
// Rotation of the octree to stand on one of its corners.
Eigen::Quaterniond transform_to_world();
+12 -2
View File
@@ -128,6 +128,9 @@ Polylines Fill::fill_surface(const Surface *surface, const FillParams &params)
{
// Perform offset.
Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing)));
// Orca: Separated infills move the box center onto each body; origin-aligned patterns follow it.
const Point shift = this->aligned_to_origin() && ! empty(this->bounding_box) ? this->bounding_box.center() : Point::Zero();
translate(expp, -shift);
// Create the infills for each of the regions.
Polylines polylines_out;
for (size_t i = 0; i < expp.size(); ++ i)
@@ -137,6 +140,8 @@ Polylines Fill::fill_surface(const Surface *surface, const FillParams &params)
_infill_direction(surface),
std::move(expp[i]),
polylines_out);
for (Polyline &pl : polylines_out)
pl.translate(shift);
return polylines_out;
}
@@ -1591,13 +1596,18 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered
// The extended bounding box of the whole object that covers any rotation of every layer.
BoundingBox Fill::extended_object_bounding_box() const
{
BoundingBox out = bounding_box;
// Orca: Extend about the box center, which separated infills move off the origin.
const Point c = this->bounding_box.center();
BoundingBox out = this->bounding_box;
out.translate(-c.x(), -c.y());
out.merge(Point(out.min.y(), out.min.x()));
out.merge(Point(out.max.y(), out.max.x()));
// The bounding box is scaled by sqrt(2.) to ensure that the bounding box
// covers any possible rotations.
return out.scaled(sqrt(2.));
out = out.scaled(sqrt(2.));
out.translate(c.x(), c.y());
return out;
}
void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params)
+3
View File
@@ -188,6 +188,9 @@ public:
// Return true if infill has a consistent pattern between layers.
virtual bool has_consistent_pattern() const { return false; }
// Orca: Is the pattern laid out from the origin instead of the bounding box center?
virtual bool aligned_to_origin() const { return false; }
// Perform the fill.
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
virtual ThickPolylines fill_surface_arachne(const Surface* surface, const FillParams& params);
+1
View File
@@ -19,6 +19,7 @@ public:
Fill *clone() const override { return new FillCrossHatch(*this); };
~FillCrossHatch() override {}
bool is_self_crossing() override { return false; }
bool aligned_to_origin() const override { return true; }
protected:
void _fill_surface_single(
+1
View File
@@ -20,6 +20,7 @@ public:
// require bridge flow since most of this pattern hangs in air
bool use_bridge_flow() const override { return false; }
bool is_self_crossing() override { return false; }
bool aligned_to_origin() const override { return true; }
// Correction applied to regular infill angle to maximize printing
// speed in default configuration (degrees)
+1
View File
@@ -20,6 +20,7 @@ class FillHoneycomb : public Fill
public:
~FillHoneycomb() override {}
bool is_self_crossing() override { return false; }
bool aligned_to_origin() const override { return true; }
protected:
Fill* clone() const override { return new FillHoneycomb(*this); };
+7 -18
View File
@@ -2750,23 +2750,6 @@ static void polylines_from_paths(const std::vector<MonotonicRegionLink> &path, c
}
}
// The extended bounding box of the whole object that covers any rotation of every layer.
BoundingBox FillRectilinear::extended_object_bounding_box() const {
// Build the extension around the box center. The transpose merge and the sqrt(2.) scaling
// (which covers any possible rotation) are both defined about the origin, so a box that is not
// origin-centered — e.g. a separated-infill box re-centered on a single assembly part — would be
// distorted. Shift to the origin first and back afterwards; for the default origin-centered box
// the two translations cancel and this is identical to the original behavior.
const Point c = this->bounding_box.center();
BoundingBox out = this->bounding_box;
out.translate(-c.x(), -c.y());
out.merge(Point(out.min.y(), out.min.x()));
out.merge(Point(out.max.y(), out.max.x()));
out = out.scaled(sqrt(2.));
out.translate(c.x(), c.y());
return out;
}
bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillParams &params, float angleBase, float pattern_shift, Polylines &polylines_out)
{
// At the end, only the new polylines will be rotated back.
@@ -2801,7 +2784,13 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
// For infill that needs to be consistent between layers (like Zig Zag),
// we use bounding box of whole object to match vertical lines between layers.
BoundingBox bounding_box_src = poly_with_offset.bounding_box_src();
BoundingBox bounding_box = this->has_consistent_pattern() ? this->extended_object_bounding_box() : bounding_box_src;
BoundingBox bounding_box = bounding_box_src;
if (this->has_consistent_pattern()) {
// Orca: The polygons are rotated about the origin, so follow the box center to where it was rotated.
const Point c = this->bounding_box.center();
bounding_box = this->extended_object_bounding_box();
bounding_box.translate(c.rotated(- rotate_vector.first) - c);
}
// define flow spacing according to requested density
if (params.full_infill() && !params.dont_adjust) {
-3
View File
@@ -42,9 +42,6 @@ protected:
};
bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out);
bool fill_surface_trapezoidal(const Surface *surface, FillParams params, Polylines &polylines_out, int Pattern_type);
// The extended bounding box of the whole object that covers any rotation of every layer.
BoundingBox extended_object_bounding_box() const;
};
class FillAlignedRectilinear : public FillRectilinear
+1
View File
@@ -31,6 +31,7 @@ public:
Polylines& polylines_out) override;
bool is_self_crossing() override { return false; }
bool aligned_to_origin() const override { return true; }
// Density adjustment to have a good %of weight.
static constexpr double DensityAdjust = 2.1;
+1
View File
@@ -31,6 +31,7 @@ public:
Polylines& polylines_out) override;
bool is_self_crossing() override { return false; }
bool aligned_to_origin() const override { return true; }
};
+4 -4
View File
@@ -241,14 +241,14 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(object->config().support_line_width.value > 0) ? object->config().support_line_width : object->config().line_width,
// if object->config().support_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament-1)),
float(nozzle_diameter_for_filament(object->print()->config(), object->config().support_filament, object->print()->is_BBL_printer())),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}
//BBS
Flow support_transition_flow(const PrintObject* object)
{
//BBS: support transition of tree support is bridge flow
float dmr = float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament - 1));
float dmr = float(nozzle_diameter_for_filament(object->print()->config(), object->config().support_filament, object->print()->is_BBL_printer()));
return Flow::bridging_flow(dmr, dmr);
}
@@ -260,7 +260,7 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig
frSupportMaterial,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(width.value > 0) ? width : object->config().line_width,
float(print_config.nozzle_diameter.get_at(object->config().support_filament-1)),
float(nozzle_diameter_for_filament(print_config, object->config().support_filament, object->print()->is_BBL_printer())),
(layer_height > 0.f) ? layer_height : float(print_config.initial_layer_print_height.value));
}
@@ -271,7 +271,7 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(object->config().support_line_width > 0) ? object->config().support_line_width : object->config().line_width,
// if object->config().support_interface_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_interface_filament-1)),
float(nozzle_diameter_for_filament(object->print()->config(), object->config().support_interface_filament, object->print()->is_BBL_printer())),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}
-1
View File
@@ -55,7 +55,6 @@
#include "TDataStd_Name.hxx"
#include "BRepBuilderAPI_Transform.hxx"
#include "TopExp_Explorer.hxx"
#include "TopExp_Explorer.hxx"
#include "BRep_Tool.hxx"
#include "BRepTools.hxx"
#include <IMeshTools_Parameters.hxx>
+41 -65
View File
@@ -43,7 +43,6 @@
#include "Print.hpp"
#include "Utils.hpp"
#include "ClipperUtils.hpp"
#include "libslic3r.h"
#include "LocalesUtils.hpp"
#include "libslic3r/format.hpp"
#include "Time.hpp"
@@ -155,14 +154,9 @@ static const float g_purge_volume_one_time = 135.f;
static const int g_max_flush_count = 4;
static const size_t g_max_label_object = 64;
static bool is_bambu_x2d_printer(const FullPrintConfig &config)
{
return config.printer_model.value == "Bambu Lab X2D";
}
// Multi-nozzle printer predicate: an extruder carries a nozzle cluster (extruder_max_nozzle_count
// entry > 1). Today only H2C profiles trip it, so every existing single- and dual-extruder printer
// is excluded and keeps its historic placeholder values.
// is excluded and keeps its historic first-filament marker.
static bool is_multi_nozzle_printer(const FullPrintConfig &config)
{
return std::any_of(config.extruder_max_nozzle_count.values.begin(),
@@ -170,33 +164,20 @@ static bool is_multi_nozzle_printer(const FullPrintConfig &config)
[](int v) { return v > 1; });
}
static int hotend_id_for_gcode_placeholder(const FullPrintConfig &config, int hotend_id)
// current_hotend / next_hotend value. On a BBL printer: the real nozzle id only while the print uses a
// dynamic nozzle map (a filament moves between nozzles across layers), else -1. Bambu firmware reads an
// explicit hotend index as a request for the Filament Track Switch and rejects the job on a printer
// without one. group_result may be null on slicing paths that don't populate it, which resolves to -1.
// Any other printer has no firmware hotend selection and gets the filament's extruder index, whatever
// its nozzle map, so existing custom G-code keeps its values.
static int hotend_id_for_gcode_placeholder(const std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> &group_result,
bool is_bbl_printer, int filament_id, int extruder_id, int layer_id = -1)
{
return is_bambu_x2d_printer(config) ? -1 : hotend_id;
}
// current_hotend / next_hotend value. For multi-nozzle printers a dynamic nozzle map yields the real
// nozzle id, a static map yields -1:
// - multi-nozzle (H2C): dynamic nozzle map -> real nozzle id; static -> -1.
// The dynamic branch is dormant today: the selector create() overload that sets the flag has no
// callers yet (deferred with the nozzle-assignment pipeline), so H2C currently resolves to -1.
// - X2D: keeps its historic -1 (single-nozzle -> falls through to the fallback helper).
// - every other (existing single-nozzle) printer: keeps its historic extruder-id value, so
// existing g-code stays byte-identical.
// group_result may be null on slicing paths that don't populate it -> the dynamic branch is simply
// skipped, so we never dereference null.
static int hotend_id_for_gcode_placeholder(const FullPrintConfig &config,
const std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> &group_result,
int filament_id,
int extruder_id,
int layer_id = -1)
{
if (is_multi_nozzle_printer(config)) {
if (group_result && group_result->is_support_dynamic_nozzle_map() && filament_id >= 0)
return group_result->get_nozzle_id(filament_id, layer_id);
return -1;
}
return hotend_id_for_gcode_placeholder(config, extruder_id);
if (!is_bbl_printer)
return extruder_id;
if (group_result && group_result->is_support_dynamic_nozzle_map() && filament_id >= 0)
return group_result->get_nozzle_id(filament_id, layer_id);
return -1;
}
// Logical nozzle id for the *_nozzle_id placeholders. Null-safe: falls back to the
@@ -211,24 +192,20 @@ static int nozzle_id_for_gcode_placeholder(const std::shared_ptr<MultiNozzleUtil
// Init variants: the start-gcode init sites (first_non_support_hotend / initial_no_support_hotend /
// current_hotend / initial_nozzle_id / filament_start current_nozzle_id) use get_first_nozzle_for_filament
// (the nozzle a filament FIRST uses) rather than the layer-based get_nozzle_id. Same hotend-value semantics
// as hotend_id_for_gcode_placeholder above (multi-nozzle static -> -1; dynamic branch dormant;
// existing printers -> extruder id; X2D -> -1); they differ from the layer-based helper only on the dormant
// dynamic path for a filament first used after layer 0.
static int first_hotend_id_for_gcode_placeholder(const FullPrintConfig &config,
const std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> &group_result,
int filament_id,
int extruder_id)
// (the nozzle a filament FIRST uses) rather than the layer-based get_nozzle_id. Same hotend-value rule
// as hotend_id_for_gcode_placeholder above; they differ from the layer-based helper only on the dynamic
// path for a filament first used after layer 0.
static int first_hotend_id_for_gcode_placeholder(const std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> &group_result,
bool is_bbl_printer, int filament_id, int extruder_id)
{
if (is_multi_nozzle_printer(config)) {
if (group_result && group_result->is_support_dynamic_nozzle_map() && filament_id >= 0) {
auto nozzle = group_result->get_first_nozzle_for_filament(filament_id);
if (nozzle)
return nozzle->group_id;
}
return -1;
if (!is_bbl_printer)
return extruder_id;
if (group_result && group_result->is_support_dynamic_nozzle_map() && filament_id >= 0) {
auto nozzle = group_result->get_first_nozzle_for_filament(filament_id);
if (nozzle)
return nozzle->group_id;
}
return hotend_id_for_gcode_placeholder(config, extruder_id);
return -1;
}
static int first_nozzle_id_for_gcode_placeholder(const std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> &group_result,
@@ -1215,12 +1192,11 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id));
config.set_key_value("next_extruder", new ConfigOptionInt(new_filament_id));
// current_hotend/next_hotend (see hotend_id_for_gcode_placeholder): multi-nozzle H2C -> -1
// (static; dynamic branch dormant), X2D -> -1, existing printers -> extruder id.
const bool is_bbl_printer = gcodegen.m_print->is_BBL_printer();
config.set_key_value("current_hotend", new ConfigOptionInt(
hotend_id_for_gcode_placeholder(gcodegen.m_config, group_result, old_filament_id, old_extruder_id, m_layer_idx)));
hotend_id_for_gcode_placeholder(group_result, is_bbl_printer, old_filament_id, old_extruder_id, m_layer_idx)));
config.set_key_value("next_hotend", new ConfigOptionInt(
hotend_id_for_gcode_placeholder(gcodegen.m_config, group_result, new_filament_id, (int) gcodegen.get_extruder_id(new_filament_id), m_layer_idx)));
hotend_id_for_gcode_placeholder(group_result, is_bbl_printer, new_filament_id, (int) gcodegen.get_extruder_id(new_filament_id), m_layer_idx)));
config.set_key_value("current_nozzle_id", new ConfigOptionInt(old_nozzle_id));
config.set_key_value("next_nozzle_id", new ConfigOptionInt(next_nozzle_id));
config.set_key_value("current_filament_id", new ConfigOptionInt(old_filament_id));
@@ -1499,7 +1475,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
gcodegen.placeholder_parser().set("current_nozzle_id",
nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, m_layer_idx));
gcodegen.placeholder_parser().set("current_hotend",
hotend_id_for_gcode_placeholder(gcodegen.m_config, group_result, new_filament_id, new_extruder_id, m_layer_idx));
hotend_id_for_gcode_placeholder(group_result, gcodegen.m_print->is_BBL_printer(), new_filament_id, new_extruder_id, m_layer_idx));
{
size_t fi = gcodegen.get_filament_config_index(new_filament_id);
gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(fi));
@@ -3310,20 +3286,19 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
first_non_support_hotends.reserve(first_non_support_filaments.size());
for (int filament_id : first_non_support_filaments)
first_non_support_hotends.push_back(filament_id < 0 ? -1 :
first_hotend_id_for_gcode_placeholder(m_config, group_result, filament_id, (int) get_extruder_id(filament_id)));
first_hotend_id_for_gcode_placeholder(group_result, is_bbl_printers, filament_id, (int) get_extruder_id(filament_id)));
this->placeholder_parser().set("first_non_support_tools", new ConfigOptionInts(first_non_support_filaments));
this->placeholder_parser().set("first_non_support_filaments", new ConfigOptionInts(first_non_support_filaments));
this->placeholder_parser().set("first_non_support_hotend", new ConfigOptionInts(first_non_support_hotends));
this->placeholder_parser().set("initial_no_support_tool", initial_non_support_extruder_id);
this->placeholder_parser().set("initial_no_support_extruder", initial_non_support_extruder_id);
// initial_no_support_hotend/current_hotend (see first_hotend_id_for_gcode_placeholder): multi-nozzle
// H2C -> -1 (static; dynamic branch dormant), X2D -> -1, existing printers -> extruder id.
this->placeholder_parser().set("initial_no_support_hotend",
first_hotend_id_for_gcode_placeholder(m_config, group_result, (int) initial_non_support_extruder_id, (int) get_extruder_id(initial_non_support_extruder_id)));
first_hotend_id_for_gcode_placeholder(group_result, is_bbl_printers, (int) initial_non_support_extruder_id,
(int) get_extruder_id(initial_non_support_extruder_id)));
this->placeholder_parser().set("current_extruder", initial_extruder_id);
this->placeholder_parser().set("current_hotend",
first_hotend_id_for_gcode_placeholder(m_config, group_result, (int) initial_extruder_id, extruder_id));
first_hotend_id_for_gcode_placeholder(group_result, is_bbl_printers, (int) initial_extruder_id, extruder_id));
this->placeholder_parser().set("current_filament_id", (int) initial_extruder_id);
this->placeholder_parser().set("current_extruder_id", extruder_id);
this->placeholder_parser().set("current_nozzle_id",
@@ -3832,7 +3807,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
file.writeln(filament_start_gcode);
// Mark the first filament used in print. Multi-nozzle printers (H2C) get ";VT%d H%d" where
// H = dynamic ? nozzle_id : -1; existing single-nozzle printers keep the bare ";VT%d" so their
// g-code stays byte-identical. (The dynamic branch is dormant, so H2C currently emits H-1.)
// g-code stays byte-identical.
if (is_multi_nozzle_printer(m_config)) {
int initial_nozzle_id = -1;
if (group_result && group_result->is_support_dynamic_nozzle_map()) {
@@ -9616,6 +9591,9 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
this->placeholder_parser().set("current_extruder_id", new_extruder_id);
this->placeholder_parser().set("current_nozzle_id",
nozzle_id_for_gcode_placeholder(m_print->get_layered_nozzle_group_result(), (int) new_filament_id, new_extruder_id, m_layer_index));
this->placeholder_parser().set("current_hotend",
hotend_id_for_gcode_placeholder(m_print->get_layered_nozzle_group_result(), m_print->is_BBL_printer(), (int) new_filament_id,
new_extruder_id, m_layer_index));
{
size_t fi = get_filament_config_index(new_filament_id);
this->placeholder_parser().set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(fi));
@@ -9794,12 +9772,10 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
dyn_config.set_key_value("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed));
dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id));
dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int)new_filament_id));
// current_hotend/next_hotend (see hotend_id_for_gcode_placeholder): multi-nozzle H2C -> -1
// (static; dynamic branch dormant), X2D -> -1, existing printers -> extruder id.
dyn_config.set_key_value("current_hotend", new ConfigOptionInt(
hotend_id_for_gcode_placeholder(m_config, group_result, old_filament_id, old_extruder_id, m_layer_index)));
hotend_id_for_gcode_placeholder(group_result, m_print->is_BBL_printer(), old_filament_id, old_extruder_id, m_layer_index)));
dyn_config.set_key_value("next_hotend", new ConfigOptionInt(
hotend_id_for_gcode_placeholder(m_config, group_result, (int) new_filament_id, new_extruder_id, m_layer_index)));
hotend_id_for_gcode_placeholder(group_result, m_print->is_BBL_printer(), (int) new_filament_id, new_extruder_id, m_layer_index)));
dyn_config.set_key_value("current_nozzle_id", new ConfigOptionInt(old_nozzle_id));
dyn_config.set_key_value("next_nozzle_id", new ConfigOptionInt(next_nozzle_id));
dyn_config.set_key_value("current_filament_id", new ConfigOptionInt(old_filament_id));
@@ -9977,7 +9953,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
this->placeholder_parser().set("current_extruder", new_filament_id);
this->placeholder_parser().set("current_hotend",
hotend_id_for_gcode_placeholder(m_config, group_result, (int) new_filament_id, new_extruder_id, m_layer_index));
hotend_id_for_gcode_placeholder(group_result, m_print->is_BBL_printer(), (int) new_filament_id, new_extruder_id, m_layer_index));
// Orca: keep the global current-tool identity coherent for later contexts (see append_tcr).
this->placeholder_parser().set("current_filament_id", (int) new_filament_id);
this->placeholder_parser().set("current_extruder_id", new_extruder_id);
-2
View File
@@ -148,8 +148,6 @@ struct ConflictComputeResult
using ConflictComputeOpt = std::optional<ConflictComputeResult>;
using ConflictObjName = std::optional<std::pair<std::string, std::string>>;
struct ConflictChecker
{
static ConflictResultOpt find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs, std::optional<const FakeWipeTower *> wtdptr);
-2
View File
@@ -27,8 +27,6 @@
#include "../PrintConfig.hpp"
#include "../Utils.hpp"
#include "Print.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/libslic3r.h"
#include <boost/log/trivial.hpp>
#include <string>
+1 -4
View File
@@ -22,9 +22,6 @@
namespace Slic3r {
namespace PreciseSeam {
// Import EnforcedBlockedSeamPoint from SeamPlacerImpl namespace for convenience
using SeamPlacerImpl::EnforcedBlockedSeamPoint;
// Geometry and its exterior bounds are prepared together, then treated as read-only.
struct ModifierRegion {
ExPolygon polygon;
@@ -137,7 +134,7 @@ SegmentExtraction extract_perimeter_segments(const PreparedPerimeter &prepared,
// Result of weak modifier segment processing
struct WeakModifierSegment {
EnforcedBlockedSeamPoint type; // Enforced/Blocked/Neutral
SeamPlacerImpl::EnforcedBlockedSeamPoint type; // Enforced/Blocked/Neutral
Point left_point; // Coordinates of left (first) point of segment
PerimeterPosition left_position; // Position on the source perimeter before insertion/refinement.
Point right_point; // Coordinates of right (last) point of segment
+1 -2
View File
@@ -40,8 +40,7 @@ std::string get_error_string(const ThumbnailErrors& errors);
typedef std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>> GCodeThumbnailDefinitionsList;
using namespace std::literals;
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext = "PNG"sv);
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext = "PNG");
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const ConfigBase &config);
-580
View File
@@ -1,580 +0,0 @@
#include "GCodeSender.hpp"
#include <iostream>
#include <istream>
#include <string>
#include <thread>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/lexical_cast.hpp>
#if defined(__APPLE__) || defined(__OpenBSD__)
#include <termios.h>
#endif
#ifdef __APPLE__
#include <sys/ioctl.h>
#include <IOKit/serial/ioss.h>
#endif
#ifdef __linux__
#include <sys/ioctl.h>
#include <fcntl.h>
#include "/usr/include/asm-generic/ioctls.h"
/* The following definitions are kindly borrowed from:
/usr/include/asm-generic/termbits.h
Unfortunately we cannot just include that one because
it would redefine the "struct termios" already defined
the <termios.h> already included by Boost.ASIO. */
#define K_NCCS 19
struct termios2 {
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_line;
cc_t c_cc[K_NCCS];
speed_t c_ispeed;
speed_t c_ospeed;
};
#define BOTHER CBAUDEX
#endif
//#define DEBUG_SERIAL
#ifdef DEBUG_SERIAL
#include <cstdlib>
#include <fstream>
std::fstream fs;
#endif
#define KEEP_SENT 20
namespace Slic3r {
GCodeSender::GCodeSender()
: io(), serial(io), can_send(false), sent(0), open(false), error(false),
connected(false), queue_paused(false)
{
#ifdef DEBUG_SERIAL
std::srand(std::time(nullptr));
#endif
}
GCodeSender::~GCodeSender()
{
this->disconnect();
}
bool
GCodeSender::connect(std::string devname, unsigned int baud_rate)
{
this->disconnect();
this->set_error_status(false);
try {
this->serial.open(devname);
this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::odd));
this->serial.set_option(boost::asio::serial_port_base::character_size(boost::asio::serial_port_base::character_size(8)));
this->serial.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
this->serial.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
this->set_baud_rate(baud_rate);
this->serial.close();
this->serial.open(devname);
this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
// set baud rate again because set_option overwrote it
this->set_baud_rate(baud_rate);
this->open = true;
this->reset();
} catch (boost::system::system_error &) {
this->set_error_status(true);
return false;
}
// a reset firmware expect line numbers to start again from 1
this->sent = 0;
this->last_sent.clear();
/* Initialize debugger */
#ifdef DEBUG_SERIAL
fs.open("serial.txt", std::fstream::out | std::fstream::trunc);
#endif
// this gives some work to the io_service before it is started
// (post() runs the supplied function in its thread)
this->io.post(boost::bind(&GCodeSender::do_read, this));
// start reading in the background thread
boost::thread t(boost::bind(&boost::asio::io_service::run, &this->io));
this->background_thread.swap(t);
// always send a M105 to check for connection because firmware might be silent on connect
//FIXME Vojtech: This is being sent too early, leading to line number synchronization issues,
// from which the GCodeSender never recovers.
// this->send("M105", true);
return true;
}
void
GCodeSender::set_baud_rate(unsigned int baud_rate)
{
try {
// This does not support speeds > 115200
this->serial.set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
} catch (boost::system::system_error &) {
boost::asio::serial_port::native_handle_type handle = this->serial.native_handle();
#if __APPLE__
termios ios;
::tcgetattr(handle, &ios);
::cfsetspeed(&ios, baud_rate);
speed_t newSpeed = baud_rate;
ioctl(handle, IOSSIOSPEED, &newSpeed);
::tcsetattr(handle, TCSANOW, &ios);
#elif __linux__
termios2 ios;
if (ioctl(handle, TCGETS2, &ios))
printf("Error in TCGETS2: %s\n", strerror(errno));
ios.c_ispeed = ios.c_ospeed = baud_rate;
ios.c_cflag &= ~CBAUD;
ios.c_cflag |= BOTHER | CLOCAL | CREAD;
ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
ios.c_cc[VTIME] = 1;
if (ioctl(handle, TCSETS2, &ios))
printf("Error in TCSETS2: %s\n", strerror(errno));
#elif __OpenBSD__
struct termios ios;
::tcgetattr(handle, &ios);
::cfsetspeed(&ios, baud_rate);
if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0)
printf("Failed to set baud rate: %s\n", strerror(errno));
#else
//throw Slic3r::InvalidArgument("OS does not currently support custom bauds");
#endif
}
}
void
GCodeSender::disconnect()
{
if (!this->open) return;
this->open = false;
this->connected = false;
this->io.post(boost::bind(&GCodeSender::do_close, this));
this->background_thread.join();
this->io.reset();
/*
if (this->error_status()) {
throw(boost::system::system_error(boost::system::error_code(),
"Error while closing the device"));
}
*/
#ifdef DEBUG_SERIAL
fs << "DISCONNECTED" << std::endl << std::flush;
fs.close();
#endif
}
bool
GCodeSender::is_connected() const
{
return this->connected;
}
bool
GCodeSender::wait_connected(unsigned int timeout) const
{
using namespace boost::posix_time;
ptime t0 = second_clock::local_time() + seconds(timeout);
while (!this->connected) {
if (second_clock::local_time() > t0) return false;
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
}
return true;
}
size_t
GCodeSender::queue_size() const
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
return this->queue.size();
}
void
GCodeSender::pause_queue()
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->queue_paused = true;
}
void
GCodeSender::resume_queue()
{
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->queue_paused = false;
}
this->send();
}
void
GCodeSender::purge_queue(bool priority)
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
if (priority) {
// clear priority queue
std::list<std::string> empty;
std::swap(this->priqueue, empty);
} else {
// clear queue
std::queue<std::string> empty;
std::swap(this->queue, empty);
this->queue_paused = false;
}
}
// purge log and return its contents
std::vector<std::string>
GCodeSender::purge_log()
{
boost::lock_guard<boost::mutex> l(this->log_mutex);
std::vector<std::string> retval;
retval.reserve(this->log.size());
while (!this->log.empty()) {
retval.push_back(this->log.front());
this->log.pop();
}
return retval;
}
std::string
GCodeSender::getT() const
{
boost::lock_guard<boost::mutex> l(this->log_mutex);
return this->T;
}
std::string
GCodeSender::getB() const
{
boost::lock_guard<boost::mutex> l(this->log_mutex);
return this->B;
}
void
GCodeSender::do_close()
{
this->set_error_status(false);
boost::system::error_code ec;
this->serial.cancel(ec);
if (ec) this->set_error_status(true);
this->serial.close(ec);
if (ec) this->set_error_status(true);
}
void
GCodeSender::set_error_status(bool e)
{
boost::lock_guard<boost::mutex> l(this->error_mutex);
this->error = e;
}
bool
GCodeSender::error_status() const
{
boost::lock_guard<boost::mutex> l(this->error_mutex);
return this->error;
}
void
GCodeSender::do_read()
{
// read one line
boost::asio::async_read_until(
this->serial,
this->read_buffer,
'\n',
boost::bind(
&GCodeSender::on_read,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
void
GCodeSender::on_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
this->set_error_status(false);
if (error) {
#ifdef __APPLE__
if (error.value() == 45) {
// OS X bug: http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html
this->do_read();
return;
}
#endif
// printf("ERROR: [%d] %s\n", error.value(), error.message().c_str());
// error can be true even because the serial port was closed.
// In this case it is not a real error, so ignore.
if (this->open) {
this->do_close();
this->set_error_status(true);
}
return;
}
std::istream is(&this->read_buffer);
std::string line;
std::getline(is, line);
if (!line.empty()) {
#ifdef DEBUG_SERIAL
fs << "<< " << line << std::endl << std::flush;
#endif
// note that line might contain \r at its end
// parse incoming line
if (!this->connected
&& (boost::starts_with(line, "start")
|| boost::starts_with(line, "Grbl ")
|| boost::starts_with(line, "ok")
|| boost::contains(line, "T:"))) {
this->connected = true;
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->can_send = true;
}
this->send();
} else if (boost::starts_with(line, "ok")) {
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->can_send = true;
}
this->send();
} else if (boost::istarts_with(line, "resend") // Marlin uses "Resend: "
|| boost::istarts_with(line, "rs")) {
// extract the first number from line
boost::algorithm::trim_left_if(line, !boost::algorithm::is_digit());
size_t toresend = boost::lexical_cast<size_t>(line.substr(0, line.find_first_not_of("0123456789")));
#ifdef DEBUG_SERIAL
fs << "!! line num out of sync: toresend = " << toresend << ", sent = " << sent << ", last_sent.size = " << last_sent.size() << std::endl;
#endif
if (toresend > this->sent - this->last_sent.size() && toresend <= this->sent) {
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
const auto lines_to_resend = this->sent - toresend + 1;
#ifdef DEBUG_SERIAL
fs << "!! resending " << lines_to_resend << " lines" << std::endl;
#endif
// move the unsent lines to priqueue
this->priqueue.insert(
this->priqueue.begin(), // insert at the beginning
this->last_sent.begin() + this->last_sent.size() - lines_to_resend,
this->last_sent.end()
);
// we can empty last_sent because it's not useful anymore
this->last_sent.clear();
// start resending with the requested line number
this->sent = toresend - 1;
this->can_send = true;
}
this->send();
} else {
printf("Cannot resend %zu (oldest we have is %zu)\n", toresend, this->sent - this->last_sent.size());
}
} else if (boost::starts_with(line, "wait")) {
// ignore
} else {
// push any other line into the log
boost::lock_guard<boost::mutex> l(this->log_mutex);
this->log.push(line);
}
// parse temperature info
{
size_t pos = line.find("T:");
if (pos != std::string::npos && line.size() > pos + 2) {
// we got temperature info
boost::lock_guard<boost::mutex> l(this->log_mutex);
this->T = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2));
pos = line.find("B:");
if (pos != std::string::npos && line.size() > pos + 2) {
// we got bed temperature info
this->B = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2));
}
}
}
}
this->do_read();
}
void
GCodeSender::send(const std::vector<std::string> &lines, bool priority)
{
// append lines to queue
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
for (std::vector<std::string>::const_iterator line = lines.begin(); line != lines.end(); ++line) {
if (priority) {
this->priqueue.push_back(*line);
} else {
this->queue.push(*line);
}
}
}
this->send();
}
void
GCodeSender::send(const std::string &line, bool priority)
{
// append line to queue
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
if (priority) {
this->priqueue.push_back(line);
} else {
this->queue.push(line);
}
}
this->send();
}
void
GCodeSender::send()
{
this->io.post(boost::bind(&GCodeSender::do_send, this));
}
void
GCodeSender::do_send()
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
// printer is not connected or we're still waiting for the previous ack
if (!this->can_send) return;
std::string line;
while (!this->priqueue.empty() || (!this->queue.empty() && !this->queue_paused)) {
if (!this->priqueue.empty()) {
line = this->priqueue.front();
this->priqueue.pop_front();
} else {
line = this->queue.front();
this->queue.pop();
}
// strip comments
size_t comment_pos = line.find_first_of(';');
if (comment_pos != std::string::npos)
line.erase(comment_pos, std::string::npos);
boost::algorithm::trim(line);
// if line is not empty, send it
if (!line.empty()) break;
// if line is empty, process next item in queue
}
if (line.empty()) return;
// compute full line
++ this->sent;
#ifndef DEBUG_SERIAL
const auto line_num = this->sent;
#else
// In DEBUG_SERIAL mode, test line re-synchronization by sending bad line number 1/4 of the time
const auto line_num = std::rand() < RAND_MAX/4 ? 0 : this->sent;
#endif
std::string full_line = "N" + boost::lexical_cast<std::string>(line_num) + " " + line;
// calculate checksum
int cs = 0;
for (std::string::const_iterator it = full_line.begin(); it != full_line.end(); ++it)
cs = cs ^ *it;
// write line to device
full_line += "*";
full_line += boost::lexical_cast<std::string>(cs);
full_line += "\n";
#ifdef DEBUG_SERIAL
fs << ">> " << full_line << std::flush;
#endif
this->last_sent.push_back(line);
this->can_send = false;
while (this->last_sent.size() > KEEP_SENT) {
this->last_sent.pop_front();
}
// we can't supply boost::asio::buffer(full_line) to async_write() because full_line is on the
// stack and the buffer would lose its underlying storage causing memory corruption
std::ostream os(&this->write_buffer);
os << full_line;
boost::asio::async_write(this->serial, this->write_buffer, boost::bind(&GCodeSender::on_write, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void
GCodeSender::on_write(const boost::system::error_code& error,
size_t bytes_transferred)
{
this->set_error_status(false);
if (error) {
if (this->open) {
this->do_close();
this->set_error_status(true);
}
return;
}
this->do_send();
}
void
GCodeSender::set_DTR(bool on)
{
#if defined(_WIN32) && !defined(__SYMBIAN32__)
boost::asio::serial_port_service::native_handle_type handle = this->serial.native_handle();
if (on)
EscapeCommFunction(handle, SETDTR);
else
EscapeCommFunction(handle, CLRDTR);
#else
int fd = this->serial.native_handle();
int status;
ioctl(fd, TIOCMGET, &status);
if (on)
status |= TIOCM_DTR;
else
status &= ~TIOCM_DTR;
ioctl(fd, TIOCMSET, &status);
#endif
}
void
GCodeSender::reset()
{
set_DTR(false);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
set_DTR(true);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
set_DTR(false);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
} // namespace Slic3r
-81
View File
@@ -1,81 +0,0 @@
#ifndef slic3r_GCodeSender_hpp_
#define slic3r_GCodeSender_hpp_
#include "libslic3r.h"
#include <queue>
#include <boost/core/noncopyable.hpp>
#include <cstddef>
#include <boost/asio/io_service.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/asio/streambuf.hpp>
#include <list>
#include <deque>
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread.hpp>
namespace Slic3r {
namespace asio = boost::asio;
class GCodeSender : private boost::noncopyable {
public:
GCodeSender();
~GCodeSender();
bool connect(std::string devname, unsigned int baud_rate);
void send(const std::vector<std::string> &lines, bool priority = false);
void send(const std::string &s, bool priority = false);
void disconnect();
bool error_status() const;
bool is_connected() const;
bool wait_connected(unsigned int timeout = 3) const;
size_t queue_size() const;
void pause_queue();
void resume_queue();
void purge_queue(bool priority = false);
std::vector<std::string> purge_log();
std::string getT() const;
std::string getB() const;
void set_DTR(bool on);
void reset();
private:
asio::io_service io;
asio::serial_port serial;
boost::thread background_thread;
boost::asio::streambuf read_buffer, write_buffer;
bool open; // whether the serial socket is connected
bool connected; // whether the printer is online
bool error;
mutable boost::mutex error_mutex;
// this mutex guards queue, priqueue, can_send, queue_paused, sent, last_sent
mutable boost::mutex queue_mutex;
std::queue<std::string> queue;
std::list<std::string> priqueue;
bool can_send;
bool queue_paused;
size_t sent;
std::deque<std::string> last_sent;
// this mutex guards log, T, B
mutable boost::mutex log_mutex;
std::queue<std::string> log;
std::string T, B;
void set_baud_rate(unsigned int baud_rate);
void set_error_status(bool e);
void do_send();
void on_write(const boost::system::error_code& error, size_t bytes_transferred);
void do_close();
void do_read();
void on_read(const boost::system::error_code& error, size_t bytes_transferred);
void send();
};
} // namespace Slic3r
#endif /* slic3r_GCodeSender_hpp_ */
-2
View File
@@ -94,8 +94,6 @@ struct Circle {
using Circlef = Circle<Vec2f>;
using Circled = Circle<Vec2d>;
using CircleSqf = CircleSq<Vec2f>;
using CircleSqd = CircleSq<Vec2d>;
/// Find the center of the circle corresponding to the vector of Points as an arc.
Point circle_center_taubin_newton(const Points::const_iterator& input_start, const Points::const_iterator& input_end, size_t cycles = 20);
-1
View File
@@ -1,7 +1,6 @@
#include <boost/log/trivial.hpp>
#include "MedialAxis.hpp"
#include <boost/log/trivial.hpp>
#include <boost/polygon/polygon.hpp>
#include <cassert>
#include <cmath>
+8 -9
View File
@@ -33,7 +33,7 @@ class PrintObject;
class Print;
namespace FillAdaptive {
struct Octree;
struct RegionOctrees;
};
namespace FillLightning {
@@ -170,10 +170,10 @@ public:
ExPolygons lslices;
ExPolygons lslices_extrudable; // BBS: the extrudable part of lslices used for tree support
std::vector<BoundingBox> lslices_bboxes;
// Orca: for separated infills / per-model centering. Aligned with lslices: for each island, the
// full bounding box of the 3D connected body (across all layers) it belongs to. Populated by
// PrintObject::infill() only when the feature is used; empty otherwise.
std::vector<BoundingBox> lslices_separated_component_bboxes;
// Orca: for separated infills / per-model centering / octree infills. Aligned with lslices: for each
// island, the 3D connected body (across all layers) it belongs to, indexing
// PrintObject::separated_body_bboxes(). Populated by PrintObject::prepare_infill() only when needed.
std::vector<size_t> lslices_separated_component_ids;
// BBS
ExPolygons loverhangs;
@@ -207,10 +207,9 @@ public:
static bool is_perimeter_compatible(const Print& print, const PrintRegion& a, const PrintRegion& b);
void make_perimeters();
// Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr); }
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr);
Polylines generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree *adaptive_fill_octree,
FillAdaptive::Octree *support_fill_octree,
void make_fills() { this->make_fills(nullptr); }
void make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator = nullptr);
Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees *fill_octrees,
FillLightning::Generator* lightning_generator) const;
void make_ironing();
// Returns the filament id (1-based) the region is ironed with, or -1 when the
+1 -1
View File
@@ -62,7 +62,7 @@ Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const
const PrintObject &print_object = *this->layer()->object();
Flow bridge_flow;
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will fall back to zero'th element, so everything is all right.
auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1));
auto nozzle_diameter = float(nozzle_diameter_for_filament(print_object.print()->config(), region.extruder(role), print_object.print()->is_BBL_printer()));
const ConfigOptionFloatOrPercent& bridge_width_opt = region_config.bridge_line_width;
const double bridge_width = bridge_width_opt.get_abs_value(nozzle_diameter);
const bool has_bridge_width = bridge_width > 0.;
+3 -4
View File
@@ -154,13 +154,12 @@ template<class ExecutionPolicy, class Enable = void> struct _Loop
};
// Add Specialization for using ExecutionTBB for parallel loops.
using namespace Slic3r;
template<> struct _Loop<ExecutionTBB>
template<> struct _Loop<Slic3r::ExecutionTBB>
{
template<class It, class Fn> static void for_each_idx(It from, It to, Fn&& fn)
{
execution::for_each(
ex_tbb, size_t(0), size_t(to - from), [&from, &fn](size_t i) { fn(from[i], i); }, execution::max_concurrency(ex_tbb));
Slic3r::execution::for_each(
Slic3r::ex_tbb, size_t(0), size_t(to - from), [&from, &fn](size_t i) { fn(from[i], i); }, Slic3r::execution::max_concurrency(Slic3r::ex_tbb));
}
};
+22 -1
View File
@@ -1045,7 +1045,28 @@ void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& b
// But we can force it to work by spliting the src mesh into disconnected components,
// and do booleans seperately, then merge all the results.
indexed_triangle_set all_its;
if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") {
if (boolean_opts == "A_NOT_B") {
// Each cut can leave the source with several disconnected components, which mcut rejects
// in the next dispatch, so re-split after every cut part (e.g. each letter of a text).
std::vector<indexed_triangle_set> parts = std::move(src_parts);
for (size_t j = 0; j < cut_parts.size(); j++) {
auto cut_part = triangle_mesh_to_mcut(cut_parts[j]);
std::vector<indexed_triangle_set> next_parts;
for (indexed_triangle_set &part : parts) {
auto src_part = triangle_mesh_to_mcut(part);
if (do_boolean_single(*src_part, *cut_part, boolean_opts)) {
TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part);
std::vector<indexed_triangle_set> pieces = its_split(tri_part.its);
std::move(pieces.begin(), pieces.end(), std::back_inserter(next_parts));
} else
next_parts.emplace_back(std::move(part));
}
parts = std::move(next_parts);
}
for (const indexed_triangle_set &part : parts)
its_merge(all_its, part);
}
else if (boolean_opts == "UNION") {
for (size_t i = 0; i < src_parts.size(); i++) {
auto src_part = triangle_mesh_to_mcut(src_parts[i]);
for (size_t j = 0; j < cut_parts.size(); j++) {
+3 -1
View File
@@ -22,7 +22,6 @@
#include "Format/AssimpImport.hpp"
#include "ClipperUtils.hpp"
#include "Exception.hpp"
#include "Model.hpp"
#include "ModelArrange.hpp"
#include "Arrange.hpp"
#include "Geometry.hpp"
@@ -1697,6 +1696,9 @@ indexed_triangle_set ModelObject::raw_indexed_triangle_set() const
size_t j = out.indices.size();
append(out.vertices, v->mesh().its.vertices);
append(out.indices, v->mesh().its.indices);
// Orca: Point the volume's triangles at its own vertices, which follow those of the volumes before it.
for (size_t k = j; k < out.indices.size(); ++ k)
out.indices[k] += stl_triangle_vertex_indices::Constant(int(i));
const Transform3d& m = v->get_matrix();
for (; i < out.vertices.size(); ++ i)
out.vertices[i] = (m * out.vertices[i].cast<double>()).cast<float>().eval();
-1
View File
@@ -18,7 +18,6 @@
#include "TriangleMesh.hpp"
#include "CustomGCode.hpp"
#include "calib.hpp"
#include "enum_bitmask.hpp"
#include "TextConfiguration.hpp"
#include "EmbossShape.hpp"
#include "TriangleSelector.hpp"
+8 -34
View File
@@ -3593,24 +3593,6 @@ void PresetBundle::export_selections(AppConfig &config)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": printer %1%, print %2%, filaments[0] %3% ")%printers.get_selected_preset_name() % prints.get_selected_preset_name() %filament_presets[0];
}
// Preserve metadata only for existing colour slots; new slots get false/empty defaults.
static void resize_mixed_filament_metadata(DynamicPrintConfig &config, size_t old_slot_count, size_t new_slot_count)
{
auto resize = [old_slot_count, new_slot_count](auto *opt) {
if (opt) {
opt->values.resize(std::min(old_slot_count, opt->values.size()));
opt->values.resize(new_slot_count);
}
};
resize(config.option<ConfigOptionBools>("filament_is_mixed"));
resize(config.option<ConfigOptionStrings>("filament_mixed_components"));
resize(config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios"));
resize(config.option<ConfigOptionBools>("filament_mixed_gradient"));
resize(config.option<ConfigOptionStrings>("filament_mixed_gradient_range"));
resize(config.option<ConfigOptionStrings>("filament_mixed_gradient_curve"));
resize(config.option<ConfigOptionBools>("filament_mixed_gradient_per_part"));
}
void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
{
unsigned old_filament_count = this->filament_presets.size();
@@ -3894,16 +3876,6 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
maps.erase(j);
}
}
}
if (is_placeholder) {
// Orca: an empty AMS slot is a placeholder, not a filament - never synthesize a
// preset for it. Kept here only to keep index alignment with ams_infos; the sync
// drops it so no filament slot is created or retained for an empty tray.
ams_filament_presets.push_back("");
ams_filament_colors.push_back("");
ams_filament_color_types.push_back("");
ams_multi_color_filment.push_back({});
} else if (use_map) {
ams_filament_presets.push_back("Generic PLA");//for unknow matieral
auto default_unknown_color = "#CECECE";
ams_filament_colors.push_back(default_unknown_color);
@@ -3912,6 +3884,12 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
filament_multi_color.push_back(default_unknown_color);
}
ams_multi_color_filment.push_back(filament_multi_color);
} else if (is_placeholder) {
// Orca: push placeholders to keep index alignment with ams_infos
ams_filament_presets.push_back("");
ams_filament_colors.push_back("");
ams_filament_color_types.push_back("");
ams_multi_color_filment.push_back({});
}
continue;
}
@@ -4275,10 +4253,6 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
result_multi_colors.push_back(
i < ams_multi_color_filment.size() ? ams_multi_color_filment[i]
: std::vector<std::string>{ams_filament_colors[i]});
} else if (i < ams_infos.size() && ams_infos[i].is_placeholder) {
// Orca: an empty AMS slot is a placeholder, not a filament, so drop it instead of
// keeping or filling a slot for it. The device AMS panel still shows the empty tray.
continue;
} else if (i < exist_presets.size()) {
// Empty tray or beyond tray count: keep existing filament
result_colors.push_back(exist_colors[i]);
@@ -4303,8 +4277,8 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
filament_color_type->values = result_color_types;
this->filament_presets = result_presets;
ams_multi_color_filment = result_multi_colors;
filament_map->values.resize(result_presets.size(), 1);
filament_volume_map->values.resize(result_presets.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
filament_map->values.resize(total, 1);
filament_volume_map->values.resize(total, static_cast<int>(NozzleVolumeType::nvtStandard));
} else {
// BBL: existing wholesale replace
filament_color->values = ams_filament_colors;
+9 -8
View File
@@ -2229,7 +2229,7 @@ StringObjectException Print::validate(std::vector<StringObjectException> *warnin
for (const PrintRegion &region : object->all_regions()) {
const auto &bridge_width_opt = region.config().bridge_line_width;
for (FlowRole bridge_role : { frPerimeter, frInfill, frSolidInfill, frTopSolidInfill }) {
const double nozzle_diameter = m_config.nozzle_diameter.get_at(region.extruder(bridge_role) - 1);
const double nozzle_diameter = nozzle_diameter_for_filament(m_config, region.extruder(bridge_role), this->is_BBL_printer());
const double bridge_width = bridge_width_opt.get_abs_value(nozzle_diameter);
if (bridge_width <= 0.)
continue;
@@ -2606,7 +2606,7 @@ Flow Print::brim_flow() const
frPerimeter,
// Flow::new_from_config_width takes care of the percent to value substitution
width,
(float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().outer_wall_filament_id-1),
(float)nozzle_diameter_for_filament(m_config, m_print_regions.front()->config().outer_wall_filament_id, this->is_BBL_printer()),
(float)this->skirt_first_layer_height());
}
@@ -2623,12 +2623,13 @@ Flow Print::skirt_flow() const
extruders and take the one with, say, the smallest index;
The same logic should be applied to the code that selects the extruder during G-code
generation as well. */
return Flow::new_from_config_width(frPerimeter,
// Flow::new_from_config_width takes care of the percent to value substitution
width,
(float) m_config.nozzle_diameter.get_at(
m_objects.empty() ? 0 : m_objects.front()->config().support_filament - 1),
(float) this->skirt_first_layer_height());
return Flow::new_from_config_width(
frPerimeter,
// Flow::new_from_config_width takes care of the percent to value substitution
width,
// ORCA: resolve the actual nozzle the support filament is printed with (dual-nozzle printers).
(float)nozzle_diameter_for_filament(m_config, m_objects.empty() ? 0 : m_objects.front()->config().support_filament, this->is_BBL_printer()),
(float)this->skirt_first_layer_height());
}
bool Print::has_support_material() const
+6 -3
View File
@@ -375,6 +375,8 @@ public:
Transform3d trafo_centered() const
{ Transform3d t = this->trafo(); t.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0)); return t; }
const PrintInstances& instances() const { return m_instances; }
// Orca: Bounding box of each connected body, indexed by Layer::lslices_separated_component_ids.
const std::vector<BoundingBox>& separated_body_bboxes() const { return m_separated_body_bboxes; }
PrintInstances &instances() { return m_instances; }
// Whoever will get a non-const pointer to PrintObject will be able to modify its layers.
@@ -581,8 +583,8 @@ private:
void discover_horizontal_shells();
void combine_infill();
void _generate_support_material();
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface*, float>>& surfaces_w_bottom_z) const;
FillAdaptive::RegionOctrees prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface*, const Layer*>>& surfaces_w_layer) const;
FillLightning::GeneratorPtr prepare_lightning_infill_data();
// BBS
@@ -614,7 +616,8 @@ private:
// so that next call to make_perimeters() performs a union() before computing loops
bool m_typed_slices = false;
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> m_adaptive_fill_octrees;
FillAdaptive::RegionOctrees m_adaptive_fill_octrees;
std::vector<BoundingBox> m_separated_body_bboxes;
FillLightning::GeneratorPtr m_lightning_generator;
std::vector < VolumeSlices > firstLayerObjSliceByVolume;
+27 -3
View File
@@ -133,6 +133,15 @@ size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id)
return 0;
}
double nozzle_diameter_for_filament(const PrintConfig& config, int filament_id, bool is_bbl_printer)
{
int extruder = filament_id;
if (is_bbl_printer && config.nozzle_diameter.size() > 1 &&
filament_id >= 1 && static_cast<size_t>(filament_id - 1) < config.filament_map.size())
extruder = config.filament_map.get_at(filament_id - 1);
return config.nozzle_diameter.get_at(extruder - 1);
}
// Orca: input shaping values types by flavor
std::vector<std::string> get_shaper_type_values_for_flavor(GCodeFlavor flavor)
@@ -7602,8 +7611,8 @@ void PrintConfigDef::init_fff_params()
"whole assembly. Parts that touch or overlap are treated as one body and share a center; separate parts "
"(or distinct 3D objects) each get their own.\n"
"Useful when an assembly groups several objects that should each keep a consistent, self-centered infill.\n"
"Affects line and grid patterns and rotation-template infills.\n"
"Patterns locked to global coordinates (Gyroid, Honeycomb, TPMS, ...) are unaffected.");
"Adaptive Cubic and Support Cubic always center each part on itself, and Lightning infill is generated for "
"the whole object and is unaffected.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
@@ -10845,6 +10854,21 @@ void set_filament_dev_options(DynamicPrintConfig &config, const std::vector<cons
}
}
void resize_mixed_filament_metadata(DynamicPrintConfig &config, size_t old_slot_count, size_t new_slot_count)
{
auto resize = [old_slot_count, new_slot_count](auto *opt) {
opt->values.resize(std::min(old_slot_count, opt->values.size()));
opt->values.resize(new_slot_count);
};
resize(config.option<ConfigOptionBools>("filament_is_mixed", true));
resize(config.option<ConfigOptionStrings>("filament_mixed_components", true));
resize(config.option<ConfigOptionStrings>("filament_mixed_sublayer_ratios", true));
resize(config.option<ConfigOptionBools>("filament_mixed_gradient", true));
resize(config.option<ConfigOptionStrings>("filament_mixed_gradient_range", true));
resize(config.option<ConfigOptionStrings>("filament_mixed_gradient_curve", true));
resize(config.option<ConfigOptionBools>("filament_mixed_gradient_per_part", true));
}
//used for object/region config
//use the smallest of multiple to single
@@ -12975,7 +12999,7 @@ CustomGcodeSpecificConfigDef::CustomGcodeSpecificConfigDef()
// Common Defs
def = this->add("layer_num", coInt);
def->label = L("Layer number");
def->tooltip = L("Index of the current layer. One-based (i.e. first layer is number 1).");
def->tooltip = L("Index of the current layer. Zero-based (i.e. first layer is number 0), except in extrusion role change G-code, where it is one-based.");
def = this->add("layer_z", coFloat);
def->label = L("Layer Z");
+19 -4
View File
@@ -136,25 +136,31 @@ enum InfillPattern : int {
ipCount,
};
// Orca: Infill patterns whose alignment origin follows the fill bounding box, so the
// "separated_infills" option can re-center them per connected body. Patterns evaluated in
// absolute/global coordinates (Gyroid, TPMS, Honeycomb, CrossHatch, ...) or that are shape-relative
// (Concentric) ignore that bounding box and are therefore excluded.
// Orca: Infill patterns that the "separated_infills" option can center on each connected body.
inline bool is_separable_infill_pattern(InfillPattern pattern)
{
switch (pattern) {
case ipMonotonic:
case ipMonotonicLine:
case ipRectilinear:
case ipAlignedRectilinear:
case ipZigZag:
case ipCrossZag:
case ipLockedZag:
case ipLine:
case ipGrid:
case ipTriangles:
case ipStars: // tri-hexagon
case ipCubic:
case ipQuarterCubic:
case ipHoneycomb:
case ip3DHoneycomb:
case ipLateralHoneycomb:
case ipLateralLattice:
case ipCrossHatch:
case ipTpmsD:
case ipTpmsFK:
case ipGyroid:
case ipHilbertCurve:
case ipArchimedeanChords:
case ipOctagramSpiral:
@@ -164,6 +170,9 @@ inline bool is_separable_infill_pattern(InfillPattern pattern)
}
}
// Orca: Infill patterns laid out by an octree, which each connected body always gets of its own.
inline bool is_octree_infill_pattern(InfillPattern pattern) { return pattern == ipAdaptiveCubic || pattern == ipSupportCubic; }
// Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option.
// Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more
// than one line per infill wall; a single line makes them plain crossing lines with nothing to round.
@@ -933,6 +942,10 @@ extern std::set<std::string> filament_dev_options;
// filament_configs, one config per filament in slot order, as the filaments' values one after another.
void set_filament_dev_options(DynamicPrintConfig &config, const std::vector<const DynamicPrintConfig *> &filament_configs);
// Orca: sizes the per-slot mixed-colour metadata options to new_slot_count, keeping the first
// old_slot_count values; an option the config lacks is created.
void resize_mixed_filament_metadata(DynamicPrintConfig &config, size_t old_slot_count, size_t new_slot_count);
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
extern void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config,
t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector<int>& f_map_indices);
@@ -2562,6 +2575,8 @@ static bool has_zero_flush_volume_for_used_filaments(const std::vector<T> &fv_ma
size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id);
double nozzle_diameter_for_filament(const PrintConfig& config, int filament_id, bool is_bbl_printer);
} // namespace Slic3r
// Serialization through the Cereal library
+145 -36
View File
@@ -67,6 +67,7 @@
#include <utility>
#include <boost/log/trivial.hpp>
#include <Eigen/Core>
#include <tbb/parallel_for.h>
#include <tbb/spin_mutex.h>
@@ -719,7 +720,8 @@ void PrintObject::prepare_infill()
bool needs_separated_components = false;
for (size_t i = 0; i < this->num_printing_regions(); ++ i) {
const PrintRegionConfig &rc = this->printing_region(i).config();
if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model) {
if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model ||
(rc.sparse_infill_density > 0 && is_octree_infill_pattern(rc.sparse_infill_pattern))) {
needs_separated_components = true;
break;
}
@@ -736,8 +738,9 @@ void PrintObject::prepare_infill()
if (parts <= 1 && ! (first_part != nullptr && first_part->is_splittable()))
needs_separated_components = false;
}
m_separated_body_bboxes.clear();
for (Layer *layer : m_layers)
layer->lslices_separated_component_bboxes.clear();
layer->lslices_separated_component_ids.clear();
if (needs_separated_components) {
const size_t nl = m_layers.size();
std::vector<size_t> offset(nl + 1, 0); // Orca: flat index of the first island of each layer
@@ -788,17 +791,20 @@ void PrintObject::prepare_infill()
});
}
}
// Orca: Full bounding box of each body, indexed by its union-find root.
std::vector<BoundingBox> body_bbox(nreg);
for (size_t i = 0; i < nl; ++ i)
for (size_t a = 0; a < m_layers[i]->lslices.size(); ++ a)
body_bbox[find(offset[i] + a)].merge(m_layers[i]->lslices_bboxes[a]);
// Orca: Store the body bbox for every island.
// Orca: Number the bodies by their first island and merge the bounding boxes of their islands.
std::vector<size_t> body_of_root(nreg, size_t(-1));
for (size_t i = 0; i < nl; ++ i) {
Layer *layer = m_layers[i];
layer->lslices_separated_component_bboxes.resize(layer->lslices.size());
for (size_t a = 0; a < layer->lslices.size(); ++ a)
layer->lslices_separated_component_bboxes[a] = body_bbox[find(offset[i] + a)];
layer->lslices_separated_component_ids.resize(layer->lslices.size());
for (size_t a = 0; a < layer->lslices.size(); ++ a) {
size_t &body = body_of_root[find(offset[i] + a)];
if (body == size_t(-1)) {
body = m_separated_body_bboxes.size();
m_separated_body_bboxes.emplace_back();
}
m_separated_body_bboxes[body].merge(layer->lslices_bboxes[a]);
layer->lslices_separated_component_ids[a] = body;
}
}
}
@@ -836,16 +842,13 @@ void PrintObject::infill()
if (this->set_started(posInfill)) {
m_print->set_status(35, L("Generating infill toolpath"));
const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first;
const auto& support_fill_octree = this->m_adaptive_fill_octrees.second;
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range<size_t>& range) {
[this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get());
m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees, this->m_lightning_generator.get());
}
}
);
@@ -1110,14 +1113,82 @@ void PrintObject::simplify_extrusion_path()
}
}
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface *, float>> &surfaces_w_bottom_z) const
// Orca: Separated body of the island containing a point of a layer, else of the island outline nearest within 1 mm, or -1.
static int separated_body_at(const Layer &layer, const Point &point)
{
int body = -1;
double best = scaled<double>(1.);
for (size_t i = 0; i < layer.lslices.size() && i < layer.lslices_separated_component_ids.size() && best > 0.; ++ i) {
BoundingBox bbox = layer.lslices_bboxes[i];
bbox.offset(coord_t(best));
if (! bbox.contains(point))
continue;
const double dist = layer.lslices[i].contains(point) ? 0. : (layer.lslices[i].point_projection(point) - point).cast<double>().norm();
if (dist < best) {
best = dist;
body = int(layer.lslices_separated_component_ids[i]);
}
}
return body;
}
// Orca: The object mesh in the octree frame split by separated body. Each connected component goes to the body
// most of its sampled triangles lie on, sampled a layer height inside the solid at the layer nearest to them.
static std::vector<indexed_triangle_set> split_mesh_by_body(const PrintObject &object, const indexed_triangle_set &mesh, size_t num_bodies)
{
const Eigen::Matrix3d to_object = FillAdaptive::transform_to_world().toRotationMatrix();
const double inset = object.config().layer_height.value;
std::vector<indexed_triangle_set> bodies(num_bodies);
for (const indexed_triangle_set &component : its_split(mesh)) {
std::vector<size_t> votes(num_bodies, 0);
const size_t step = std::max<size_t>(1, component.indices.size() / 8);
for (size_t i = 0; i < component.indices.size(); i += step) {
const stl_triangle_vertex_indices &tri = component.indices[i];
const Vec3d a = component.vertices[tri[0]].cast<double>(), b = component.vertices[tri[1]].cast<double>(),
d = component.vertices[tri[2]].cast<double>();
const Vec3d normal = (b - a).cross(d - a);
const double area2 = normal.norm();
const Vec3d c = to_object * ((a + b + d) / 3. - (area2 > 0. ? Vec3d(normal * (inset / area2)) : Vec3d::Zero()));
size_t lo = 0, hi = object.layer_count();
while (lo < hi) {
const size_t mid = (lo + hi) / 2;
if (object.get_layer(int(mid))->slice_z < c.z())
lo = mid + 1;
else
hi = mid;
}
if (lo == object.layer_count() || (lo > 0 && c.z() - object.get_layer(int(lo) - 1)->slice_z < object.get_layer(int(lo))->slice_z - c.z()))
-- lo;
if (const int body = separated_body_at(*object.get_layer(int(lo)), Point(scaled<coord_t>(c.x()), scaled<coord_t>(c.y()))); body >= 0)
++ votes[body];
}
if (const auto best = std::max_element(votes.begin(), votes.end()); *best > 0)
its_merge(bodies[best - votes.begin()], component);
}
return bodies;
}
FillAdaptive::RegionOctrees PrintObject::prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface *, const Layer *>> &surfaces_w_layer) const
{
using namespace FillAdaptive;
auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this);
if ((adaptive_line_spacing == 0. && support_line_spacing == 0.) || this->layers().empty())
return std::make_pair(OctreePtr(), OctreePtr());
// Orca: Each region fills with the octrees of its own line spacing, shared by the regions of equal spacing.
const std::vector<double> line_spacing = adaptive_fill_line_spacing(*this);
std::vector<std::pair<double, bool>> spacings; // Line spacing, support cubic.
RegionOctrees octrees;
octrees.region_set.assign(line_spacing.size(), -1);
for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id)
if (line_spacing[region_id] > 0.) {
const std::pair<double, bool> spacing(line_spacing[region_id], this->printing_region(region_id).config().sparse_infill_pattern == ipSupportCubic);
const auto it = std::find(spacings.begin(), spacings.end(), spacing);
octrees.region_set[region_id] = int(it - spacings.begin());
if (it == spacings.end())
spacings.push_back(spacing);
}
if (spacings.empty() || this->layers().empty())
return {};
octrees.sets.resize(spacings.size());
indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set();
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes.
@@ -1125,27 +1196,66 @@ std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare
its_transform(mesh, to_octree * this->trafo_centered(), true);
// Triangulate internal bridging surfaces.
std::vector<std::vector<Vec3d>> overhangs(std::max(surfaces_w_bottom_z.size(), size_t(1)));
std::vector<std::vector<Vec3d>> overhangs(std::max(surfaces_w_layer.size(), size_t(1)));
// ^ make sure vector is not empty, even with no briding surfaces we still want to build the adaptive trees later, some continue normally
tbb::parallel_for(tbb::blocked_range<int>(0, surfaces_w_bottom_z.size()),
[this, &to_octree, &overhangs, &surfaces_w_bottom_z](const tbb::blocked_range<int> &range) {
tbb::parallel_for(tbb::blocked_range<int>(0, surfaces_w_layer.size()),
[this, &to_octree, &overhangs, &surfaces_w_layer](const tbb::blocked_range<int> &range) {
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
for (int surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) {
std::vector<Vec3d> &out = overhangs[surface_idx];
m_print->throw_if_canceled();
append(out, triangulate_expolygon_3d(surfaces_w_bottom_z[surface_idx].first->expolygon,
surfaces_w_bottom_z[surface_idx].second));
append(out, triangulate_expolygon_3d(surfaces_w_layer[surface_idx].first->expolygon,
float(surfaces_w_layer[surface_idx].second->bottom_z())));
for (Vec3d &p : out)
p = (to_octree * p).eval();
}
});
// Orca: Each body gets the octree it has when sliced on its own, from its own triangles, for each line spacing
// its regions fill with. Body num_bodies stands for the whole object, which serves an object of a single body
// and the surfaces of bodies that have no octree of their own.
const size_t num_bodies = m_separated_body_bboxes.size();
std::vector<std::pair<size_t, size_t>> to_build; // Set, body.
std::vector<indexed_triangle_set> body_meshes;
std::vector<std::vector<Vec3d>> body_overhangs(num_bodies);
if (num_bodies > 1) {
body_meshes = split_mesh_by_body(*this, mesh, num_bodies);
for (size_t i = 0; i < surfaces_w_layer.size(); ++ i)
if (const int body = separated_body_at(*surfaces_w_layer[i].second, surfaces_w_layer[i].first->expolygon.contour.points.front()); body >= 0)
append(body_overhangs[body], overhangs[i]);
std::vector<std::vector<char>> fills(spacings.size(), std::vector<char>(num_bodies + 1, false));
for (const Layer *layer : m_layers)
for (size_t region_id = 0; region_id < layer->regions().size() && region_id < octrees.region_set.size(); ++ region_id)
if (const int set = octrees.region_set[region_id]; set >= 0)
for (const Surface &surface : layer->regions()[region_id]->fill_surfaces) {
const int body = separated_body_at(*layer, surface.expolygon.contour.points.front());
fills[set][body >= 0 && ! body_meshes[body].indices.empty() ? size_t(body) : num_bodies] = true;
}
for (size_t set = 0; set < spacings.size(); ++ set) {
octrees.sets[set].bodies.resize(num_bodies);
for (size_t body = 0; body <= num_bodies; ++ body)
if (fills[set][body])
to_build.emplace_back(set, body);
}
} else
for (size_t set = 0; set < spacings.size(); ++ set)
to_build.emplace_back(set, num_bodies);
// and gather them.
for (size_t i = 1; i < overhangs.size(); ++ i)
append(overhangs.front(), std::move(overhangs[i]));
return std::make_pair(
adaptive_line_spacing ? build_octree(mesh, overhangs.front(), adaptive_line_spacing, false) : OctreePtr(),
support_line_spacing ? build_octree(mesh, overhangs.front(), support_line_spacing, true) : OctreePtr());
tbb::parallel_for(tbb::blocked_range<size_t>(0, to_build.size()), [&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++ i) {
m_print->throw_if_canceled();
const auto [set, body] = to_build[i];
const bool object = body == num_bodies;
(object ? octrees.sets[set].object : octrees.sets[set].bodies[body]) =
build_octree(object ? mesh : body_meshes[body], object ? overhangs.front() : body_overhangs[body], spacings[set].first,
spacings[set].second);
}
});
return octrees;
}
FillLightning::GeneratorPtr PrintObject::prepare_lightning_infill_data()
@@ -2963,14 +3073,14 @@ void PrintObject::bridge_over_infill()
std::map<size_t, Polylines> infill_lines;
// SECTION to generate infill polylines
{
std::vector<std::pair<const Surface *, float>> surfaces_w_bottom_z;
std::vector<std::pair<const Surface *, const Layer *>> surfaces_w_layer;
for (const auto &pair : surfaces_by_layer) {
for (const CandidateSurface &c : pair.second) {
surfaces_w_bottom_z.emplace_back(c.original_surface, c.region->m_layer->bottom_z());
surfaces_w_layer.emplace_back(c.original_surface, c.region->m_layer);
}
}
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z);
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_layer);
std::vector<size_t> layers_to_generate_infill;
for (const auto &pair : surfaces_by_layer) {
@@ -2986,8 +3096,7 @@ void PrintObject::bridge_over_infill()
for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) {
size_t lidx = layers_to_generate_infill[job_idx];
infill_lines.at(
lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(po->m_adaptive_fill_octrees.first.get(),
po->m_adaptive_fill_octrees.second.get(),
lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees,
po->m_lightning_generator.get());
}
});
@@ -4467,8 +4576,8 @@ void PrintObject::combine_infill()
// Limit the number of combined layers to the maximum height allowed by this regions' nozzle.
//FIXME limit the layer height to max_layer_height
double nozzle_diameter = std::min(
this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament_id.value - 1),
this->print()->config().nozzle_diameter.get_at(region.config().internal_solid_filament_id.value - 1));
nozzle_diameter_for_filament(this->print()->config(), region.config().sparse_infill_filament_id.value, this->print()->is_BBL_printer()),
nozzle_diameter_for_filament(this->print()->config(), region.config().internal_solid_filament_id.value, this->print()->is_BBL_printer()));
//Orca: Limit combination of infill to up to infill_combination_max_layer_height
const double infill_combination_max_layer_height = region.config().infill_combination_max_layer_height.get_abs_value(nozzle_diameter);
+1 -1
View File
@@ -57,7 +57,7 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
auto nozzle_diameter = float(print_config.nozzle_diameter.get_at(this->extruder(role) - 1));
auto nozzle_diameter = float(nozzle_diameter_for_filament(print_config, this->extruder(role), object.print()->is_BBL_printer()));
return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height));
}
-1
View File
@@ -49,7 +49,6 @@ public:
using TColor = typename PixelRenderer::color_type;
using TValue = typename TColor::value_type;
using TPixel = typename PixelRenderer::pixel_type;
using TRawBuffer = agg::rendering_buffer;
protected:
-1
View File
@@ -44,7 +44,6 @@
#include "TDataStd_Name.hxx"
#include "BRepBuilderAPI_Transform.hxx"
#include "TopExp_Explorer.hxx"
#include "TopExp_Explorer.hxx"
#include "BRep_Tool.hxx"
#include "Font_BRepFont.hxx"
#include "Font_BRepTextBuilder.hxx"
+1 -1
View File
@@ -2180,7 +2180,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers(
// check if the sharp tails should be extended higher
bool detect_first_sharp_tail_only = false;
const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament-1));
const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(nozzle_diameter_for_filament(object.print()->config(), object.config().support_interface_filament, object.print()->is_BBL_printer()));
const coordf_t extrusion_width_scaled = scale_(extrusion_width);
if (is_auto(m_object_config->support_type.value) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) {
for (size_t layer_nr = layer_id_start; layer_nr < num_layers; layer_nr++) {
+1 -1
View File
@@ -1399,7 +1399,7 @@ void TreeSupport::generate_toolpaths()
{
const PrintObjectConfig &object_config = m_object->config();
coordf_t support_extrusion_width = m_support_params.support_extrusion_width;
coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(object_config.support_filament - 1);
coordf_t nozzle_diameter = nozzle_diameter_for_filament(*m_print_config, object_config.support_filament, m_object->print()->is_BBL_printer());
coordf_t layer_height = object_config.layer_height.value;
const size_t wall_count = object_config.tree_support_wall_count.value;
+127 -38
View File
@@ -1248,52 +1248,81 @@ bool triangles_overlap(const Tri2 &a, const Tri2 &b, float eps)
struct NetGrid
{
static constexpr int BIG_SPAN = 16;
float cell;
float eps;
std::unordered_map<uint64_t, std::vector<Tri2>> cells;
std::vector<Tri2> big;
// Each stored triangle keeps its own bounding box. Overlap testing is dominated by rejects - a cell
// holds every triangle whose box touches it, and a candidate meets only a couple of them for real -
// so paying six floats per entry to answer most of those rejects with four comparisons, instead of a
// full triangle intersection, is what makes the net affordable. Measured on a 42k-triangle patch the
// grid ran ~19 million candidate pairs per net, nearly all of them misses, and rejecting them this
// way took the net from ~175 ms to ~53 ms.
//
// The box rides inside the entry rather than in a parallel array: splitting them to scan boxes back
// to back was tried and came out slower, because each bucket then grows two vectors instead of one.
struct Entry
{
Tri2 tri;
Vec2f lo, hi;
};
float cell;
float eps;
std::unordered_map<uint64_t, std::vector<Entry>> cells;
std::vector<Entry> big;
static uint64_t key(int x, int y) { return (uint64_t(uint32_t(x)) << 32) | uint32_t(y); }
bool range(const Tri2 &t, int &x0, int &y0, int &x1, int &y1) const
static Entry entry(const Tri2 &t)
{
return Entry{ t, t[0].cwiseMin(t[1]).cwiseMin(t[2]), t[0].cwiseMax(t[1]).cwiseMax(t[2]) };
}
bool range(const Vec2f &lo, const Vec2f &hi, int &x0, int &y0, int &x1, int &y1) const
{
const Vec2f lo = t[0].cwiseMin(t[1]).cwiseMin(t[2]), hi = t[0].cwiseMax(t[1]).cwiseMax(t[2]);
x0 = int(std::floor(lo.x() / cell));
y0 = int(std::floor(lo.y() / cell));
x1 = int(std::floor(hi.x() / cell));
y1 = int(std::floor(hi.y() / cell));
return x1 - x0 <= BIG_SPAN && y1 - y0 <= BIG_SPAN;
}
// Boxes grown by eps on both sides, to match the tolerance triangles_overlap() itself works to: a
// reject here must never discard a pair that test would have called touching.
bool boxes_apart(const Entry &a, const Entry &b) const
{
return a.hi.x() + eps < b.lo.x() || b.hi.x() + eps < a.lo.x() || a.hi.y() + eps < b.lo.y() ||
b.hi.y() + eps < a.lo.y();
}
bool hits(const Entry &q, const std::vector<Entry> &bucket) const
{
for (const Entry &b : bucket)
if (!boxes_apart(q, b) && triangles_overlap(q.tri, b.tri, eps))
return true;
return false;
}
bool overlaps(const Tri2 &t) const
{
for (const Tri2 &b : big)
if (triangles_overlap(t, b, eps))
return true;
const Entry q = entry(t);
if (hits(q, big))
return true;
int x0, y0, x1, y1;
if (!range(t, x0, y0, x1, y1)) {
for (const auto &[k, tris] : cells)
for (const Tri2 &b : tris)
if (triangles_overlap(t, b, eps))
return true;
if (!range(q.lo, q.hi, x0, y0, x1, y1)) {
for (const auto &[k, bucket] : cells)
if (hits(q, bucket))
return true;
return false;
}
for (int x = x0; x <= x1; ++x)
for (int y = y0; y <= y1; ++y)
if (const auto it = cells.find(key(x, y)); it != cells.end())
for (const Tri2 &b : it->second)
if (triangles_overlap(t, b, eps))
return true;
if (const auto it = cells.find(key(x, y)); it != cells.end() && hits(q, it->second))
return true;
return false;
}
void insert(const Tri2 &t)
{
int x0, y0, x1, y1;
if (!range(t, x0, y0, x1, y1)) {
big.push_back(t);
const Entry e = entry(t);
int x0, y0, x1, y1;
if (!range(e.lo, e.hi, x0, y0, x1, y1)) {
big.push_back(e);
return;
}
for (int x = x0; x <= x1; ++x)
for (int y = y0; y <= y1; ++y)
cells[key(x, y)].push_back(t);
cells[key(x, y)].push_back(e);
}
};
} // namespace
@@ -1305,11 +1334,20 @@ std::vector<TextureIsland> compute_connected_net(const PatchUnwrap &unwrap)
if (n <= 1)
return islands;
// Chart adjacency, with one representative shared edge per adjacent pair.
// Chart adjacency, with one representative shared edge per adjacent pair: the fold line the pair is
// unfolded about.
//
// Which edge that is matters, because two charts can touch along more than one run. A chart cut open
// to flatten it - a ring opened by segment_into_charts(), say - touches its other half along *both*
// sides of the cut. Folding is rigid, so only the run the fold line belongs to comes out matching;
// every other run is left mismatched, and a mismatched run is exactly where the texture visibly
// jumps. Taking whichever edge the map happened to yield first therefore left the long side broken
// about as often as the short one. The fold line is picked from the longest run instead, so what is
// left discontinuous is the shortest boundary the pair has.
const auto edges = build_shared_edges(unwrap);
struct PairEdge { ChartEdge a, b; };
std::map<std::pair<int, int>, PairEdge> pair_edge;
std::vector<std::vector<int>> adj(static_cast<size_t>(n));
struct SharedEdge { PairEdge fold; int base_lo = -1, base_hi = -1; float length = 0.f; };
std::map<std::pair<int, int>, std::vector<SharedEdge>> pair_shared;
for (const auto &[base_edge, list] : edges) {
for (size_t i = 0; i < list.size(); ++i)
for (size_t j = i + 1; j < list.size(); ++j) {
@@ -1317,14 +1355,50 @@ std::vector<TextureIsland> compute_connected_net(const PatchUnwrap &unwrap)
if (c1 == c2 || c1 < 0 || c2 < 0 || c1 >= n || c2 >= n)
continue;
const std::pair<int, int> pk{ std::min(c1, c2), std::max(c1, c2) };
if (pair_edge.count(pk))
continue; // keep the first shared edge as the fold line for this pair
pair_edge[pk] = (c1 < c2) ? PairEdge{ list[i], list[j] } : PairEdge{ list[j], list[i] };
adj[size_t(pk.first)].push_back(pk.second);
adj[size_t(pk.second)].push_back(pk.first);
SharedEdge se;
se.fold = (c1 < c2) ? PairEdge{ list[i], list[j] } : PairEdge{ list[j], list[i] };
se.base_lo = base_edge.first;
se.base_hi = base_edge.second;
// The unwrap is scaled to true surface area, so a uv distance is a length in mm.
se.length = (unwrap.uvs[size_t(se.fold.a.uv_lo)] - unwrap.uvs[size_t(se.fold.a.uv_hi)]).norm();
pair_shared[pk].push_back(se);
}
}
std::map<std::pair<int, int>, PairEdge> pair_edge;
std::map<std::pair<int, int>, float> pair_weight; // length of the run each pair folds across
std::vector<std::vector<int>> adj(static_cast<size_t>(n));
for (const auto &[pk, shared] : pair_shared) {
// Group the pair's shared edges into runs - edges joined end to end through a base vertex - and
// total each run's length.
std::unordered_map<int, int> local;
for (const SharedEdge &se : shared)
for (const int v : { se.base_lo, se.base_hi })
local.emplace(v, int(local.size()));
UnionFind runs(local.size());
for (const SharedEdge &se : shared)
runs.unite(local[se.base_lo], local[se.base_hi]);
std::unordered_map<int, float> run_length;
std::unordered_map<int, size_t> run_first;
for (size_t i = 0; i < shared.size(); ++i) {
const int root = runs.find(local[shared[i].base_lo]);
run_length[root] += shared[i].length;
run_first.emplace(root, i);
}
int best_root = -1;
float best_len = -1.f;
for (const auto &[root, len] : run_length)
if (len > best_len) { best_len = len; best_root = root; }
if (best_root < 0)
continue;
pair_edge[pk] = shared[run_first[best_root]].fold;
pair_weight[pk] = best_len;
adj[size_t(pk.first)].push_back(pk.second);
adj[size_t(pk.second)].push_back(pk.first);
}
// Per chart: its vertices, its triangles and its flattened area.
std::vector<std::vector<int>> chart_verts(static_cast<size_t>(n)), chart_tris(static_cast<size_t>(n));
std::vector<float> chart_area(static_cast<size_t>(n), 0.f);
@@ -1378,15 +1452,29 @@ std::vector<TextureIsland> compute_connected_net(const PatchUnwrap &unwrap)
for (const int t : chart_tris[size_t(root)])
grid.insert(placed(m, t));
}
std::queue<int> q;
q.push(root);
// Grown strongest-adjacency-first (Prim, not breadth-first): a chart is folded onto whichever
// neighbour it shares the longest boundary with, among everything reachable so far. Order matters
// because only the fold a chart is actually reached by comes out matching - every other boundary
// it has is left to chance. Taking neighbours in breadth-first order, biggest-area first, let a
// far-off branch claim a chart across a short boundary before its true neighbour was reached, and
// the long boundary they shared then stayed broken. That is the visible seam next to a hole: a
// ring is cut into two halves that share a long boundary, and whichever half was reached first
// took the other one along some unrelated edge.
using Candidate = std::pair<float, std::pair<int, int>>; // weight, (from, to)
std::priority_queue<Candidate> q;
const auto push_neighbours = [&](int p) {
for (const int c : adj[size_t(p)])
if (net_of[size_t(c)] < 0 && !chart_tris[size_t(c)].empty()) {
const auto w = pair_weight.find({ std::min(p, c), std::max(p, c) });
q.push({ w == pair_weight.end() ? 0.f : w->second, { p, c } });
}
};
push_neighbours(root);
while (!q.empty()) {
const int p = q.front();
const auto [weight, link] = q.top();
q.pop();
std::vector<int> neighbours = adj[size_t(p)];
std::stable_sort(neighbours.begin(), neighbours.end(),
[&chart_area](int a, int b) { return chart_area[size_t(a)] > chart_area[size_t(b)]; });
for (const int c : neighbours) {
const int p = link.first, c = link.second;
{
if (net_of[size_t(c)] >= 0 || chart_tris[size_t(c)].empty())
continue;
const auto it = pair_edge.find({ std::min(p, c), std::max(p, c) });
@@ -1417,7 +1505,7 @@ std::vector<TextureIsland> compute_connected_net(const PatchUnwrap &unwrap)
for (const Tri2 &t : tris)
grid.insert(t);
net_of[size_t(c)] = net;
q.push(c);
push_neighbours(c);
}
}
}
@@ -5053,4 +5141,5 @@ indexed_triangle_set cut_mesh_at_steps(const indexed_triangle_set &mesh, const s
return out;
}
} // namespace Slic3r
@@ -17,9 +17,6 @@ typedef std::vector<Color> ColorList;
typedef std::array<double, 3> ColorDouble;
typedef std::array<std::size_t, 3> RGB;
// Function pointer type that points to a specific color-difference function based on the chosen method.
using DistanceFunction = double (*)(const Color&, const Color&);
// Color space used for computing color differences.
enum struct ColorDifferenceMethod : std::size_t {
RGB = 0, // Simplest and fastest
+3 -2
View File
@@ -24,8 +24,6 @@
namespace Slic3r { namespace tex2color {
namespace PMP = CGAL::Polygon_mesh_processing;
// Default upper bound on the number of half-edges in any single boundary cycle
// that CloseBoundariesAndRepairManifoldness will attempt to triangulate. The
// cost of triangulate_hole grows non-linearly with cycle length, so this caps
@@ -61,6 +59,7 @@ struct BoundaryEdgeStats
// any pre-processing (e.g. stitch_borders) needed for the count to be meaningful.
inline BoundaryEdgeStats ComputeBoundaryEdgeStats(const cgalutils::CGALMesh& cgal_mesh)
{
namespace PMP = CGAL::Polygon_mesh_processing;
using CGALMesh = cgalutils::CGALMesh;
using HalfedgeDescriptor = boost::graph_traits<CGALMesh>::halfedge_descriptor;
@@ -87,6 +86,7 @@ inline BoundaryEdgeStats ComputeBoundaryEdgeStats(const cgalutils::CGALMesh& cga
// boundary statistics; entering this function always triggers triangulation.
inline void CloseBoundariesAndRepairManifoldness(cgalutils::CGALMesh& cgal_mesh)
{
namespace PMP = CGAL::Polygon_mesh_processing;
using CGALMesh = cgalutils::CGALMesh;
using HalfedgeDescriptor = boost::graph_traits<CGALMesh>::halfedge_descriptor;
using FaceDescriptor = boost::graph_traits<CGALMesh>::face_descriptor;
@@ -112,6 +112,7 @@ inline bool RepairMesh(const TriMesh& mesh,
AlgoCancelCallback cancel_callback = nullptr,
const RepairSetting& setting = RepairSetting{})
{
namespace PMP = CGAL::Polygon_mesh_processing;
using Clock = std::chrono::steady_clock;
auto elapsed_ms = [](Clock::time_point t0) {
return std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
-2
View File
@@ -25,8 +25,6 @@
#include <initializer_list>
#include <string_view>
#include <regex>
#include <string_view>
#include <algorithm>
#include <boost/system/error_code.hpp>
#include <boost/algorithm/string.hpp>
-2
View File
@@ -432,8 +432,6 @@ inline IntegerOnly<I, I> fast_round_up(double a)
return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5));
}
template<class T> using SamePair = std::pair<T, T>;
} // namespace Slic3r
#endif
+20 -9
View File
@@ -19,7 +19,6 @@
#include "slic3r/GUI/Widgets/Label.hpp"
#include <utility>
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/HyperLink.hpp"
#include <cstdlib>
#include <cstdio>
#include "slic3r/GUI/DeviceCore/DevDefs.h"
@@ -48,6 +47,7 @@
#include <wx/panel.h>
#include <wx/textctrl.h>
#include <wx/string.h>
#include <wx/utils.h>
#include <wx/valtext.h>
#include <wx/sizer.h>
#include <wx/peninfobase.h>
@@ -103,6 +103,14 @@ void AMSMaterialsSetting::create()
m_sizer_button->Add(0, 0, 1, wxEXPAND, 0);
// Orca: link to the Orca Slicer pressure-advance wiki (region-agnostic).
m_wiki_ctrl = new Button(this, "", "toolbar_wiki", 0, 15);
auto wiki_url = "https://www.orcaslicer.com/wiki/pressure_advance_calib";
m_wiki_ctrl->SetToolTip(_L("Wiki Guide") + "\n" + wiki_url);
m_wiki_ctrl->SetStyle(ButtonStyle::Confirm, ButtonType::Circle);
m_wiki_ctrl->SetCanFocus(false);
m_wiki_ctrl->Bind(wxEVT_LEFT_DOWN, ([wiki_url](auto& e) {wxLaunchDefaultBrowser(wiki_url);}));
m_button_confirm = new Button(this, _L("Confirm"));
m_button_confirm->SetStyle(ButtonStyle::Confirm, ButtonType::Choice);
m_button_confirm->Bind(wxEVT_BUTTON, &AMSMaterialsSetting::on_select_ok, this);
@@ -115,8 +123,9 @@ void AMSMaterialsSetting::create()
m_button_close->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
m_button_close->Bind(wxEVT_BUTTON, &AMSMaterialsSetting::on_select_close, this);
m_sizer_button->Add(m_button_confirm, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(20));
m_sizer_button->Add(m_button_reset, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(20));
m_sizer_button->Add(m_wiki_ctrl, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(20));
m_sizer_button->Add(m_button_confirm, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(10));
m_sizer_button->Add(m_button_reset, 0, wxALIGN_CENTER | wxRIGHT, FromDIP(10));
m_sizer_button->Add(m_button_close, 0, wxALIGN_CENTER, 0);
m_sizer_main->Add(m_panel_normal, 0, wxALL, FromDIP(2));
@@ -340,11 +349,7 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent)
m_ratio_text->SetForegroundColour(wxColour(50, 58, 61));
m_ratio_text->SetFont(Label::Head_14);
// Orca: link to the Orca Slicer pressure-advance wiki (region-agnostic).
wxString link_url = "https://www.orcaslicer.com/wiki/pressure_advance_calib";
m_wiki_ctrl = new HyperLink(parent, _L("Wiki Guide"), link_url);
cali_title_sizer->Add(m_ratio_text, 0, wxALIGN_CENTER_VERTICAL);
cali_title_sizer->Add(m_wiki_ctrl, 0, wxALIGN_CENTER_VERTICAL);
wxBoxSizer *m_sizer_cali_resutl = new wxBoxSizer(wxHORIZONTAL);
// pa profile
@@ -1012,7 +1017,10 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0);
if (machine_diameter == 0.0f && preset_bundle) {
const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter");
if (opt) machine_diameter = static_cast<const ConfigOptionFloats *>(opt)->values[0];
if (opt) {
const auto &nd = static_cast<const ConfigOptionFloats *>(opt)->values;
if (!nd.empty()) machine_diameter = nd.size() > 1 ? nd[1] : nd[0];
}
}
stream << std::fixed << std::setprecision(1) << machine_diameter;
std::string nozzle_diameter_str = stream.str();
@@ -1291,7 +1299,10 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0);
if (machine_diameter == 0.0f) {
const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter");
if (opt) machine_diameter = static_cast<const ConfigOptionFloats *>(opt)->values[0];
if (opt) {
const auto &nd = static_cast<const ConfigOptionFloats *>(opt)->values;
if (!nd.empty()) machine_diameter = nd.size() > 1 ? nd[1] : nd[0];
}
}
stream << std::fixed << std::setprecision(1) << machine_diameter;
}
+1 -2
View File
@@ -14,7 +14,6 @@
#include "Widgets/CheckBox.hpp"
#include "Widgets/ComboBox.hpp"
#include "Widgets/TextInput.hpp"
#include "Widgets/HyperLink.hpp"
#include "slic3r/Utils/CalibUtils.hpp"
#include <wx/anybutton.h>
#include <wx/colour.h>
@@ -194,7 +193,7 @@ protected:
wxPanel * m_panel_kn;
wxStaticText* m_ratio_text;
HyperLink * m_wiki_ctrl;
Button * m_wiki_ctrl;
wxStaticText* m_k_param;
TextInput* m_input_k_val;
wxStaticText* m_n_param;
-2
View File
@@ -17,7 +17,6 @@
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
@@ -30,7 +29,6 @@
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include "GUI_Utils.hpp"
-3
View File
@@ -19,20 +19,17 @@
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/grid.h>
#include <wx/dataview.h>
#include <wx/panel.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/statbox.h>
#include <wx/tglbtn.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <map>
-3
View File
@@ -28,7 +28,6 @@
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/grid.h>
#include <wx/dataview.h>
#include <wx/panel.h>
@@ -36,14 +35,12 @@
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/gbsizer.h>
#include <wx/statbox.h>
#include <wx/tglbtn.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/webrequest.h>
#include <map>
#include <vector>
-1
View File
@@ -8,7 +8,6 @@
#include <memory>
#include <string>
#include <functional>
#include <string>
#include "Jobs/ProgressIndicator.hpp"
-1
View File
@@ -10,7 +10,6 @@
#include <memory>
#include <string>
#include <functional>
#include <string>
#include <wx/string.h>
#include "Jobs/ProgressIndicator.hpp"
-1
View File
@@ -9,7 +9,6 @@
#include <memory>
#include <string>
#include <functional>
#include <string>
#include <wx/hyperlink.h>
#include "Jobs/ProgressIndicator.hpp"
-1
View File
@@ -9,7 +9,6 @@
#include <memory>
#include <string>
#include <functional>
#include <string>
#include <wx/hyperlink.h>
#include "Jobs/ProgressIndicator.hpp"
@@ -18,7 +18,6 @@
#include <wx/string.h>
#include <wx/toplevel.h>
#include <wx/timer.h>
#include "Widgets/Button.hpp"
#include "CapsuleButton.hpp"
namespace Slic3r::GUI { struct IntEvent; }
-1
View File
@@ -46,7 +46,6 @@
#include "libslic3r/Model.hpp"
#include "libslic3r/Polygon.hpp"
#include "MainFrame.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "Jobs/BoostThreadWorker.hpp"
#include "Jobs/PlaterWorker.hpp"
-1
View File
@@ -29,7 +29,6 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/Utils/Bonjour.hpp"
#include "slic3r/GUI/Widgets/DialogButtons.hpp"
-2
View File
@@ -11,7 +11,6 @@
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
@@ -24,7 +23,6 @@
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include "GUI_Utils.hpp"
+4 -2
View File
@@ -21,7 +21,6 @@
#include "slic3r/GUI/PresetComboBoxes.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include <wx/anybutton.h>
#include "slic3r/GUI/Widgets/HyperLink.hpp"
#include <wx/utils.h>
#include <wx/arrstr.h>
#include "slic3r/GUI/Widgets/PopupWindow.hpp"
@@ -494,7 +493,10 @@ void CaliPageCaption::init_bitmaps() {
void CaliPageCaption::create_wiki(wxWindow* parent)
{
// ORCA standardized HyperLink
m_wiki_text = new HyperLink(parent, _L("Wiki Guide"));
m_wiki_text = new Button(parent, "", "toolbar_wiki", 0, 15);
m_wiki_text->SetToolTip(_L("Wiki Guide"));
m_wiki_text->SetStyle(ButtonStyle::Confirm, ButtonType::Circle);
m_wiki_text->SetCanFocus(false);
m_wiki_text->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) {
if (!m_wiki_url.empty())
wxLaunchDefaultBrowser(m_wiki_url);
+1 -2
View File
@@ -7,7 +7,6 @@
#include "Widgets/TextInput.hpp"
#include "Widgets/AMSControl.hpp"
#include "Widgets/ProgressBar.hpp"
#include "Widgets/HyperLink.hpp"
#include "wxExtensions.hpp"
#include "PresetComboBoxes.hpp"
@@ -155,7 +154,7 @@ private:
void init_bitmaps();
void create_wiki(wxWindow* parent);
HyperLink* m_wiki_text; // ORCA
Button* m_wiki_text; // ORCA
wxString m_wiki_url;
ScalableBitmap m_prev_bmp_normal;
ScalableBitmap m_prev_bmp_hover;
+1 -5
View File
@@ -1,4 +1,3 @@
// #include "libslic3r/GCodeSender.hpp"
#include "ConfigManipulation.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
@@ -872,10 +871,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
toggle_line("center_of_surface_pattern", has_centered_surface);
// Orca: separate infills
bool is_internal_infill_separable = is_separable_infill_pattern(config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->value) ||
config->opt_string("sparse_infill_rotate_template") != "" ||
config->opt_string("solid_infill_rotate_template") != "";
toggle_line("separated_infills", is_internal_infill_separable);
toggle_line("separated_infills", is_separable_infill_pattern(pattern));
// Fill order is only meaningful for the center-based surface fill patterns; hide it otherwise.
auto is_centered_fill = [](InfillPattern p) { return p == ipConcentric || p == ipSpiralInset || p == ipArchimedeanChords || p == ipOctagramSpiral; };
-3
View File
@@ -198,6 +198,3 @@ template<> struct std::hash<NozzleDef>
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
};
};
// key(extruder_id) -> { key1(nozzle type info), val1( number of the nozzle type)}
using ExtruderNozzleInfos = std::unordered_map<int, std::unordered_map<NozzleDef, int>>;
@@ -15,7 +15,6 @@
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <wx/string.h>
#include <wx/colour.h>
-1
View File
@@ -11,7 +11,6 @@
#include "GUI_App.hpp"
#include "DeviceErrorDialog.hpp"
#include "Plater.hpp"
#include "GUI_App.hpp"
#include "ReleaseNote.hpp"
#include <string>
#include <boost/log/trivial.hpp>
+18 -12
View File
@@ -4,7 +4,6 @@
#include "wx/dcgraph.h"
#include "I18N.hpp"
#include "PartPlate.hpp"
#include "Widgets/HyperLink.hpp"
#include <wx/colour.h>
#include "libslic3r/Config.hpp"
#include "libslic3r/PrintConfig.hpp"
@@ -27,6 +26,7 @@
#include "slic3r/GUI/Widgets/StaticBox.hpp"
#include <utility>
#include "slic3r/GUI/Widgets/SwitchButton.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include <wx/tglbtn.h>
namespace Slic3r { namespace GUI {
@@ -203,18 +203,24 @@ FilamentGroupPopup::FilamentGroupPopup(wxWindow *parent) : PopupWindow(parent, w
{
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
// ORCA Unified hyperlinks
video_link = new HyperLink(this, _L("Video tutorial"));
video_link->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& e)
{
play_dual_extruder_slice_video();
wxGetApp().app_config->set("play_slicing_video", "false");
});
button_sizer->Add(video_link, 0, wxLEFT, horizontal_margin + FromDIP(3));
button_sizer->AddStretchSpacer();
wiki_link = new HyperLink(this, _L("Wiki Guide"));
wiki_link->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent&) { open_filament_group_wiki(); });
button_sizer->Add(wiki_link, 0, wxLEFT, horizontal_margin);
auto wiki_btn = new Button(this, "", "toolbar_wiki", 0, 15);
wiki_btn->SetToolTip(_L("Wiki Guide") + "\n" + "https://e.bambulab.com/t?c=mOkvsXkJ9pldGYp9");
wiki_btn->SetStyle(ButtonStyle::Confirm, ::ButtonType::Circle);
wiki_btn->SetCanFocus(false);
wiki_btn->Bind(wxEVT_LEFT_DOWN, ([](auto& e) {open_filament_group_wiki();}));
auto video_btn = new Button(this, "", "toolbar_video_guide", 0, 15);
video_btn->SetToolTip(_L("Video Guide"));
video_btn->SetStyle(ButtonStyle::Confirm, ::ButtonType::Circle);
video_btn->SetCanFocus(false);
video_btn->Bind(wxEVT_LEFT_DOWN, ([](auto& e) {
play_dual_extruder_slice_video();
wxGetApp().app_config->set("play_slicing_video", "false");
}));
button_sizer->Add(wiki_btn , 0, wxLEFT, horizontal_margin + FromDIP(3));
button_sizer->Add(video_btn, 0, wxLEFT, FromDIP(10));
top_sizer->Add(button_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, horizontal_margin);
}
+1 -5
View File
@@ -29,6 +29,7 @@ public:
FilamentGroupPopup(wxWindow *parent);
void tryPopup(Plater* plater,PartPlate* plate, bool slice_all);
void tryClose();
void Dismiss() override;
FilamentMapMode GetSelectedMode() const { return m_mode; }
private:
@@ -40,7 +41,6 @@ private:
void OnLeaveWindow(wxMouseEvent &);
void OnEnterWindow(wxMouseEvent &);
void OnTimer(wxTimerEvent &event);
void Dismiss();
void CreateBmps();
@@ -80,10 +80,6 @@ private:
wxBitmap unchecked_hover_bmp;
wxBitmap global_tag_bmp;
wxStaticText *wiki_link;
wxStaticText *video_link;
StaticBox *m_smart_filament_panel{nullptr};
wxSizerItem *m_smart_filament_spacer{nullptr};
SwitchButton *m_smart_filament_switch{nullptr};
+6 -30
View File
@@ -32,6 +32,7 @@
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GLModel.hpp"
#include "slic3r/GUI/GLShader.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoUtils.hpp"
#include "libslic3r/Point.hpp"
#include <math.h>
#include "libvgcode/include/Viewer.hpp"
@@ -3127,27 +3128,6 @@ void GCodeViewer::render_legend_color_arr_recommen(float window_padding)
}
};
auto link_filament_group_wiki = [&](const std::string& label) {
ImVec2 wiki_part_size = ImGui::CalcTextSize(label.c_str());
ImColor HyperColor = ImColor(0, 150, 136, 255); // ORCA match color
ImGui::PushStyleColor(ImGuiCol_Text, HyperColor.Value);
imgui.text(label.c_str());
ImGui::PopStyleColor();
// ORCA use underline to match hyperlink style
ImVec2 lineEnd = ImGui::GetItemRectMax();
lineEnd.y -= 2.0f;
ImVec2 lineStart = lineEnd;
lineStart.x = ImGui::GetItemRectMin().x;
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, HyperColor);
// click behavior
if (ImGui::IsMouseHoveringRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), true)) {
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
open_filament_group_wiki();
}
}
};
auto draw_dash_line = [&](ImDrawList* draw_list, int dash_length = 5, int gap_length = 3) {
ImVec2 p1 = ImGui::GetCursorScreenPos();
ImVec2 p2 = ImVec2(p1.x + ImGui::GetContentRegionAvail().x, p1.y);
@@ -3221,7 +3201,7 @@ void GCodeViewer::render_legend_color_arr_recommen(float window_padding)
else
tips_count = 5;
float AMS_container_height = ams_item_height + line_height * tips_count + line_height;
float AMS_container_height = ams_item_height + line_height * tips_count + line_height + window_padding * 2.f;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0)); // this shold be 0 since its child of gcodeviewer
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(window_padding * 3, 0));
@@ -3236,10 +3216,6 @@ void GCodeViewer::render_legend_color_arr_recommen(float window_padding)
ImGui::Dummy({window_padding, window_padding});
ImGui::PushStyleColor(ImGuiCol_Separator, ImVec4(1.0f,1.0f,1.0f,0.6f));
imgui.bold_text(_u8L("Filament Grouping"));
ImGui::SameLine();
std::string tip_str = _u8L("Why this grouping");
ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - window_padding - ImGui::CalcTextSize(tip_str.c_str()).x);
link_filament_group_wiki(tip_str);
ImGui::Separator();
ImGui::PopStyleColor();
ImGui::Dummy({window_padding, window_padding});
@@ -3344,6 +3320,9 @@ void GCodeViewer::render_legend_color_arr_recommen(float window_padding)
}
ImGui::Dummy({window_padding, window_padding});
GLGizmoUtils::render_wiki_guide_button(*wxGetApp().plater()->get_current_canvas3D(), m_scale,"https://e.bambulab.com/t?c=mOkvsXkJ9pldGYp9");
ImGui::SameLine();
if (!is_optimal_group) {
link_text_set_to_optional(_u8L("Set to Optimal"));
ImGui::SameLine();
@@ -3352,10 +3331,7 @@ void GCodeViewer::render_legend_color_arr_recommen(float window_padding)
}
link_text(_u8L("Regroup filament"));
ImGui::SameLine();
std::string wiki_str = _u8L("Wiki Guide"); // ORCA
ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - window_padding - ImGui::CalcTextSize(wiki_str.c_str()).x);
link_filament_group_wiki(wiki_str);
ImGui::Dummy({window_padding, window_padding});
ImGui::EndChild();
}
+9 -5
View File
@@ -2427,11 +2427,15 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
if (m_canvas_type == ECanvasType::CanvasView3D) {
// m_show_bed gates the plate list too: hiding the bed but leaving its grid and outline
// floating would read as a rendering fault rather than a deliberate view option.
// Design tab: while its reference planes are up they draw their own axes from the modeling
// origin, where the bed's triad would otherwise sit on top of them.
if (show_bed)
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(),
m_show_world_axes && !(m_design_sketch_tool != nullptr && m_design_sketch_tool->draws_reference_axes()));
if (show_bed) {
bool show_axes = m_show_world_axes;
#ifdef SLIC3R_CAD
// Design tab: while its reference planes are up they draw their own axes from the modeling
// origin, where the bed's triad would otherwise sit on top of them.
show_axes = show_axes && !(m_design_sketch_tool != nullptr && m_design_sketch_tool->draws_reference_axes());
#endif
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes);
}
m_frame_profiler.mark("bed");
if (show_bed) //BBS: add outline logic
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
-3
View File
@@ -183,13 +183,10 @@ wxDECLARE_EVENT(EVT_GLCANVAS_PLATE_NAME_CHANGE, SimpleEvent);
//BBS: declare EVT_GLCANVAS_PLATE_SELECT
wxDECLARE_EVENT(EVT_GLCANVAS_PLATE_SELECT, SimpleEvent);
using Vec2dEvent = Event<Vec2d>;
// _bool_ value is used as a indicator of selection in the 3DScene
using RBtnEvent = Event<std::pair<Vec2d, bool>>;
using RBtnPlateEvent = Event<std::pair<Vec2d, int>>;
template <size_t N> using Vec2dsEvent = ArrayEvent<Vec2d, N>;
using Vec3dEvent = Event<Vec3d>;
template <size_t N> using Vec3dsEvent = ArrayEvent<Vec3d, N>;
using HeightProfileSmoothEvent = Event<HeightProfileSmoothingParams>;
-1
View File
@@ -46,7 +46,6 @@
#include "nanosvg/nanosvgrast.h"
#include "libslic3r/Utils.hpp"
#include "GUI_App.hpp"
#include <boost/log/trivial.hpp>
#include <wx/dcgraph.h>
#include <wx/dcmemory.h>
-1
View File
@@ -50,7 +50,6 @@
#include "AboutDialog.hpp"
#include "MsgDialog.hpp"
#include "Plater.hpp"
#include "format.hpp"
#include "WebUserLoginDialog.hpp"
-2
View File
@@ -172,12 +172,10 @@
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/I18N.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/InstanceLock.hpp"
#include "libslic3r/Thread.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/Utils.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/plugin/host/PluginHostUi.hpp"
#include "slic3r/plugin/PythonInterpreter.hpp"
@@ -27,6 +27,7 @@
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/OpenGLManager.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Shortcuts.hpp"
#include "slic3r/GUI/TextureLibrary.hpp"
#include "slic3r/GUI/TextureProjectorFrame.hpp"
#include "slic3r/GUI/UVEditorCanvas.hpp"
@@ -407,11 +408,48 @@ GLGizmoTextureDisplacement::GLGizmoTextureDisplacement(GLCanvas3D& parent, const
bool GLGizmoTextureDisplacement::on_init()
{
m_shortcut = Shortcut::GizmoDisplacement;
const wxString ctrl = GUI::shortkey_ctrl_prefix();
const wxString alt = GUI::shortkey_alt_prefix();
const wxString shift = GUI::shortkey_shift_prefix();
m_desc["cursor_size"] = _L("Brush size");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
m_desc["remove_layer"] = _L("Remove");
m_desc["bake"] = _L_CONTEXT("Bake", "Texture Displacement");
m_desc["paint"] = _L("Paint");
m_desc["erase"] = _L("Erase");
m_desc["gap_area"] = _L("Gap area");
m_desc["smart_fill_angle"] = _L("Smart fill angle");
m_desc["toggle_wireframe"] = _L("Toggle Wireframe");
std::pair<wxString, wxString> paint_shortcut = {_L("Left mouse button"), m_desc["paint"]};
std::pair<wxString, wxString> erase_shortcut = {shift + _L("Left mouse button"), m_desc["erase"]};
std::pair<wxString, wxString> toggle_wireframe_shortcut = {alt + shift + _L_CONTEXT("Enter", "Keyboard Shortcut"), m_desc["toggle_wireframe"]};
m_shortcuts_brush = {
paint_shortcut,
erase_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["cursor_size"]},
toggle_wireframe_shortcut
};
m_shortcuts_bucket_fill = {
paint_shortcut,
erase_shortcut,
{ctrl + _L("Mouse wheel"), m_desc["smart_fill_angle"]},
toggle_wireframe_shortcut
};
m_shortcuts_gap_fill = {
{ctrl + _L("Mouse wheel"), m_desc["gap_area"]},
toggle_wireframe_shortcut
};
return true;
}
@@ -523,7 +561,15 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
// volume for a shaded pass that then draws nothing is what made the model vanish - most obviously
// with zero layers, but equally with a layer that has no texture picked yet.
const bool use_shaded = m_use_shaded_preview && m_shaded_preview_glmodel.is_initialized() && shaded_preview_ready();
const bool use_true_preview = !use_shaded && m_preview_glmodel.is_initialized();
// Checker/Distortion are built from the *base* patch and drawn with a polygon offset, which biases
// depth values - it does not move the geometry. It therefore cannot win against a surface that
// genuinely stands in front, and the displaced preview does exactly that: it rises above the base
// surface by the layer's depth. Drawn underneath a UV-check overlay it simply occludes it, which is
// why those two modes looked like they did nothing. Leave it out and let the undisplaced volume show
// through instead (toggle_model_objects_visibility below) - that one *is* coincident with the
// overlay, which is what the offset assumes, and it is the surface whose mapping is being inspected.
const bool use_true_preview = !use_shaded && m_uv_check_mode == UVCheckMode::None &&
m_preview_glmodel.is_initialized();
// In Checker/Distortion mode the UV-check overlay *is* the surface visualization the user is
// looking at, so the opaque paint-selection highlight must not be drawn on top of it - same
// reasoning as skipping it for the shaded preview (see bug #12). Without this the painted area
@@ -551,7 +597,13 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
render_triangles(selection);
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
}
} else if (show_paint_overlay) {
} else {
// render_triangles() *is* the model in a painter gizmo (it draws every model-part volume with the
// selector's colours), not an overlay on top of one - so it still has to run under a UV-check
// overlay, or nothing draws the surface at all and the checker floats alone over an empty scene.
// Deliberately without the depth bias the branch above applies: the checker/heatmap is drawn later
// with its own -1 offset and has to win against this. Biasing both by the same amount is what made
// the painted area cover the checker and is why this call used to be skipped outright.
render_triangles(selection);
}
@@ -5313,6 +5365,12 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
return;
ModelVolume *mv = texture_volume();
float scale = m_parent.get_scale();
#ifdef WIN32
int dpi = get_dpi_for_window(wxGetApp().GetTopWindow());
scale *= (float) dpi / (float) DPI_DEFAULT;
#endif // WIN32
const float approx_height = m_imgui->scaled(24.f);
y = std::min(y, bottom_limit - approx_height);
@@ -5742,14 +5800,20 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
const int cur_mode = m_use_shaded_preview ? 1 :
m_uv_check_mode == UVCheckMode::Checker ? 2 :
m_uv_check_mode == UVCheckMode::Distortion ? 3 : 0;
int new_mode = cur_mode;
bool wf_toggle = false;
const wxString distortion_na = active == nullptr ? _L("Add a layer first.") :
active->projection_method != TextureProjectionMethod::LSCM ?
_L("Needs the active layer mapped with Unwrap (LSCM).") :
wxString();
// Distortion over a layer that stopped being an unwrap shows nothing at all, so fall back to Normal.
if (cur_mode == 3 && !distortion_na.empty())
int new_mode = cur_mode;
bool wf_toggle = false;
bool open_uv_editor = false;
// Checker and Distortion both draw *the unwrap* - the first the texture grid laid over it, the second
// its stretch - so they only mean anything for a layer mapped with Unwrap (LSCM). On the default
// triplanar mapping (or cylindrical / spherical / from view) they are faded out with the reason in the
// tooltip, rather than being offered and then showing nothing.
const wxString uv_view_na = active == nullptr ? _L("Add a layer first.") :
active->projection_method != TextureProjectionMethod::LSCM ?
_L("Only for a layer mapped with Unwrap (LSCM) - set the "
"active layer's Mapping to Unwrap to use this view.") :
wxString();
// Either view over a layer that stopped being an unwrap shows nothing at all, so fall back to Normal.
if ((cur_mode == 2 || cur_mode == 3) && !uv_view_na.empty())
new_mode = 0;
const float x0 = ImGui::GetCursorPosX();
@@ -5768,12 +5832,20 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
ImGui::SameLine(0.f, gap_s);
if (icon_toggle(703, "texture_displacement_checker.svg", cur_mode == 2, icon_md, _L("Checker"),
_L("Checker - a test grid instead of the texture. Where the squares stay square the "
"texture is undistorted; where they stretch, it will too")))
new_mode = 2;
"texture is undistorted; where they stretch, it will too. Opens the UV editor if "
"it is closed"),
uv_view_na)) {
new_mode = 2;
open_uv_editor = true;
}
ImGui::SameLine(0.f, gap_s);
if (icon_toggle(704, "texture_displacement_distortion.svg", cur_mode == 3, icon_md, _L("Distortion"),
_L("Distortion - blue-to-red stretch heatmap over the unwrap"), distortion_na))
new_mode = 3;
_L("Distortion - blue-to-red stretch heatmap over the unwrap. Opens the UV editor if "
"it is closed"),
uv_view_na)) {
new_mode = 3;
open_uv_editor = true;
}
vsep(icon_md);
if (icon_toggle(705, "texture_displacement_wireframe.svg", m_wireframe_overlay, icon_md, _L("Wireframe"),
_L("Wireframe - overlay the mesh edges; independent of the view above")))
@@ -5788,6 +5860,13 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
hover_tip(_u8L("Rebuilds the preview as soon as anything changes. Turn it off on a heavy model if painting "
"or dragging a slider starts to stutter - the preview then waits until you let go."));
// Both are views of the unwrap, so picking one brings the UV editor up with it - including when that
// view is already the active one and only the pane is missing.
if (open_uv_editor && !m_show_uv_editor) {
m_show_uv_editor = true;
if (new_mode == cur_mode)
update_uv_editor(); // otherwise apply_view_mode() below does it
}
if (new_mode != cur_mode)
apply_view_mode(new_mode);
if (wf_toggle) {
@@ -6891,18 +6970,34 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
}
const float button_h = std::round(frame_h * 1.25f);
const float third = std::floor((panel_w - style.ItemSpacing.x) / 3.f);
if (busy) {
if (ImGui::Button((_u8L("Stop") + "##stop").c_str(), ImVec2(third, button_h)))
wxGetApp().plater()->get_ui_job_worker().cancel_all();
hover_tip(_u8L("Stops the bake. Whatever it had already finished stays on the model, and can be undone."));
} else {
if (ImGui::Button((_u8L("Close") + "##close").c_str(), ImVec2(third, button_h)))
m_parent.reset_all_gizmos();
hover_tip(_u8L("Closes the tool without baking. Your paint, layers and settings stay with the model."));
}
const float button_w = std::max({
ImGui::CalcTextSize(_u8L("Close").c_str()).x,
ImGui::CalcTextSize(_u8L("Stop").c_str()).x,
ImGui::CalcTextSize(_u8L("Preparing...").c_str()).x,
ImGui::CalcTextSize(_u8L("Baking...").c_str()).x,
}) + m_imgui->scaled(0.5f);
const float row_y = ImGui::GetCursorPosY();
const float icon_h = 21.f * scale;
const float row_h = std::max(icon_h, button_h);
const std::vector<std::pair<wxString, wxString>> shortcut =
m_tool_type == ToolType::BUCKET_FILL ? m_shortcuts_bucket_fill
: m_tool_type == ToolType::SMART_FILL ? m_shortcuts_bucket_fill
: m_tool_type == ToolType::BRUSH ? m_shortcuts_brush
: m_tool_type == ToolType::GAP_FILL ? m_shortcuts_gap_fill
: std::vector<std::pair<wxString, wxString>>{};
ImGui::SetCursorPosY(row_y + (row_h - icon_h) * .5f); // center vertically
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(int(m_imgui->scaled(.5f)), style.ItemSpacing.y));
GLGizmoUtils::render_tooltip_button(m_imgui, m_parent, shortcut, x, y);
ImGui::SameLine();
GLGizmoUtils::render_wiki_guide_button(m_parent, scale, "https://www.orcaslicer.com/wiki/print_prepare/prepare_texture_displacement");
ImGui::SameLine();
GLGizmoUtils::render_video_guide_button(m_parent, scale, "https://www.youtube.com/watch?v=D7w3tG1kdvE");
ImGui::PopStyleVar(1);
ImGui::SameLine(x0 + panel_w - button_w * 2 - style.ItemSpacing.x);
const bool can_bake = !busy && mv != nullptr && mv->is_texture_displacement_painted();
const std::string bake_label = m_prepare_in_progress ? _u8L("Preparing...") :
m_bake_in_progress ? _u8L("Baking...") :
@@ -6910,7 +7005,8 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
GLGizmoUtils::push_orca_button_style();
m_imgui->push_bold_font();
m_imgui->disabled_begin(!can_bake);
if (ImGui::Button((bake_label + "##bake").c_str(), ImVec2(x0 + panel_w - ImGui::GetCursorPosX(), button_h))) {
ImGui::SetCursorPosY(row_y + (row_h - button_h) * .5f); // center vertically
if (ImGui::Button((bake_label + "##bake").c_str(), ImVec2(button_w, button_h))) {
// Standard mode's Bake is the whole pipeline (remesh -> refine -> displace); Pro's is only the
// displacement, because there the user has already prepared the mesh with the controls above.
if (pro_mode())
@@ -6935,6 +7031,17 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"land on - all in one step."),
wrap_w);
ImGui::SameLine();
if (busy) {
if (ImGui::Button((_u8L("Stop") + "##stop").c_str(), ImVec2(button_w, button_h)))
wxGetApp().plater()->get_ui_job_worker().cancel_all();
hover_tip(_u8L("Stops the bake. Whatever it had already finished stays on the model, and can be undone."));
} else {
if (ImGui::Button((_u8L("Close") + "##close").c_str(), ImVec2(button_w, button_h)))
m_parent.reset_all_gizmos();
hover_tip(_u8L("Closes the tool without baking. Your paint, layers and settings stay with the model."));
}
// What Bake will produce, and which layers it will skip.
if (mv != nullptr) {
const std::string base_count = base_k > 0 ? Slic3r::format(_u8L("%1% k"), base_k) :

Some files were not shown because too many files have changed in this diff Show More