mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-09 08:41:14 +00:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaab2d6d8b | ||
|
|
018c4f49ee | ||
|
|
4c74d84b94 | ||
|
|
65978fc87a | ||
|
|
9a9285a894 | ||
|
|
ae59656fa4 | ||
|
|
627ae12dd3 | ||
|
|
b3490a1cdc | ||
|
|
c7e7fa2a0b | ||
|
|
16380e8560 | ||
|
|
785a1946a6 | ||
|
|
8790b07773 | ||
|
|
1ec9b315f5 | ||
|
|
e6be22dd4c | ||
|
|
ee2c40ea85 | ||
|
|
3baacc6b75 | ||
|
|
1a0af2fe57 | ||
|
|
c017629d11 |
+2
-2
@@ -1084,10 +1084,10 @@ endif ()
|
||||
|
||||
find_path(SPNAV_INCLUDE_DIR spnav.h)
|
||||
if (SPNAV_INCLUDE_DIR)
|
||||
find_library(SPNAV_LIB NAMES libspnav.a) # Force linking libspnav statically
|
||||
find_library(SPNAV_LIB NAMES libspnav.a spnav)
|
||||
if (SPNAV_LIB)
|
||||
add_definitions(-DHAVE_SPNAV)
|
||||
message(STATUS "SPNAV library found")
|
||||
message(STATUS "SPNAV library found: ${SPNAV_LIB}")
|
||||
else()
|
||||
message(STATUS "SPNAV library NOT found, Spacenavd not supported")
|
||||
endif()
|
||||
|
||||
@@ -104,8 +104,8 @@ fi
|
||||
|
||||
CMAKE_VERSION=$(cmake --version | head -1 | sed 's/[^0-9]*\([0-9]*\).*/\1/')
|
||||
if [ "$CMAKE_VERSION" -ge 4 ] 2>/dev/null; then
|
||||
export CMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
export CMAKE_POLICY_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
|
||||
export CMAKE_POLICY_VERSION_MINIMUM=3.10
|
||||
export CMAKE_POLICY_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.10"
|
||||
echo "Detected CMake 4.x, adding compatibility flag (env + cmake arg)"
|
||||
else
|
||||
export CMAKE_POLICY_COMPAT=""
|
||||
|
||||
Vendored
+4
-4
@@ -1,5 +1,5 @@
|
||||
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "4.0")
|
||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "" FORCE)
|
||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.10 CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
#
|
||||
@@ -24,7 +24,7 @@ endif()
|
||||
# therefore, unfortunately, the installation cannot be copied/moved elsewhere without re-installing wxWidgets.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
if (APPLE)
|
||||
# if CMAKE_OSX_DEPLOYMENT_TARGET is not set, set it to 12.0 (the lowest Xcode 27 accepts)
|
||||
if (NOT CMAKE_OSX_DEPLOYMENT_TARGET)
|
||||
@@ -224,7 +224,7 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE)
|
||||
${_source_dir_arg}
|
||||
${_gen}
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.10
|
||||
-DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR}
|
||||
-DCMAKE_MODULE_PATH:STRING=${PROJECT_SOURCE_DIR}/../cmake/modules
|
||||
-DCMAKE_PREFIX_PATH:STRING=${DESTDIR}
|
||||
@@ -279,7 +279,7 @@ else()
|
||||
${_source_dir_arg}
|
||||
${_gen}
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.10
|
||||
-DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR}
|
||||
-DCMAKE_PREFIX_PATH:STRING=${DESTDIR}
|
||||
-DCMAKE_IGNORE_PREFIX_PATH:STRING=${CMAKE_IGNORE_PREFIX_PATH}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
diff --git a/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx b/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx
|
||||
index 6f63781..9b1c08e 100644
|
||||
--- a/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx
|
||||
+++ b/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx
|
||||
@@ -210,10 +210,10 @@ private:
|
||||
|
||||
// Define two pcurves of the seam-edge.
|
||||
occ::handle<Geom2d_Curve> aPC1, aPC2;
|
||||
- double af, al;
|
||||
+ double af, al, af1, al1;
|
||||
|
||||
aE.Orientation(TopAbs_FORWARD);
|
||||
- aPC1 = BRep_Tool::CurveOnSurface(aE, aF, af, al);
|
||||
+ aPC1 = BRep_Tool::CurveOnSurface(aE, aF, af1, al1);
|
||||
|
||||
aE.Orientation(TopAbs_REVERSED);
|
||||
aPC2 = BRep_Tool::CurveOnSurface(aE, aF, af, al);
|
||||
@@ -224,7 +224,9 @@ private:
|
||||
}
|
||||
|
||||
// Select the correct pcurve of the seam-edge.
|
||||
- const gp_Pnt2d& aFPntOfPC1 = aPC1->Value(aPC1->FirstParameter());
|
||||
+ // Use the edge's first parameter. A Geom2d_Line's FirstParameter() is -Precision::Infinite(),
|
||||
+ // where a direction of (2e-16, -1) from rounding error gives an X far outside the U range.
|
||||
+ const gp_Pnt2d aFPntOfPC1 = aPC1->Value(af1);
|
||||
|
||||
if (std::abs(aLPntOfIPC1.X() - aFPntOfPC1.X()) > Precision::Confusion())
|
||||
{
|
||||
Vendored
+7
@@ -25,9 +25,16 @@ endif()
|
||||
# shipped bytes. Windows ships only the DLLs libslic3r links, so the tab adds the TKFillet,
|
||||
# TKOffset and TKBool DLLs. See docs/HLSD/design-tab.md.
|
||||
|
||||
if (IN_GIT_REPO)
|
||||
set(OCCT_DIRECTORY_FLAG --directory ${BINARY_DIR_REL}/dep_OCCT-prefix/src/dep_OCCT)
|
||||
endif ()
|
||||
|
||||
orcaslicer_add_cmake_project(OCCT
|
||||
URL https://github.com/Open-Cascade-SAS/OCCT/archive/refs/tags/V8_0_1.zip
|
||||
URL_HASH SHA256=7c033d917ee8f040c0512d289dcc5f02c148889d5bac17c3e25639accb44f0da
|
||||
# Makes BRepMesh triangulate cone faces whose seam pcurve is slightly tilted
|
||||
# (Open-Cascade-SAS/OCCT#572); remove the patch once an OCCT release includes the fix.
|
||||
PATCH_COMMAND git apply ${OCCT_DIRECTORY_FLAG} --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-BRepMesh-seam-pcurve-at-edge-parameter.patch
|
||||
#DEPENDS dep_Boost
|
||||
DEPENDS ${FREETYPE_PKG}
|
||||
CMAKE_ARGS
|
||||
|
||||
@@ -79,6 +79,13 @@ the same `infill_body()`. The octree of the whole object is built only for an
|
||||
object of a single body, or when some body received no triangles, which then
|
||||
uses it.
|
||||
|
||||
The line spacing of an octree comes from the density, line width and multiline
|
||||
count of a region, so a modifier or a part with its own density needs octrees of
|
||||
its own. `adaptive_fill_line_spacing()` gives the spacing of each region, and
|
||||
`FillAdaptive::RegionOctrees` holds one set of octrees per distinct spacing,
|
||||
shared by the regions that have it. A set is built only for the bodies its
|
||||
regions fill. The fill takes the set of its region, then the octree of its body.
|
||||
|
||||
## Patterns left out
|
||||
|
||||
Lightning grows its trees over the whole object, so moving a reference point
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -46,7 +46,7 @@ RUN set -eux; \
|
||||
tar -xzf /tmp/assimp.tar.gz -C /tmp/assimp-src --strip-components=1; \
|
||||
DESTDIR=/OrcaSlicer/deps/build/destdir/usr/local; \
|
||||
cmake -S /tmp/assimp-src -B /tmp/assimp-build -G Ninja \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.10 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="$DESTDIR" \
|
||||
-DCMAKE_PREFIX_PATH="$DESTDIR" \
|
||||
|
||||
+6
-2
@@ -1820,6 +1820,8 @@ int CLI::run(int argc, char **argv)
|
||||
old_printable_width = static_cast<int>(old_printable_bbox.size().x());
|
||||
old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
|
||||
}
|
||||
// A 3mf can carry an empty project_settings.config - the models in
|
||||
// resources/handy_models do - and opt_float() dereferences without checking.
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
old_printable_height = (int)(config.opt_float("printable_height"));
|
||||
|
||||
@@ -2505,7 +2507,8 @@ int CLI::run(int argc, char **argv)
|
||||
orig_printable_width = static_cast<int>(orig_printable_bbox.size().x());
|
||||
orig_printable_depth = static_cast<int>(orig_printable_bbox.size().y());
|
||||
}
|
||||
orig_printable_height = (int)(config.opt_float("printable_height"));
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
orig_printable_height = (int)(config.opt_float("printable_height"));
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(":%1%, check printable size: old_printable_width=%2%, orig_printable_width=%3%, old_printable_depth=%4%, orig_printable_depth=%5%, old_printable_height=%6%, orig_printable_height=%7%")
|
||||
%__LINE__ %old_printable_width %orig_printable_width %old_printable_depth %orig_printable_depth %old_printable_height %orig_printable_height;
|
||||
if ((orig_printable_width > 0) && (orig_printable_depth > 0) && (orig_printable_height > 0))
|
||||
@@ -4617,7 +4620,8 @@ int CLI::run(int argc, char **argv)
|
||||
BoundingBoxf temp_printable_bbox(temp_printable_area);
|
||||
printer_plate.printable_width = static_cast<int>(temp_printable_bbox.size().x());
|
||||
printer_plate.printable_depth = static_cast<int>(temp_printable_bbox.size().y());
|
||||
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
|
||||
}
|
||||
if (temp_exclude_area.size() >= 4) {
|
||||
printer_plate.exclude_width = (int)(temp_exclude_area[2].x() - temp_exclude_area[0].x());
|
||||
|
||||
@@ -303,10 +303,11 @@ else
|
||||
fi
|
||||
|
||||
has_host_runtime_library() {
|
||||
local lib_name path
|
||||
local lib_name="\$1"
|
||||
local path
|
||||
|
||||
if command -v ldconfig >/dev/null 2>&1; then
|
||||
if ldconfig -p 2>/dev/null | grep -Fq " \$lib_name"; then
|
||||
if ldconfig -p 2>/dev/null | grep -Fq "\$lib_name ("; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1320,7 +1320,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
|
||||
#endif
|
||||
|
||||
// friend to Layer
|
||||
void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator)
|
||||
void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator)
|
||||
{
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
layerm->fills.clear();
|
||||
@@ -1353,7 +1353,7 @@ void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
f->fixed_angle = surface_fill.params.fixed_angle;
|
||||
const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees;
|
||||
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
|
||||
f->print_config = &this->object()->print()->config();
|
||||
f->print_object_config = &this->object()->config();
|
||||
if (surface_fill.params.pattern == ipConcentricInternal) {
|
||||
@@ -1516,7 +1516,7 @@ void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const
|
||||
* - For lightning/adaptive patterns, the respective generators are wired so their
|
||||
* polylines match the final infill layout.
|
||||
*/
|
||||
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator) const
|
||||
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator) const
|
||||
{
|
||||
LockRegionParam skin_inner_param;
|
||||
std::vector<SurfaceFill> surface_fills = group_fills(*this, skin_inner_param);
|
||||
@@ -1574,7 +1574,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
f->fixed_angle = surface_fill.params.fixed_angle;
|
||||
const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees;
|
||||
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
|
||||
f->print_config = &this->object()->print()->config();
|
||||
f->print_object_config = &this->object()->config();
|
||||
|
||||
|
||||
@@ -301,88 +301,25 @@ void OctreeDeleter::operator()(Octree *p) {
|
||||
delete p;
|
||||
}
|
||||
|
||||
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object)
|
||||
std::vector<double> adaptive_fill_line_spacing(const PrintObject &print_object)
|
||||
{
|
||||
// Output, spacing for icAdaptiveCubic and icSupportCubic
|
||||
double adaptive_line_spacing = 0.;
|
||||
double support_line_spacing = 0.;
|
||||
|
||||
enum class Tristate {
|
||||
Yes,
|
||||
No,
|
||||
Maybe
|
||||
};
|
||||
struct RegionFillData {
|
||||
Tristate has_adaptive_infill;
|
||||
Tristate has_support_infill;
|
||||
double density;
|
||||
double extrusion_width;
|
||||
};
|
||||
std::vector<RegionFillData> region_fill_data;
|
||||
region_fill_data.reserve(print_object.num_printing_regions());
|
||||
bool build_octree = false;
|
||||
std::vector<double> line_spacing(print_object.num_printing_regions(), 0.);
|
||||
const std::vector<double> &nozzle_diameters = print_object.print()->config().nozzle_diameter.values;
|
||||
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
|
||||
double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
|
||||
for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) {
|
||||
const PrintRegionConfig &config = print_object.printing_region(region_id).config();
|
||||
bool nonempty = config.sparse_infill_density > 0;
|
||||
bool has_adaptive_infill = nonempty && config.sparse_infill_pattern == ipAdaptiveCubic;
|
||||
bool has_support_infill = nonempty && config.sparse_infill_pattern == ipSupportCubic;
|
||||
double sparse_infill_line_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter);
|
||||
region_fill_data.push_back(RegionFillData({
|
||||
has_adaptive_infill ? Tristate::Maybe : Tristate::No,
|
||||
has_support_infill ? Tristate::Maybe : Tristate::No,
|
||||
config.sparse_infill_density,
|
||||
sparse_infill_line_width != 0. ? sparse_infill_line_width : default_infill_extrusion_width
|
||||
}));
|
||||
build_octree |= has_adaptive_infill || has_support_infill;
|
||||
for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id) {
|
||||
const PrintRegionConfig &config = print_object.printing_region(region_id).config();
|
||||
if (config.sparse_infill_density <= 0 || ! is_octree_infill_pattern(config.sparse_infill_pattern) ||
|
||||
std::none_of(print_object.layers().begin(), print_object.layers().end(), [region_id](const Layer *layer) {
|
||||
return region_id < layer->regions().size() && ! layer->regions()[region_id]->fill_surfaces.empty();
|
||||
}))
|
||||
continue;
|
||||
double extrusion_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter);
|
||||
if (extrusion_width == 0.)
|
||||
extrusion_width = default_infill_extrusion_width;
|
||||
line_spacing[region_id] = extrusion_width / ((config.sparse_infill_density / 100.0f) * 0.333333333f) * config.fill_multiline.value;
|
||||
}
|
||||
|
||||
if (build_octree) {
|
||||
// Compute the average of above parameters over all layers
|
||||
for (const Layer *layer : print_object.layers())
|
||||
for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) {
|
||||
RegionFillData &rd = region_fill_data[region_id];
|
||||
if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
|
||||
rd.has_adaptive_infill = Tristate::Yes;
|
||||
if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
|
||||
rd.has_support_infill = Tristate::Yes;
|
||||
}
|
||||
|
||||
double adaptive_fill_density = 0.;
|
||||
double adaptive_infill_extrusion_width = 0.;
|
||||
int adaptive_cnt = 0;
|
||||
double support_fill_density = 0.;
|
||||
double support_infill_extrusion_width = 0.;
|
||||
int support_cnt = 0;
|
||||
|
||||
for (const RegionFillData &rd : region_fill_data) {
|
||||
if (rd.has_adaptive_infill == Tristate::Yes) {
|
||||
adaptive_fill_density += rd.density;
|
||||
adaptive_infill_extrusion_width += rd.extrusion_width;
|
||||
++ adaptive_cnt;
|
||||
} else if (rd.has_support_infill == Tristate::Yes) {
|
||||
support_fill_density += rd.density;
|
||||
support_infill_extrusion_width += rd.extrusion_width;
|
||||
++ support_cnt;
|
||||
}
|
||||
}
|
||||
|
||||
auto to_line_spacing = [](int cnt, double density, double extrusion_width) {
|
||||
if (cnt) {
|
||||
density /= double(cnt);
|
||||
extrusion_width /= double(cnt);
|
||||
return extrusion_width / ((density / 100.0f) * 0.333333333f);
|
||||
} else
|
||||
return 0.;
|
||||
};
|
||||
const int n_multiline = print_object.printing_region(0).config().fill_multiline.value;
|
||||
adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width) * n_multiline;
|
||||
support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width) * n_multiline;
|
||||
}
|
||||
|
||||
return std::make_pair(adaptive_line_spacing, support_line_spacing);
|
||||
return line_spacing;
|
||||
}
|
||||
|
||||
// Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion
|
||||
|
||||
@@ -59,11 +59,22 @@ struct Octrees
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate line spacing for
|
||||
// 1) adaptive cubic infill
|
||||
// 2) adaptive internal support cubic infill
|
||||
// Returns zero for a particular infill type if no such infill is to be generated.
|
||||
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object);
|
||||
// Orca: The octrees of each line spacing the regions of an object fill with.
|
||||
struct RegionOctrees
|
||||
{
|
||||
std::vector<Octrees> sets;
|
||||
// Index into sets for each region, -1 for a region without adaptive or support cubic infill.
|
||||
std::vector<int> region_set;
|
||||
|
||||
const Octrees *region(size_t region_id) const
|
||||
{
|
||||
return region_id < region_set.size() && region_set[region_id] >= 0 ? &sets[region_set[region_id]] : nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Line spacing of the adaptive or support cubic infill of each region of the object,
|
||||
// zero for a region that generates no such infill.
|
||||
std::vector<double> adaptive_fill_line_spacing(const PrintObject &print_object);
|
||||
|
||||
// Rotation of the octree to stand on one of its corners.
|
||||
Eigen::Quaterniond transform_to_world();
|
||||
|
||||
@@ -33,7 +33,7 @@ class PrintObject;
|
||||
class Print;
|
||||
|
||||
namespace FillAdaptive {
|
||||
struct Octrees;
|
||||
struct RegionOctrees;
|
||||
};
|
||||
|
||||
namespace FillLightning {
|
||||
@@ -207,10 +207,9 @@ public:
|
||||
static bool is_perimeter_compatible(const Print& print, const PrintRegion& a, const PrintRegion& b);
|
||||
void make_perimeters();
|
||||
// Phony version of make_fills() without parameters for Perl integration only.
|
||||
void make_fills() { this->make_fills(nullptr, nullptr); }
|
||||
void make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator = nullptr);
|
||||
Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees *adaptive_fill_octrees,
|
||||
const FillAdaptive::Octrees *support_fill_octrees,
|
||||
void make_fills() { this->make_fills(nullptr); }
|
||||
void make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator = nullptr);
|
||||
Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees *fill_octrees,
|
||||
FillLightning::Generator* lightning_generator) const;
|
||||
void make_ironing();
|
||||
// Returns the filament id (1-based) the region is ironed with, or -1 when the
|
||||
|
||||
@@ -1045,7 +1045,28 @@ void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& b
|
||||
// But we can force it to work by spliting the src mesh into disconnected components,
|
||||
// and do booleans seperately, then merge all the results.
|
||||
indexed_triangle_set all_its;
|
||||
if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") {
|
||||
if (boolean_opts == "A_NOT_B") {
|
||||
// Each cut can leave the source with several disconnected components, which mcut rejects
|
||||
// in the next dispatch, so re-split after every cut part (e.g. each letter of a text).
|
||||
std::vector<indexed_triangle_set> parts = std::move(src_parts);
|
||||
for (size_t j = 0; j < cut_parts.size(); j++) {
|
||||
auto cut_part = triangle_mesh_to_mcut(cut_parts[j]);
|
||||
std::vector<indexed_triangle_set> next_parts;
|
||||
for (indexed_triangle_set &part : parts) {
|
||||
auto src_part = triangle_mesh_to_mcut(part);
|
||||
if (do_boolean_single(*src_part, *cut_part, boolean_opts)) {
|
||||
TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part);
|
||||
std::vector<indexed_triangle_set> pieces = its_split(tri_part.its);
|
||||
std::move(pieces.begin(), pieces.end(), std::back_inserter(next_parts));
|
||||
} else
|
||||
next_parts.emplace_back(std::move(part));
|
||||
}
|
||||
parts = std::move(next_parts);
|
||||
}
|
||||
for (const indexed_triangle_set &part : parts)
|
||||
its_merge(all_its, part);
|
||||
}
|
||||
else if (boolean_opts == "UNION") {
|
||||
for (size_t i = 0; i < src_parts.size(); i++) {
|
||||
auto src_part = triangle_mesh_to_mcut(src_parts[i]);
|
||||
for (size_t j = 0; j < cut_parts.size(); j++) {
|
||||
|
||||
@@ -583,7 +583,7 @@ private:
|
||||
void discover_horizontal_shells();
|
||||
void combine_infill();
|
||||
void _generate_support_material();
|
||||
std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> prepare_adaptive_infill_data(
|
||||
FillAdaptive::RegionOctrees prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface*, const Layer*>>& surfaces_w_layer) const;
|
||||
FillLightning::GeneratorPtr prepare_lightning_infill_data();
|
||||
|
||||
@@ -616,7 +616,7 @@ private:
|
||||
// so that next call to make_perimeters() performs a union() before computing loops
|
||||
bool m_typed_slices = false;
|
||||
|
||||
std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> m_adaptive_fill_octrees;
|
||||
FillAdaptive::RegionOctrees m_adaptive_fill_octrees;
|
||||
std::vector<BoundingBox> m_separated_body_bboxes;
|
||||
FillLightning::GeneratorPtr m_lightning_generator;
|
||||
|
||||
|
||||
@@ -848,7 +848,7 @@ void PrintObject::infill()
|
||||
[this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
m_print->throw_if_canceled();
|
||||
m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees.first, &m_adaptive_fill_octrees.second, this->m_lightning_generator.get());
|
||||
m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees, this->m_lightning_generator.get());
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1168,14 +1168,27 @@ static std::vector<indexed_triangle_set> split_mesh_by_body(const PrintObject &o
|
||||
return bodies;
|
||||
}
|
||||
|
||||
std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> PrintObject::prepare_adaptive_infill_data(
|
||||
FillAdaptive::RegionOctrees PrintObject::prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface *, const Layer *>> &surfaces_w_layer) const
|
||||
{
|
||||
using namespace FillAdaptive;
|
||||
|
||||
auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this);
|
||||
if ((adaptive_line_spacing == 0. && support_line_spacing == 0.) || this->layers().empty())
|
||||
// Orca: Each region fills with the octrees of its own line spacing, shared by the regions of equal spacing.
|
||||
const std::vector<double> line_spacing = adaptive_fill_line_spacing(*this);
|
||||
std::vector<std::pair<double, bool>> spacings; // Line spacing, support cubic.
|
||||
RegionOctrees octrees;
|
||||
octrees.region_set.assign(line_spacing.size(), -1);
|
||||
for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id)
|
||||
if (line_spacing[region_id] > 0.) {
|
||||
const std::pair<double, bool> spacing(line_spacing[region_id], this->printing_region(region_id).config().sparse_infill_pattern == ipSupportCubic);
|
||||
const auto it = std::find(spacings.begin(), spacings.end(), spacing);
|
||||
octrees.region_set[region_id] = int(it - spacings.begin());
|
||||
if (it == spacings.end())
|
||||
spacings.push_back(spacing);
|
||||
}
|
||||
if (spacings.empty() || this->layers().empty())
|
||||
return {};
|
||||
octrees.sets.resize(spacings.size());
|
||||
|
||||
indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set();
|
||||
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes.
|
||||
@@ -1198,44 +1211,50 @@ std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> PrintObject::prepare_ada
|
||||
}
|
||||
});
|
||||
|
||||
// Orca: Each body gets the octree it has when sliced on its own, from its own triangles.
|
||||
std::pair<Octrees, Octrees> octrees;
|
||||
const size_t num_bodies = m_separated_body_bboxes.size();
|
||||
bool need_object = num_bodies <= 1;
|
||||
// Orca: Each body gets the octree it has when sliced on its own, from its own triangles, for each line spacing
|
||||
// its regions fill with. Body num_bodies stands for the whole object, which serves an object of a single body
|
||||
// and the surfaces of bodies that have no octree of their own.
|
||||
const size_t num_bodies = m_separated_body_bboxes.size();
|
||||
std::vector<std::pair<size_t, size_t>> to_build; // Set, body.
|
||||
std::vector<indexed_triangle_set> body_meshes;
|
||||
std::vector<std::vector<Vec3d>> body_overhangs(num_bodies);
|
||||
if (num_bodies > 1) {
|
||||
const std::vector<indexed_triangle_set> body_meshes = split_mesh_by_body(*this, mesh, num_bodies);
|
||||
need_object = std::any_of(body_meshes.begin(), body_meshes.end(), [](const indexed_triangle_set &its) { return its.indices.empty(); });
|
||||
std::vector<std::vector<Vec3d>> body_overhangs(num_bodies);
|
||||
body_meshes = split_mesh_by_body(*this, mesh, num_bodies);
|
||||
for (size_t i = 0; i < surfaces_w_layer.size(); ++ i)
|
||||
if (const int body = separated_body_at(*surfaces_w_layer[i].second, surfaces_w_layer[i].first->expolygon.contour.points.front()); body >= 0)
|
||||
append(body_overhangs[body], overhangs[i]);
|
||||
if (adaptive_line_spacing)
|
||||
octrees.first.bodies.resize(num_bodies);
|
||||
if (support_line_spacing)
|
||||
octrees.second.bodies.resize(num_bodies);
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_bodies), [&, adaptive_spacing = adaptive_line_spacing, support_spacing = support_line_spacing](
|
||||
const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t body = range.begin(); body < range.end(); ++ body) {
|
||||
m_print->throw_if_canceled();
|
||||
if (body_meshes[body].indices.empty())
|
||||
continue;
|
||||
if (adaptive_spacing)
|
||||
octrees.first.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], adaptive_spacing, false);
|
||||
if (support_spacing)
|
||||
octrees.second.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], support_spacing, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
std::vector<std::vector<char>> fills(spacings.size(), std::vector<char>(num_bodies + 1, false));
|
||||
for (const Layer *layer : m_layers)
|
||||
for (size_t region_id = 0; region_id < layer->regions().size() && region_id < octrees.region_set.size(); ++ region_id)
|
||||
if (const int set = octrees.region_set[region_id]; set >= 0)
|
||||
for (const Surface &surface : layer->regions()[region_id]->fill_surfaces) {
|
||||
const int body = separated_body_at(*layer, surface.expolygon.contour.points.front());
|
||||
fills[set][body >= 0 && ! body_meshes[body].indices.empty() ? size_t(body) : num_bodies] = true;
|
||||
}
|
||||
for (size_t set = 0; set < spacings.size(); ++ set) {
|
||||
octrees.sets[set].bodies.resize(num_bodies);
|
||||
for (size_t body = 0; body <= num_bodies; ++ body)
|
||||
if (fills[set][body])
|
||||
to_build.emplace_back(set, body);
|
||||
}
|
||||
} else
|
||||
for (size_t set = 0; set < spacings.size(); ++ set)
|
||||
to_build.emplace_back(set, num_bodies);
|
||||
|
||||
// and gather them.
|
||||
for (size_t i = 1; i < overhangs.size(); ++ i)
|
||||
append(overhangs.front(), std::move(overhangs[i]));
|
||||
|
||||
// Orca: The object's octree only serves bodies that have none of their own.
|
||||
if (need_object && adaptive_line_spacing)
|
||||
octrees.first.object = build_octree(mesh, overhangs.front(), adaptive_line_spacing, false);
|
||||
if (need_object && support_line_spacing)
|
||||
octrees.second.object = build_octree(mesh, overhangs.front(), support_line_spacing, true);
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, to_build.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++ i) {
|
||||
m_print->throw_if_canceled();
|
||||
const auto [set, body] = to_build[i];
|
||||
const bool object = body == num_bodies;
|
||||
(object ? octrees.sets[set].object : octrees.sets[set].bodies[body]) =
|
||||
build_octree(object ? mesh : body_meshes[body], object ? overhangs.front() : body_overhangs[body], spacings[set].first,
|
||||
spacings[set].second);
|
||||
}
|
||||
});
|
||||
return octrees;
|
||||
}
|
||||
|
||||
@@ -3077,8 +3096,7 @@ void PrintObject::bridge_over_infill()
|
||||
for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) {
|
||||
size_t lidx = layers_to_generate_infill[job_idx];
|
||||
infill_lines.at(
|
||||
lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees.first,
|
||||
&po->m_adaptive_fill_octrees.second,
|
||||
lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees,
|
||||
po->m_lightning_generator.get());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -58,6 +58,8 @@ public:
|
||||
static bool parse_color(const std::string& scolor, unsigned char* rgb_out);
|
||||
static bool parse_color4(const std::string& scolor, unsigned char* rgba_out);
|
||||
|
||||
// Rasterizes the SVG into a freshly generated GL texture; the caller owns it and has to delete
|
||||
// it (ImGuiWrapper::svg_texture() caches the result for the whole session).
|
||||
static bool load_from_svg_file_change_color(const std::string &filename, unsigned width, unsigned height, ImTextureID &texture_id, const char *hexColor);
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
FilamentGroupPopup(wxWindow *parent);
|
||||
void tryPopup(Plater* plater,PartPlate* plate, bool slice_all);
|
||||
void tryClose();
|
||||
void Dismiss() override;
|
||||
|
||||
FilamentMapMode GetSelectedMode() const { return m_mode; }
|
||||
private:
|
||||
@@ -40,7 +41,6 @@ private:
|
||||
void OnLeaveWindow(wxMouseEvent &);
|
||||
void OnEnterWindow(wxMouseEvent &);
|
||||
void OnTimer(wxTimerEvent &event);
|
||||
void Dismiss();
|
||||
|
||||
void CreateBmps();
|
||||
|
||||
|
||||
@@ -3444,7 +3444,8 @@ void GLCanvas3D::load_sla_preview()
|
||||
|
||||
void GLCanvas3D::bind_event_handlers()
|
||||
{
|
||||
if (m_canvas != nullptr) {
|
||||
// Every view switch binds, so binding twice would run each handler twice per event.
|
||||
if (m_canvas != nullptr && !m_event_handlers_bound) {
|
||||
m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
|
||||
m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
|
||||
m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
|
||||
@@ -3454,9 +3455,9 @@ void GLCanvas3D::bind_event_handlers()
|
||||
m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
|
||||
m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this);
|
||||
m_toolbar_highlighter.set_timer_owner(m_canvas, 0);
|
||||
m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_toolbar_highlighter.blink(); });
|
||||
m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, &GLCanvas3D::on_toolbar_highlighter_timer, this);
|
||||
m_gizmo_highlighter.set_timer_owner(m_canvas, 0);
|
||||
m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_gizmo_highlighter.blink(); });
|
||||
m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, &GLCanvas3D::on_gizmo_highlighter_timer, this);
|
||||
m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
|
||||
m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
|
||||
m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
|
||||
@@ -3471,14 +3472,7 @@ void GLCanvas3D::bind_event_handlers()
|
||||
m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
|
||||
m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
|
||||
m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
|
||||
m_canvas->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& evt) {
|
||||
// The key-up that would commit a keyboard edit goes to whatever took the focus.
|
||||
if (m_selection_edit.kind != SelectionEdit::None)
|
||||
finish_selection_edit();
|
||||
ImGui::SetWindowFocus(nullptr);
|
||||
render();
|
||||
evt.Skip();
|
||||
});
|
||||
m_canvas->Bind(wxEVT_KILL_FOCUS, &GLCanvas3D::on_kill_focus, this);
|
||||
m_event_handlers_bound = true;
|
||||
|
||||
m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
|
||||
@@ -3516,6 +3510,9 @@ void GLCanvas3D::unbind_event_handlers()
|
||||
m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
|
||||
m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
|
||||
m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
|
||||
m_canvas->Unbind(wxEVT_KILL_FOCUS, &GLCanvas3D::on_kill_focus, this);
|
||||
m_canvas->Unbind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, &GLCanvas3D::on_toolbar_highlighter_timer, this);
|
||||
m_canvas->Unbind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, &GLCanvas3D::on_gizmo_highlighter_timer, this);
|
||||
m_event_handlers_bound = false;
|
||||
|
||||
m_canvas->Unbind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
|
||||
@@ -5036,6 +5033,26 @@ void GLCanvas3D::on_set_focus(wxFocusEvent& evt)
|
||||
m_is_touchpad_navigation = wxGetApp().app_config->get_bool("camera_navigation_style");
|
||||
}
|
||||
|
||||
void GLCanvas3D::on_kill_focus(wxFocusEvent& evt)
|
||||
{
|
||||
// The key-up that would commit a keyboard edit goes to whatever took the focus.
|
||||
if (m_selection_edit.kind != SelectionEdit::None)
|
||||
finish_selection_edit();
|
||||
ImGui::SetWindowFocus(nullptr);
|
||||
render();
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void GLCanvas3D::on_toolbar_highlighter_timer(wxTimerEvent& evt)
|
||||
{
|
||||
m_toolbar_highlighter.blink();
|
||||
}
|
||||
|
||||
void GLCanvas3D::on_gizmo_highlighter_timer(wxTimerEvent& evt)
|
||||
{
|
||||
m_gizmo_highlighter.blink();
|
||||
}
|
||||
|
||||
bool GLCanvas3D::clicked_button_matches_action(const wxMouseEvent& evt, const MouseAction action, const std::map<MouseButton, MouseAction>& mappings) const
|
||||
{
|
||||
MouseButton clicked = MouseButton::None;
|
||||
|
||||
@@ -1207,6 +1207,9 @@ public:
|
||||
void on_gesture(wxGestureEvent& evt);
|
||||
void on_paint(wxPaintEvent& evt);
|
||||
void on_set_focus(wxFocusEvent& evt);
|
||||
void on_kill_focus(wxFocusEvent& evt);
|
||||
void on_toolbar_highlighter_timer(wxTimerEvent& evt);
|
||||
void on_gizmo_highlighter_timer(wxTimerEvent& evt);
|
||||
void force_set_focus();
|
||||
|
||||
enum class MouseButton { None, Left, Middle, Right };
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include <wx/gdicmn.h>
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
@@ -2081,9 +2082,15 @@ wxMenu* MenuFactory::instance_menu()
|
||||
return &m_instance_menu;
|
||||
}
|
||||
|
||||
MenuWithSeparators* MenuFactory::new_transient_menu()
|
||||
{
|
||||
m_transient_menu = std::make_unique<MenuWithSeparators>();
|
||||
return m_transient_menu.get();
|
||||
}
|
||||
|
||||
wxMenu* MenuFactory::layer_menu()
|
||||
{
|
||||
MenuWithSeparators* menu = new MenuWithSeparators();
|
||||
MenuWithSeparators* menu = new_transient_menu();
|
||||
append_menu_item_settings(menu);
|
||||
|
||||
return menu;
|
||||
@@ -2109,13 +2116,13 @@ wxMenu* MenuFactory::multi_selection_menu()
|
||||
}
|
||||
|
||||
if (all_plates) {
|
||||
wxMenu* menu = new MenuWithSeparators();
|
||||
wxMenu* menu = new_transient_menu();
|
||||
append_menu_item_replace_all_with_stl(menu);
|
||||
return menu;
|
||||
}
|
||||
if (undefined_type)
|
||||
return nullptr;
|
||||
wxMenu* menu = new MenuWithSeparators();
|
||||
wxMenu* menu = new_transient_menu();
|
||||
if (!multi_volume) {
|
||||
int index = 0;
|
||||
if (obj_list()->can_merge_to_multipart_object()) {
|
||||
@@ -2189,7 +2196,7 @@ wxMenu* MenuFactory::assemble_multi_selection_menu()
|
||||
// show this menu only for Objects(Instances mixed with Objects)/Volumes selection
|
||||
return nullptr;
|
||||
|
||||
wxMenu* menu = new MenuWithSeparators();
|
||||
wxMenu* menu = new_transient_menu();
|
||||
append_menu_item_set_visible(menu);
|
||||
//append_menu_item_fix_through_cgal(menu);
|
||||
//append_menu_item_simplify(menu);
|
||||
@@ -2235,7 +2242,7 @@ wxMenu* MenuFactory::plate_menu()
|
||||
|
||||
wxMenu* MenuFactory::assemble_object_menu()
|
||||
{
|
||||
wxMenu* menu = new MenuWithSeparators();
|
||||
wxMenu* menu = new_transient_menu();
|
||||
// Set Visible
|
||||
append_menu_item_set_visible(menu);
|
||||
// Delete
|
||||
@@ -2255,7 +2262,7 @@ wxMenu* MenuFactory::assemble_object_menu()
|
||||
|
||||
wxMenu* MenuFactory::assemble_part_menu()
|
||||
{
|
||||
wxMenu* menu = new MenuWithSeparators();
|
||||
wxMenu* menu = new_transient_menu();
|
||||
|
||||
append_menu_item_set_visible(menu);
|
||||
append_menu_item_delete(menu);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define slic3r_GUI_Factories_hpp_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
@@ -121,6 +122,12 @@ private:
|
||||
MenuWithSeparators m_assemble_part_menu;
|
||||
|
||||
wxMenu m_filament_action_menu;
|
||||
|
||||
// The selection dependent menus are rebuilt for every popup, so they cannot be members that
|
||||
// outlive a build like the ones above; this owns the current one and destroys the previous.
|
||||
// One slot is enough because PopupMenu() is synchronous: the menu a caller was handed is gone
|
||||
// from the screen before anything can ask for the next one.
|
||||
std::unique_ptr<MenuWithSeparators> m_transient_menu;
|
||||
|
||||
|
||||
// Removed/Prepended Items according to the view mode
|
||||
@@ -128,6 +135,9 @@ private:
|
||||
std::array<wxMenuItem*, mtCount> items_decrease;
|
||||
std::array<wxMenuItem*, mtCount> items_set_number_of_copies;
|
||||
|
||||
// Replaces m_transient_menu with an empty menu and returns it.
|
||||
MenuWithSeparators* new_transient_menu();
|
||||
|
||||
void create_default_menu();
|
||||
void create_common_object_menu(wxMenu *menu);
|
||||
void create_object_menu();
|
||||
|
||||
@@ -2317,6 +2317,10 @@ void GLGizmoMeasure::update_measurement_result()
|
||||
void GLGizmoMeasure::reset_all_pick()
|
||||
{
|
||||
std::map<GLVolume*, std::shared_ptr<PickRaycaster>>().swap(m_mesh_raycaster_map);
|
||||
// register_single_mesh_pick() fills both maps in lockstep, so the measurings have to go with
|
||||
// the raycasters; otherwise the entries keyed on the GLVolumes of the previous selection stay
|
||||
// behind for the rest of the session.
|
||||
std::map<GLVolume*, std::shared_ptr<Measure::Measuring>>().swap(m_mesh_measure_map);
|
||||
reset_gripper_pick(GripperType::UNDEFINE,true);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,7 @@ enum class PainterGizmoType {
|
||||
FDM_SUPPORTS,
|
||||
SEAM,
|
||||
MM_SEGMENTATION,
|
||||
FUZZY_SKIN,
|
||||
TEXTURE_DISPLACEMENT
|
||||
FUZZY_SKIN
|
||||
};
|
||||
|
||||
class TriangleSelectorGUI : public TriangleSelector {
|
||||
@@ -112,7 +111,9 @@ class TriangleSelectorPatch : public TriangleSelectorGUI {
|
||||
public:
|
||||
explicit TriangleSelectorPatch(const TriangleMesh& mesh, const std::vector<ColorRGBA> ebt_colors, float edge_limit = 0.6f)
|
||||
: TriangleSelectorGUI(mesh, edge_limit), m_ebt_colors(ebt_colors) {}
|
||||
virtual ~TriangleSelectorPatch() = default;
|
||||
// Releases the VAO and the per-patch VBOs built by finalize_triangle_indices(). The base class
|
||||
// already deletes GL buffers from its GLModel members here, so this needs no context of its own.
|
||||
virtual ~TriangleSelectorPatch() { release_geometry(); }
|
||||
|
||||
// Render current selection. Transformation matrices are supposed
|
||||
// to be already set.
|
||||
|
||||
@@ -384,6 +384,7 @@ ImGuiWrapper::~ImGuiWrapper()
|
||||
{
|
||||
//destroy_fonts_texture();
|
||||
destroy_font();
|
||||
destroy_svg_textures();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
@@ -565,6 +566,12 @@ bool ImGuiWrapper::update_key_data(wxKeyEvent &evt)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// SVG icons rasterized into GL textures, keyed on file name, size and recolor. Cleared as a whole
|
||||
// from new_frame() once it grows past MAX_SVG_TEXTURES, which is safe there: the previous frame has
|
||||
// been rendered and the frame about to be recorded asks for every icon it draws again.
|
||||
static std::map<std::string, ImTextureID> s_svg_textures;
|
||||
static const size_t MAX_SVG_TEXTURES = 256;
|
||||
|
||||
void ImGuiWrapper::new_frame()
|
||||
{
|
||||
if (m_new_frame_open) {
|
||||
@@ -575,6 +582,11 @@ void ImGuiWrapper::new_frame()
|
||||
init_font(true);
|
||||
}
|
||||
|
||||
// Recolored icons accumulate one texture per color the session has shown; drop them before
|
||||
// anything references them again. This frame recreates the handful it actually draws.
|
||||
if (s_svg_textures.size() > MAX_SVG_TEXTURES)
|
||||
destroy_svg_textures();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
ImGui::NewFrame();
|
||||
@@ -1827,8 +1839,7 @@ bool menu_item_with_icon(const char *label, const char *shortcut, ImVec2 icon_si
|
||||
if (icon_color != 0)
|
||||
ImGui::RenderFrame(icon_pos, icon_pos + icon_size, icon_color);
|
||||
else {
|
||||
static ImTextureID transparent;
|
||||
IMTexture::load_from_svg_file(Slic3r::resources_dir() + "/images/transparent.svg", icon_size.x, icon_size.y, transparent);
|
||||
ImTextureID transparent = ImGuiWrapper::svg_texture(Slic3r::resources_dir() + "/images/transparent.svg", icon_size.x, icon_size.y);
|
||||
window->DrawList->AddImage(transparent, icon_pos, icon_pos + icon_size, { 0,0 }, { 1,1 }, ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, 1.f)));
|
||||
}
|
||||
}
|
||||
@@ -3408,6 +3419,36 @@ bool ImGuiWrapper::display_initialized() const
|
||||
return io.DisplaySize.x >= 0.0f && io.DisplaySize.y >= 0.0f;
|
||||
}
|
||||
|
||||
ImTextureID ImGuiWrapper::svg_texture(const std::string& filename, unsigned width, unsigned height, const char* hex_color)
|
||||
{
|
||||
std::string key = filename + "|" + std::to_string(width) + "x" + std::to_string(height);
|
||||
if (hex_color != nullptr)
|
||||
key += std::string("|") + hex_color;
|
||||
|
||||
const auto it = s_svg_textures.find(key);
|
||||
if (it != s_svg_textures.end())
|
||||
return it->second;
|
||||
|
||||
ImTextureID texture_id = nullptr;
|
||||
const bool loaded = (hex_color != nullptr) ?
|
||||
BitmapCache::load_from_svg_file_change_color(filename, width, height, texture_id, hex_color) :
|
||||
IMTexture::load_from_svg_file(filename, width, height, texture_id);
|
||||
if (!loaded)
|
||||
return nullptr;
|
||||
|
||||
s_svg_textures.emplace(std::move(key), texture_id);
|
||||
return texture_id;
|
||||
}
|
||||
|
||||
void ImGuiWrapper::destroy_svg_textures()
|
||||
{
|
||||
for (const auto& texture : s_svg_textures) {
|
||||
GLuint texture_id = (GLuint)(intptr_t)texture.second;
|
||||
glsafe(::glDeleteTextures(1, &texture_id));
|
||||
}
|
||||
s_svg_textures.clear();
|
||||
}
|
||||
|
||||
void ImGuiWrapper::destroy_font()
|
||||
{
|
||||
if (m_font_texture != 0) {
|
||||
@@ -3487,7 +3528,6 @@ void ImGuiWrapper::filament_group(const std::string& filament_type, const char*
|
||||
//ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
std::string id = std::to_string(static_cast<unsigned int> (filament_id + 1));
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
static ImTextureID transparent;
|
||||
ImVec2 text_size = ImGui::CalcTextSize(filament_type.c_str());
|
||||
// BBS image sizing based on text width (DPI scaling)
|
||||
float img_width = ImGui::CalcTextSize("ABC").x;
|
||||
@@ -3500,7 +3540,7 @@ void ImGuiWrapper::filament_group(const std::string& filament_type, const char*
|
||||
if (rgba[3] == 0x00) {
|
||||
svg_path = "/images/outlined_rect_transparent.svg";
|
||||
}
|
||||
BitmapCache::load_from_svg_file_change_color(Slic3r::resources_dir() + svg_path, img_size.x, img_size.y, transparent, hex_color);
|
||||
ImTextureID transparent = svg_texture(Slic3r::resources_dir() + svg_path, img_size.x, img_size.y, hex_color);
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
|
||||
|
||||
@@ -108,6 +108,14 @@ public:
|
||||
// Hash of every draw list's vertices, indices and commands.
|
||||
static ImGuiID draw_data_signature(const ImDrawData* draw_data);
|
||||
|
||||
// A GL texture holding an SVG icon rasterized at width x height, optionally recolored.
|
||||
// Rasterizing an SVG is far too expensive to redo for every frame that draws the icon, and the
|
||||
// texture the previous frame generated would leak, so the result is kept until the frame that
|
||||
// finds the cache overgrown drops it (and rebuilds only what it still draws).
|
||||
static ImTextureID svg_texture(const std::string& filename, unsigned width, unsigned height, const char* hex_color = nullptr);
|
||||
// Deletes every texture svg_texture() handed out. Requires a current GL context.
|
||||
static void destroy_svg_textures();
|
||||
|
||||
float scaled(float x) const { return x * m_font_size; }
|
||||
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
|
||||
/// <summary>
|
||||
|
||||
@@ -2309,6 +2309,12 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
|
||||
bool slice = true;
|
||||
|
||||
// The Slice-plate hover popup is a transient popup that keeps grabbing
|
||||
// the mouse capture while shown. Left behind the modal grouping dialog it
|
||||
// would starve that dialog of mouse events, so close it synchronously first.
|
||||
if (m_filament_group_popup)
|
||||
m_filament_group_popup->Dismiss();
|
||||
|
||||
auto curr_plate = m_plater->get_partplate_list().get_curr_plate();
|
||||
#ifdef __linux__
|
||||
PresetBundle* preset = wxGetApp().preset_bundle;
|
||||
|
||||
@@ -2250,11 +2250,12 @@ void NotificationManager::close_and_delete_self(PopNotification * self)
|
||||
}
|
||||
|
||||
void NotificationManager::remove_notification_of_type(const NotificationType type) {
|
||||
// Seven notification types may have several instances alive at once, so erase every match:
|
||||
// stopping at the first one leaves the rest (and the ObjectIDs they hold) behind.
|
||||
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
|
||||
std::unique_ptr<PopNotification> ¬ification = *it;
|
||||
if (notification->get_type() == type) {
|
||||
it = m_pop_notifications.erase(it);
|
||||
break;
|
||||
} else
|
||||
++it;
|
||||
}
|
||||
|
||||
@@ -3128,7 +3128,7 @@ void PartPlate::set_vase_mode_related_object_config(int obj_id) {
|
||||
new_conf.set_key_value("detect_thin_wall", new ConfigOptionBool(false));
|
||||
new_conf.set_key_value("timelapse_type", new ConfigOptionEnum<TimelapseType>(tlTraditional));
|
||||
new_conf.set_key_value("overhang_reverse", new ConfigOptionBool(false));
|
||||
auto applying_keys = global_config->diff(new_conf);
|
||||
const auto applying_keys = global_config->diff(new_conf);
|
||||
|
||||
for (ModelObject* object : obj_ptrs) {
|
||||
ModelConfigObject& config = object->config;
|
||||
@@ -3137,8 +3137,8 @@ void PartPlate::set_vase_mode_related_object_config(int obj_id) {
|
||||
config.set_key_value(opt_key, new_conf.option(opt_key)->clone());
|
||||
}
|
||||
|
||||
applying_keys = config.get().diff(new_conf);
|
||||
for (auto opt_key : applying_keys) {
|
||||
const auto object_keys = config.get().diff(new_conf);
|
||||
for (auto opt_key : object_keys) {
|
||||
config.set_key_value(opt_key, new_conf.option(opt_key)->clone());
|
||||
}
|
||||
}
|
||||
|
||||
+33
-28
@@ -8800,6 +8800,20 @@ void read_binary_stl(const std::string& filename, std::string& model_id, std::st
|
||||
return;
|
||||
}
|
||||
|
||||
// Logs what show_substitutions_info() would list, for loads that don't show the dialog.
|
||||
static void log_substitutions(const ConfigSubstitutions& substitutions, const std::string& source)
|
||||
{
|
||||
for (const ConfigSubstitution& substitution : substitutions)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Loading " << source << ": " << substitution.opt_def->opt_key << " = \"" << substitution.old_value
|
||||
<< "\" replaced with \"" << substitution.new_value->serialize() << "\"";
|
||||
}
|
||||
|
||||
static void log_substitutions(const PresetsConfigSubstitutions& substitutions, const std::string& source)
|
||||
{
|
||||
for (const PresetConfigSubstitutions& preset : substitutions)
|
||||
log_substitutions(preset.substitutions, source + " (preset " + preset.preset_name + ")");
|
||||
}
|
||||
|
||||
// BBS: backup & restore
|
||||
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files,
|
||||
LoadStrategy strategy,
|
||||
@@ -9101,7 +9115,6 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
Semver app_version = *(Semver::parse(SoftFever_VERSION));
|
||||
const wxString load_3mf_title = _L("Load 3MF");
|
||||
const wxString newer_3mf_title = _L("Newer 3MF version");
|
||||
const wxString bambu_project_title = _L("BambuStudio Project");
|
||||
const wxString msg_unsupported_geometry = _L("The 3MF is not supported by OrcaSlicer, loading geometry data only.");
|
||||
const wxString msg_old_orca_geometry = _L("The 3MF file was generated by an old OrcaSlicer version, loading geometry data only.");
|
||||
const wxString msg_older_geometry = _L("The 3MF file was generated by an older version, loading geometry data only.");
|
||||
@@ -9112,6 +9125,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
<< boost::format("3MF import message [%1%]: %2% | file: %3%") % into_u8(title) % into_u8(text) % path.string();
|
||||
show_info(q, text, title);
|
||||
};
|
||||
// Untagged files up to 2.3.2 may also come from OrcaSlicer, which only started tagging its 3MFs after it.
|
||||
const bool is_bambu_studio_project = en_3mf_file_type == En3mfType::From_BBS && file_version > Semver(2, 3, 2);
|
||||
if (en_3mf_file_type == En3mfType::From_Prusa) {
|
||||
// do not reset the model config
|
||||
load_config = false;
|
||||
@@ -9171,8 +9186,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
else if (en_3mf_file_type == En3mfType::From_BBS) {
|
||||
// No OrcaSlicer tag - check Bambu/Application version
|
||||
Semver orca_tag_start_version(2, 3, 2);
|
||||
if (file_version <= orca_tag_start_version) {
|
||||
if (!is_bambu_studio_project) {
|
||||
// Compatible old version (before OrcaSlicer tagging was introduced after 2.3.2).
|
||||
// Any version prior or equal to 2.3.2 is older than the current one, no version warnings needed.
|
||||
// Still apply migration fixes for known old versions.
|
||||
@@ -9205,33 +9219,17 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
} else {
|
||||
// BambuStudio project (version > 2.3.2 without OrcaSlicer tag)
|
||||
// Report that a BambuStudio project is being imported and compare with SLIC3R_VERSION
|
||||
Semver slic3r_version = *(Semver::parse(SLIC3R_VERSION));
|
||||
if (load_config && config_loaded.empty()) {
|
||||
load_config = false;
|
||||
log_and_show_3mf_info(msg_bambu_geometry, load_3mf_title);
|
||||
}
|
||||
else if (load_config && (file_version > slic3r_version)) {
|
||||
// BambuStudio file version is newer than our compatible SLIC3R_VERSION
|
||||
if (config_substitutions.unrecogized_keys.size() > 0) {
|
||||
wxString text = wxString::Format(_L("The 3MF was created by BambuStudio (version %s), which is newer than the compatible version %s. Found unrecognized settings:"),
|
||||
file_version.to_string(), slic3r_version.to_string());
|
||||
text += "\n";
|
||||
wxString context = text;
|
||||
wxString append = _L("You should update your software.\n");
|
||||
context += "\n\n";
|
||||
context += append;
|
||||
log_and_show_3mf_info(context, bambu_project_title);
|
||||
} else {
|
||||
wxString text = wxString::Format(_L("The 3MF was created by BambuStudio (version %s), which is newer than the compatible version %s. Some settings may not be fully compatible."),
|
||||
file_version.to_string(), slic3r_version.to_string());
|
||||
text += "\n";
|
||||
log_and_show_3mf_info(text, bambu_project_title);
|
||||
}
|
||||
} else if (load_config && !published_config.published) {
|
||||
// BambuStudio version is older or same as our SLIC3R_VERSION
|
||||
wxString text = _L("The 3MF was created by BambuStudio. Some settings may differ from OrcaSlicer.");
|
||||
log_and_show_3mf_info(text, bambu_project_title);
|
||||
else if (load_config) {
|
||||
// Logged, not shown: it is the same for every BambuStudio project and needs no action.
|
||||
std::string unrecognized;
|
||||
for (const std::string& key : config_substitutions.unrecogized_keys)
|
||||
unrecognized += (unrecognized.empty() ? "" : ", ") + key;
|
||||
BOOST_LOG_TRIVIAL(info) << "BambuStudio " << file_version.to_string() << " project " << path.string()
|
||||
<< ", unrecognized settings: " << (unrecognized.empty() ? "none" : unrecognized);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9320,7 +9318,10 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
PresetsConfigSubstitutions preset_substitutions;
|
||||
PresetBundle & preset_bundle = *wxGetApp().preset_bundle;
|
||||
preset_substitutions = preset_bundle.load_project_embedded_presets(project_presets, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
if (!preset_substitutions.empty()) show_substitutions_info(preset_substitutions);
|
||||
if (is_bambu_studio_project)
|
||||
log_substitutions(preset_substitutions, path.string());
|
||||
else if (!preset_substitutions.empty())
|
||||
show_substitutions_info(preset_substitutions);
|
||||
}
|
||||
if (project_presets.size() > 0) {
|
||||
for (unsigned int i = 0; i < project_presets.size(); i++) { delete project_presets[i]; }
|
||||
@@ -9358,7 +9359,11 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
notify_manager->bbl_show_3mf_warn_notification(error_message);
|
||||
}
|
||||
}
|
||||
if (!config_substitutions.empty()) show_substitutions_info(config_substitutions.substitutions, filename.string());
|
||||
// BambuStudio projects routinely carry values Orca replaces; log them rather than showing a dialog on every open.
|
||||
if (is_bambu_studio_project)
|
||||
log_substitutions(config_substitutions.substitutions, path.string());
|
||||
else if (!config_substitutions.empty())
|
||||
show_substitutions_info(config_substitutions.substitutions, filename.string());
|
||||
|
||||
// BBS
|
||||
if (load_model && !load_config) {
|
||||
|
||||
@@ -947,13 +947,12 @@ std::string Http::tls_global_init()
|
||||
|
||||
std::string Http::tls_system_cert_store()
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
#ifdef OPENSSL_CERT_OVERRIDE
|
||||
ret = ::getenv(X509_get_default_cert_file_env());
|
||||
if (const char *cert_file = ::getenv(X509_get_default_cert_file_env()))
|
||||
return cert_file;
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string Http::url_encode(const std::string &str)
|
||||
|
||||
@@ -25,11 +25,19 @@ extern const char* const INSTALL_STATE_FILE;
|
||||
// Plugin config and orca.host.ui payloads both cross the boundary as plain JSON-compatible
|
||||
// values, so both go through these.
|
||||
|
||||
inline pybind11::object json_to_py(const nlohmann::json& j)
|
||||
// Maximum nesting depth for JSON <-> Python conversion. A self-referential or pathologically
|
||||
// deep value would otherwise recurse until the native C stack overflows, an uncatchable crash;
|
||||
// past this bound we raise instead. 200 is far beyond any legitimate plugin config or UI payload.
|
||||
inline constexpr int kMaxJsonConversionDepth = 200;
|
||||
|
||||
inline pybind11::object json_to_py(const nlohmann::json& j, int depth = 0)
|
||||
{
|
||||
namespace py = pybind11;
|
||||
using json = nlohmann::json;
|
||||
|
||||
if (depth > kMaxJsonConversionDepth)
|
||||
throw py::value_error("Plugin JSON value nested too deeply");
|
||||
|
||||
switch (j.type()) {
|
||||
case json::value_t::null: return py::none();
|
||||
case json::value_t::boolean: return py::bool_(j.get<bool>());
|
||||
@@ -40,24 +48,27 @@ inline pybind11::object json_to_py(const nlohmann::json& j)
|
||||
case json::value_t::array: {
|
||||
py::list lst;
|
||||
for (const auto& e : j)
|
||||
lst.append(json_to_py(e));
|
||||
lst.append(json_to_py(e, depth + 1));
|
||||
return lst;
|
||||
}
|
||||
case json::value_t::object: {
|
||||
py::dict d;
|
||||
for (auto it = j.begin(); it != j.end(); ++it)
|
||||
d[py::str(it.key())] = json_to_py(it.value());
|
||||
d[py::str(it.key())] = json_to_py(it.value(), depth + 1);
|
||||
return d;
|
||||
}
|
||||
default: return py::none();
|
||||
}
|
||||
}
|
||||
|
||||
inline nlohmann::json py_to_json(const pybind11::handle& o)
|
||||
inline nlohmann::json py_to_json(const pybind11::handle& o, int depth = 0)
|
||||
{
|
||||
namespace py = pybind11;
|
||||
using json = nlohmann::json;
|
||||
|
||||
if (depth > kMaxJsonConversionDepth)
|
||||
throw py::value_error("Plugin value nested too deeply (possible cycle)");
|
||||
|
||||
if (o.is_none())
|
||||
return json(nullptr);
|
||||
if (py::isinstance<py::bool_>(o)) // bool before int (bool subclasses int in Python)
|
||||
@@ -73,13 +84,13 @@ inline nlohmann::json py_to_json(const pybind11::handle& o)
|
||||
if (py::isinstance<py::dict>(o)) {
|
||||
json obj = json::object();
|
||||
for (auto item : py::reinterpret_borrow<py::dict>(o))
|
||||
obj[py::str(item.first).cast<std::string>()] = py_to_json(item.second);
|
||||
obj[py::str(item.first).cast<std::string>()] = py_to_json(item.second, depth + 1);
|
||||
return obj;
|
||||
}
|
||||
if (py::isinstance<py::list>(o) || py::isinstance<py::tuple>(o)) {
|
||||
json arr = json::array();
|
||||
for (auto e : o)
|
||||
arr.push_back(py_to_json(e));
|
||||
arr.push_back(py_to_json(e, depth + 1));
|
||||
return arr;
|
||||
}
|
||||
return py::str(o).cast<std::string>(); // fallback: str()
|
||||
|
||||
@@ -11,6 +11,13 @@ endif ()
|
||||
|
||||
add_test(NAME cli_strict_mode
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_strict.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON})
|
||||
add_test(NAME cli_empty_project_config
|
||||
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_empty_project_config.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON} ${CMAKE_SOURCE_DIR})
|
||||
set_tests_properties(cli_empty_project_config PROPERTIES
|
||||
LABELS "CLI;RequiresApp"
|
||||
SKIP_RETURN_CODE 77
|
||||
TIMEOUT 300)
|
||||
|
||||
set_tests_properties(cli_strict_mode PROPERTIES
|
||||
LABELS "CLI;RequiresApp"
|
||||
SKIP_RETURN_CODE 77
|
||||
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regression check: a 3mf whose Metadata/project_settings.config carries no settings must load.
|
||||
#
|
||||
# The CLI reads printable_height out of the project config with opt_float(), which dereferences
|
||||
# what option<>() returns. With create = false that is null when the key is absent, so a project
|
||||
# saved without settings used to take the CLI down with a segfault. Both models in
|
||||
# resources/handy_models are such files.
|
||||
#
|
||||
# usage: test_cli_empty_project_config.sh <orca-slicer binary> <python3> <source dir>
|
||||
set -u
|
||||
|
||||
BIN="${1:-}"
|
||||
PY="${2:-python3}"
|
||||
SRC="${3:-}"
|
||||
# 77 is the test's SKIP_RETURN_CODE.
|
||||
[ -x "$BIN" ] || { echo "SKIP: orca-slicer binary not found: $BIN"; exit 77; }
|
||||
[ -f "$SRC/resources/handy_models/OrcaBadge.3mf" ] || { echo "SKIP: handy model not found"; exit 77; }
|
||||
|
||||
WORK="$(mktemp -d "${TMPDIR:-/tmp}/orca-cli-emptycfg.XXXXXX")"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
mkdir -p "$WORK/datadir"
|
||||
|
||||
# Rewrite the project settings to an empty object, so the test holds no matter what the shipped
|
||||
# models carry later on.
|
||||
cp "$SRC/resources/handy_models/OrcaBadge.3mf" "$WORK/empty_config.3mf"
|
||||
"$PY" - "$WORK/empty_config.3mf" <<'PYEOF'
|
||||
import shutil, sys, zipfile
|
||||
|
||||
path = sys.argv[1]
|
||||
entry = "Metadata/project_settings.config"
|
||||
with zipfile.ZipFile(path) as src:
|
||||
items = [(i, src.read(i.filename)) for i in src.infolist()]
|
||||
with zipfile.ZipFile(path + ".new", "w", zipfile.ZIP_DEFLATED) as dst:
|
||||
seen = False
|
||||
for info, data in items:
|
||||
if info.filename == entry:
|
||||
data, seen = b"{\n}\n", True
|
||||
dst.writestr(info, data)
|
||||
if not seen:
|
||||
dst.writestr(entry, b"{\n}\n")
|
||||
shutil.move(path + ".new", path)
|
||||
PYEOF
|
||||
|
||||
"$BIN" --datadir "$WORK/datadir" --info "$WORK/empty_config.3mf" > "$WORK/info.txt" 2>&1
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FAIL: --info on a project with empty settings exited $rc"
|
||||
tail -20 "$WORK/info.txt"
|
||||
exit 1
|
||||
fi
|
||||
grep -q "size_x" "$WORK/info.txt" || { echo "FAIL: --info printed no geometry"; cat "$WORK/info.txt"; exit 1; }
|
||||
echo "PASS: a project with empty settings loads"
|
||||
@@ -0,0 +1,166 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('Open CASCADE Model'),'2;1');
|
||||
FILE_NAME('Open CASCADE Shape Model','2026-10-08T09:57:33',('Author'),(
|
||||
'Open CASCADE'),'Open CASCADE STEP processor 8.0','Open CASCADE 8.0'
|
||||
,'Unknown');
|
||||
FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1 = APPLICATION_PROTOCOL_DEFINITION('international standard',
|
||||
'automotive_design',2000,#2);
|
||||
#2 = APPLICATION_CONTEXT(
|
||||
'core data for automotive mechanical design processes');
|
||||
#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10);
|
||||
#4 = PRODUCT_DEFINITION_SHAPE('','',#5);
|
||||
#5 = PRODUCT_DEFINITION('design','',#6,#9);
|
||||
#6 = PRODUCT_DEFINITION_FORMATION('','',#7);
|
||||
#7 = PRODUCT('Open CASCADE STEP translator 8.0 1',
|
||||
'Open CASCADE STEP translator 8.0 1','',(#8));
|
||||
#8 = PRODUCT_CONTEXT('',#2,'mechanical');
|
||||
#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
|
||||
#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#121);
|
||||
#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14);
|
||||
#12 = CARTESIAN_POINT('',(0.,0.,0.));
|
||||
#13 = DIRECTION('',(0.,0.,1.));
|
||||
#14 = DIRECTION('',(1.,0.,-0.));
|
||||
#15 = MANIFOLD_SOLID_BREP('',#16);
|
||||
#16 = CLOSED_SHELL('',(#17,#113,#117));
|
||||
#17 = ADVANCED_FACE('',(#18),#31,.T.);
|
||||
#18 = FACE_BOUND('',#19,.T.);
|
||||
#19 = EDGE_LOOP('',(#20,#58,#81,#112));
|
||||
#20 = ORIENTED_EDGE('',*,*,#21,.F.);
|
||||
#21 = EDGE_CURVE('',#22,#22,#24,.T.);
|
||||
#22 = VERTEX_POINT('',#23);
|
||||
#23 = CARTESIAN_POINT('',(-36.4,0.,25.));
|
||||
#24 = SURFACE_CURVE('',#25,(#30,#42),.PCURVE_S1.);
|
||||
#25 = CIRCLE('',#26,36.4);
|
||||
#26 = AXIS2_PLACEMENT_3D('',#27,#28,#29);
|
||||
#27 = CARTESIAN_POINT('',(0.,0.,25.));
|
||||
#28 = DIRECTION('',(0.,0.,1.));
|
||||
#29 = DIRECTION('',(1.,0.,-0.));
|
||||
#30 = PCURVE('',#31,#36);
|
||||
#31 = CONICAL_SURFACE('',#32,36.4,0.785398163397);
|
||||
#32 = AXIS2_PLACEMENT_3D('',#33,#34,#35);
|
||||
#33 = CARTESIAN_POINT('',(0.,0.,25.));
|
||||
#34 = DIRECTION('',(0.,0.,1.));
|
||||
#35 = DIRECTION('',(1.,0.,-0.));
|
||||
#36 = DEFINITIONAL_REPRESENTATION('',(#37),#41);
|
||||
#37 = LINE('',#38,#39);
|
||||
#38 = CARTESIAN_POINT('',(-6.28318530718,3.552713678801E-15));
|
||||
#39 = VECTOR('',#40,1.);
|
||||
#40 = DIRECTION('',(1.,0.));
|
||||
#41 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||
) );
|
||||
#42 = PCURVE('',#43,#48);
|
||||
#43 = PLANE('',#44);
|
||||
#44 = AXIS2_PLACEMENT_3D('',#45,#46,#47);
|
||||
#45 = CARTESIAN_POINT('',(0.,0.,25.));
|
||||
#46 = DIRECTION('',(-0.,-0.,-1.));
|
||||
#47 = DIRECTION('',(-1.,0.,0.));
|
||||
#48 = DEFINITIONAL_REPRESENTATION('',(#49),#57);
|
||||
#49 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#50,#51,#52,#53,#54,#55,#56),
|
||||
.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
|
||||
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
|
||||
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
|
||||
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
|
||||
'') );
|
||||
#50 = CARTESIAN_POINT('',(-36.4,0.));
|
||||
#51 = CARTESIAN_POINT('',(-36.4,63.046649395507));
|
||||
#52 = CARTESIAN_POINT('',(18.2,31.523324697754));
|
||||
#53 = CARTESIAN_POINT('',(72.8,8.915428697793E-15));
|
||||
#54 = CARTESIAN_POINT('',(18.2,-31.52332469775));
|
||||
#55 = CARTESIAN_POINT('',(-36.4,-63.0466493955));
|
||||
#56 = CARTESIAN_POINT('',(-36.4,0.));
|
||||
#57 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||
) );
|
||||
#58 = ORIENTED_EDGE('',*,*,#59,.T.);
|
||||
#59 = EDGE_CURVE('',#22,#60,#62,.T.);
|
||||
#60 = VERTEX_POINT('',#61);
|
||||
#61 = CARTESIAN_POINT('',(-11.4,0.,3.552713678801E-15));
|
||||
#62 = SEAM_CURVE('',#63,(#67,#74),.PCURVE_S1.);
|
||||
#63 = LINE('',#64,#65);
|
||||
#64 = CARTESIAN_POINT('',(-36.4,0.,25.));
|
||||
#65 = VECTOR('',#66,1.);
|
||||
#66 = DIRECTION('',(0.707106781187,8.659560562349E-17,-0.707106781187));
|
||||
#67 = PCURVE('',#31,#68);
|
||||
#68 = DEFINITIONAL_REPRESENTATION('',(#69),#73);
|
||||
#69 = LINE('',#70,#71);
|
||||
#70 = CARTESIAN_POINT('',(-3.14159265359,3.552713678801E-15));
|
||||
#71 = VECTOR('',#72,1.);
|
||||
#72 = DIRECTION('',(2.13162820728E-16,-1.));
|
||||
#73 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||
) );
|
||||
#74 = PCURVE('',#31,#75);
|
||||
#75 = DEFINITIONAL_REPRESENTATION('',(#76),#80);
|
||||
#76 = LINE('',#77,#78);
|
||||
#77 = CARTESIAN_POINT('',(3.14159265359,3.552713678801E-15));
|
||||
#78 = VECTOR('',#79,1.);
|
||||
#79 = DIRECTION('',(2.13162820728E-16,-1.));
|
||||
#80 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||
) );
|
||||
#81 = ORIENTED_EDGE('',*,*,#82,.F.);
|
||||
#82 = EDGE_CURVE('',#60,#60,#83,.T.);
|
||||
#83 = SURFACE_CURVE('',#84,(#89,#96),.PCURVE_S1.);
|
||||
#84 = CIRCLE('',#85,11.4);
|
||||
#85 = AXIS2_PLACEMENT_3D('',#86,#87,#88);
|
||||
#86 = CARTESIAN_POINT('',(0.,0.,3.552713678801E-15));
|
||||
#87 = DIRECTION('',(0.,0.,-1.));
|
||||
#88 = DIRECTION('',(1.,0.,0.));
|
||||
#89 = PCURVE('',#31,#90);
|
||||
#90 = DEFINITIONAL_REPRESENTATION('',(#91),#95);
|
||||
#91 = LINE('',#92,#93);
|
||||
#92 = CARTESIAN_POINT('',(6.28318530718,-25.));
|
||||
#93 = VECTOR('',#94,1.);
|
||||
#94 = DIRECTION('',(-1.,0.));
|
||||
#95 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||
) );
|
||||
#96 = PCURVE('',#97,#102);
|
||||
#97 = PLANE('',#98);
|
||||
#98 = AXIS2_PLACEMENT_3D('',#99,#100,#101);
|
||||
#99 = CARTESIAN_POINT('',(0.,0.,3.552713678801E-15));
|
||||
#100 = DIRECTION('',(0.,0.,1.));
|
||||
#101 = DIRECTION('',(1.,0.,-0.));
|
||||
#102 = DEFINITIONAL_REPRESENTATION('',(#103),#111);
|
||||
#103 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#104,#105,#106,#107,#108,#109
|
||||
,#110),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
|
||||
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
|
||||
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
|
||||
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
|
||||
'') );
|
||||
#104 = CARTESIAN_POINT('',(11.4,0.));
|
||||
#105 = CARTESIAN_POINT('',(11.4,-19.74537920628));
|
||||
#106 = CARTESIAN_POINT('',(-5.7,-9.872689603143));
|
||||
#107 = CARTESIAN_POINT('',(-22.8,-2.792194702056E-15));
|
||||
#108 = CARTESIAN_POINT('',(-5.7,9.872689603143));
|
||||
#109 = CARTESIAN_POINT('',(11.4,19.745379206285));
|
||||
#110 = CARTESIAN_POINT('',(11.4,0.));
|
||||
#111 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||
) );
|
||||
#112 = ORIENTED_EDGE('',*,*,#59,.F.);
|
||||
#113 = ADVANCED_FACE('',(#114),#43,.F.);
|
||||
#114 = FACE_BOUND('',#115,.F.);
|
||||
#115 = EDGE_LOOP('',(#116));
|
||||
#116 = ORIENTED_EDGE('',*,*,#21,.F.);
|
||||
#117 = ADVANCED_FACE('',(#118),#97,.F.);
|
||||
#118 = FACE_BOUND('',#119,.F.);
|
||||
#119 = EDGE_LOOP('',(#120));
|
||||
#120 = ORIENTED_EDGE('',*,*,#82,.F.);
|
||||
#121 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
|
||||
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#125)) GLOBAL_UNIT_ASSIGNED_CONTEXT
|
||||
((#122,#123,#124)) REPRESENTATION_CONTEXT('Context #1',
|
||||
'3D Context with UNIT and UNCERTAINTY') );
|
||||
#122 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
|
||||
#123 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
|
||||
#124 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
|
||||
#125 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#122,
|
||||
'distance_accuracy_value','confusion accuracy');
|
||||
#126 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7));
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -1780,7 +1780,7 @@ TEST_CASE("Sparse plane-path anchors match the printed infill", "[Fill][Internal
|
||||
const AABBTreeLines::LinesDistancer<Line> printed_tree(to_lines(printed));
|
||||
|
||||
// Orca: Exclude perimeter connections: anchoring and extrusion can trim those differently.
|
||||
const Polylines anchors = intersection_pl(layer.generate_sparse_infill_polylines_for_anchoring(nullptr, nullptr, nullptr),
|
||||
const Polylines anchors = intersection_pl(layer.generate_sparse_infill_polylines_for_anchoring(nullptr, nullptr),
|
||||
shrink(to_polygons(layer.lslices), scale_(3.)));
|
||||
REQUIRE_FALSE(anchors.empty());
|
||||
double max_distance = 0.;
|
||||
@@ -1792,8 +1792,9 @@ TEST_CASE("Sparse plane-path anchors match the printed infill", "[Fill][Internal
|
||||
CHECK(unscale<double>(max_distance) <= config.opt_float("resolution"));
|
||||
}
|
||||
|
||||
// Orca: Slices the meshes as the parts of one object, where they are.
|
||||
static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::vector<TriangleMesh> &parts)
|
||||
// Orca: Slices the meshes as the parts of one object, where they are, with modifiers of their own config.
|
||||
static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::vector<TriangleMesh> &parts,
|
||||
const std::vector<std::pair<TriangleMesh, DynamicPrintConfig>> &modifiers = {})
|
||||
{
|
||||
config.set_deserialize_strict({{"layer_height", 0.2},
|
||||
{"initial_layer_print_height", 0.2},
|
||||
@@ -1804,6 +1805,8 @@ static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::ve
|
||||
Slic3r::Test::init_print({parts.front()}, print, model, config, nullptr, false);
|
||||
for (size_t i = 1; i < parts.size(); ++ i)
|
||||
model.objects.front()->add_volume(TriangleMesh(parts[i]), ModelVolumeType::MODEL_PART, false);
|
||||
for (const auto &[mesh, modifier_config] : modifiers)
|
||||
model.objects.front()->add_volume(TriangleMesh(mesh), ModelVolumeType::PARAMETER_MODIFIER, false)->config.apply(modifier_config);
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
return print;
|
||||
@@ -1988,3 +1991,33 @@ TEST_CASE("Adaptive infill fills each body like the body sliced alone", "[Fill][
|
||||
CHECK(unmatched.first < 0.02);
|
||||
CHECK(unmatched.second < 0.02);
|
||||
}
|
||||
|
||||
TEST_CASE("Adaptive infill of a modifier leaves the density of the other regions", "[Fill][Regression]")
|
||||
{
|
||||
const std::string pattern = GENERATE("adaptivecubic", "supportcubic");
|
||||
CAPTURE(pattern);
|
||||
auto config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({{"sparse_infill_pattern", pattern},
|
||||
{"sparse_infill_density", "15%"},
|
||||
{"top_shell_layers", 0},
|
||||
{"bottom_shell_layers", 0}});
|
||||
TriangleMesh bodies = make_cube(30, 30, 6), second = make_cube(30, 30, 6);
|
||||
second.translate(40, 0, 0);
|
||||
bodies.merge(second);
|
||||
// Orca: A denser modifier over the right half of the second body.
|
||||
TriangleMesh modifier = make_cube(20, 40, 10);
|
||||
modifier.translate(55, -5, -2);
|
||||
DynamicPrintConfig dense = config;
|
||||
dense.set_deserialize_strict({{"sparse_infill_density", "60%"}});
|
||||
Print print, print_sparse, print_dense;
|
||||
slice_parts(print, config, {bodies}, {{modifier, dense}});
|
||||
slice_parts(print_sparse, config, {bodies});
|
||||
slice_parts(print_dense, dense, {bodies});
|
||||
|
||||
// Orca: Bed regions 3 mm inside the walls and the modifier, away from the links along them.
|
||||
auto rect = [](double x0, double y0, double x1, double y1) {
|
||||
return Polygon({Point::new_scale(x0, y0), Point::new_scale(x1, y0), Point::new_scale(x1, y1), Point::new_scale(x0, y1)});
|
||||
};
|
||||
CHECK(unmatched_between_prints(print, print_sparse, erInternalInfill, {rect(3, 3, 27, 27), rect(43, 3, 52, 27)}) < 0.02);
|
||||
CHECK(unmatched_between_prints(print, print_dense, erInternalInfill, {rect(58, 3, 67, 27)}) < 0.02);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_geometry.cpp
|
||||
test_multimaterial_segmentation.cpp
|
||||
test_placeholder_parser.cpp
|
||||
test_png_read_write.cpp
|
||||
test_polygon.cpp
|
||||
test_mutable_polygon.cpp
|
||||
test_mutable_priority_queue.cpp
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
|
||||
#include <catch2/matchers/catch_matchers.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
#include <vector>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
@@ -24,3 +26,39 @@ TEST_CASE("CGAL and TriangleMesh conversions", "[MeshBoolean]") {
|
||||
|
||||
REQUIRE(! MeshBoolean::cgal::does_self_intersect(M));
|
||||
}
|
||||
|
||||
TEST_CASE("mcut difference handles source splits between cuts", "[MeshBoolean]") {
|
||||
TriangleMesh body = make_cube(30., 10., 10.);
|
||||
|
||||
TriangleMesh tool;
|
||||
|
||||
// First cut splits the source into two disconnected components.
|
||||
TriangleMesh slab = make_cube(2., 12., 20.);
|
||||
slab.translate(Vec3f(14.f, -1.f, -5.f));
|
||||
its_merge(tool.its, slab.its);
|
||||
|
||||
// These cuts must still be applied after the source has been split.
|
||||
TriangleMesh left_hole = make_cube(4., 4., 20.);
|
||||
left_hole.translate(Vec3f(3.f, 3.f, -5.f));
|
||||
its_merge(tool.its, left_hole.its);
|
||||
|
||||
TriangleMesh right_hole = make_cube(4., 4., 20.);
|
||||
right_hole.translate(Vec3f(21.f, 3.f, -5.f));
|
||||
its_merge(tool.its, right_hole.its);
|
||||
|
||||
std::vector<TriangleMesh> result;
|
||||
MeshBoolean::mcut::make_boolean(body, tool, result, "A_NOT_B");
|
||||
|
||||
REQUIRE(result.size() == 1);
|
||||
|
||||
const std::vector<indexed_triangle_set> components =
|
||||
its_split(result.front().its);
|
||||
|
||||
REQUIRE(components.size() == 2);
|
||||
|
||||
// 3000 - 200 - 160 - 160 = 2480.
|
||||
REQUIRE_THAT(
|
||||
result.front().volume(),
|
||||
Catch::Matchers::WithinRel(2480., 1e-3)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "libslic3r/PNGReadWrite.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
// libpng reports a corrupt or truncated file by longjmp()ing out of the decoder, so the decoders have
|
||||
// to come back with false rather than crash or hand back a half filled image.
|
||||
namespace {
|
||||
|
||||
// A real PNG, produced by the writer next door, so the bytes are a file libpng accepts.
|
||||
std::vector<uint8_t> encoded_png(size_t w, size_t h)
|
||||
{
|
||||
std::vector<uint8_t> pixels(w * h);
|
||||
for (size_t i = 0; i < pixels.size(); ++ i)
|
||||
pixels[i] = uint8_t((i * 7) % 256);
|
||||
|
||||
const boost::filesystem::path path = boost::filesystem::temp_directory_path() /
|
||||
boost::filesystem::unique_path("png_rw_%%%%%%%%.png");
|
||||
REQUIRE(png::write_gray_to_file(path.string(), w, h, pixels));
|
||||
std::vector<uint8_t> bytes;
|
||||
{
|
||||
std::ifstream ifs(path.string(), std::ios::binary);
|
||||
bytes.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
|
||||
}
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(path, ec);
|
||||
REQUIRE(bytes.size() > 64);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
png::ReadBuf buf_of(const std::vector<uint8_t> &bytes, size_t size)
|
||||
{
|
||||
return png::ReadBuf{ bytes.data(), size };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("A whole PNG decodes", "[PNG]") {
|
||||
const std::vector<uint8_t> bytes = encoded_png(24, 16);
|
||||
|
||||
png::ImageGreyscale grey;
|
||||
REQUIRE(png::decode_png(buf_of(bytes, bytes.size()), grey));
|
||||
CHECK(grey.cols == 24);
|
||||
CHECK(grey.rows == 16);
|
||||
CHECK(grey.buf.size() == 24 * 16);
|
||||
}
|
||||
|
||||
TEST_CASE("A truncated PNG is refused instead of crashing", "[PNG]") {
|
||||
const std::vector<uint8_t> bytes = encoded_png(64, 64);
|
||||
|
||||
// Cut past the signature: inside the header, and inside the pixel data. Not in the trailing
|
||||
// chunks - decode_png() does not read those, so a file missing only its IEND still decodes, and
|
||||
// that is the pre-existing contract rather than anything this change touches.
|
||||
const size_t size = GENERATE_COPY(size_t(16), size_t(40), bytes.size() / 2, bytes.size() * 3 / 4);
|
||||
REQUIRE(size < bytes.size());
|
||||
|
||||
png::ImageGreyscale grey;
|
||||
CHECK_FALSE(png::decode_png(buf_of(bytes, size), grey));
|
||||
|
||||
png::ImageColorscale colour;
|
||||
CHECK_FALSE(png::decode_colored_png(buf_of(bytes, size), colour));
|
||||
}
|
||||
|
||||
TEST_CASE("A PNG whose body is garbage is refused", "[PNG]") {
|
||||
std::vector<uint8_t> bytes = encoded_png(32, 32);
|
||||
// Keep the signature, scribble over everything after it.
|
||||
for (size_t i = 8; i < bytes.size(); ++ i)
|
||||
bytes[i] = uint8_t(0xA5);
|
||||
|
||||
png::ImageGreyscale grey;
|
||||
CHECK_FALSE(png::decode_png(buf_of(bytes, bytes.size()), grey));
|
||||
|
||||
png::ImageColorscale colour;
|
||||
CHECK_FALSE(png::decode_colored_png(buf_of(bytes, bytes.size()), colour));
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Format/STEP.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
@@ -73,6 +74,23 @@ TEST_CASE("A security classification assignment does not crash import", "[Step]"
|
||||
CHECK(model.objects.front()->volumes.front()->mesh().facets_count() == 4); // a tetrahedron
|
||||
}
|
||||
|
||||
// The fixture is a truncated cone whose seam pcurves have the direction (2.1e-16, -1).
|
||||
TEST_CASE("A cone with a slightly tilted seam imports as a closed mesh", "[Step]")
|
||||
{
|
||||
const std::string path = std::string(TEST_DATA_DIR) + PATH_SEPARATOR "cone_tilted_seam_pcurve.step";
|
||||
|
||||
Model model;
|
||||
bool cancel = false;
|
||||
Step step(path);
|
||||
|
||||
REQUIRE(step.load() == Step::Step_Status::LOAD_SUCCESS);
|
||||
REQUIRE(step.mesh(&model, cancel, false) == Step::Step_Status::MESH_SUCCESS);
|
||||
|
||||
REQUIRE(model.objects.size() == 1);
|
||||
REQUIRE(model.objects.front()->volumes.size() == 1);
|
||||
CHECK(its_num_open_edges(model.objects.front()->volumes.front()->mesh().its) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("isUtf8 recognises two, three and four byte sequences", "[Step]")
|
||||
{
|
||||
CHECK(StepPreProcessor::isUtf8("\xC3\xA9")); // U+00E9
|
||||
|
||||
@@ -34,6 +34,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_plugin_sort.cpp
|
||||
test_plugin_cloud_metadata.cpp
|
||||
test_plugin_audit.cpp
|
||||
test_plugin_json_depth.cpp
|
||||
test_shortcuts.cpp
|
||||
test_file_url.cpp
|
||||
test_user_manager.cpp
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <slic3r/plugin/PluginFsUtils.hpp>
|
||||
#include <slic3r/plugin/PluginManager.hpp>
|
||||
#include <slic3r/plugin/PythonInterpreter.hpp>
|
||||
|
||||
#include "plugin_test_utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <utility>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <pybind11/embed.h>
|
||||
#include <pybind11/gil.h>
|
||||
#include <pybind11/pytypes.h>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
// Brings the embedded interpreter up for one test and tears it down before boost::log does,
|
||||
// mirroring the ScopedPluginManager idiom in the other plugin tests.
|
||||
struct ScopedPluginManager
|
||||
{
|
||||
ScopedDataDir python_data_dir{"plugin-json-depth"};
|
||||
bool initialized = PluginManager::instance().initialize();
|
||||
~ScopedPluginManager()
|
||||
{
|
||||
PluginManager::instance().shutdown();
|
||||
PythonInterpreter::instance().shutdown();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("py_to_json raises instead of overflowing on pathologically deep input", "[PluginHost][Python]")
|
||||
{
|
||||
ScopedPluginManager manager;
|
||||
REQUIRE(manager.initialized);
|
||||
namespace py = pybind11;
|
||||
py::gil_scoped_acquire gil;
|
||||
|
||||
// [[[ ... 0 ... ]]] nested 300 deep: past the 200 conversion-depth cap, but shallow enough
|
||||
// that the pre-fix code returns without crashing, so a regression fails cleanly rather than
|
||||
// taking the process down. Built in C++ so the test does not depend on Python builtins.
|
||||
py::object deep = py::int_(0);
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
py::list wrapper;
|
||||
wrapper.append(deep);
|
||||
deep = std::move(wrapper);
|
||||
}
|
||||
CHECK_THROWS_AS(py_to_json(deep), std::exception);
|
||||
}
|
||||
|
||||
TEST_CASE("py_to_json still converts reasonably nested input", "[PluginHost][Python]")
|
||||
{
|
||||
ScopedPluginManager manager;
|
||||
REQUIRE(manager.initialized);
|
||||
namespace py = pybind11;
|
||||
py::gil_scoped_acquire gil;
|
||||
|
||||
py::dict d;
|
||||
d["a"] = py::int_(1);
|
||||
py::list inner;
|
||||
inner.append(py::str("x"));
|
||||
inner.append(py::int_(2));
|
||||
d["b"] = inner;
|
||||
|
||||
const nlohmann::json j = py_to_json(d);
|
||||
CHECK(j.at("a").get<std::int64_t>() == 1);
|
||||
CHECK(j.at("b").at(0).get<std::string>() == "x");
|
||||
CHECK(j.at("b").at(1).get<std::int64_t>() == 2);
|
||||
}
|
||||
Reference in New Issue
Block a user