mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-11 03:11:49 +08:00
Merge branch 'master' into fs_emboss
This commit is contained in:
commit
581aab2dd7
19
resources/icons/fdm_supports_.svg
Normal file
19
resources/icons/fdm_supports_.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<g id="paint_x5F_supports">
|
||||
<path fill="#ED6B21" d="M88,38.93c-0.83,0-1.5,0.67-1.5,1.5V70.5h-5V45.14c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V70.5h-5V49.8
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5v20.7h-5V53.84c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V70.5h-5V49.8
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5v20.7h-5V45.14c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V70.5h-5V40.43
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V72v10.99c0,3.59,2.92,6.51,6.51,6.51h2.98c0.67,0.01,6.51,0.24,6.51,6.5v16
|
||||
c0,3.29,1.99,9.5,9.5,9.5s9.5-6.21,9.5-9.5V96c0-6.26,5.84-6.49,6.5-6.5h3c3.59,0,6.5-2.92,6.5-6.5V72V40.43
|
||||
C89.5,39.6,88.83,38.93,88,38.93z M86.5,83c0,1.93-1.57,3.5-3.5,3.5h-3c-3.29,0-9.5,1.99-9.5,9.5v15.99
|
||||
c-0.01,0.67-0.24,6.51-6.5,6.51s-6.49-5.84-6.5-6.5V96c0-7.51-6.21-9.5-9.5-9.5h-2.99c-1.94,0-3.51-1.57-3.51-3.51V73.5h45V83z"/>
|
||||
<g>
|
||||
<path fill="#808080" d="M64,48.03c-0.26,0-0.52-0.07-0.75-0.2l-48-27.69c-0.46-0.27-0.75-0.76-0.75-1.3V8c0-0.83,0.67-1.5,1.5-1.5
|
||||
s1.5,0.67,1.5,1.5v9.98L64,44.8l46.5-26.83V8c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5v10.84c0,0.54-0.29,1.03-0.75,1.3
|
||||
l-48,27.69C64.52,47.97,64.26,48.03,64,48.03z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
28
resources/icons/mmu_segmentation_.svg
Normal file
28
resources/icons/mmu_segmentation_.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<path fill="#808080" d="M52.87,108.38c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5S58.11,108.38,52.87,108.38z
|
||||
M52.87,92.38c-3.58,0-6.5,2.92-6.5,6.5s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S56.46,92.38,52.87,92.38z M29.82,83.59
|
||||
c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5S35.06,83.59,29.82,83.59z M29.82,67.59c-3.58,0-6.5,2.92-6.5,6.5
|
||||
s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S33.4,67.59,29.82,67.59z M34,49.86c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5
|
||||
S39.24,49.86,34,49.86z M34,33.86c-3.58,0-6.5,2.92-6.5,6.5s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S37.59,33.86,34,33.86z M64,35.21
|
||||
c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5S69.24,35.21,64,35.21z M64,19.21c-3.58,0-6.5,2.92-6.5,6.5
|
||||
s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S67.58,19.21,64,19.21z M96.1,52.24c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5
|
||||
S101.34,52.24,96.1,52.24z M96.1,36.24c-3.58,0-6.5,2.92-6.5,6.5s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S99.69,36.24,96.1,36.24z
|
||||
M72.54,120.87c2.6-0.39,4.78-2.06,5.81-4.46c1.06-2.47,0.77-5.29-0.8-7.52c-3.1-4.43-4.49-9.87-3.92-15.31
|
||||
c0.26-2.47,0.94-4.89,2.03-7.17c0.36-0.75,0.04-1.64-0.71-2c-0.75-0.36-1.64-0.04-2,0.71c-1.23,2.6-2.01,5.34-2.3,8.15
|
||||
c-0.64,6.16,0.94,12.32,4.45,17.34c0.96,1.38,1.15,3.11,0.5,4.62c-0.63,1.47-1.91,2.44-3.5,2.68c-3.29,0.49-6.66,0.68-10.01,0.57
|
||||
c-28.61-0.99-51.7-24.18-52.56-52.79c-0.46-15.2,5.2-29.48,15.94-40.21S50.49,9.07,65.68,9.53c28.62,0.86,51.8,23.94,52.79,52.56
|
||||
c0.11,3.25-0.06,6.51-0.52,9.69c-0.24,1.66-1.31,3.06-2.87,3.73c-1.52,0.66-3.16,0.5-4.49-0.42c-3.29-2.3-7.17-3.8-11.21-4.34
|
||||
c-0.83-0.11-1.58,0.47-1.68,1.29c-0.11,0.82,0.47,1.58,1.29,1.68c3.57,0.47,6.99,1.79,9.89,3.82c2.17,1.52,4.94,1.78,7.4,0.72
|
||||
c2.52-1.09,4.26-3.36,4.65-6.06c0.48-3.36,0.67-6.8,0.55-10.22c-1.04-30.19-25.5-54.55-55.7-55.45
|
||||
c-16.02-0.48-31.1,5.49-42.42,16.81C12.02,34.66,6.05,49.73,6.53,65.77c0.9,30.19,25.26,54.66,55.45,55.7
|
||||
c0.67,0.02,1.34,0.04,2.01,0.04C66.86,121.5,69.73,121.29,72.54,120.87z"/>
|
||||
<path fill="#ED6B21" d="M115.41,105.01l-27.66-38.8c7.76-12.6-22.89-18.09-27.92-23.34c-2.48-2.6-0.44,35.31,15.58,32.26
|
||||
l29.74,37.59c0.54,0.91,3.45,5.54,7.39,6.36c0.39,0.08,0.78,0.12,1.16,0.12c1.26,0,2.48-0.42,3.58-1.25
|
||||
c1.36-1.02,2.14-2.41,2.27-4.01C119.87,109.95,116.14,105.79,115.41,105.01z M78.44,74.13c1.24-0.57,2.54-1.37,3.92-2.42
|
||||
c1.39-1.05,2.53-2.06,3.45-3.04l6.94,9.73l-6.85,5.15L78.44,74.13z M116.56,113.69c-0.06,0.76-0.4,1.35-1.08,1.85
|
||||
c-0.77,0.58-1.51,0.76-2.33,0.6c-2.38-0.49-4.75-3.78-5.46-5.01c-0.04-0.06-0.08-0.12-0.12-0.18L87.76,85.91l6.73-5.06l18.53,25.99
|
||||
c0.04,0.06,0.09,0.12,0.14,0.17C114.11,107.98,116.75,111.3,116.56,113.69z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
35
resources/icons/seam_.svg
Normal file
35
resources/icons/seam_.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<g id="paint_x5F_seams_2_">
|
||||
|
||||
<polyline fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
120,32 64,8 8,32 8,96 64,120 "/>
|
||||
<path fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||
M120,96"/>
|
||||
|
||||
<polyline fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
8,32 64,56 64,120 "/>
|
||||
|
||||
<line fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="64" y1="56" x2="120" y2="32"/>
|
||||
|
||||
<line fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="64" y1="120" x2="120" y2="96"/>
|
||||
|
||||
<line fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="120" y1="96" x2="120" y2="32"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="88.05" y1="53.69" x2="95.96" y2="50.3"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="95.96" y1="58.3" x2="103.99" y2="54.86"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="88.05" y1="69.69" x2="95.96" y2="66.3"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="80.05" y1="81.12" x2="88.05" y2="77.69"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="71.94" y1="92.6" x2="80.05" y2="89.12"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="80.05" y1="97.12" x2="88.05" y2="93.69"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="88.05" y1="101.69" x2="96.13" y2="98.23"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
17
resources/icons/shape_gallery.svg
Normal file
17
resources/icons/shape_gallery.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="shape_x5F_gallery_x5F_2">
|
||||
<path fill="#808080" d="M11,6.72c0.14,0,0.25,0.11,0.25,0.25V14c0,0.14-0.11,0.25-0.25,0.25H2c-0.14,0-0.25-0.11-0.25-0.25V6.97
|
||||
c0-0.14,0.11-0.25,0.25-0.25H11 M11,5.97H2c-0.55,0-1,0.45-1,1V14c0,0.55,0.45,1,1,1h9c0.55,0,1-0.45,1-1V6.97
|
||||
C12,6.42,11.55,5.97,11,5.97L11,5.97z"/>
|
||||
<path fill="#808080" d="M14,2H5C4.45,2,4,2.45,4,3v1h0.75V3c0-0.14,0.11-0.25,0.25-0.25h9c0.14,0,0.25,0.11,0.25,0.25v8
|
||||
c0,0.14-0.11,0.25-0.25,0.25h-0.5V12H14c0.55,0,1-0.45,1-1V3C15,2.45,14.55,2,14,2z"/>
|
||||
<path fill="#808080" d="M12.5,4h-9c-0.55,0-1,0.45-1,1v0.97h0.75V5c0-0.14,0.11-0.25,0.25-0.25h9c0.14,0,0.25,0.11,0.25,0.25v7.5
|
||||
c0,0.14-0.11,0.25-0.25,0.25H12v0.75h0.5c0.55,0,1-0.45,1-1V5C13.5,4.45,13.05,4,12.5,4z"/>
|
||||
<path fill="#ED6B21" d="M9.07,11.7V9.3c0-0.18-0.1-0.34-0.25-0.43l-2.07-1.2c-0.15-0.09-0.35-0.09-0.5,0l-2.07,1.2
|
||||
C4.02,8.96,3.93,9.13,3.93,9.3v2.39c0,0.18,0.1,0.34,0.25,0.43l2.07,1.2c0.15,0.09,0.35,0.09,0.5,0l2.07-1.2
|
||||
C8.98,12.04,9.07,11.87,9.07,11.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
18
resources/icons/sinking.svg
Normal file
18
resources/icons/sinking.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="sinking">
|
||||
<g>
|
||||
<path fill="#808080" d="M15,10.5H1c-0.28,0-0.5-0.22-0.5-0.5S0.72,9.5,1,9.5h14c0.28,0,0.5,0.22,0.5,0.5S15.28,10.5,15,10.5z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#808080" d="M14.78,5.47L5.97,1.05C5.88,1.01,5.78,1,5.69,1.03c-0.09,0.03-0.17,0.1-0.22,0.19L2.15,7.83
|
||||
C2.09,7.95,2.1,8.09,2.17,8.2c0.07,0.11,0.19,0.18,0.32,0.18h11.02c0.14,0,0.27-0.08,0.33-0.21l1.1-2.19
|
||||
C15.04,5.79,14.97,5.56,14.78,5.47z"/>
|
||||
<path fill="#ED6B21" d="M11.82,11.8c-0.07-0.11-0.19-0.18-0.32-0.18H4.99c-0.17,0-0.32,0.12-0.36,0.29
|
||||
c-0.04,0.17,0.04,0.34,0.2,0.42l5.21,2.61c0.05,0.03,0.11,0.04,0.17,0.04c0.04,0,0.08-0.01,0.12-0.02
|
||||
c0.09-0.03,0.17-0.1,0.22-0.19l1.31-2.61C11.9,12.05,11.89,11.91,11.82,11.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -303,7 +303,7 @@ set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
find_package(CGAL 4.13 REQUIRED)
|
||||
find_package(CGAL REQUIRED)
|
||||
cmake_policy(POP)
|
||||
|
||||
add_library(libslic3r_cgal STATIC MeshBoolean.hpp MeshBoolean.cpp
|
||||
|
@ -539,7 +539,7 @@ void Layer::make_ironing()
|
||||
fill_params.density = 1.;
|
||||
fill_params.monotonic = true;
|
||||
|
||||
for (size_t i = 0; i < by_extruder.size(); ++ i) {
|
||||
for (size_t i = 0; i < by_extruder.size();) {
|
||||
// Find span of regions equivalent to the ironing operation.
|
||||
IroningParams &ironing_params = by_extruder[i];
|
||||
size_t j = i;
|
||||
@ -589,14 +589,17 @@ void Layer::make_ironing()
|
||||
polygons_append(infills, surface.expolygon);
|
||||
}
|
||||
}
|
||||
// Trim the top surfaces with half the nozzle diameter.
|
||||
ironing_areas = intersection_ex(polys, offset(this->lslices, - float(scale_(0.5 * nozzle_dmr))));
|
||||
if (! infills.empty()) {
|
||||
|
||||
if (! infills.empty() || j > i + 1) {
|
||||
// Ironing over more than a single region or over solid internal infill.
|
||||
if (! infills.empty())
|
||||
// For IroningType::AllSolid only:
|
||||
// Add solid infill areas for layers, that contain some non-ironable infil (sparse infill, bridge infill).
|
||||
append(infills, to_polygons(std::move(ironing_areas)));
|
||||
ironing_areas = union_safety_offset_ex(infills);
|
||||
append(polys, std::move(infills));
|
||||
polys = union_safety_offset(polys);
|
||||
}
|
||||
// Trim the top surfaces with half the nozzle diameter.
|
||||
ironing_areas = intersection_ex(polys, offset(this->lslices, - float(scale_(0.5 * nozzle_dmr))));
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
@ -626,6 +629,9 @@ void Layer::make_ironing()
|
||||
flow_mm3_per_mm, extrusion_width, float(extrusion_height));
|
||||
}
|
||||
}
|
||||
|
||||
// Regions up to j were processed.
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1998,13 +1998,19 @@ GCode::LayerResult GCode::process_layer(
|
||||
// Either printing all copies of all objects, or just a single copy of a single object.
|
||||
assert(single_object_instance_idx == size_t(-1) || layers.size() == 1);
|
||||
|
||||
// First object, support and raft layer, if available.
|
||||
const Layer *object_layer = nullptr;
|
||||
const SupportLayer *support_layer = nullptr;
|
||||
const SupportLayer *raft_layer = nullptr;
|
||||
for (const LayerToPrint &l : layers) {
|
||||
if (l.object_layer != nullptr && object_layer == nullptr)
|
||||
if (l.object_layer && ! object_layer)
|
||||
object_layer = l.object_layer;
|
||||
if (l.support_layer != nullptr && support_layer == nullptr)
|
||||
if (l.support_layer) {
|
||||
if (! support_layer)
|
||||
support_layer = l.support_layer;
|
||||
if (! raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers())
|
||||
raft_layer = support_layer;
|
||||
}
|
||||
}
|
||||
const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer;
|
||||
GCode::LayerResult result { {}, layer.id(), false, last_layer };
|
||||
@ -2406,7 +2412,7 @@ GCode::LayerResult GCode::process_layer(
|
||||
log_memory_info();
|
||||
|
||||
result.gcode = std::move(gcode);
|
||||
result.cooling_buffer_flush = object_layer || last_layer;
|
||||
result.cooling_buffer_flush = object_layer || raft_layer || last_layer;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ void Layer::make_slices()
|
||||
Polygons slices_p;
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
polygons_append(slices_p, to_polygons(layerm->slices.surfaces));
|
||||
slices = union_ex(slices_p);
|
||||
slices = union_safety_offset_ex(slices_p);
|
||||
}
|
||||
|
||||
this->lslices.clear();
|
||||
|
@ -3188,7 +3188,7 @@ void PrintConfigDef::init_sla_params()
|
||||
|
||||
def = this->add("relative_correction_y", coFloat);
|
||||
def->label = L("Printer scaling correction in Y axis");
|
||||
def->full_label = L("Printer scaling X axis correction");
|
||||
def->full_label = L("Printer scaling Y axis correction");
|
||||
def->tooltip = L("Printer scaling correction in Y axis");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
@ -3196,7 +3196,7 @@ void PrintConfigDef::init_sla_params()
|
||||
|
||||
def = this->add("relative_correction_z", coFloat);
|
||||
def->label = L("Printer scaling correction in Z axis");
|
||||
def->full_label = L("Printer scaling X axis correction");
|
||||
def->full_label = L("Printer scaling Z axis correction");
|
||||
def->tooltip = L("Printer scaling correction in Z axis");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
|
@ -1480,7 +1480,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
|
||||
overhang_polygons = to_polygons(layer.lslices);
|
||||
#endif
|
||||
// Expand for better stability.
|
||||
contact_polygons = expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
|
||||
contact_polygons = object_config.raft_expansion.value > 0 ? expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value)) : overhang_polygons;
|
||||
}
|
||||
else if (! layer.regions().empty())
|
||||
{
|
||||
|
@ -9,6 +9,112 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Check if the line is whole inside the sphere, or it is partially inside (intersecting) the sphere.
|
||||
// Inspired by Christer Ericson's Real-Time Collision Detection, pp. 177-179.
|
||||
static bool test_line_inside_sphere(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &sphere_p, const float sphere_radius)
|
||||
{
|
||||
const float sphere_radius_sqr = Slic3r::sqr(sphere_radius);
|
||||
const Vec3f line_dir = line_b - line_a; // n
|
||||
const Vec3f origins_diff = line_a - sphere_p; // m
|
||||
|
||||
const float m_dot_m = origins_diff.dot(origins_diff);
|
||||
// Check if any of the end-points of the line is inside the sphere.
|
||||
if (m_dot_m <= sphere_radius_sqr || (line_b - sphere_p).squaredNorm() <= sphere_radius_sqr)
|
||||
return true;
|
||||
|
||||
// Check if the infinite line is going through the sphere.
|
||||
const float n_dot_n = line_dir.dot(line_dir);
|
||||
const float m_dot_n = origins_diff.dot(line_dir);
|
||||
|
||||
const float eq_a = n_dot_n;
|
||||
const float eq_b = m_dot_n;
|
||||
const float eq_c = m_dot_m - sphere_radius_sqr;
|
||||
|
||||
const float discr = eq_b * eq_b - eq_a * eq_c;
|
||||
// A negative discriminant corresponds to the infinite line infinite not going through the sphere.
|
||||
if (discr < 0.f)
|
||||
return false;
|
||||
|
||||
// Check if the finite line is going through the sphere.
|
||||
const float discr_sqrt = std::sqrt(discr);
|
||||
const float t1 = (-eq_b - discr_sqrt) / eq_a;
|
||||
if (0.f <= t1 && t1 <= 1.f)
|
||||
return true;
|
||||
|
||||
const float t2 = (-eq_b + discr_sqrt) / eq_a;
|
||||
if (0.f <= t2 && t2 <= 1.f && discr_sqrt > 0.f)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the line is whole inside the finite cylinder, or it is partially inside (intersecting) the finite cylinder.
|
||||
// Inspired by Christer Ericson's Real-Time Collision Detection, pp. 194-198.
|
||||
static bool test_line_inside_cylinder(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &cylinder_P, const Vec3f &cylinder_Q, const float cylinder_radius)
|
||||
{
|
||||
assert(cylinder_P != cylinder_Q);
|
||||
const Vec3f cylinder_dir = cylinder_Q - cylinder_P; // d
|
||||
auto is_point_inside_finite_cylinder = [&cylinder_P, &cylinder_Q, &cylinder_radius, &cylinder_dir](const Vec3f &pt) {
|
||||
const Vec3f first_center_diff = cylinder_P - pt;
|
||||
const Vec3f second_center_diff = cylinder_Q - pt;
|
||||
// First, check if the point pt is laying between planes defined by cylinder_p and cylinder_q.
|
||||
// Then check if it is inside the cylinder between cylinder_p and cylinder_q.
|
||||
return first_center_diff.dot(cylinder_dir) <= 0 && second_center_diff.dot(cylinder_dir) >= 0 &&
|
||||
(first_center_diff.cross(cylinder_dir).norm() / cylinder_dir.norm()) <= cylinder_radius;
|
||||
};
|
||||
|
||||
// Check if any of the end-points of the line is inside the cylinder.
|
||||
if (is_point_inside_finite_cylinder(line_a) || is_point_inside_finite_cylinder(line_b))
|
||||
return true;
|
||||
|
||||
// Check if the line is going through the cylinder.
|
||||
const Vec3f origins_diff = line_a - cylinder_P; // m
|
||||
const Vec3f line_dir = line_b - line_a; // n
|
||||
|
||||
const float m_dot_d = origins_diff.dot(cylinder_dir);
|
||||
const float n_dot_d = line_dir.dot(cylinder_dir);
|
||||
const float d_dot_d = cylinder_dir.dot(cylinder_dir);
|
||||
|
||||
const float n_dot_n = line_dir.dot(line_dir);
|
||||
const float m_dot_n = origins_diff.dot(line_dir);
|
||||
const float m_dot_m = origins_diff.dot(origins_diff);
|
||||
|
||||
const float eq_a = d_dot_d * n_dot_n - n_dot_d * n_dot_d;
|
||||
const float eq_b = d_dot_d * m_dot_n - n_dot_d * m_dot_d;
|
||||
const float eq_c = d_dot_d * (m_dot_m - Slic3r::sqr(cylinder_radius)) - m_dot_d * m_dot_d;
|
||||
|
||||
const float discr = eq_b * eq_b - eq_a * eq_c;
|
||||
// A negative discriminant corresponds to the infinite line not going through the infinite cylinder.
|
||||
if (discr < 0.0f)
|
||||
return false;
|
||||
|
||||
// Check if the finite line is going through the finite cylinder.
|
||||
const float discr_sqrt = std::sqrt(discr);
|
||||
const float t1 = (-eq_b - discr_sqrt) / eq_a;
|
||||
if (0.f <= t1 && t1 <= 1.f)
|
||||
if (const float cylinder_endcap_t1 = m_dot_d + t1 * n_dot_d; 0.f <= cylinder_endcap_t1 && cylinder_endcap_t1 <= d_dot_d)
|
||||
return true;
|
||||
|
||||
const float t2 = (-eq_b + discr_sqrt) / eq_a;
|
||||
if (0.f <= t2 && t2 <= 1.f)
|
||||
if (const float cylinder_endcap_t2 = (m_dot_d + t2 * n_dot_d); 0.f <= cylinder_endcap_t2 && cylinder_endcap_t2 <= d_dot_d)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the line is whole inside the capsule, or it is partially inside (intersecting) the capsule.
|
||||
static bool test_line_inside_capsule(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &capsule_p, const Vec3f &capsule_q, const float capsule_radius) {
|
||||
assert(capsule_p != capsule_q);
|
||||
|
||||
// Check if the line intersect any of the spheres forming the capsule.
|
||||
if (test_line_inside_sphere(line_a, line_b, capsule_p, capsule_radius) || test_line_inside_sphere(line_a, line_b, capsule_q, capsule_radius))
|
||||
return true;
|
||||
|
||||
// Check if the line intersects the cylinder between the centers of the spheres.
|
||||
return test_line_inside_cylinder(line_a, line_b, capsule_p, capsule_q, capsule_radius);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool TriangleSelector::verify_triangle_midpoints(const Triangle &tr) const
|
||||
{
|
||||
@ -124,24 +230,20 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
|
||||
return this->select_unsplit_triangle(hit, facet_idx, neighbors);
|
||||
}
|
||||
|
||||
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
const Vec3f& source, float radius,
|
||||
CursorType cursor_type, EnforcerBlockerType new_state,
|
||||
const Transform3d& trafo, const Transform3d& trafo_no_translate,
|
||||
bool triangle_splitting, const ClippingPlane &clp, float highlight_by_angle_deg)
|
||||
void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
|
||||
// Save current cursor center, squared radius and camera direction, so we don't
|
||||
// have to pass it around.
|
||||
m_cursor = Cursor(hit, source, radius, cursor_type, trafo, clp);
|
||||
m_cursor = std::move(cursor);
|
||||
|
||||
// In case user changed cursor size since last time, update triangle edge limit.
|
||||
// It is necessary to compare the internal radius in m_cursor! radius is in
|
||||
// world coords and does not change after scaling.
|
||||
if (m_old_cursor_radius_sqr != m_cursor.radius_sqr) {
|
||||
set_edge_limit(std::sqrt(m_cursor.radius_sqr) / 5.f);
|
||||
m_old_cursor_radius_sqr = m_cursor.radius_sqr;
|
||||
if (m_old_cursor_radius_sqr != m_cursor->radius_sqr) {
|
||||
set_edge_limit(std::sqrt(m_cursor->radius_sqr) / 5.f);
|
||||
m_old_cursor_radius_sqr = m_cursor->radius_sqr;
|
||||
}
|
||||
|
||||
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
|
||||
@ -163,7 +265,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
if (select_triangle(facet, new_state, triangle_splitting)) {
|
||||
// add neighboring facets to list to be processed later
|
||||
for (int neighbor_idx : m_neighbors[facet])
|
||||
if (neighbor_idx >= 0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx)))
|
||||
if (neighbor_idx >= 0 && m_cursor->is_facet_visible(neighbor_idx, m_face_normals))
|
||||
facets_to_check.push_back(neighbor_idx);
|
||||
}
|
||||
}
|
||||
@ -788,11 +890,11 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei
|
||||
|
||||
assert(this->verify_triangle_neighbors(*tr, neighbors));
|
||||
|
||||
int num_of_inside_vertices = vertices_inside(facet_idx);
|
||||
int num_of_inside_vertices = m_cursor->vertices_inside(*tr, m_vertices);
|
||||
|
||||
if (num_of_inside_vertices == 0
|
||||
&& ! is_pointer_in_triangle(facet_idx)
|
||||
&& ! is_edge_inside_cursor(facet_idx))
|
||||
&& ! m_cursor->is_pointer_in_triangle(*tr, m_vertices)
|
||||
&& ! m_cursor->is_edge_inside_cursor(*tr, m_vertices))
|
||||
return false;
|
||||
|
||||
if (num_of_inside_vertices == 3) {
|
||||
@ -840,7 +942,7 @@ void TriangleSelector::set_facet(int facet_idx, EnforcerBlockerType state)
|
||||
}
|
||||
|
||||
// called by select_patch()->select_triangle()...select_triangle()
|
||||
// to decide which sides of the traingle to split and to actually split it calling set_division() and perform_split().
|
||||
// to decide which sides of the triangle to split and to actually split it calling set_division() and perform_split().
|
||||
void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
{
|
||||
if (m_triangles[facet_idx].is_split()) {
|
||||
@ -864,9 +966,9 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
|
||||
// In case the object is non-uniformly scaled, transform the
|
||||
// points to world coords.
|
||||
if (! m_cursor.uniform_scaling) {
|
||||
if (! m_cursor->uniform_scaling) {
|
||||
for (size_t i=0; i<pts.size(); ++i) {
|
||||
pts_transformed[i] = m_cursor.trafo * (*pts[i]);
|
||||
pts_transformed[i] = m_cursor->trafo * (*pts[i]);
|
||||
pts[i] = &pts_transformed[i];
|
||||
}
|
||||
}
|
||||
@ -897,54 +999,65 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
perform_split(facet_idx, neighbors, old_type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Is pointer in a triangle?
|
||||
bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const
|
||||
{
|
||||
const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v;
|
||||
const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v;
|
||||
const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v;
|
||||
return m_cursor.is_pointer_in_triangle(p1, p2, p3);
|
||||
bool TriangleSelector::Cursor::is_pointer_in_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const {
|
||||
const Vec3f& p1 = vertices[tr.verts_idxs[0]].v;
|
||||
const Vec3f& p2 = vertices[tr.verts_idxs[1]].v;
|
||||
const Vec3f& p3 = vertices[tr.verts_idxs[2]].v;
|
||||
return this->is_pointer_in_triangle(p1, p2, p3);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Determine whether this facet is potentially visible (still can be obscured).
|
||||
bool TriangleSelector::faces_camera(int facet) const
|
||||
bool TriangleSelector::Cursor::is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector<Vec3f> &face_normals)
|
||||
{
|
||||
assert(facet < m_orig_size_indices);
|
||||
Vec3f n = m_face_normals[facet];
|
||||
if (! m_cursor.uniform_scaling)
|
||||
n = m_cursor.trafo_normal * n;
|
||||
return n.dot(m_cursor.dir) < 0.;
|
||||
assert(facet_idx < int(face_normals.size()));
|
||||
Vec3f n = face_normals[facet_idx];
|
||||
if (!cursor.uniform_scaling)
|
||||
n = cursor.trafo_normal * n;
|
||||
return n.dot(cursor.dir) < 0.f;
|
||||
}
|
||||
|
||||
|
||||
// How many vertices of a triangle are inside the circle?
|
||||
int TriangleSelector::vertices_inside(int facet_idx) const
|
||||
int TriangleSelector::Cursor::vertices_inside(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
int inside = 0;
|
||||
for (size_t i=0; i<3; ++i) {
|
||||
if (m_cursor.is_mesh_point_inside(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v))
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
if (this->is_mesh_point_inside(vertices[tr.verts_idxs[i]].v))
|
||||
++inside;
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const
|
||||
// Is any edge inside Sphere cursor?
|
||||
bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v;
|
||||
if (! m_cursor.uniform_scaling)
|
||||
pts[i] = m_cursor.trafo * pts[i];
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f& p = m_cursor.center;
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
if (test_line_inside_sphere(edge_a, edge_b, this->center, this->radius))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::Circle::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f &p = this->center;
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &a = pts[side];
|
||||
const Vec3f &b = pts[side < 2 ? side + 1 : 0];
|
||||
@ -954,15 +1067,13 @@ bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const
|
||||
|
||||
// vector is 3D vector from center to the intersection. What we want to
|
||||
// measure is length of its projection onto plane perpendicular to dir.
|
||||
float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f);
|
||||
if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm())
|
||||
float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(this->dir), 2.f);
|
||||
if (dist_sqr < this->radius_sqr && t >= 0.f && t <= (b - a).norm())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Recursively remove all subtriangles.
|
||||
void TriangleSelector::undivide_triangle(int facet_idx)
|
||||
{
|
||||
@ -1002,7 +1113,6 @@ void TriangleSelector::undivide_triangle(int facet_idx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelector::remove_useless_children(int facet_idx)
|
||||
{
|
||||
// Check that all children are leafs of the same type. If not, try to
|
||||
@ -1041,8 +1151,6 @@ void TriangleSelector::remove_useless_children(int facet_idx)
|
||||
tr.set_state(first_child_type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::garbage_collect()
|
||||
{
|
||||
// First make a map from old to new triangle indices.
|
||||
@ -1103,7 +1211,6 @@ TriangleSelector::TriangleSelector(const TriangleMesh& mesh)
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelector::reset()
|
||||
{
|
||||
m_vertices.clear();
|
||||
@ -1124,17 +1231,11 @@ void TriangleSelector::reset()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::set_edge_limit(float edge_limit)
|
||||
{
|
||||
m_edge_limit_sqr = std::pow(edge_limit, 2.f);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state)
|
||||
{
|
||||
for (int i : {a, b, c}) {
|
||||
@ -1693,54 +1794,132 @@ void TriangleSelector::seed_fill_apply_on_triangles(EnforcerBlockerType new_stat
|
||||
}
|
||||
}
|
||||
|
||||
TriangleSelector::Cursor::Cursor(
|
||||
const Vec3f& center_, const Vec3f& source_, float radius_world,
|
||||
CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_)
|
||||
: center{center_},
|
||||
source{source_},
|
||||
type{type_},
|
||||
trafo{trafo_.cast<float>()},
|
||||
clipping_plane(clipping_plane_)
|
||||
TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: source{source_}, trafo{trafo_.cast<float>()}, clipping_plane{clipping_plane_}
|
||||
{
|
||||
Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor();
|
||||
if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) {
|
||||
radius_sqr = float(std::pow(radius_world / sf(0), 2));
|
||||
radius = float(radius_world / sf(0));
|
||||
radius_sqr = float(Slic3r::sqr(radius_world / sf(0)));
|
||||
uniform_scaling = true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// In case that the transformation is non-uniform, all checks whether
|
||||
// something is inside the cursor should be done in world coords.
|
||||
// First transform center, source and dir in world coords and remember
|
||||
// that we did this.
|
||||
center = trafo * center;
|
||||
// First transform source in world coords and remember that we did this.
|
||||
source = trafo * source;
|
||||
uniform_scaling = false;
|
||||
radius_sqr = radius_world * radius_world;
|
||||
radius = radius_world;
|
||||
radius_sqr = Slic3r::sqr(radius_world);
|
||||
trafo_normal = trafo.linear().inverse().transpose();
|
||||
}
|
||||
}
|
||||
|
||||
TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, const Vec3f& source_, float radius_world, const Transform3d& trafo_, const ClippingPlane &clipping_plane_)
|
||||
: center{center_}, Cursor(source_, radius_world, trafo_, clipping_plane_)
|
||||
{
|
||||
// In case that the transformation is non-uniform, all checks whether
|
||||
// something is inside the cursor should be done in world coords.
|
||||
// Because of the center is transformed.
|
||||
if (!uniform_scaling)
|
||||
center = trafo * center;
|
||||
|
||||
// Calculate dir, in whatever coords is appropriate.
|
||||
dir = (center - source).normalized();
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a cursor?
|
||||
bool TriangleSelector::Cursor::is_mesh_point_inside(const Vec3f &point) const
|
||||
TriangleSelector::DoublePointCursor::DoublePointCursor(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: first_center{first_center_}, second_center{second_center_}, Cursor(source_, radius_world, trafo_, clipping_plane_)
|
||||
{
|
||||
if (!uniform_scaling) {
|
||||
first_center = trafo * first_center_;
|
||||
second_center = trafo * second_center_;
|
||||
}
|
||||
|
||||
// Calculate dir, in whatever coords is appropriate.
|
||||
dir = (first_center - source).normalized();
|
||||
}
|
||||
|
||||
// Returns true if clipping plane is not active or if the point not clipped by clipping plane.
|
||||
inline static bool is_mesh_point_not_clipped(const Vec3f &point, const TriangleSelector::ClippingPlane &clipping_plane)
|
||||
{
|
||||
return !clipping_plane.is_active() || !clipping_plane.is_mesh_point_clipped(point);
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Sphere cursor?
|
||||
bool TriangleSelector::Sphere::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
if ((center - transformed_point).squaredNorm() < radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Circle cursor?
|
||||
bool TriangleSelector::Circle::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
const Vec3f diff = center - transformed_point;
|
||||
const bool is_point_inside = (type == CIRCLE ? (diff - diff.dot(dir) * dir).squaredNorm() : diff.squaredNorm()) < radius_sqr;
|
||||
|
||||
if (is_point_inside && clipping_plane.is_active())
|
||||
return !clipping_plane.is_mesh_point_clipped(point);
|
||||
if ((diff - diff.dot(dir) * dir).squaredNorm() < radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
return is_point_inside;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Capsule3D cursor?
|
||||
bool TriangleSelector::Capsule3D::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
const Vec3f first_center_diff = this->first_center - transformed_point;
|
||||
const Vec3f second_center_diff = this->second_center - transformed_point;
|
||||
if (first_center_diff.squaredNorm() < this->radius_sqr || second_center_diff.squaredNorm() < this->radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
// First, check if the point pt is laying between planes defined by first_center and second_center.
|
||||
// Then check if it is inside the cylinder between first_center and second_center.
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
if (first_center_diff.dot(centers_diff) <= 0.f && second_center_diff.dot(centers_diff) >= 0.f && (first_center_diff.cross(centers_diff).norm() / centers_diff.norm()) <= this->radius)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Capsule2D cursor?
|
||||
bool TriangleSelector::Capsule2D::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
const Vec3f first_center_diff = this->first_center - transformed_point;
|
||||
const Vec3f first_center_diff_projected = first_center_diff - first_center_diff.dot(this->dir) * this->dir;
|
||||
if (first_center_diff_projected.squaredNorm() < this->radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
const Vec3f second_center_diff = this->second_center - transformed_point;
|
||||
const Vec3f second_center_diff_projected = second_center_diff - second_center_diff.dot(this->dir) * this->dir;
|
||||
if (second_center_diff_projected.squaredNorm() < this->radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
const Vec3f centers_diff_projected = centers_diff - centers_diff.dot(this->dir) * this->dir;
|
||||
|
||||
// First, check if the point is laying between first_center and second_center.
|
||||
if (first_center_diff_projected.dot(centers_diff_projected) <= 0.f && second_center_diff_projected.dot(centers_diff_projected) >= 0.f) {
|
||||
// Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center.
|
||||
const Vec3f rectangle_da_dir = centers_diff.cross(this->dir);
|
||||
// Vector pointing from first_center to the point 'A' of the rectangle.
|
||||
const Vec3f first_center_rectangle_a_diff = rectangle_da_dir.normalized() * this->radius;
|
||||
const Vec3f rectangle_a = this->first_center - first_center_rectangle_a_diff;
|
||||
const Vec3f rectangle_d = this->first_center + first_center_rectangle_a_diff;
|
||||
// Now check if the point is laying inside the rectangle between circles with centers in first_center and second_center.
|
||||
if ((rectangle_a - transformed_point).dot(rectangle_da_dir) <= 0.f && (rectangle_d - transformed_point).dot(rectangle_da_dir) >= 0.f)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_,
|
||||
const Vec3f& p2_,
|
||||
const Vec3f& p3_) const
|
||||
{
|
||||
static bool is_circle_pointer_inside_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_, const Vec3f ¢er, const Vec3f &dir, const bool uniform_scaling, const Transform3f &trafo) {
|
||||
const Vec3f& q1 = center + dir;
|
||||
const Vec3f& q2 = center - dir;
|
||||
|
||||
@ -1761,4 +1940,108 @@ bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_,
|
||||
return signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos;
|
||||
}
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::SinglePointCursor::is_pointer_in_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_) const
|
||||
{
|
||||
return is_circle_pointer_inside_triangle(p1_, p2_, p3_, center, dir, uniform_scaling, trafo);
|
||||
}
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::DoublePointCursor::is_pointer_in_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_) const
|
||||
{
|
||||
return is_circle_pointer_inside_triangle(p1_, p2_, p3_, first_center, dir, uniform_scaling, trafo) ||
|
||||
is_circle_pointer_inside_triangle(p1_, p2_, p3_, second_center, dir, uniform_scaling, trafo);
|
||||
}
|
||||
|
||||
bool line_plane_intersection(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &plane_origin, const Vec3f &plane_normal, Vec3f &out_intersection)
|
||||
{
|
||||
Vec3f line_dir = line_b - line_a;
|
||||
float t_denominator = plane_normal.dot(line_dir);
|
||||
if (t_denominator == 0.f)
|
||||
return false;
|
||||
|
||||
// Compute 'd' in plane equation by using some point (origin) on the plane
|
||||
float plane_d = plane_normal.dot(plane_origin);
|
||||
if (float t = (plane_d - plane_normal.dot(line_a)) / t_denominator; t >= 0.f && t <= 1.f) {
|
||||
out_intersection = line_a + t * line_dir;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
if (test_line_inside_capsule(edge_a, edge_b, this->first_center, this->second_center, this->radius))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
// Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center.
|
||||
const Vec3f rectangle_da_dir = centers_diff.cross(this->dir);
|
||||
// Vector pointing from first_center to the point 'A' of the rectangle.
|
||||
const Vec3f first_center_rectangle_a_diff = rectangle_da_dir.normalized() * this->radius;
|
||||
const Vec3f rectangle_a = this->first_center - first_center_rectangle_a_diff;
|
||||
const Vec3f rectangle_d = this->first_center + first_center_rectangle_a_diff;
|
||||
|
||||
auto edge_inside_rectangle = [&self = std::as_const(*this), ¢ers_diff](const Vec3f &edge_a, const Vec3f &edge_b, const Vec3f &plane_origin, const Vec3f &plane_normal) -> bool {
|
||||
Vec3f intersection(-1.f, -1.f, -1.f);
|
||||
if (line_plane_intersection(edge_a, edge_b, plane_origin, plane_normal, intersection)) {
|
||||
// Now check if the intersection point is inside the rectangle. That means it is between 'first_center' and 'second_center', resp. between 'A' and 'B'.
|
||||
if (self.first_center.dot(centers_diff) <= intersection.dot(centers_diff) && intersection.dot(centers_diff) <= self.second_center.dot(centers_diff))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
const Vec3f edge_dir = edge_b - edge_a;
|
||||
const Vec3f edge_dir_n = edge_dir.normalized();
|
||||
|
||||
float t1 = (this->first_center - edge_a).dot(edge_dir_n);
|
||||
float t2 = (this->second_center - edge_a).dot(edge_dir_n);
|
||||
Vec3f vector1 = edge_a + t1 * edge_dir_n - this->first_center;
|
||||
Vec3f vector2 = edge_a + t2 * edge_dir_n - this->second_center;
|
||||
|
||||
// Vectors vector1 and vector2 are 3D vector from centers to the intersections. What we want to
|
||||
// measure is length of its projection onto plane perpendicular to dir.
|
||||
if (float dist = vector1.squaredNorm() - std::pow(vector1.dot(this->dir), 2.f); dist < this->radius_sqr && t1 >= 0.f && t1 <= edge_dir.norm())
|
||||
return true;
|
||||
|
||||
if (float dist = vector2.squaredNorm() - std::pow(vector2.dot(this->dir), 2.f); dist < this->radius_sqr && t2 >= 0.f && t2 <= edge_dir.norm())
|
||||
return true;
|
||||
|
||||
// Check if the edge is passing through the rectangle between first_center and second_center.
|
||||
if (edge_inside_rectangle(edge_a, edge_b, rectangle_a, (rectangle_d - rectangle_a)) || edge_inside_rectangle(edge_a, edge_b, rectangle_d, (rectangle_a - rectangle_d)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -15,7 +15,12 @@ enum class EnforcerBlockerType : int8_t;
|
||||
|
||||
// Following class holds information about selected triangles. It also has power
|
||||
// to recursively subdivide the triangles and make the selection finer.
|
||||
class TriangleSelector {
|
||||
class TriangleSelector
|
||||
{
|
||||
protected:
|
||||
class Triangle;
|
||||
struct Vertex;
|
||||
|
||||
public:
|
||||
enum CursorType {
|
||||
CIRCLE,
|
||||
@ -35,6 +40,146 @@ public:
|
||||
bool is_mesh_point_clipped(const Vec3f &point) const { return normal.dot(point) - offset > 0.f; }
|
||||
};
|
||||
|
||||
class Cursor
|
||||
{
|
||||
public:
|
||||
Cursor() = delete;
|
||||
virtual ~Cursor() = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const;
|
||||
|
||||
virtual bool is_mesh_point_inside(const Vec3f &point) const = 0;
|
||||
virtual bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const = 0;
|
||||
virtual int vertices_inside(const Triangle &tr, const std::vector<Vertex> &vertices) const;
|
||||
virtual bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const = 0;
|
||||
virtual bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const = 0;
|
||||
|
||||
static bool is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector<Vec3f> &face_normals);
|
||||
|
||||
protected:
|
||||
explicit Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_);
|
||||
|
||||
Transform3f trafo;
|
||||
Vec3f source;
|
||||
|
||||
bool uniform_scaling;
|
||||
Transform3f trafo_normal;
|
||||
float radius;
|
||||
float radius_sqr;
|
||||
Vec3f dir = Vec3f(0.f, 0.f, 0.f);
|
||||
|
||||
ClippingPlane clipping_plane; // Clipping plane to limit painting to not clipped facets only
|
||||
|
||||
friend TriangleSelector;
|
||||
};
|
||||
|
||||
class SinglePointCursor : public Cursor
|
||||
{
|
||||
public:
|
||||
SinglePointCursor() = delete;
|
||||
~SinglePointCursor() override = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override;
|
||||
|
||||
static std::unique_ptr<Cursor> cursor_factory(const Vec3f ¢er, const Vec3f &camera_pos, const float cursor_radius, const CursorType cursor_type, const Transform3d &trafo_matrix, const ClippingPlane &clipping_plane)
|
||||
{
|
||||
assert(cursor_type == TriangleSelector::CursorType::CIRCLE || cursor_type == TriangleSelector::CursorType::SPHERE);
|
||||
if (cursor_type == TriangleSelector::CursorType::SPHERE)
|
||||
return std::make_unique<TriangleSelector::Sphere>(center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
else
|
||||
return std::make_unique<TriangleSelector::Circle>(center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit SinglePointCursor(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_);
|
||||
|
||||
Vec3f center;
|
||||
};
|
||||
|
||||
class DoublePointCursor : public Cursor
|
||||
{
|
||||
public:
|
||||
DoublePointCursor() = delete;
|
||||
~DoublePointCursor() override = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override;
|
||||
|
||||
static std::unique_ptr<Cursor> cursor_factory(const Vec3f &first_center, const Vec3f &second_center, const Vec3f &camera_pos, const float cursor_radius, const CursorType cursor_type, const Transform3d &trafo_matrix, const ClippingPlane &clipping_plane)
|
||||
{
|
||||
assert(cursor_type == TriangleSelector::CursorType::CIRCLE || cursor_type == TriangleSelector::CursorType::SPHERE);
|
||||
if (cursor_type == TriangleSelector::CursorType::SPHERE)
|
||||
return std::make_unique<TriangleSelector::Capsule3D>(first_center, second_center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
else
|
||||
return std::make_unique<TriangleSelector::Capsule2D>(first_center, second_center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit DoublePointCursor(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_);
|
||||
|
||||
Vec3f first_center;
|
||||
Vec3f second_center;
|
||||
};
|
||||
|
||||
class Sphere : public SinglePointCursor
|
||||
{
|
||||
public:
|
||||
Sphere() = delete;
|
||||
explicit Sphere(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_){};
|
||||
~Sphere() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
|
||||
};
|
||||
|
||||
class Circle : public SinglePointCursor
|
||||
{
|
||||
public:
|
||||
Circle() = delete;
|
||||
explicit Circle(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_){};
|
||||
~Circle() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
|
||||
{
|
||||
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
|
||||
}
|
||||
};
|
||||
|
||||
class Capsule3D : public DoublePointCursor
|
||||
{
|
||||
public:
|
||||
Capsule3D() = delete;
|
||||
explicit Capsule3D(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: TriangleSelector::DoublePointCursor(first_center_, second_center_, source_, radius_world, trafo_, clipping_plane_)
|
||||
{}
|
||||
~Capsule3D() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
|
||||
};
|
||||
|
||||
class Capsule2D : public DoublePointCursor
|
||||
{
|
||||
public:
|
||||
Capsule2D() = delete;
|
||||
explicit Capsule2D(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: TriangleSelector::DoublePointCursor(first_center_, second_center_, source_, radius_world, trafo_, clipping_plane_)
|
||||
{}
|
||||
~Capsule2D() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
|
||||
{
|
||||
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
|
||||
}
|
||||
};
|
||||
|
||||
std::pair<std::vector<Vec3i>, std::vector<Vec3i>> precompute_all_neighbors() const;
|
||||
void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out, std::vector<Vec3i> &neighbors_normal_out) const;
|
||||
|
||||
@ -51,16 +196,11 @@ public:
|
||||
[[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const;
|
||||
|
||||
// Select all triangles fully inside the circle, subdivide where needed.
|
||||
void select_patch(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
const Vec3f &source, // camera position (mesh coords)
|
||||
float radius, // radius of the cursor
|
||||
CursorType type, // current type of cursor
|
||||
void select_patch(int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
std::unique_ptr<Cursor> &&cursor, // Cursor containing information about the point where to start, camera position (mesh coords), matrix to get from mesh to world, and its shape and type.
|
||||
EnforcerBlockerType new_state, // enforcer or blocker?
|
||||
const Transform3d &trafo, // matrix to get from mesh to world
|
||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
||||
bool triangle_splitting, // If triangles will be split base on the cursor or not
|
||||
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
|
||||
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
|
||||
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
@ -195,39 +335,16 @@ protected:
|
||||
int m_orig_size_vertices = 0;
|
||||
int m_orig_size_indices = 0;
|
||||
|
||||
// Cache for cursor position, radius and direction.
|
||||
struct Cursor {
|
||||
Cursor() = default;
|
||||
Cursor(const Vec3f& center_, const Vec3f& source_, float radius_world,
|
||||
CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_);
|
||||
bool is_mesh_point_inside(const Vec3f &pt) const;
|
||||
bool is_pointer_in_triangle(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) const;
|
||||
|
||||
Vec3f center;
|
||||
Vec3f source;
|
||||
Vec3f dir;
|
||||
float radius_sqr;
|
||||
CursorType type;
|
||||
Transform3f trafo;
|
||||
Transform3f trafo_normal;
|
||||
bool uniform_scaling;
|
||||
ClippingPlane clipping_plane;
|
||||
};
|
||||
|
||||
Cursor m_cursor;
|
||||
std::unique_ptr<Cursor> m_cursor;
|
||||
float m_old_cursor_radius_sqr;
|
||||
|
||||
// Private functions:
|
||||
private:
|
||||
bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting);
|
||||
bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting);
|
||||
int vertices_inside(int facet_idx) const;
|
||||
bool faces_camera(int facet) const;
|
||||
void undivide_triangle(int facet_idx);
|
||||
void split_triangle(int facet_idx, const Vec3i &neighbors);
|
||||
void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
|
||||
bool is_pointer_in_triangle(int facet_idx) const;
|
||||
bool is_edge_inside_cursor(int facet_idx) const;
|
||||
bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const;
|
||||
int push_triangle(int a, int b, int c, int source_triangle, EnforcerBlockerType state = EnforcerBlockerType{0});
|
||||
void perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state);
|
||||
|
@ -72,15 +72,10 @@ namespace Slic3r {
|
||||
#if ENABLE_SMOOTH_NORMALS
|
||||
static void smooth_normals_corner(TriangleMesh& mesh, std::vector<stl_normal>& normals)
|
||||
{
|
||||
mesh.repair();
|
||||
|
||||
using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
|
||||
std::vector<stl_normal> face_normals(mesh.stl.stats.number_of_facets);
|
||||
for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
|
||||
face_normals[i] = mesh.stl.facet_start[i].normal;
|
||||
}
|
||||
std::vector<Vec3f> face_normals = its_face_normals(mesh.its);
|
||||
|
||||
Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(),
|
||||
Eigen::Index(mesh.its.vertices.size()), 3).cast<double>();
|
||||
@ -102,8 +97,6 @@ static void smooth_normals_corner(TriangleMesh& mesh, std::vector<stl_normal>& n
|
||||
|
||||
static void smooth_normals_vertex(TriangleMesh& mesh, std::vector<stl_normal>& normals)
|
||||
{
|
||||
mesh.repair();
|
||||
|
||||
using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
|
||||
|
@ -779,8 +779,8 @@ void GUI_App::post_init()
|
||||
show_send_system_info_dialog_if_needed();
|
||||
}
|
||||
#ifdef _WIN32
|
||||
// Run external updater on Windows.
|
||||
if (! run_updater_win())
|
||||
// Run external updater on Windows if version check is enabled.
|
||||
if (this->preset_updater->version_check_enabled() && ! run_updater_win())
|
||||
// "prusaslicer-updater.exe" was not started, run our own update check.
|
||||
#endif // _WIN32
|
||||
this->preset_updater->slic3r_update_notify();
|
||||
@ -1487,6 +1487,7 @@ void GUI_App::update_fonts(const MainFrame *main_frame)
|
||||
m_normal_font = main_frame->normal_font();
|
||||
m_small_font = m_normal_font;
|
||||
m_bold_font = main_frame->normal_font().Bold();
|
||||
m_link_font = m_bold_font.Underlined();
|
||||
m_em_unit = main_frame->em_unit();
|
||||
m_code_font.SetPointSize(m_normal_font.GetPointSize());
|
||||
}
|
||||
|
@ -133,6 +133,7 @@ private:
|
||||
wxFont m_bold_font;
|
||||
wxFont m_normal_font;
|
||||
wxFont m_code_font;
|
||||
wxFont m_link_font;
|
||||
|
||||
int m_em_unit; // width of a "m"-symbol in pixels for current system font
|
||||
// Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls
|
||||
@ -218,6 +219,7 @@ public:
|
||||
const wxFont& bold_font() { return m_bold_font; }
|
||||
const wxFont& normal_font() { return m_normal_font; }
|
||||
const wxFont& code_font() { return m_code_font; }
|
||||
const wxFont& link_font() { return m_link_font; }
|
||||
int em_unit() const { return m_em_unit; }
|
||||
bool tabs_as_menu() const;
|
||||
wxSize get_min_size() const;
|
||||
|
@ -13,7 +13,8 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
@ -223,6 +224,126 @@ bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transfo
|
||||
return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
|
||||
}
|
||||
|
||||
// Interpolate points between the previous and current mouse positions, which are then projected onto the object.
|
||||
// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector<GLGizmoPainterBase::ProjectedMousePosition>
|
||||
// with the same mesh_idx, but all items in std::vector<GLGizmoPainterBase::ProjectedMousePosition> always have the same mesh_idx.
|
||||
std::vector<std::vector<GLGizmoPainterBase::ProjectedMousePosition>> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector<Transform3d> &trafo_matrices) const
|
||||
{
|
||||
// List of mouse positions that will be used as seeds for painting.
|
||||
std::vector<Vec2d> mouse_positions{mouse_position};
|
||||
if (m_last_mouse_click != Vec2d::Zero()) {
|
||||
// In case current mouse position is far from the last one,
|
||||
// add several positions from between into the list, so there
|
||||
// are no gaps in the painted region.
|
||||
if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) {
|
||||
const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1);
|
||||
for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx)
|
||||
mouse_positions.emplace_back(mouse_position + patch_idx * diff);
|
||||
mouse_positions.emplace_back(m_last_mouse_click);
|
||||
}
|
||||
}
|
||||
|
||||
const Camera &camera = wxGetApp().plater()->get_camera();
|
||||
std::vector<ProjectedMousePosition> mesh_hit_points;
|
||||
mesh_hit_points.reserve(mouse_position.size());
|
||||
|
||||
// In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't.
|
||||
for (const Vec2d &mp : mouse_positions) {
|
||||
update_raycast_cache(mp, camera, trafo_matrices);
|
||||
mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet});
|
||||
if (m_rr.mesh_id == -1)
|
||||
break;
|
||||
}
|
||||
|
||||
// Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx.
|
||||
std::vector<std::vector<ProjectedMousePosition>> mesh_hit_points_by_mesh;
|
||||
for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) {
|
||||
size_t next_mesh_hit_point = curr_mesh_hit_point + 1;
|
||||
if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) {
|
||||
mesh_hit_points_by_mesh.emplace_back();
|
||||
mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point));
|
||||
prev_mesh_hit_point = next_mesh_hit_point;
|
||||
}
|
||||
}
|
||||
|
||||
auto on_same_facet = [](std::vector<ProjectedMousePosition> &hit_points) -> bool {
|
||||
for (const ProjectedMousePosition &mesh_hit_point : hit_points)
|
||||
if (mesh_hit_point.facet_idx != hit_points.front().facet_idx)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
struct Plane
|
||||
{
|
||||
Vec3d origin;
|
||||
Vec3d first_axis;
|
||||
Vec3d second_axis;
|
||||
};
|
||||
auto find_plane = [](std::vector<ProjectedMousePosition> &hit_points) -> std::optional<Plane> {
|
||||
assert(hit_points.size() >= 3);
|
||||
for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) {
|
||||
const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast<double>();
|
||||
const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast<double>();
|
||||
const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast<double>();
|
||||
|
||||
const Vec3d first_vec = first_point - second_point;
|
||||
const Vec3d second_vec = third_point - second_point;
|
||||
|
||||
// If three points aren't collinear, then there exists only one plane going through all points.
|
||||
if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) {
|
||||
const Vec3d first_axis_vec_n = first_vec.normalized();
|
||||
// Make second_vec perpendicular to first_axis_vec_n using Gram–Schmidt orthogonalization process
|
||||
const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized();
|
||||
return Plane{second_point, first_axis_vec_n, second_axis_vec_n};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
for(std::vector<ProjectedMousePosition> &hit_points : mesh_hit_points_by_mesh) {
|
||||
assert(!hit_points.empty());
|
||||
if (hit_points.back().mesh_idx == -1)
|
||||
break;
|
||||
|
||||
if (hit_points.size() <= 2)
|
||||
continue;
|
||||
|
||||
if (on_same_facet(hit_points)) {
|
||||
hit_points = {hit_points.front(), hit_points.back()};
|
||||
} else if (std::optional<Plane> plane = find_plane(hit_points); plane) {
|
||||
Polyline polyline;
|
||||
polyline.points.reserve(hit_points.size());
|
||||
// Project hit_points into its plane to simplified them in the next step.
|
||||
for (auto &hit_point : hit_points) {
|
||||
const Vec3d &point = hit_point.mesh_hit.cast<double>();
|
||||
const double x_cord = plane->first_axis.dot(point - plane->origin);
|
||||
const double y_cord = plane->second_axis.dot(point - plane->origin);
|
||||
polyline.points.emplace_back(scale_(x_cord), scale_(y_cord));
|
||||
}
|
||||
|
||||
polyline.simplify(scale_(m_cursor_radius) / 10.);
|
||||
|
||||
const int mesh_idx = hit_points.front().mesh_idx;
|
||||
std::vector<ProjectedMousePosition> new_hit_points;
|
||||
new_hit_points.reserve(polyline.points.size());
|
||||
// Project 2D simplified hit_points beck to 3D.
|
||||
for (const Point &point : polyline.points) {
|
||||
const double x_cord = unscale<double>(point.x());
|
||||
const double y_cord = unscale<double>(point.y());
|
||||
const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis;
|
||||
const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast<float>());
|
||||
new_hit_points.push_back({new_hit_point.cast<float>(), mesh_idx, size_t(facet_idx)});
|
||||
}
|
||||
|
||||
hit_points = new_hit_points;
|
||||
} else {
|
||||
hit_points = {hit_points.front(), hit_points.back()};
|
||||
}
|
||||
}
|
||||
|
||||
return mesh_hit_points_by_mesh;
|
||||
}
|
||||
|
||||
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
|
||||
// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
|
||||
@ -295,28 +416,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
const Transform3d instance_trafo = mi->get_transformation().get_matrix();
|
||||
const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true);
|
||||
|
||||
// List of mouse positions that will be used as seeds for painting.
|
||||
std::vector<Vec2d> mouse_positions{mouse_position};
|
||||
|
||||
// In case current mouse position is far from the last one,
|
||||
// add several positions from between into the list, so there
|
||||
// are no gaps in the painted region.
|
||||
{
|
||||
if (m_last_mouse_click == Vec2d::Zero())
|
||||
m_last_mouse_click = mouse_position;
|
||||
// resolution describes minimal distance limit using circle radius
|
||||
// as a unit (e.g., 2 would mean the patches will be touching).
|
||||
double resolution = 0.7;
|
||||
double diameter_px = resolution * m_cursor_radius * camera.get_zoom();
|
||||
int patches_in_between = int(((mouse_position - m_last_mouse_click).norm() - diameter_px) / diameter_px);
|
||||
if (patches_in_between > 0) {
|
||||
Vec2d diff = (mouse_position - m_last_mouse_click)/(patches_in_between+1);
|
||||
for (int i=1; i<=patches_in_between; ++i)
|
||||
mouse_positions.emplace_back(m_last_mouse_click + i*diff);
|
||||
}
|
||||
}
|
||||
m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
|
||||
|
||||
// Precalculate transformations of individual meshes.
|
||||
std::vector<Transform3d> trafo_matrices;
|
||||
std::vector<Transform3d> trafo_matrices_not_translate;
|
||||
@ -326,50 +425,70 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
|
||||
}
|
||||
|
||||
// Now "click" into all the prepared points and spill paint around them.
|
||||
for (const Vec2d& mp : mouse_positions) {
|
||||
update_raycast_cache(mp, camera, trafo_matrices);
|
||||
std::vector<std::vector<ProjectedMousePosition>> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices);
|
||||
m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
|
||||
|
||||
bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
|
||||
for (const std::vector<ProjectedMousePosition> &projected_mouse_positions : projected_mouse_positions_by_mesh) {
|
||||
assert(!projected_mouse_positions.empty());
|
||||
const int mesh_idx = projected_mouse_positions.front().mesh_idx;
|
||||
const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
|
||||
|
||||
// The mouse button click detection is enabled when there is a valid hit.
|
||||
// Missing the object entirely
|
||||
// shall not capture the mouse.
|
||||
if (m_rr.mesh_id != -1) {
|
||||
if (mesh_idx != -1)
|
||||
if (m_button_down == Button::None)
|
||||
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
|
||||
}
|
||||
|
||||
if (m_rr.mesh_id == -1) {
|
||||
// In case we have no valid hit, we can return. The event will be stopped when
|
||||
// dragging while painting (to prevent scene rotations and moving the object)
|
||||
if (mesh_idx == -1)
|
||||
return dragging_while_painting;
|
||||
}
|
||||
|
||||
const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id];
|
||||
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id];
|
||||
const Transform3d &trafo_matrix = trafo_matrices[mesh_idx];
|
||||
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx];
|
||||
|
||||
// Calculate direction from camera to the hit (in mesh coords):
|
||||
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
|
||||
|
||||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
assert(mesh_idx < int(m_triangle_selectors.size()));
|
||||
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
|
||||
if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
|
||||
for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) {
|
||||
assert(projected_mouse_position.mesh_idx == mesh_idx);
|
||||
const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
|
||||
const int facet_idx = int(projected_mouse_position.facet_idx);
|
||||
m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
|
||||
if (m_tool_type == ToolType::SMART_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
|
||||
m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
|
||||
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false, true);
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true);
|
||||
else if (m_tool_type == ToolType::BUCKET_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true, true);
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true);
|
||||
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
} else if (m_tool_type == ToolType::BRUSH)
|
||||
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
|
||||
new_state, trafo_matrix, trafo_matrix_not_translate, m_triangle_splitting_enabled, clp,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
}
|
||||
} else if (m_tool_type == ToolType::BRUSH) {
|
||||
assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE);
|
||||
|
||||
if (projected_mouse_positions.size() == 1) {
|
||||
const ProjectedMousePosition &first_position = projected_mouse_positions.front();
|
||||
std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit,
|
||||
camera_pos, m_cursor_radius,
|
||||
m_cursor_type, trafo_matrix, clp);
|
||||
m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate,
|
||||
m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
} else {
|
||||
for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) {
|
||||
auto second_position_it = first_position_it + 1;
|
||||
std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp);
|
||||
m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_triangle_selectors[mesh_idx]->request_update_render_data();
|
||||
m_last_mouse_click = mouse_position;
|
||||
}
|
||||
|
||||
|
@ -156,6 +156,13 @@ protected:
|
||||
SMART_FILL
|
||||
};
|
||||
|
||||
struct ProjectedMousePosition
|
||||
{
|
||||
Vec3f mesh_hit;
|
||||
int mesh_idx;
|
||||
size_t facet_idx;
|
||||
};
|
||||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_smart_fill_angle = 30.f;
|
||||
@ -188,6 +195,8 @@ protected:
|
||||
TriangleSelector::ClippingPlane get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<ProjectedMousePosition>> get_projected_mouse_positions(const Vec2d &mouse_position, double resolution, const std::vector<Transform3d> &trafo_matrices) const;
|
||||
|
||||
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
|
||||
void update_raycast_cache(const Vec2d& mouse_position,
|
||||
const Camera& camera,
|
||||
|
@ -319,8 +319,13 @@ static void add_tabs_as_menu(wxMenuBar* bar, MainFrame* main_frame, wxWindow* ba
|
||||
|
||||
bar_parent->Bind(wxEVT_MENU_OPEN, [main_frame, bar, is_mainframe_menu](wxMenuEvent& event) {
|
||||
wxMenu* const menu = event.GetMenu();
|
||||
if (!menu || menu->GetMenuItemCount() > 0)
|
||||
if (!menu || menu->GetMenuItemCount() > 0) {
|
||||
// If we are here it means that we open regular menu and not a tab used as a menu
|
||||
event.Skip(); // event.Skip() is verry important to next processing of the wxEVT_UPDATE_UI by this menu items.
|
||||
// If wxEVT_MENU_OPEN will not be pocessed in next event queue then MenuItems of this menu will never caught wxEVT_UPDATE_UI
|
||||
// and, as a result, "check/radio value" will not be updated
|
||||
return;
|
||||
}
|
||||
|
||||
// update tab selection
|
||||
|
||||
@ -1398,7 +1403,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
if (!input_files.IsEmpty())
|
||||
m_plater->sidebar().obj_list()->load_shape_object_from_gallery(input_files);
|
||||
}
|
||||
}, "cog", nullptr, []() {return true; }, this);
|
||||
}, "shape_gallery", nullptr, []() {return true; }, this);
|
||||
|
||||
windowMenu->AppendSeparator();
|
||||
append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"),
|
||||
|
@ -304,7 +304,13 @@ Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
|
||||
return closest_point.cast<float>();
|
||||
}
|
||||
|
||||
|
||||
int MeshRaycaster::get_closest_facet(const Vec3f &point) const
|
||||
{
|
||||
int facet_idx = 0;
|
||||
Vec3d closest_point;
|
||||
m_emesh.squared_distance(point.cast<double>(), facet_idx, closest_point);
|
||||
return facet_idx;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
@ -151,6 +151,9 @@ public:
|
||||
|
||||
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
|
||||
|
||||
// Given a point in mesh coords, the method returns the closest facet from mesh.
|
||||
int get_closest_facet(const Vec3f &point) const;
|
||||
|
||||
Vec3f get_triangle_normal(size_t facet_idx) const;
|
||||
|
||||
private:
|
||||
|
@ -33,36 +33,6 @@ wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClicke
|
||||
wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
|
||||
wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent);
|
||||
|
||||
const NotificationManager::NotificationData NotificationManager::basic_notifications[] = {
|
||||
{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotificationLevel, 10, _u8L("3D Mouse disconnected.") },
|
||||
{NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("Configuration update is available."), _u8L("See more."),
|
||||
[](wxEvtHandler* evnthndlr) {
|
||||
if (evnthndlr != nullptr)
|
||||
wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{NotificationType::EmptyColorChangeCode, NotificationLevel::PrintInfoNotificationLevel, 10,
|
||||
_u8L("You have just added a G-code for color change, but its value is empty.\n"
|
||||
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
|
||||
{NotificationType::EmptyAutoColorChange, NotificationLevel::PrintInfoNotificationLevel, 10,
|
||||
_u8L("No color change event was added to the print. The print does not look like a sign.") },
|
||||
{NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10,
|
||||
_u8L("Desktop integration was successful.") },
|
||||
{NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
|
||||
_u8L("Desktop integration failed.") },
|
||||
{NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10,
|
||||
_u8L("Undo desktop integration was successful.") },
|
||||
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
|
||||
_u8L("Undo desktop integration failed.") },
|
||||
{NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") },
|
||||
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
|
||||
// wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
|
||||
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
|
||||
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") },
|
||||
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
|
||||
};
|
||||
|
||||
namespace {
|
||||
/* // not used?
|
||||
ImFont* add_default_font(float pixel_size)
|
||||
@ -393,8 +363,7 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons
|
||||
std::string line;
|
||||
|
||||
for (size_t i = 0; i < (m_multiline ? m_endlines.size() : std::min(m_endlines.size(), (size_t)2)); i++) {
|
||||
if (m_endlines[i] > m_text1.size())
|
||||
break;
|
||||
assert(m_endlines.size() > i && m_text1.size() >= m_endlines[i]);
|
||||
line.clear();
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(starting_y + i * shift_y);
|
||||
@ -681,6 +650,7 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper&
|
||||
float starting_y = m_line_height / 2;//10;
|
||||
float shift_y = m_line_height;// -m_line_height / 20;
|
||||
for (size_t i = 0; i < m_lines_count; i++) {
|
||||
assert(m_text1.size() >= m_endlines[i]);
|
||||
if (m_text1.size() >= m_endlines[i]) {
|
||||
std::string line = m_text1.substr(last_end, m_endlines[i] - last_end);
|
||||
last_end = m_endlines[i];
|
||||
@ -801,6 +771,7 @@ void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& img
|
||||
// hypertext is not rendered at all. If it is needed, it needs to be added here.
|
||||
// m_endlines should have endline for each line and then for hypertext thus m_endlines[1] should always be in m_text1
|
||||
if (m_multiline) {
|
||||
assert(m_text1.size() >= m_endlines[0] || m_text1.size() >= m_endlines[1]);
|
||||
if(m_endlines[0] > m_text1.size() || m_endlines[1] > m_text1.size())
|
||||
return;
|
||||
// two lines text (what doesnt fit, wont show), one line bar
|
||||
@ -815,6 +786,7 @@ void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& img
|
||||
render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
} else {
|
||||
assert(m_text1.size() >= m_endlines[0]);
|
||||
if (m_endlines[0] > m_text1.size())
|
||||
return;
|
||||
//one line text, one line bar
|
||||
|
@ -749,7 +749,37 @@ private:
|
||||
NotificationType::SimplifySuggestion
|
||||
};
|
||||
//prepared (basic) notifications
|
||||
static const NotificationData basic_notifications[];
|
||||
// non-static so its not loaded too early. If static, the translations wont load correctly.
|
||||
const std::vector<NotificationData> basic_notifications = {
|
||||
{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotificationLevel, 10, _u8L("3D Mouse disconnected.") },
|
||||
{NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("Configuration update is available."), _u8L("See more."),
|
||||
[](wxEvtHandler* evnthndlr) {
|
||||
if (evnthndlr != nullptr)
|
||||
wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{NotificationType::EmptyColorChangeCode, NotificationLevel::PrintInfoNotificationLevel, 10,
|
||||
_u8L("You have just added a G-code for color change, but its value is empty.\n"
|
||||
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
|
||||
{NotificationType::EmptyAutoColorChange, NotificationLevel::PrintInfoNotificationLevel, 10,
|
||||
_u8L("No color change event was added to the print. The print does not look like a sign.") },
|
||||
{NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10,
|
||||
_u8L("Desktop integration was successful.") },
|
||||
{NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
|
||||
_u8L("Desktop integration failed.") },
|
||||
{NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10,
|
||||
_u8L("Undo desktop integration was successful.") },
|
||||
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
|
||||
_u8L("Undo desktop integration failed.") },
|
||||
{NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") },
|
||||
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
|
||||
// wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
|
||||
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
|
||||
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") },
|
||||
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}//namespace GUI
|
||||
|
@ -28,17 +28,6 @@ static wxSize get_bitmap_size(const wxBitmap& bmp)
|
||||
#endif
|
||||
}
|
||||
|
||||
static wxString get_url(const wxString& path_end, bool get_default = false)
|
||||
{
|
||||
if (path_end.IsEmpty())
|
||||
return wxEmptyString;
|
||||
|
||||
wxString language = wxGetApp().app_config->get("translation_language");
|
||||
wxString lang_marker = language.IsEmpty() ? "en" : language.BeforeFirst('_');
|
||||
|
||||
return wxString("https://help.prusa3d.com/") + lang_marker + "/article/" + path_end;
|
||||
}
|
||||
|
||||
OG_CustomCtrl::OG_CustomCtrl( wxWindow* parent,
|
||||
OptionsGroup* og,
|
||||
const wxPoint& pos /* = wxDefaultPosition*/,
|
||||
@ -264,7 +253,7 @@ void OG_CustomCtrl::OnMotion(wxMouseEvent& event)
|
||||
line.is_focused = is_point_in_rect(pos, line.rect_label);
|
||||
if (line.is_focused) {
|
||||
if (!suppress_hyperlinks && !line.og_line.label_path.empty())
|
||||
tooltip = get_url(line.og_line.label_path) +"\n\n";
|
||||
tooltip = OptionsGroup::get_url(line.og_line.label_path) +"\n\n";
|
||||
tooltip += line.og_line.label_tooltip;
|
||||
break;
|
||||
}
|
||||
@ -577,7 +566,7 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
|
||||
bool is_url_string = false;
|
||||
if (ctrl->opt_group->label_width != 0 && !label.IsEmpty()) {
|
||||
const wxColour* text_clr = (option_set.size() == 1 && field ? field->label_color() : og_line.full_Label_color);
|
||||
is_url_string = !suppress_hyperlinks && !og_line.label_path.IsEmpty();
|
||||
is_url_string = !suppress_hyperlinks && !og_line.label_path.empty();
|
||||
h_pos = draw_text(dc, wxPoint(h_pos, v_pos), label + ":", text_clr, ctrl->opt_group->label_width * ctrl->m_em_unit, is_url_string);
|
||||
}
|
||||
|
||||
@ -619,7 +608,7 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
|
||||
if (is_url_string)
|
||||
is_url_string = false;
|
||||
else if(opt == option_set.front())
|
||||
is_url_string = !suppress_hyperlinks && !og_line.label_path.IsEmpty();
|
||||
is_url_string = !suppress_hyperlinks && !og_line.label_path.empty();
|
||||
h_pos = draw_text(dc, wxPoint(h_pos, v_pos), label, field ? field->label_color() : nullptr, ctrl->opt_group->sublabel_width * ctrl->m_em_unit, is_url_string);
|
||||
}
|
||||
|
||||
@ -766,36 +755,10 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBi
|
||||
|
||||
bool OG_CustomCtrl::CtrlLine::launch_browser() const
|
||||
{
|
||||
if (!is_focused || og_line.label_path.IsEmpty())
|
||||
if (!is_focused || og_line.label_path.empty())
|
||||
return false;
|
||||
|
||||
bool launch = true;
|
||||
|
||||
if (get_app_config()->get("suppress_hyperlinks").empty()) {
|
||||
RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxYES_NO);
|
||||
dialog.ShowCheckBox(_L("Remember my choice"));
|
||||
int answer = dialog.ShowModal();
|
||||
|
||||
if (dialog.IsCheckBoxChecked()) {
|
||||
wxString preferences_item = _L("Suppress to open hyperlink in browser");
|
||||
wxString msg =
|
||||
_L("PrusaSlicer will remember your choice.") + "\n\n" +
|
||||
_L("You will not be asked about it again on label hovering.") + "\n\n" +
|
||||
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
|
||||
|
||||
MessageDialog msg_dlg(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
if (msg_dlg.ShowModal() == wxID_CANCEL)
|
||||
return false;
|
||||
|
||||
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
|
||||
}
|
||||
|
||||
launch = answer == wxID_YES;
|
||||
}
|
||||
if (launch)
|
||||
launch = get_app_config()->get("suppress_hyperlinks") != "1";
|
||||
|
||||
return launch && wxLaunchDefaultBrowser(get_url(og_line.label_path));
|
||||
return OptionsGroup::launch_browser(og_line.label_path);
|
||||
}
|
||||
|
||||
} // GUI
|
||||
|
@ -46,10 +46,10 @@ struct InfoItemAtributes {
|
||||
|
||||
const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
|
||||
// info_item Type info_item Name info_item BitmapName
|
||||
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports" }, },
|
||||
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam" }, },
|
||||
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation"}, },
|
||||
{ InfoItemType::Sinking, {L("Sinking"), "support_blocker"}, },
|
||||
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, },
|
||||
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, },
|
||||
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, },
|
||||
{ InfoItemType::Sinking, {L("Sinking"), "sinking"}, },
|
||||
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, },
|
||||
};
|
||||
|
||||
@ -1680,6 +1680,9 @@ void ObjectDataViewModel::Rescale()
|
||||
m_warning_bmp = create_scaled_bitmap(WarningIcon);
|
||||
m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon);
|
||||
|
||||
for (auto item : INFO_ITEMS)
|
||||
m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name);
|
||||
|
||||
wxDataViewItemArray all_items;
|
||||
GetAllChildren(wxDataViewItem(0), all_items);
|
||||
|
||||
@ -1703,6 +1706,8 @@ void ObjectDataViewModel::Rescale()
|
||||
node->m_bmp = create_scaled_bitmap(LayerRootIcon);
|
||||
case itLayer:
|
||||
node->m_bmp = create_scaled_bitmap(LayerIcon);
|
||||
case itInfo:
|
||||
node->m_bmp = m_info_bmps.at(node->m_info_item_type);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "Plater.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "OG_CustomCtrl.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <wx/numformatter.h>
|
||||
@ -10,6 +12,7 @@
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
@ -504,7 +507,7 @@ void OptionsGroup::clear(bool destroy_custom_ctrl)
|
||||
m_fields.clear();
|
||||
}
|
||||
|
||||
Line OptionsGroup::create_single_option_line(const Option& option, const wxString& path/* = wxEmptyString*/) const {
|
||||
Line OptionsGroup::create_single_option_line(const Option& option, const std::string& path/* = std::string()*/) const {
|
||||
// Line retval{ _(option.opt.label), _(option.opt.tooltip) };
|
||||
wxString tooltip = _(option.opt.tooltip);
|
||||
edit_tooltip(tooltip);
|
||||
@ -962,6 +965,54 @@ void ConfigOptionsGroup::change_opt_value(const t_config_option_key& opt_key, co
|
||||
m_modelconfig->touch();
|
||||
}
|
||||
|
||||
wxString OptionsGroup::get_url(const std::string& path_end)
|
||||
{
|
||||
if (path_end.empty())
|
||||
return wxEmptyString;
|
||||
|
||||
wxString language = get_app_config()->get("translation_language");
|
||||
wxString lang_marker = language.IsEmpty() ? "en" : language.BeforeFirst('_');
|
||||
|
||||
return wxString("https://help.prusa3d.com/") + lang_marker + wxString("/article/" + path_end);
|
||||
}
|
||||
|
||||
bool OptionsGroup::launch_browser(const std::string& path_end)
|
||||
{
|
||||
bool launch = true;
|
||||
|
||||
if (get_app_config()->get("suppress_hyperlinks").empty()) {
|
||||
RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxYES_NO);
|
||||
dialog.ShowCheckBox(_L("Remember my choice"));
|
||||
int answer = dialog.ShowModal();
|
||||
|
||||
if (dialog.IsCheckBoxChecked()) {
|
||||
wxString preferences_item = _L("Suppress to open hyperlink in browser");
|
||||
wxString msg =
|
||||
_L("PrusaSlicer will remember your choice.") + "\n\n" +
|
||||
_L("You will not be asked about it again on label hovering.") + "\n\n" +
|
||||
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
|
||||
|
||||
MessageDialog msg_dlg(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
if (msg_dlg.ShowModal() == wxID_CANCEL)
|
||||
return false;
|
||||
|
||||
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
|
||||
}
|
||||
|
||||
launch = answer == wxID_YES;
|
||||
}
|
||||
if (launch)
|
||||
launch = get_app_config()->get("suppress_hyperlinks") != "1";
|
||||
|
||||
return launch && wxLaunchDefaultBrowser(OptionsGroup::get_url(path_end));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------------------------
|
||||
// ogStaticText
|
||||
//-------------------------------------------------------------------------------------------
|
||||
|
||||
ogStaticText::ogStaticText(wxWindow* parent, const wxString& text) :
|
||||
wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize)
|
||||
{
|
||||
@ -979,5 +1030,37 @@ void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/)
|
||||
GetParent()->Layout();
|
||||
}
|
||||
|
||||
void ogStaticText::SetPathEnd(const std::string& link)
|
||||
{
|
||||
if (get_app_config()->get("suppress_hyperlinks") != "1")
|
||||
SetToolTip(OptionsGroup::get_url(link));
|
||||
|
||||
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) {
|
||||
if (HasCapture())
|
||||
return;
|
||||
this->CaptureMouse();
|
||||
event.Skip();
|
||||
} );
|
||||
Bind(wxEVT_LEFT_UP, [link, this](wxMouseEvent& event) {
|
||||
if (!HasCapture())
|
||||
return;
|
||||
ReleaseMouse();
|
||||
OptionsGroup::launch_browser(link);
|
||||
event.Skip();
|
||||
} );
|
||||
Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& event) { FocusText(true) ; event.Skip(); });
|
||||
Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { FocusText(false); event.Skip(); });
|
||||
}
|
||||
|
||||
void ogStaticText::FocusText(bool focus)
|
||||
{
|
||||
if (get_app_config()->get("suppress_hyperlinks") == "1")
|
||||
return;
|
||||
|
||||
SetFont(focus ? Slic3r::GUI::wxGetApp().link_font() :
|
||||
Slic3r::GUI::wxGetApp().normal_font());
|
||||
Refresh();
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
@ -53,7 +53,7 @@ class Line {
|
||||
public:
|
||||
wxString label;
|
||||
wxString label_tooltip;
|
||||
wxString label_path;
|
||||
std::string label_path;
|
||||
|
||||
size_t full_width {0};
|
||||
wxColour* full_Label_color {nullptr};
|
||||
@ -133,8 +133,8 @@ public:
|
||||
// delete all controls from the option group
|
||||
void clear(bool destroy_custom_ctrl = false);
|
||||
|
||||
Line create_single_option_line(const Option& option, const wxString& path = wxEmptyString) const;
|
||||
void append_single_option_line(const Option& option, const wxString& path = wxEmptyString) { append_line(create_single_option_line(option, path)); }
|
||||
Line create_single_option_line(const Option& option, const std::string& path = std::string()) const;
|
||||
void append_single_option_line(const Option& option, const std::string& path = std::string()) { append_line(create_single_option_line(option, path)); }
|
||||
void append_separator();
|
||||
|
||||
// return a non-owning pointer reference
|
||||
@ -219,6 +219,10 @@ protected:
|
||||
virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value);
|
||||
virtual void back_to_initial_value(const std::string& opt_key) {}
|
||||
virtual void back_to_sys_value(const std::string& opt_key) {}
|
||||
|
||||
public:
|
||||
static wxString get_url(const std::string& path_end);
|
||||
static bool launch_browser(const std::string& path_end);
|
||||
};
|
||||
|
||||
class ConfigOptionsGroup: public OptionsGroup {
|
||||
@ -239,17 +243,17 @@ public:
|
||||
void set_config_category_and_type(const wxString &category, int type) { m_config_category = category; m_config_type = type; }
|
||||
void set_config(DynamicPrintConfig* config) { m_config = config; m_modelconfig = nullptr; }
|
||||
Option get_option(const std::string& opt_key, int opt_index = -1);
|
||||
Line create_single_option_line(const std::string& title, const wxString& path = wxEmptyString, int idx = -1) /*const*/{
|
||||
Line create_single_option_line(const std::string& title, const std::string& path = std::string(), int idx = -1) /*const*/{
|
||||
Option option = get_option(title, idx);
|
||||
return OptionsGroup::create_single_option_line(option, path);
|
||||
}
|
||||
Line create_single_option_line(const Option& option, const wxString& path = wxEmptyString) const {
|
||||
Line create_single_option_line(const Option& option, const std::string& path = std::string()) const {
|
||||
return OptionsGroup::create_single_option_line(option, path);
|
||||
}
|
||||
void append_single_option_line(const Option& option, const wxString& path = wxEmptyString) {
|
||||
void append_single_option_line(const Option& option, const std::string& path = std::string()) {
|
||||
OptionsGroup::append_single_option_line(option, path);
|
||||
}
|
||||
void append_single_option_line(const std::string title, const wxString& path = wxEmptyString, int idx = -1)
|
||||
void append_single_option_line(const std::string title, const std::string& path = std::string(), int idx = -1)
|
||||
{
|
||||
Option option = get_option(title, idx);
|
||||
append_single_option_line(option, path);
|
||||
@ -298,6 +302,9 @@ public:
|
||||
~ogStaticText() {}
|
||||
|
||||
void SetText(const wxString& value, bool wrap = true);
|
||||
// Set special path end. It will be used to generation of the hyperlink on info page
|
||||
void SetPathEnd(const std::string& link);
|
||||
void FocusText(bool focus);
|
||||
};
|
||||
|
||||
}}
|
||||
|
@ -141,11 +141,10 @@ bool Plater::has_illegal_filename_characters(const std::string& name)
|
||||
|
||||
void Plater::show_illegal_characters_warning(wxWindow* parent)
|
||||
{
|
||||
show_error(parent, _L("The supplied name is not valid;") + "\n" +
|
||||
show_error(parent, _L("The provided name is not valid;") + "\n" +
|
||||
_L("the following characters are not allowed:") + " <>:/\\|?*\"");
|
||||
}
|
||||
|
||||
|
||||
// Sidebar widgets
|
||||
|
||||
// struct InfoBox : public wxStaticBox
|
||||
@ -239,6 +238,7 @@ void ObjectInfo::show_sizer(bool show)
|
||||
void ObjectInfo::msw_rescale()
|
||||
{
|
||||
manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name));
|
||||
info_icon->SetBitmap(create_scaled_bitmap("info"));
|
||||
}
|
||||
|
||||
void ObjectInfo::update_warning_icon(const std::string& warning_icon_name)
|
||||
@ -1137,6 +1137,7 @@ void Sidebar::sys_color_changed()
|
||||
|
||||
for (wxWindow* win : std::vector<wxWindow*>{ this, p->sliced_info->GetStaticBox(), p->object_info->GetStaticBox(), p->btn_reslice, p->btn_export_gcode })
|
||||
wxGetApp().UpdateDarkUI(win);
|
||||
p->object_info->msw_rescale();
|
||||
for (wxWindow* win : std::vector<wxWindow*>{ p->scrolled, p->presets_panel })
|
||||
wxGetApp().UpdateAllStaticTextDarkUI(win);
|
||||
for (wxWindow* btn : std::vector<wxWindow*>{ p->btn_reslice, p->btn_export_gcode })
|
||||
@ -2225,6 +2226,7 @@ Plater::priv::~priv()
|
||||
{
|
||||
if (config != nullptr)
|
||||
delete config;
|
||||
// Saves the database of visited (already shown) hints into hints.ini.
|
||||
notification_manager->deactivate_loaded_hints();
|
||||
}
|
||||
|
||||
@ -2744,16 +2746,17 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
|
||||
_L("Object too large?"));
|
||||
}
|
||||
|
||||
// Now ObjectList uses GLCanvas3D::is_object_sinkin() to show/hide "Sinking" InfoItem,
|
||||
// so 3D-scene should be updated before object additing to the ObjectList
|
||||
this->view3D->reload_scene(false, (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH);
|
||||
|
||||
notification_manager->close_notification_of_type(NotificationType::UpdatedItemsInfo);
|
||||
for (const size_t idx : obj_idxs) {
|
||||
wxGetApp().obj_list()->add_object_to_list(idx);
|
||||
}
|
||||
|
||||
update();
|
||||
// Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(),
|
||||
// which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call
|
||||
for (const size_t idx : obj_idxs)
|
||||
wxGetApp().obj_list()->update_info_items(idx);
|
||||
|
||||
object_list_changed();
|
||||
|
||||
this->schedule_background_process();
|
||||
@ -5664,10 +5667,15 @@ void Plater::export_gcode(bool prefer_removable)
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
output_path = into_path(dlg.GetPath());
|
||||
while (has_illegal_filename_characters(output_path.filename().string())) {
|
||||
show_illegal_characters_warning(this);
|
||||
show_error(this, _L("The provided file name is not valid.") + "\n" +
|
||||
_L("The following characters are not allowed by a FAT file system:") + " <>:/\\|?*\"");
|
||||
dlg.SetFilename(from_path(output_path.filename()));
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
output_path = into_path(dlg.GetPath());
|
||||
else {
|
||||
output_path.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1432,7 +1432,7 @@ void TabPrint::build()
|
||||
load_initial_data();
|
||||
|
||||
auto page = add_options_page(L("Layers and perimeters"), "layers");
|
||||
wxString category_path = "layers-and-perimeters_1748#";
|
||||
std::string category_path = "layers-and-perimeters_1748#";
|
||||
auto optgroup = page->new_optgroup(L("Layer height"));
|
||||
optgroup->append_single_option_line("layer_height", category_path + "layer-height");
|
||||
optgroup->append_single_option_line("first_layer_height", category_path + "first-layer-height");
|
||||
@ -1673,6 +1673,12 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
optgroup = page->new_optgroup(L("Post-processing scripts"), 0);
|
||||
line = { "", "" };
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
return description_line_widget(parent, &m_post_process_explanation);
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
option = optgroup->get_option("post_process");
|
||||
option.opt.full_width = true;
|
||||
option.opt.height = 5;//50;
|
||||
@ -1688,7 +1694,7 @@ void TabPrint::build()
|
||||
page = add_options_page(L("Dependencies"), "wrench.png");
|
||||
optgroup = page->new_optgroup(L("Profile dependencies"));
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_printers);
|
||||
});
|
||||
|
||||
@ -1721,6 +1727,12 @@ void TabPrint::update_description_lines()
|
||||
m_top_bottom_shell_thickness_explanation->SetText(
|
||||
from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle)));
|
||||
}
|
||||
|
||||
if (m_active_page && m_active_page->title() == "Output options" && m_post_process_explanation) {
|
||||
m_post_process_explanation->SetText(
|
||||
_u8L("Post processing scripts shall modify G-code file in place."));
|
||||
m_post_process_explanation->SetPathEnd("post-processing-scripts_283913");
|
||||
}
|
||||
}
|
||||
|
||||
void TabPrint::toggle_options()
|
||||
@ -1774,6 +1786,7 @@ void TabPrint::clear_pages()
|
||||
|
||||
m_recommended_thin_wall_thickness_description_line = nullptr;
|
||||
m_top_bottom_shell_thickness_explanation = nullptr;
|
||||
m_post_process_explanation = nullptr;
|
||||
}
|
||||
|
||||
bool Tab::validate_custom_gcode(const wxString& title, const std::string& gcode)
|
||||
@ -1938,7 +1951,7 @@ void TabFilament::build()
|
||||
optgroup->append_line(line);
|
||||
|
||||
page = add_options_page(L("Cooling"), "cooling");
|
||||
wxString category_path = "cooling_127569#";
|
||||
std::string category_path = "cooling_127569#";
|
||||
optgroup = page->new_optgroup(L("Enable"));
|
||||
optgroup->append_single_option_line("fan_always_on");
|
||||
optgroup->append_single_option_line("cooling");
|
||||
@ -1999,7 +2012,7 @@ void TabFilament::build()
|
||||
optgroup->append_single_option_line("filament_cooling_initial_speed");
|
||||
optgroup->append_single_option_line("filament_cooling_final_speed");
|
||||
|
||||
create_line_with_widget(optgroup.get(), "filament_ramming_parameters", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "filament_ramming_parameters", "", [this](wxWindow* parent) {
|
||||
auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
|
||||
wxGetApp().UpdateDarkUI(ramming_dialog_btn);
|
||||
ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
@ -2055,7 +2068,7 @@ void TabFilament::build()
|
||||
|
||||
page = add_options_page(L("Dependencies"), "wrench.png");
|
||||
optgroup = page->new_optgroup(L("Profile dependencies"));
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_printers);
|
||||
});
|
||||
|
||||
@ -2063,7 +2076,7 @@ void TabFilament::build()
|
||||
option.opt.full_width = true;
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_prints", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_prints", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_prints);
|
||||
});
|
||||
|
||||
@ -2480,8 +2493,7 @@ void TabPrinter::build_sla()
|
||||
|
||||
optgroup = page->new_optgroup(L("Corrections"));
|
||||
line = Line{ m_config->def()->get("relative_correction")->full_label, "" };
|
||||
std::vector<std::string> axes{ "X", "Y", "Z" };
|
||||
for (auto& axis : axes) {
|
||||
for (auto& axis : { "X", "Y", "Z" }) {
|
||||
auto opt = optgroup->get_option(std::string("relative_correction_") + char(std::tolower(axis[0])));
|
||||
opt.opt.label = axis;
|
||||
line.append_option(opt);
|
||||
@ -2590,7 +2602,7 @@ PageShp TabPrinter::build_kinematics_page()
|
||||
optgroup->append_line(line);
|
||||
}
|
||||
|
||||
std::vector<std::string> axes{ "x", "y", "z", "e" };
|
||||
const std::vector<std::string> axes{ "x", "y", "z", "e" };
|
||||
optgroup = page->new_optgroup(L("Maximum feedrates"));
|
||||
for (const std::string &axis : axes) {
|
||||
append_option_line(optgroup, "machine_max_feedrate_" + axis);
|
||||
@ -2695,7 +2707,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/)
|
||||
m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page);
|
||||
|
||||
auto optgroup = page->new_optgroup(L("Size"));
|
||||
optgroup->append_single_option_line("nozzle_diameter", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("nozzle_diameter", "", extruder_idx);
|
||||
|
||||
optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value)
|
||||
{
|
||||
@ -2734,32 +2746,32 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/)
|
||||
};
|
||||
|
||||
optgroup = page->new_optgroup(L("Layer height limits"));
|
||||
optgroup->append_single_option_line("min_layer_height", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("max_layer_height", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("min_layer_height", "", extruder_idx);
|
||||
optgroup->append_single_option_line("max_layer_height", "", extruder_idx);
|
||||
|
||||
|
||||
optgroup = page->new_optgroup(L("Position (for multi-extruder printers)"));
|
||||
optgroup->append_single_option_line("extruder_offset", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("extruder_offset", "", extruder_idx);
|
||||
|
||||
optgroup = page->new_optgroup(L("Retraction"));
|
||||
optgroup->append_single_option_line("retract_length", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_lift", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_length", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_lift", "", extruder_idx);
|
||||
Line line = { L("Only lift Z"), "" };
|
||||
line.append_option(optgroup->get_option("retract_lift_above", extruder_idx));
|
||||
line.append_option(optgroup->get_option("retract_lift_below", extruder_idx));
|
||||
optgroup->append_line(line);
|
||||
|
||||
optgroup->append_single_option_line("retract_speed", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("deretract_speed", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_restart_extra", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_before_travel", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_layer_change", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("wipe", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_before_wipe", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_speed", "", extruder_idx);
|
||||
optgroup->append_single_option_line("deretract_speed", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_restart_extra", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_before_travel", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_layer_change", "", extruder_idx);
|
||||
optgroup->append_single_option_line("wipe", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_before_wipe", "", extruder_idx);
|
||||
|
||||
optgroup = page->new_optgroup(L("Retraction when tool is disabled (advanced settings for multi-extruder setups)"));
|
||||
optgroup->append_single_option_line("retract_length_toolchange", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_restart_extra_toolchange", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_length_toolchange", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_restart_extra_toolchange", "", extruder_idx);
|
||||
|
||||
optgroup = page->new_optgroup(L("Preview"));
|
||||
|
||||
@ -2787,7 +2799,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/)
|
||||
|
||||
return sizer;
|
||||
};
|
||||
line = optgroup->create_single_option_line("extruder_colour", wxEmptyString, extruder_idx);
|
||||
line = optgroup->create_single_option_line("extruder_colour", "", extruder_idx);
|
||||
line.append_widget(reset_to_filament_color);
|
||||
optgroup->append_line(line);
|
||||
}
|
||||
@ -3736,7 +3748,7 @@ void Tab::update_ui_from_settings()
|
||||
}
|
||||
}
|
||||
|
||||
void Tab::create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const wxString& path, widget_t widget)
|
||||
void Tab::create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const std::string& path, widget_t widget)
|
||||
{
|
||||
Line line = optgroup->create_single_option_line(opt_key);
|
||||
line.widget = widget;
|
||||
@ -4204,8 +4216,7 @@ void TabSLAMaterial::build()
|
||||
|
||||
optgroup = page->new_optgroup(L("Corrections"));
|
||||
auto line = Line{ m_config->def()->get("material_correction")->full_label, "" };
|
||||
std::vector<std::string> axes{ "X", "Y", "Z" };
|
||||
for (auto& axis : axes) {
|
||||
for (auto& axis : { "X", "Y", "Z" }) {
|
||||
auto opt = optgroup->get_option(std::string("material_correction_") + char(std::tolower(axis[0])));
|
||||
opt.opt.label = axis;
|
||||
line.append_option(opt);
|
||||
@ -4224,7 +4235,7 @@ void TabSLAMaterial::build()
|
||||
page = add_options_page(L("Dependencies"), "wrench.png");
|
||||
optgroup = page->new_optgroup(L("Profile dependencies"));
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_printers);
|
||||
});
|
||||
|
||||
@ -4232,7 +4243,7 @@ void TabSLAMaterial::build()
|
||||
option.opt.full_width = true;
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_prints", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_prints", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_prints);
|
||||
});
|
||||
|
||||
@ -4371,7 +4382,7 @@ void TabSLAPrint::build()
|
||||
page = add_options_page(L("Dependencies"), "wrench");
|
||||
optgroup = page->new_optgroup(L("Profile dependencies"));
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_printers);
|
||||
});
|
||||
|
||||
|
@ -351,7 +351,7 @@ public:
|
||||
bool validate_custom_gcodes_was_shown{ false };
|
||||
|
||||
protected:
|
||||
void create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const wxString& path, widget_t widget);
|
||||
void create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const std::string& path, widget_t widget);
|
||||
wxSizer* compatible_widget_create(wxWindow* parent, PresetDependencies &deps);
|
||||
void compatible_widget_reload(PresetDependencies &deps);
|
||||
void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false);
|
||||
@ -387,6 +387,7 @@ public:
|
||||
private:
|
||||
ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr;
|
||||
ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr;
|
||||
ogStaticText* m_post_process_explanation = nullptr;
|
||||
};
|
||||
|
||||
class TabFilament : public Tab
|
||||
|
@ -5,13 +5,15 @@
|
||||
#include <exception>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <wx/progdlg.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "Http.hpp"
|
||||
@ -24,9 +26,16 @@ namespace pt = boost::property_tree;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef WIN32
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
namespace {
|
||||
std::string substitute_host(const std::string& orig_addr, const std::string sub_addr)
|
||||
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
|
||||
{
|
||||
// put ipv6 into [] brackets
|
||||
if (sub_addr.find(':') != std::string::npos && sub_addr.at(0) != '[')
|
||||
sub_addr = "[" + sub_addr + "]";
|
||||
|
||||
#if 0
|
||||
//URI = scheme ":"["//"[userinfo "@"] host [":" port]] path["?" query]["#" fragment]
|
||||
std::string final_addr = orig_addr;
|
||||
// http
|
||||
@ -35,9 +44,16 @@ std::string substitute_host(const std::string& orig_addr, const std::string sub_
|
||||
// userinfo
|
||||
size_t at = orig_addr.find("@");
|
||||
host_start = (at != std::string::npos && at > host_start ? at + 1 : host_start);
|
||||
// end of host, could be port, subpath (could be query or fragment?)
|
||||
size_t host_end = orig_addr.find_first_of(":/?#", host_start);
|
||||
host_end = (host_end == std::string::npos ? orig_addr.length() : host_end);
|
||||
// end of host, could be port(:), subpath(/) (could be query(?) or fragment(#)?)
|
||||
// or it will be ']' if address is ipv6 )
|
||||
size_t potencial_host_end = orig_addr.find_first_of(":/", host_start);
|
||||
// if there are more ':' it must be ipv6
|
||||
if (potencial_host_end != std::string::npos && orig_addr[potencial_host_end] == ':' && orig_addr.rfind(':') != potencial_host_end) {
|
||||
size_t ipv6_end = orig_addr.find(']', host_start);
|
||||
// DK: Uncomment and replace orig_addr.length() if we want to allow subpath after ipv6 without [] parentheses.
|
||||
potencial_host_end = (ipv6_end != std::string::npos ? ipv6_end + 1 : orig_addr.length()); //orig_addr.find('/', host_start));
|
||||
}
|
||||
size_t host_end = (potencial_host_end != std::string::npos ? potencial_host_end : orig_addr.length());
|
||||
// now host_start and host_end should mark where to put resolved addr
|
||||
// check host_start. if its nonsense, lets just use original addr (or resolved addr?)
|
||||
if (host_start >= orig_addr.length()) {
|
||||
@ -45,8 +61,38 @@ std::string substitute_host(const std::string& orig_addr, const std::string sub_
|
||||
}
|
||||
final_addr.replace(host_start, host_end - host_start, sub_addr);
|
||||
return final_addr;
|
||||
#else
|
||||
// Using the new CURL API for handling URL. https://everything.curl.dev/libcurl/url
|
||||
// If anything fails, return the input unchanged.
|
||||
std::string out = orig_addr;
|
||||
CURLU *hurl = curl_url();
|
||||
if (hurl) {
|
||||
// Parse the input URL.
|
||||
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, orig_addr.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Replace the address.
|
||||
rc = curl_url_set(hurl, CURLUPART_HOST, sub_addr.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Extract a string fromt the CURL URL handle.
|
||||
char *url;
|
||||
rc = curl_url_get(hurl, CURLUPART_URL, &url, 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
out = url;
|
||||
curl_free(url);
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to extract the URL after substitution";
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to substitute host " << sub_addr << " in URL " << orig_addr;
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to parse URL " << orig_addr;
|
||||
curl_url_cleanup(hurl);
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to allocate curl_url";
|
||||
return out;
|
||||
#endif
|
||||
}
|
||||
} //namespace
|
||||
#endif // WIN32
|
||||
|
||||
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
|
||||
m_host(config->opt_string("print_host")),
|
||||
@ -103,9 +149,11 @@ bool OctoPrint::test(wxString &msg) const
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.on_ip_resolve([&](std::string address) {
|
||||
msg = boost::nowide::widen(address);
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Remember resolved address to be reused at successive REST API call.
|
||||
msg = GUI::from_u8(address);
|
||||
})
|
||||
#endif
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
@ -131,35 +179,38 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
|
||||
const auto upload_filename = upload_data.upload_path.filename();
|
||||
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||
|
||||
wxString test_msg;
|
||||
if (! test(test_msg)) {
|
||||
error_fn(std::move(test_msg));
|
||||
// If test fails, test_msg_or_host_ip contains the error message.
|
||||
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||
wxString test_msg_or_host_ip;
|
||||
if (! test(test_msg_or_host_ip)) {
|
||||
error_fn(std::move(test_msg_or_host_ip));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url;
|
||||
bool res = true;
|
||||
|
||||
bool allow_ip_resolve = GUI::get_app_config()->get("allow_ip_resolve") == "1";
|
||||
|
||||
if (m_host.find("https://") == 0 || test_msg.empty() || !allow_ip_resolve) {
|
||||
#ifdef WIN32
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || GUI::get_app_config()->get("allow_ip_resolve") != "1")
|
||||
#endif // _WIN32
|
||||
{
|
||||
// If https is entered we assume signed ceritificate is being used
|
||||
// IP resolving will not happen - it could resolve into address not being specified in cert
|
||||
url = make_url("api/files/local");
|
||||
} else {
|
||||
}
|
||||
#ifdef WIN32
|
||||
else {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Curl uses easy_getinfo to get ip address of last successful transaction.
|
||||
// If it got the address use it instead of the stored in "host" variable.
|
||||
// This new address returns in "test_msg" variable.
|
||||
// This new address returns in "test_msg_or_host_ip" variable.
|
||||
// Solves troubles of uploades failing with name address.
|
||||
std::string resolved_addr = boost::nowide::narrow(test_msg);
|
||||
// put ipv6 into [] brackets
|
||||
if (resolved_addr.find(':') != std::string::npos && resolved_addr.at(0) != '[')
|
||||
resolved_addr = "[" + resolved_addr + "]";
|
||||
// in original address (m_host) replace host for resolved ip
|
||||
std::string final_addr = substitute_host(m_host, resolved_addr);
|
||||
BOOST_LOG_TRIVIAL(debug) << "Upload address after ip resolve: " << final_addr;
|
||||
url = make_url("api/files/local", final_addr);
|
||||
url = substitute_host(make_url("api/files/local"), GUI::into_u8(test_msg_or_host_ip));
|
||||
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
|
||||
% name
|
||||
@ -225,21 +276,6 @@ std::string OctoPrint::make_url(const std::string &path) const
|
||||
}
|
||||
}
|
||||
|
||||
std::string OctoPrint::make_url(const std::string& path, const std::string& addr) const
|
||||
{
|
||||
std::string hst = addr.empty() ? m_host : addr;
|
||||
if (hst.find("http://") == 0 || hst.find("https://") == 0) {
|
||||
if (hst.back() == '/') {
|
||||
return (boost::format("%1%%2%") % hst % path).str();
|
||||
}
|
||||
else {
|
||||
return (boost::format("%1%/%2%") % hst % path).str();
|
||||
}
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%") % hst % path).str();
|
||||
}
|
||||
}
|
||||
|
||||
SL1Host::SL1Host(DynamicPrintConfig *config) :
|
||||
OctoPrint(config),
|
||||
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||
|
@ -44,7 +44,6 @@ private:
|
||||
|
||||
virtual void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
std::string make_url(const std::string& path, const std::string& addr) const;
|
||||
};
|
||||
|
||||
class SL1Host: public OctoPrint
|
||||
|
@ -954,4 +954,9 @@ void PresetUpdater::on_update_notification_confirm()
|
||||
}
|
||||
}
|
||||
|
||||
bool PresetUpdater::version_check_enabled() const
|
||||
{
|
||||
return p->enabled_version_check;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,6 +57,9 @@ public:
|
||||
bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
|
||||
void on_update_notification_confirm();
|
||||
|
||||
bool version_check_enabled() const;
|
||||
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
|
@ -13,6 +13,8 @@ namespace Utils {
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
// Generic command / response TCP telnet like console class.
|
||||
// Used by the MKS host to send G-code commands to test connection ("M105") and to start printing ("M23 filename", "M24").
|
||||
class TCPConsole
|
||||
{
|
||||
public:
|
||||
|
Loading…
x
Reference in New Issue
Block a user