Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2021-09-01 13:28:10 +02:00
commit 639cf17e19
67 changed files with 1800 additions and 687 deletions

View File

@ -4,7 +4,7 @@
# PrusaSlicer # PrusaSlicer
You may want to check the [PrusaSlicer project page](https://www.prusa3d.com/prusaslicer/). You may want to check the [PrusaSlicer project page](https://www.prusa3d.com/prusaslicer/).
Prebuilt Windows, OSX and Linux binaries are available through the [git releases page](https://github.com/prusa3d/PrusaSlicer/releases) or from the [Prusa3D downloads page](https://www.prusa3d.com/drivers/). Prebuilt Windows, OSX and Linux binaries are available through the [git releases page](https://github.com/prusa3d/PrusaSlicer/releases) or from the [Prusa3D downloads page](https://www.prusa3d.com/drivers/). There are also [3rd party Linux builds available](https://github.com/prusa3d/PrusaSlicer/wiki/PrusaSlicer-on-Linux---binary-distributions).
PrusaSlicer takes 3D models (STL, OBJ, AMF) and converts them into G-code PrusaSlicer takes 3D models (STL, OBJ, AMF) and converts them into G-code
instructions for FFF printers or PNG layers for mSLA 3D printers. It's instructions for FFF printers or PNG layers for mSLA 3D printers. It's

View File

@ -11,6 +11,9 @@
* openssl * openssl
* nlopt * nlopt
* openvdb: This library depends on other libs, namely boost, zlib, openexr, blosc (not strictly), etc... * openvdb: This library depends on other libs, namely boost, zlib, openexr, blosc (not strictly), etc...
* CGAL: Needs additional dependencies
* MPFR
* GMP
## External libraries in source tree ## External libraries in source tree
* ad-mesh: Lots of customization, have to be bundled in the source tree. * ad-mesh: Lots of customization, have to be bundled in the source tree.

View File

@ -1,6 +1,12 @@
# Building PrusaSlicer on UNIX/Linux # Building PrusaSlicer on UNIX/Linux
Please understand that PrusaSlicer team cannot support compilation on all possible Linux distros. Namely, we cannot help trouble shooting OpenGL driver issues or dependency issues if compiled against distro provided libraries. We can only support PrusaSlicer compiled the same way we do compile PrusaSlicer for our [binary builds](https://github.com/prusa3d/PrusaSlicer/releases), that means linked statically agains the dependencies compiled with the `deps` scripts.
Instead of compiling PrusaSlicer from source code, one may consider to install PrusaSlicer [pre-compiled by contributors](https://github.com/prusa3d/PrusaSlicer/wiki/PrusaSlicer-on-Linux---binary-distributions).
### How to build
PrusaSlicer uses the CMake build system and requires several dependencies. PrusaSlicer uses the CMake build system and requires several dependencies.
The dependencies can be listed in the `deps` directory in individual subdirectories, although they don't necessarily need to be as recent The dependencies can be listed in the `deps` directory in individual subdirectories, although they don't necessarily need to be as recent
as the versions listed - generally versions available on conservative Linux distros such as Debian stable, Ubuntu LTS releases or Fedora are likely sufficient. as the versions listed - generally versions available on conservative Linux distros such as Debian stable, Ubuntu LTS releases or Fedora are likely sufficient.
@ -32,10 +38,11 @@ Note: We say _mostly independent_ because it's still expected the system will pr
To do this, go to the `deps` directory, create a `build` subdirectory (or the like) and use: To do this, go to the `deps` directory, create a `build` subdirectory (or the like) and use:
cmake .. -DDESTDIR=<target destdir> cmake .. -DDESTDIR=<target destdir> -DDEP_DOWNLOAD_DIR=<download cache dir>
where the target destdir is a directory of your choosing where the dependencies will be installed. where the target destdir is a directory of your choosing where the dependencies will be installed.
You can also omit the `DESTDIR` option to use the default, in that case the `destdir` will be created inside the `build` directory where `cmake` is run. You can also omit the `DESTDIR` option to use the default, in that case the `destdir` will be created inside the `build` directory where `cmake` is run. The optional `DEP_DOWNLOAD_DIR` argument specifies a directory to cache the downloaded
source packages for each dependent library. Can be useful for repeated builds, to avoid unnecessary network traffic.
Once the dependencies have been built, in order to pass the destdir path to the **top-level** PrusaSlicer `CMakeLists.txt` script, use the `CMAKE_PREFIX_PATH` option along with turning on `SLIC3R_STATIC`: Once the dependencies have been built, in order to pass the destdir path to the **top-level** PrusaSlicer `CMakeLists.txt` script, use the `CMAKE_PREFIX_PATH` option along with turning on `SLIC3R_STATIC`:
@ -87,3 +94,13 @@ If you instead want PrusaSlicer installed in a structure according to the File S
This will make PrusaSlicer look for a fixed-location `share/slic3r-prusa3d` directory instead (note that the location becomes hardcoded). This will make PrusaSlicer look for a fixed-location `share/slic3r-prusa3d` directory instead (note that the location becomes hardcoded).
You can then use the `make install` target to install PrusaSlicer. You can then use the `make install` target to install PrusaSlicer.
### Desktop Integration (PrusaSlicer 2.4 and newer)
If PrusaSlicer is to be distributed as an AppImage or a binary blob (.tar.gz and similar), then a desktop integration support is compiled in by default: PrusaSlicer will offer to integrate with desktop by manually copying the desktop file and application icon into user's desktop configuration. The built-in desktop integration is also handy on Crosstini (Linux on Chrome OS).
If PrusaSlicer is compiled with `SLIC3R_FHS` enabled, then a desktop integration support will not be integrated. One may want to disable desktop integration by running
cmake .. -DSLIC3R_DESKTOP_INTEGRATION=0
when building PrusaSlicer for flatpack or snap, where the desktop integration is performed by the installer.

View File

@ -1,5 +1,5 @@
min_slic3r_version = 2.4.0-alpha0 min_slic3r_version = 2.4.0-alpha0
1.4.0-alpha7 Updated brim_offset value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles. 1.4.0-alpha7 Updated brim_separation value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles.
1.4.0-alpha6 Added nozzle priming after M600. Added nozzle diameter checks for 0.8 nozzle printer profiles. Updated FW version. Increased number of top solid infill layers (0.2 layer height). 1.4.0-alpha6 Added nozzle priming after M600. Added nozzle diameter checks for 0.8 nozzle printer profiles. Updated FW version. Increased number of top solid infill layers (0.2 layer height).
1.4.0-alpha5 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S). 1.4.0-alpha5 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S).
1.4.0-alpha4 Decreased Area Fill (SL1S). 1.4.0-alpha4 Decreased Area Fill (SL1S).

View File

@ -144,7 +144,7 @@ bridge_angle = 0
bridge_flow_ratio = 1 bridge_flow_ratio = 1
bridge_speed = 25 bridge_speed = 25
brim_width = 0 brim_width = 0
brim_offset = 0.1 brim_separation = 0.1
clip_multipart_objects = 1 clip_multipart_objects = 1
compatible_printers = compatible_printers =
complete_objects = 0 complete_objects = 0

View File

@ -0,0 +1,12 @@
#version 110
uniform vec4 uniform_color;
uniform float emission_factor;
// x = tainted, y = specular;
varying vec2 intensity;
void main()
{
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a);
}

View File

@ -0,0 +1,46 @@
#version 110
#define INTENSITY_CORRECTION 0.6
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
#define LIGHT_TOP_SHININESS 20.0
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
#define INTENSITY_AMBIENT 0.3
// vertex attributes
attribute vec3 v_position;
attribute vec3 v_normal;
// instance attributes
attribute vec3 i_offset;
attribute vec2 i_scales;
// x = tainted, y = specular;
varying vec2 intensity;
void main()
{
// First transform the normal into camera space and normalize the result.
vec3 eye_normal = normalize(gl_NormalMatrix * v_normal);
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec4 world_position = vec4(v_position * vec3(vec2(i_scales.x), i_scales.y) + i_offset, 1.0);
vec3 eye_position = (gl_ModelViewMatrix * world_position).xyz;
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
// Perform the same lighting calculation for the 2nd light source (no specular applied).
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
gl_Position = gl_ProjectionMatrix * vec4(eye_position, 1.0);
}

View File

@ -15,11 +15,6 @@
#include "Utils.hpp" // for next_highest_power_of_2() #include "Utils.hpp" // for next_highest_power_of_2()
extern "C"
{
// Ray-Triangle Intersection Test Routines by Tomas Moller, May 2000
#include <igl/raytri.c>
}
// Definition of the ray intersection hit structure. // Definition of the ray intersection hit structure.
#include <igl/Hit.h> #include <igl/Hit.h>
@ -231,6 +226,9 @@ namespace detail {
const VectorType origin; const VectorType origin;
const VectorType dir; const VectorType dir;
const VectorType invdir; const VectorType invdir;
// epsilon for ray-triangle intersection, see intersect_triangle1()
const double eps;
}; };
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType> template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
@ -283,44 +281,91 @@ namespace detail {
return tmin < t1 && tmax > t0; return tmin < t1 && tmax > t0;
} }
// The following intersect_triangle() is derived from raytri.c routine intersect_triangle1()
// Ray-Triangle Intersection Test Routines
// Different optimizations of my and Ben Trumbore's
// code from journals of graphics tools (JGT)
// http://www.acm.org/jgt/
// by Tomas Moller, May 2000
template<typename V, typename W> template<typename V, typename W>
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool> std::enable_if_t<std::is_same<typename V::Scalar, double>::value&& std::is_same<typename W::Scalar, double>::value, bool>
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { intersect_triangle(const V &orig, const V &dir, const W &vert0, const W &vert1, const W &vert2, double &t, double &u, double &v, double eps)
return intersect_triangle1(const_cast<double*>(origin.data()), const_cast<double*>(dir.data()), {
const_cast<double*>(v0.data()), const_cast<double*>(v1.data()), const_cast<double*>(v2.data()), // find vectors for two edges sharing vert0
&t, &u, &v); const V edge1 = vert1 - vert0;
const V edge2 = vert2 - vert0;
// begin calculating determinant - also used to calculate U parameter
const V pvec = dir.cross(edge2);
// if determinant is near zero, ray lies in plane of triangle
const double det = edge1.dot(pvec);
V qvec;
if (det > eps) {
// calculate distance from vert0 to ray origin
V tvec = orig - vert0;
// calculate U parameter and test bounds
u = tvec.dot(pvec);
if (u < 0.0 || u > det)
return false;
// prepare to test V parameter
qvec = tvec.cross(edge1);
// calculate V parameter and test bounds
v = dir.dot(qvec);
if (v < 0.0 || u + v > det)
return false;
} else if (det < -eps) {
// calculate distance from vert0 to ray origin
V tvec = orig - vert0;
// calculate U parameter and test bounds
u = tvec.dot(pvec);
if (u > 0.0 || u < det)
return false;
// prepare to test V parameter
qvec = tvec.cross(edge1);
// calculate V parameter and test bounds
v = dir.dot(qvec);
if (v > 0.0 || u + v < det)
return false;
} else
// ray is parallel to the plane of the triangle
return false;
double inv_det = 1.0 / det;
// calculate t, ray intersects triangle
t = edge2.dot(qvec) * inv_det;
u *= inv_det;
v *= inv_det;
return true;
} }
template<typename V, typename W> template<typename V, typename W>
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && !std::is_same<typename W::Scalar, double>::value, bool> std::enable_if_t<std::is_same<typename V::Scalar, double>::value && !std::is_same<typename W::Scalar, double>::value, bool>
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
using Vector = Eigen::Matrix<double, 3, 1>; return intersect_triangle(origin, dir, v0.template cast<double>(), v1.template cast<double>(), v2.template cast<double>(), t, u, v, eps);
Vector w0 = v0.template cast<double>();
Vector w1 = v1.template cast<double>();
Vector w2 = v2.template cast<double>();
return intersect_triangle1(const_cast<double*>(origin.data()), const_cast<double*>(dir.data()),
w0.data(), w1.data(), w2.data(), &t, &u, &v);
} }
template<typename V, typename W> template<typename V, typename W>
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool> std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool>
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
using Vector = Eigen::Matrix<double, 3, 1>; return intersect_triangle(origin.template cast<double>(), dir.template cast<double>(), v0, v1, v2, t, u, v, eps);
Vector o = origin.template cast<double>();
Vector d = dir.template cast<double>();
return intersect_triangle1(o.data(), d.data(), const_cast<double*>(v0.data()), const_cast<double*>(v1.data()), const_cast<double*>(v2.data()), &t, &u, &v);
} }
template<typename V, typename W> template<typename V, typename W>
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && ! std::is_same<typename W::Scalar, double>::value, bool> std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && ! std::is_same<typename W::Scalar, double>::value, bool>
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
using Vector = Eigen::Matrix<double, 3, 1>; return intersect_triangle(origin.template cast<double>(), dir.template cast<double>(), v0.template cast<double>(), v1.template cast<double>(), v2.template cast<double>(), t, u, v, eps);
Vector o = origin.template cast<double>(); }
Vector d = dir.template cast<double>();
Vector w0 = v0.template cast<double>(); template<typename Tree>
Vector w1 = v1.template cast<double>(); double intersect_triangle_epsilon(const Tree &tree) {
Vector w2 = v2.template cast<double>(); double eps = 0.000001;
return intersect_triangle1(o.data(), d.data(), w0.data(), w1.data(), w2.data(), &t, &u, &v); if (! tree.empty()) {
const typename Tree::BoundingBox &bbox = tree.nodes().front().bbox;
double l = (bbox.max() - bbox.min()).cwiseMax();
if (l > 0)
eps /= (l * l);
}
return eps;
} }
template<typename RayIntersectorType, typename Scalar> template<typename RayIntersectorType, typename Scalar>
@ -343,7 +388,7 @@ namespace detail {
if (intersect_triangle( if (intersect_triangle(
ray_intersector.origin, ray_intersector.dir, ray_intersector.origin, ray_intersector.dir,
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
t, u, v) t, u, v, ray_intersector.eps)
&& t > 0.) { && t > 0.) {
hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) }; hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) };
return true; return true;
@ -388,7 +433,7 @@ namespace detail {
if (intersect_triangle( if (intersect_triangle(
ray_intersector.origin, ray_intersector.dir, ray_intersector.origin, ray_intersector.dir,
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
t, u, v) t, u, v, ray_intersector.eps)
&& t > 0.) { && t > 0.) {
ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) }); ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) });
} }
@ -623,12 +668,15 @@ inline bool intersect_ray_first_hit(
// Direction of the ray. // Direction of the ray.
const VectorType &dir, const VectorType &dir,
// First intersection of the ray with the indexed triangle set. // First intersection of the ray with the indexed triangle set.
igl::Hit &hit) igl::Hit &hit,
// Epsilon for the ray-triangle intersection, it should be proportional to an average triangle edge length.
const double eps = 0.000001)
{ {
using Scalar = typename VectorType::Scalar; using Scalar = typename VectorType::Scalar;
auto ray_intersector = detail::RayIntersector<VertexType, IndexedFaceType, TreeType, VectorType> { auto ray_intersector = detail::RayIntersector<VertexType, IndexedFaceType, TreeType, VectorType> {
vertices, faces, tree, vertices, faces, tree,
origin, dir, VectorType(dir.cwiseInverse()) origin, dir, VectorType(dir.cwiseInverse()),
eps
}; };
return ! tree.empty() && detail::intersect_ray_recursive_first_hit( return ! tree.empty() && detail::intersect_ray_recursive_first_hit(
ray_intersector, size_t(0), std::numeric_limits<Scalar>::infinity(), hit); ray_intersector, size_t(0), std::numeric_limits<Scalar>::infinity(), hit);
@ -652,11 +700,14 @@ inline bool intersect_ray_all_hits(
// Direction of the ray. // Direction of the ray.
const VectorType &dir, const VectorType &dir,
// All intersections of the ray with the indexed triangle set, sorted by parameter t. // All intersections of the ray with the indexed triangle set, sorted by parameter t.
std::vector<igl::Hit> &hits) std::vector<igl::Hit> &hits,
// Epsilon for the ray-triangle intersection, it should be proportional to an average triangle edge length.
const double eps = 0.000001)
{ {
auto ray_intersector = detail::RayIntersectorHits<VertexType, IndexedFaceType, TreeType, VectorType> { auto ray_intersector = detail::RayIntersectorHits<VertexType, IndexedFaceType, TreeType, VectorType> {
{ vertices, faces, {tree}, { vertices, faces, {tree},
origin, dir, VectorType(dir.cwiseInverse()) } origin, dir, VectorType(dir.cwiseInverse()),
eps }
}; };
if (! tree.empty()) { if (! tree.empty()) {
ray_intersector.hits.reserve(8); ray_intersector.hits.reserve(8);

View File

@ -134,10 +134,10 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev
Polygons islands; Polygons islands;
for (const PrintObject *object : top_level_objects_with_brim) { for (const PrintObject *object : top_level_objects_with_brim) {
//FIXME how about the brim type? //FIXME how about the brim type?
auto brim_offset = float(scale_(object->config().brim_offset.value)); auto brim_separation = float(scale_(object->config().brim_separation.value));
Polygons islands_object; Polygons islands_object;
for (const ExPolygon &ex_poly : get_print_object_bottom_layer_expolygons(*object)) { for (const ExPolygon &ex_poly : get_print_object_bottom_layer_expolygons(*object)) {
Polygons contour_offset = offset(ex_poly.contour, brim_offset); Polygons contour_offset = offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare);
for (Polygon &poly : contour_offset) for (Polygon &poly : contour_offset)
poly.douglas_peucker(SCALED_RESOLUTION); poly.douglas_peucker(SCALED_RESOLUTION);
@ -166,7 +166,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) { for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
const PrintObject *object = print.objects()[print_object_idx]; const PrintObject *object = print.objects()[print_object_idx];
const BrimType brim_type = object->config().brim_type.value; const BrimType brim_type = object->config().brim_type.value;
const float brim_offset = scale_(object->config().brim_offset.value); const float brim_separation = scale_(object->config().brim_separation.value);
const float brim_width = scale_(object->config().brim_width.value); const float brim_width = scale_(object->config().brim_width.value);
const bool is_top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end(); const bool is_top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
@ -174,16 +174,16 @@ static ExPolygons top_level_outer_brim_area(const Print &print
ExPolygons no_brim_area_object; ExPolygons no_brim_area_object;
for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) { for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) {
if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) && is_top_outer_brim) if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) && is_top_outer_brim)
append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset))); append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare)));
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset)); append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare));
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes)); append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
if (brim_type != BrimType::btNoBrim) if (brim_type != BrimType::btNoBrim)
append(no_brim_area_object, offset_ex(ExPolygon(ex_poly.contour), brim_offset)); append(no_brim_area_object, offset_ex(ExPolygon(ex_poly.contour), brim_separation, ClipperLib::jtSquare));
no_brim_area_object.emplace_back(ex_poly.contour); no_brim_area_object.emplace_back(ex_poly.contour);
} }
@ -214,7 +214,7 @@ static ExPolygons inner_brim_area(const Print &print,
for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) { for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) {
const PrintObject *object = print.objects()[print_object_idx]; const PrintObject *object = print.objects()[print_object_idx];
const BrimType brim_type = object->config().brim_type.value; const BrimType brim_type = object->config().brim_type.value;
const float brim_offset = scale_(object->config().brim_offset.value); const float brim_separation = scale_(object->config().brim_separation.value);
const float brim_width = scale_(object->config().brim_width.value); const float brim_width = scale_(object->config().brim_width.value);
const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end(); const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
@ -226,21 +226,21 @@ static ExPolygons inner_brim_area(const Print &print,
if (top_outer_brim) if (top_outer_brim)
no_brim_area_object.emplace_back(ex_poly); no_brim_area_object.emplace_back(ex_poly);
else else
append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset))); append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare)));
} }
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner) if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner)
append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset))); append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_separation, ClipperLib::jtSquare), offset_ex(ex_poly.holes, -brim_width - brim_separation, ClipperLib::jtSquare)));
if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes)); append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes));
if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim)
append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset)); append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare));
append(holes_object, ex_poly.holes); append(holes_object, ex_poly.holes);
} }
append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_offset)); append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_separation, ClipperLib::jtSquare));
for (const PrintInstance &instance : object->instances()) { for (const PrintInstance &instance : object->instances()) {
append_and_translate(brim_area, brim_area_object, instance); append_and_translate(brim_area, brim_area_object, instance);
@ -356,12 +356,12 @@ static void make_inner_brim(const Print &print,
Flow flow = print.brim_flow(); Flow flow = print.brim_flow();
ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing())); ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing()));
Polygons loops; Polygons loops;
islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), jtSquare); islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), ClipperLib::jtSquare);
for (size_t i = 0; !islands_ex.empty(); ++i) { for (size_t i = 0; !islands_ex.empty(); ++i) {
for (ExPolygon &poly_ex : islands_ex) for (ExPolygon &poly_ex : islands_ex)
poly_ex.douglas_peucker(SCALED_RESOLUTION); poly_ex.douglas_peucker(SCALED_RESOLUTION);
polygons_append(loops, to_polygons(islands_ex)); polygons_append(loops, to_polygons(islands_ex));
islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), jtSquare); islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), ClipperLib::jtSquare);
} }
loops = union_pt_chained_outside_in(loops); loops = union_pt_chained_outside_in(loops);
@ -385,7 +385,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing())); size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing()));
for (size_t i = 0; i < num_loops; ++i) { for (size_t i = 0; i < num_loops; ++i) {
try_cancel(); try_cancel();
islands = offset(islands, float(flow.scaled_spacing()), jtSquare); islands = offset(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare);
for (Polygon &poly : islands) for (Polygon &poly : islands)
poly.douglas_peucker(SCALED_RESOLUTION); poly.douglas_peucker(SCALED_RESOLUTION);
polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing())));

View File

@ -696,10 +696,8 @@ ConfigSubstitutions ConfigBase::load_from_ini_string_commented(std::string &&dat
for (size_t i = 0; i < data.size();) for (size_t i = 0; i < data.size();)
if (i == 0 || data[i] == '\n') { if (i == 0 || data[i] == '\n') {
// Start of a line. // Start of a line.
if (i != 0) { if (data[i] == '\n') {
// Consume LF. // Consume LF, don't keep empty lines.
assert(data[i] == '\n');
// Don't keep empty lines.
if (j > 0 && data[j - 1] != '\n') if (j > 0 && data[j - 1] != '\n')
data[j ++] = data[i]; data[j ++] = data[i];
++ i; ++ i;

View File

@ -49,6 +49,17 @@ const unsigned int VERSION_3MF = 1;
const unsigned int VERSION_3MF_COMPATIBLE = 2; const unsigned int VERSION_3MF_COMPATIBLE = 2;
const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file
// Painting gizmos data version numbers
// 0 : 3MF files saved by older PrusaSlicer or the painting gizmo wasn't used. No version definition in them.
// 1 : Introduction of painting gizmos data versioning. No other changes in painting gizmos data.
const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 1;
const unsigned int SEAM_PAINTING_VERSION = 1;
const unsigned int MM_PAINTING_VERSION = 1;
const std::string SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION = "slic3rpe:FdmSupportsPaintingVersion";
const std::string SLIC3RPE_SEAM_PAINTING_VERSION = "slic3rpe:SeamPaintingVersion";
const std::string SLIC3RPE_MM_PAINTING_VERSION = "slic3rpe:MmPaintingVersion";
const std::string MODEL_FOLDER = "3D/"; const std::string MODEL_FOLDER = "3D/";
const std::string MODEL_EXTENSION = ".model"; const std::string MODEL_EXTENSION = ".model";
const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA
@ -393,6 +404,10 @@ namespace Slic3r {
unsigned int m_version; unsigned int m_version;
bool m_check_version; bool m_check_version;
unsigned int m_fdm_supports_painting_version = 0;
unsigned int m_seam_painting_version = 0;
unsigned int m_mm_painting_version = 0;
XML_Parser m_xml_parser; XML_Parser m_xml_parser;
// Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state
// after returning from XML_Parse() function, thus we keep the error state here. // after returning from XML_Parse() function, thus we keep the error state here.
@ -420,6 +435,7 @@ namespace Slic3r {
~_3MF_Importer(); ~_3MF_Importer();
bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version); bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version);
unsigned int version() const { return m_version; }
private: private:
void _destroy_xml_parser(); void _destroy_xml_parser();
@ -542,6 +558,9 @@ namespace Slic3r {
bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version) bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version)
{ {
m_version = 0; m_version = 0;
m_fdm_supports_painting_version = 0;
m_seam_painting_version = 0;
m_mm_painting_version = 0;
m_check_version = check_version; m_check_version = check_version;
m_model = &model; m_model = &model;
m_unit_factor = 1.0f; m_unit_factor = 1.0f;
@ -1668,6 +1687,12 @@ namespace Slic3r {
return true; return true;
} }
inline static void check_painting_version(unsigned int loaded_version, unsigned int highest_supported_version, const std::string &error_msg)
{
if (loaded_version > highest_supported_version)
throw version_error(error_msg);
}
bool _3MF_Importer::_handle_end_metadata() bool _3MF_Importer::_handle_end_metadata()
{ {
if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) { if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) {
@ -1680,6 +1705,24 @@ namespace Slic3r {
} }
} }
if (m_curr_metadata_name == SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION) {
m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str());
check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION,
_(L("The selected 3MF contains FDM supports painted object using a newer version of PrusaSlicer and is not compatible.")));
}
if (m_curr_metadata_name == SLIC3RPE_SEAM_PAINTING_VERSION) {
m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str());
check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION,
_(L("The selected 3MF contains seam painted object using a newer version of PrusaSlicer and is not compatible.")));
}
if (m_curr_metadata_name == SLIC3RPE_MM_PAINTING_VERSION) {
m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str());
check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION,
_(L("The selected 3MF contains multi-material painted object using a newer version of PrusaSlicer and is not compatible.")));
}
return true; return true;
} }
@ -1837,6 +1880,7 @@ namespace Slic3r {
} }
unsigned int geo_tri_count = (unsigned int)geometry.triangles.size() / 3; unsigned int geo_tri_count = (unsigned int)geometry.triangles.size() / 3;
unsigned int renamed_volumes_count = 0;
for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) {
if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) { if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) {
@ -1846,11 +1890,17 @@ namespace Slic3r {
Transform3d volume_matrix_to_object = Transform3d::Identity(); Transform3d volume_matrix_to_object = Transform3d::Identity();
bool has_transform = false; bool has_transform = false;
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
bool is_left_handed = false;
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
// extract the volume transformation from the volume's metadata, if present // extract the volume transformation from the volume's metadata, if present
for (const Metadata& metadata : volume_data.metadata) { for (const Metadata& metadata : volume_data.metadata) {
if (metadata.key == MATRIX_KEY) { if (metadata.key == MATRIX_KEY) {
volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value);
has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10);
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
is_left_handed = Slic3r::Geometry::Transformation(volume_matrix_to_object).is_left_handed();
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
break; break;
} }
} }
@ -1882,6 +1932,13 @@ namespace Slic3r {
stl_get_size(&stl); stl_get_size(&stl);
triangle_mesh.repair(); triangle_mesh.repair();
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
// PrusaSlicer older than 2.4.0 saved mirrored volumes with reversed winding of the triangles
// This caused the call to TriangleMesh::repair() to reverse all the facets because the calculated volume was negative
if (is_left_handed && stl.stats.facets_reversed > 0 && stl.stats.facets_reversed == stl.stats.original_num_facets)
stl.stats.facets_reversed = 0;
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
if (m_version == 0) { if (m_version == 0) {
// if the 3mf was not produced by PrusaSlicer and there is only one instance, // if the 3mf was not produced by PrusaSlicer and there is only one instance,
// bake the transformation into the geometry to allow the reload from disk command // bake the transformation into the geometry to allow the reload from disk command
@ -1945,6 +2002,14 @@ namespace Slic3r {
else else
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
} }
// this may happen for 3mf saved by 3rd part softwares
if (volume->name.empty()) {
volume->name = object.name;
if (renamed_volumes_count > 0)
volume->name += "_" + std::to_string(renamed_volumes_count + 1);
++renamed_volumes_count;
}
} }
return true; return true;
@ -2271,6 +2336,16 @@ namespace Slic3r {
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n";
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "</" << METADATA_TAG << ">\n"; stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "</" << METADATA_TAG << ">\n";
if (model.is_fdm_support_painted())
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
if (model.is_seam_painted())
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
if (model.is_mm_painted())
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
std::string name = xml_escape(boost::filesystem::path(filename).stem().string()); std::string name = xml_escape(boost::filesystem::path(filename).stem().string());
stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "</" << METADATA_TAG << ">\n"; stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"Designer\">" << "</" << METADATA_TAG << ">\n"; stream << " <" << METADATA_TAG << " name=\"Designer\">" << "</" << METADATA_TAG << ">\n";
@ -2506,6 +2581,10 @@ namespace Slic3r {
if (volume == nullptr) if (volume == nullptr)
continue; continue;
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
bool is_left_handed = volume->is_left_handed();
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume);
assert(volume_it != volumes_offsets.end()); assert(volume_it != volumes_offsets.end());
@ -2520,6 +2599,15 @@ namespace Slic3r {
{ {
const Vec3i &idx = its.indices[i]; const Vec3i &idx = its.indices[i];
char *ptr = buf; char *ptr = buf;
#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG <<
" v1=\"" << boost::spirit::int_ <<
"\" v2=\"" << boost::spirit::int_ <<
"\" v3=\"" << boost::spirit::int_ << "\"",
idx[is_left_handed ? 2 : 0] + volume_it->second.first_vertex_id,
idx[1] + volume_it->second.first_vertex_id,
idx[is_left_handed ? 0 : 2] + volume_it->second.first_vertex_id);
#else
boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG <<
" v1=\"" << boost::spirit::int_ << " v1=\"" << boost::spirit::int_ <<
"\" v2=\"" << boost::spirit::int_ << "\" v2=\"" << boost::spirit::int_ <<
@ -2527,6 +2615,7 @@ namespace Slic3r {
idx[0] + volume_it->second.first_vertex_id, idx[0] + volume_it->second.first_vertex_id,
idx[1] + volume_it->second.first_vertex_id, idx[1] + volume_it->second.first_vertex_id,
idx[2] + volume_it->second.first_vertex_id); idx[2] + volume_it->second.first_vertex_id);
#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT
*ptr = '\0'; *ptr = '\0';
output_buffer += buf; output_buffer += buf;
} }
@ -2961,6 +3050,19 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
return true; return true;
} }
// Perform conversions based on the config values available.
//FIXME provide a version of PrusaSlicer that stored the project file (3MF).
static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config)
{
if (! config.has("brim_separation")) {
if (auto *opt_elephant_foot = config.option<ConfigOptionFloat>("elefant_foot_compensation", false); opt_elephant_foot) {
// Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation.
auto *opt_brim_separation = config.option<ConfigOptionFloat>("brim_separation", true);
opt_brim_separation->value = opt_elephant_foot->value;
}
}
}
bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version)
{ {
if (path == nullptr || model == nullptr) if (path == nullptr || model == nullptr)
@ -2971,6 +3073,7 @@ bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionCo
_3MF_Importer importer; _3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
importer.log_errors(); importer.log_errors();
handle_legacy_project_loaded(importer.version(), config);
return res; return res;
} }

View File

@ -13,6 +13,7 @@
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "libslic3r.h" #include "libslic3r.h"
#include "LocalesUtils.hpp" #include "LocalesUtils.hpp"
#include "libslic3r/format.hpp"
#include <algorithm> #include <algorithm>
#include <cstdlib> #include <cstdlib>
@ -512,7 +513,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
|| (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions());
// Check that there are extrusions on the very first layer. // Check that there are extrusions on the very first layer. The case with empty
// first layer may result in skirt/brim in the air and maybe other issues.
if (layers_to_print.size() == 1u) { if (layers_to_print.size() == 1u) {
if (!has_extrusions) if (!has_extrusions)
throw Slic3r::SlicingError(_(L("There is an object with no extrusions in the first layer.")) + "\n" + throw Slic3r::SlicingError(_(L("There is an object with no extrusions in the first layer.")) + "\n" +
@ -534,11 +536,12 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) { if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) {
const_cast<Print*>(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, const_cast<Print*>(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
_(L("Empty layers detected. Make sure the object is printable.")) + "\n" + Slic3r::format(_(L("Empty layer detected between heights %1% and %2%. Make sure the object is printable.")),
_(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.),
std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " layers_to_print.back().print_z())
"usually caused by negligibly small extrusions or by a faulty model. Try to repair " + "\n" + Slic3r::format(_(L("Object name: %1%")), object.model_object()->name) + "\n\n"
"the model or change its orientation on the bed."))); + _(L("This is usually caused by negligibly small extrusions or by a faulty model. "
"Try to repair the model or change its orientation on the bed.")));
} }
// Remember last layer with extrusions. // Remember last layer with extrusions.
@ -1974,6 +1977,7 @@ void GCode::process_layer(
} }
gcode += this->change_layer(print_z); // this will increase m_layer_index gcode += this->change_layer(print_z); // this will increase m_layer_index
m_layer = &layer; m_layer = &layer;
m_object_layer_over_raft = false;
if (! print.config().layer_gcode.value.empty()) { if (! print.config().layer_gcode.value.empty()) {
DynamicConfig config; DynamicConfig config;
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
@ -2232,8 +2236,13 @@ void GCode::process_layer(
gcode+="; PURGING FINISHED\n"; gcode+="; PURGING FINISHED\n";
for (InstanceToPrint &instance_to_print : instances_to_print) { for (InstanceToPrint &instance_to_print : instances_to_print) {
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
// To control print speed of the 1st object layer printed over raft interface.
bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 &&
instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id();
m_config.apply(instance_to_print.print_object.config(), true); m_config.apply(instance_to_print.print_object.config(), true);
m_layer = layers[instance_to_print.layer_id].layer(); m_layer = layer_to_print.layer();
m_object_layer_over_raft = object_layer_over_raft;
if (m_config.avoid_crossing_perimeters) if (m_config.avoid_crossing_perimeters)
m_avoid_crossing_perimeters.init_layer(*m_layer); m_avoid_crossing_perimeters.init_layer(*m_layer);
if (this->config().gcode_label_objects) if (this->config().gcode_label_objects)
@ -2246,11 +2255,13 @@ void GCode::process_layer(
m_last_obj_copy = this_object_copy; m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset)); this->set_origin(unscale(offset));
if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
m_layer = layers[instance_to_print.layer_id].support_layer; m_layer = layer_to_print.support_layer;
m_object_layer_over_raft = false;
gcode += this->extrude_support( gcode += this->extrude_support(
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role)); instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role));
m_layer = layers[instance_to_print.layer_id].layer(); m_layer = layer_to_print.layer();
m_object_layer_over_raft = object_layer_over_raft;
} }
//FIXME order islands? //FIXME order islands?
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
@ -2702,6 +2713,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
double acceleration; double acceleration;
if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) { if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) {
acceleration = m_config.first_layer_acceleration.value; acceleration = m_config.first_layer_acceleration.value;
} else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) {
acceleration = m_config.first_layer_acceleration_over_raft.value;
} else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) { } else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) {
acceleration = m_config.perimeter_acceleration.value; acceleration = m_config.perimeter_acceleration.value;
} else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) { } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) {
@ -2746,6 +2759,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
speed = m_volumetric_speed / path.mm3_per_mm; speed = m_volumetric_speed / path.mm3_per_mm;
if (this->on_first_layer()) if (this->on_first_layer())
speed = m_config.get_abs_value("first_layer_speed", speed); speed = m_config.get_abs_value("first_layer_speed", speed);
else if (this->object_layer_over_raft())
speed = m_config.get_abs_value("first_layer_speed_over_raft", speed);
if (m_config.max_volumetric_speed.value > 0) { if (m_config.max_volumetric_speed.value > 0) {
// cap speed with max_volumetric_speed anyway (even if user is not using autospeed) // cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
speed = std::min( speed = std::min(

View File

@ -126,6 +126,7 @@ public:
m_layer_count(0), m_layer_count(0),
m_layer_index(-1), m_layer_index(-1),
m_layer(nullptr), m_layer(nullptr),
m_object_layer_over_raft(false),
m_volumetric_speed(0), m_volumetric_speed(0),
m_last_pos_defined(false), m_last_pos_defined(false),
m_last_extrusion_role(erNone), m_last_extrusion_role(erNone),
@ -138,7 +139,7 @@ public:
m_silent_time_estimator_enabled(false), m_silent_time_estimator_enabled(false),
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max())) m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
{} {}
~GCode() {} ~GCode() = default;
// throws std::runtime_exception on error, // throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled(). // throws CanceledException through print->throw_if_canceled().
@ -316,9 +317,11 @@ private:
unsigned int m_layer_count; unsigned int m_layer_count;
// Progress bar indicator. Increments from -1 up to layer_count. // Progress bar indicator. Increments from -1 up to layer_count.
int m_layer_index; int m_layer_index;
// Current layer processed. Insequential printing mode, only a single copy will be printed. // Current layer processed. In sequential printing mode, only a single copy will be printed.
// In non-sequential mode, all its copies will be printed. // In non-sequential mode, all its copies will be printed.
const Layer* m_layer; const Layer* m_layer;
// m_layer is an object layer and it is being printed over raft surface.
bool m_object_layer_over_raft;
double m_volumetric_speed; double m_volumetric_speed;
// Support for the extrusion role markers. Which marker is active? // Support for the extrusion role markers. Which marker is active?
ExtrusionRole m_last_extrusion_role; ExtrusionRole m_last_extrusion_role;
@ -373,6 +376,8 @@ private:
void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
// On the first printing layer. This flag triggers first layer speeds. // On the first printing layer. This flag triggers first layer speeds.
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; }
// To control print speed of 1st object layer over raft interface.
bool object_layer_over_raft() const { return m_object_layer_over_raft; }
friend ObjectByExtruder& object_by_extruder( friend ObjectByExtruder& object_by_extruder(
std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder, std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,

View File

@ -559,6 +559,21 @@ std::string Model::propose_export_file_name_and_path(const std::string &new_exte
return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string(); return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string();
} }
bool Model::is_fdm_support_painted() const
{
return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_fdm_support_painted(); });
}
bool Model::is_seam_painted() const
{
return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_seam_painted(); });
}
bool Model::is_mm_painted() const
{
return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_mm_painted(); });
}
ModelObject::~ModelObject() ModelObject::~ModelObject()
{ {
this->clear_volumes(); this->clear_volumes();
@ -735,6 +750,16 @@ void ModelObject::clear_volumes()
this->invalidate_bounding_box(); this->invalidate_bounding_box();
} }
bool ModelObject::is_fdm_support_painted() const
{
return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_fdm_support_painted(); });
}
bool ModelObject::is_seam_painted() const
{
return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_seam_painted(); });
}
bool ModelObject::is_mm_painted() const bool ModelObject::is_mm_painted() const
{ {
return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
@ -1200,9 +1225,9 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr
for (ModelVolume *volume : volumes) { for (ModelVolume *volume : volumes) {
const auto volume_matrix = volume->get_matrix(); const auto volume_matrix = volume->get_matrix();
volume->supported_facets.clear(); volume->supported_facets.reset();
volume->seam_facets.clear(); volume->seam_facets.reset();
volume->mmu_segmentation_facets.clear(); volume->mmu_segmentation_facets.reset();
if (! volume->is_model_part()) { if (! volume->is_model_part()) {
// Modifiers are not cut, but we still need to add the instance transformation // Modifiers are not cut, but we still need to add the instance transformation
@ -2021,11 +2046,11 @@ bool FacetsAnnotation::set(const TriangleSelector& selector)
return false; return false;
} }
void FacetsAnnotation::clear() void FacetsAnnotation::reset()
{ {
m_data.first.clear(); m_data.first.clear();
m_data.second.clear(); m_data.second.clear();
this->reset_timestamp(); this->touch();
} }
// Following function takes data from a triangle and encodes it as string // Following function takes data from a triangle and encodes it as string

View File

@ -285,6 +285,10 @@ public:
void clear_volumes(); void clear_volumes();
void sort_volumes(bool full_sort); void sort_volumes(bool full_sort);
bool is_multiparts() const { return volumes.size() > 1; } bool is_multiparts() const { return volumes.size() > 1; }
// Checks if any of object volume is painted using the fdm support painting gizmo.
bool is_fdm_support_painted() const;
// Checks if any of object volume is painted using the seam painting gizmo.
bool is_seam_painted() const;
// Checks if any of object volume is painted using the multi-material painting gizmo. // Checks if any of object volume is painted using the multi-material painting gizmo.
bool is_mm_painted() const; bool is_mm_painted() const;
@ -541,7 +545,10 @@ public:
indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const;
bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
bool empty() const { return m_data.first.empty(); } bool empty() const { return m_data.first.empty(); }
void clear();
// Following method clears the config and increases its timestamp, so the deleted
// state is considered changed from perspective of the undo/redo stack.
void reset();
// Serialize triangle into string, for serialization into 3MF/AMF. // Serialize triangle into string, for serialization into 3MF/AMF.
std::string get_triangle_as_string(int i) const; std::string get_triangle_as_string(int i) const;
@ -720,6 +727,8 @@ public:
this->mmu_segmentation_facets.set_new_unique_id(); this->mmu_segmentation_facets.set_new_unique_id();
} }
bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
bool is_seam_painted() const { return !this->seam_facets.empty(); }
bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
protected: protected:
@ -1124,6 +1133,13 @@ public:
// Propose an output path, replace extension. The new_extension shall contain the initial dot. // Propose an output path, replace extension. The new_extension shall contain the initial dot.
std::string propose_export_file_name_and_path(const std::string &new_extension) const; std::string propose_export_file_name_and_path(const std::string &new_extension) const;
// Checks if any of objects is painted using the fdm support painting gizmo.
bool is_fdm_support_painted() const;
// Checks if any of objects is painted using the seam painting gizmo.
bool is_seam_painted() const;
// Checks if any of objects is painted using the multi-material painting gizmo.
bool is_mm_painted() const;
private: private:
explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); } explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }
void assign_new_unique_ids_recursive(); void assign_new_unique_ids_recursive();

View File

@ -105,9 +105,6 @@ protected:
// The class tree will have virtual tables and type information. // The class tree will have virtual tables and type information.
virtual ~ObjectWithTimestamp() = default; virtual ~ObjectWithTimestamp() = default;
// Resetting timestamp to 1 indicates the object is in its initial (cleared) state.
// To be called by the derived class's clear() method.
void reset_timestamp() { m_timestamp = 1; }
// The timestamp uniquely identifies content of the derived class' data, therefore it makes sense to copy the timestamp if the content data was copied. // The timestamp uniquely identifies content of the derived class' data, therefore it makes sense to copy the timestamp if the content data was copied.
void copy_timestamp(const ObjectWithTimestamp& rhs) { m_timestamp = rhs.m_timestamp; } void copy_timestamp(const ObjectWithTimestamp& rhs) { m_timestamp = rhs.m_timestamp; }

View File

@ -428,9 +428,9 @@ static std::vector<std::string> s_Preset_print_options {
#endif /* HAS_PRESSURE_EQUALIZER */ #endif /* HAS_PRESSURE_EQUALIZER */
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", "bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration",
"bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield", "bridge_acceleration", "first_layer_acceleration", "first_layer_acceleration_over_raft", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield",
"min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "min_skirt_length", "brim_width", "brim_separation", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
"support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers",

View File

@ -88,7 +88,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"filament_cost", "filament_cost",
"filament_spool_weight", "filament_spool_weight",
"first_layer_acceleration", "first_layer_acceleration",
"first_layer_acceleration_over_raft",
"first_layer_bed_temperature", "first_layer_bed_temperature",
"first_layer_speed_over_raft",
"gcode_comments", "gcode_comments",
"gcode_label_objects", "gcode_label_objects",
"infill_acceleration", "infill_acceleration",

View File

@ -505,10 +505,10 @@ void PrintConfigDef::init_fff_params()
def->mode = comSimple; def->mode = comSimple;
def->set_default_value(new ConfigOptionEnum<BrimType>(btOuterOnly)); def->set_default_value(new ConfigOptionEnum<BrimType>(btOuterOnly));
def = this->add("brim_offset", coFloat); def = this->add("brim_separation", coFloat);
def->label = L("Brim offset"); def->label = L("Brim separation gap");
def->category = L("Skirt and brim"); def->category = L("Skirt and brim");
def->tooltip = L("The offset of the brim from the printed object. The offset is applied after the elephant foot compensation."); def->tooltip = L("Offset of brim from the printed object. The offset is applied after the elephant foot compensation.");
def->sidetext = L("mm"); def->sidetext = L("mm");
def->min = 0; def->min = 0;
def->mode = comAdvanced; def->mode = comAdvanced;
@ -1152,6 +1152,15 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert; def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0)); def->set_default_value(new ConfigOptionFloat(0));
def = this->add("first_layer_acceleration_over_raft", coFloat);
def->label = L("First object layer over raft interface");
def->tooltip = L("This is the acceleration your printer will use for first layer of object above raft interface. Set zero "
"to disable acceleration control for first layer of object above raft interface.");
def->sidetext = L("mm/s²");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("first_layer_bed_temperature", coInts); def = this->add("first_layer_bed_temperature", coInts);
def->label = L("First layer"); def->label = L("First layer");
def->full_label = L("First layer bed temperature"); def->full_label = L("First layer bed temperature");
@ -1194,6 +1203,16 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(30, false)); def->set_default_value(new ConfigOptionFloatOrPercent(30, false));
def = this->add("first_layer_speed_over_raft", coFloatOrPercent);
def->label = L("Speed of object first layer over raft interface");
def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied to all the print moves "
"of the first object layer above raft interface, regardless of their type. If expressed as a percentage "
"(for example: 40%) it will scale the default speeds.");
def->sidetext = L("mm/s or %");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloatOrPercent(30, false));
def = this->add("first_layer_temperature", coInts); def = this->add("first_layer_temperature", coInts);
def->label = L("First layer"); def->label = L("First layer");
def->full_label = L("First layer nozzle temperature"); def->full_label = L("First layer nozzle temperature");

View File

@ -449,13 +449,15 @@ protected: \
PRINT_CONFIG_CLASS_DEFINE( PRINT_CONFIG_CLASS_DEFINE(
PrintObjectConfig, PrintObjectConfig,
((ConfigOptionFloat, brim_offset)) ((ConfigOptionFloat, brim_separation))
((ConfigOptionEnum<BrimType>, brim_type)) ((ConfigOptionEnum<BrimType>, brim_type))
((ConfigOptionFloat, brim_width)) ((ConfigOptionFloat, brim_width))
((ConfigOptionBool, clip_multipart_objects)) ((ConfigOptionBool, clip_multipart_objects))
((ConfigOptionBool, dont_support_bridges)) ((ConfigOptionBool, dont_support_bridges))
((ConfigOptionFloat, elefant_foot_compensation)) ((ConfigOptionFloat, elefant_foot_compensation))
((ConfigOptionFloatOrPercent, extrusion_width)) ((ConfigOptionFloatOrPercent, extrusion_width))
((ConfigOptionFloat, first_layer_acceleration_over_raft))
((ConfigOptionFloatOrPercent, first_layer_speed_over_raft))
((ConfigOptionBool, infill_only_where_needed)) ((ConfigOptionBool, infill_only_where_needed))
// Force the generation of solid shells between adjacent materials/volumes. // Force the generation of solid shells between adjacent materials/volumes.
((ConfigOptionBool, interface_shells)) ((ConfigOptionBool, interface_shells))
@ -1064,7 +1066,9 @@ Points get_bed_shape(const SLAPrinterConfig &cfg);
class ModelConfig class ModelConfig
{ {
public: public:
void clear() { m_data.clear(); m_timestamp = 1; } // Following method clears the config and increases its timestamp, so the deleted
// state is considered changed from perspective of the undo/redo stack.
void reset() { m_data.clear(); touch(); }
void assign_config(const ModelConfig &rhs) { void assign_config(const ModelConfig &rhs) {
if (m_timestamp != rhs.m_timestamp) { if (m_timestamp != rhs.m_timestamp) {
@ -1076,7 +1080,7 @@ public:
if (m_timestamp != rhs.m_timestamp) { if (m_timestamp != rhs.m_timestamp) {
m_data = std::move(rhs.m_data); m_data = std::move(rhs.m_data);
m_timestamp = rhs.m_timestamp; m_timestamp = rhs.m_timestamp;
rhs.clear(); rhs.reset();
} }
} }

View File

@ -500,7 +500,7 @@ bool PrintObject::invalidate_state_by_config_options(
bool invalidated = false; bool invalidated = false;
for (const t_config_option_key &opt_key : opt_keys) { for (const t_config_option_key &opt_key : opt_keys) {
if ( opt_key == "brim_width" if ( opt_key == "brim_width"
|| opt_key == "brim_offset" || opt_key == "brim_separation"
|| opt_key == "brim_type") { || opt_key == "brim_type") {
// Brim is printed below supports, support invalidates brim and skirt. // Brim is printed below supports, support invalidates brim and skirt.
steps.emplace_back(posSupportMaterial); steps.emplace_back(posSupportMaterial);
@ -2294,9 +2294,13 @@ void PrintObject::project_and_append_custom_facets(
? mv->seam_facets.get_facets_strict(*mv, type) ? mv->seam_facets.get_facets_strict(*mv, type)
: mv->supported_facets.get_facets_strict(*mv, type); : mv->supported_facets.get_facets_strict(*mv, type);
if (! custom_facets.indices.empty()) if (! custom_facets.indices.empty())
#if 0
project_triangles_to_slabs(this->layers(), custom_facets, project_triangles_to_slabs(this->layers(), custom_facets,
(this->trafo_centered() * mv->get_matrix()).cast<float>(), (this->trafo_centered() * mv->get_matrix()).cast<float>(),
seam, out); seam, out);
#else
slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &out, [](){});
#endif
} }
} }

View File

@ -17,10 +17,18 @@ namespace sla {
class IndexedMesh::AABBImpl { class IndexedMesh::AABBImpl {
private: private:
AABBTreeIndirect::Tree3f m_tree; AABBTreeIndirect::Tree3f m_tree;
double m_triangle_ray_epsilon;
public: public:
void init(const indexed_triangle_set &its) void init(const indexed_triangle_set &its, bool calculate_epsilon)
{ {
m_triangle_ray_epsilon = 0.000001;
if (calculate_epsilon) {
// Calculate epsilon from average triangle edge length.
double l = its_average_edge_length(its);
if (l > 0)
m_triangle_ray_epsilon = 0.000001 * l * l;
}
m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
its.vertices, its.indices); its.vertices, its.indices);
} }
@ -31,7 +39,7 @@ public:
igl::Hit & hit) igl::Hit & hit)
{ {
AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices, AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices,
m_tree, s, dir, hit); m_tree, s, dir, hit, m_triangle_ray_epsilon);
} }
void intersect_ray(const indexed_triangle_set &its, void intersect_ray(const indexed_triangle_set &its,
@ -40,7 +48,7 @@ public:
std::vector<igl::Hit> & hits) std::vector<igl::Hit> & hits)
{ {
AABBTreeIndirect::intersect_ray_all_hits(its.vertices, its.indices, AABBTreeIndirect::intersect_ray_all_hits(its.vertices, its.indices,
m_tree, s, dir, hits); m_tree, s, dir, hits, m_triangle_ray_epsilon);
} }
double squared_distance(const indexed_triangle_set & its, double squared_distance(const indexed_triangle_set & its,
@ -60,25 +68,25 @@ public:
} }
}; };
template<class M> void IndexedMesh::init(const M &mesh) template<class M> void IndexedMesh::init(const M &mesh, bool calculate_epsilon)
{ {
BoundingBoxf3 bb = bounding_box(mesh); BoundingBoxf3 bb = bounding_box(mesh);
m_ground_level += bb.min(Z); m_ground_level += bb.min(Z);
// Build the AABB accelaration tree // Build the AABB accelaration tree
m_aabb->init(*m_tm); m_aabb->init(*m_tm, calculate_epsilon);
} }
IndexedMesh::IndexedMesh(const indexed_triangle_set& tmesh) IndexedMesh::IndexedMesh(const indexed_triangle_set& tmesh, bool calculate_epsilon)
: m_aabb(new AABBImpl()), m_tm(&tmesh) : m_aabb(new AABBImpl()), m_tm(&tmesh)
{ {
init(tmesh); init(tmesh, calculate_epsilon);
} }
IndexedMesh::IndexedMesh(const TriangleMesh &mesh) IndexedMesh::IndexedMesh(const TriangleMesh &mesh, bool calculate_epsilon)
: m_aabb(new AABBImpl()), m_tm(&mesh.its) : m_aabb(new AABBImpl()), m_tm(&mesh.its)
{ {
init(mesh); init(mesh, calculate_epsilon);
} }
IndexedMesh::~IndexedMesh() {} IndexedMesh::~IndexedMesh() {}

View File

@ -42,12 +42,14 @@ class IndexedMesh {
std::vector<DrainHole> m_holes; std::vector<DrainHole> m_holes;
#endif #endif
template<class M> void init(const M &mesh); template<class M> void init(const M &mesh, bool calculate_epsilon);
public: public:
explicit IndexedMesh(const indexed_triangle_set&); // calculate_epsilon ... calculate epsilon for triangle-ray intersection from an average triangle edge length.
explicit IndexedMesh(const TriangleMesh &mesh); // If set to false, a default epsilon is used, which works for "reasonable" meshes.
explicit IndexedMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon = false);
explicit IndexedMesh(const TriangleMesh &mesh, bool calculate_epsilon = false);
IndexedMesh(const IndexedMesh& other); IndexedMesh(const IndexedMesh& other);
IndexedMesh& operator=(const IndexedMesh&); IndexedMesh& operator=(const IndexedMesh&);

View File

@ -37,6 +37,7 @@
#define DEBUG #define DEBUG
#define _DEBUG #define _DEBUG
#undef NDEBUG #undef NDEBUG
#include "utils.hpp"
#include "SVG.hpp" #include "SVG.hpp"
#endif #endif
@ -429,7 +430,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
for (const MyLayer *layer : top_contacts) for (const MyLayer *layer : top_contacts)
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z), debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z),
union_ex(layer->polygons, false)); union_ex(layer->polygons));
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts"; BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts";
@ -447,7 +448,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
for (size_t layer_id = 0; layer_id < object.layers().size(); ++ layer_id) for (size_t layer_id = 0; layer_id < object.layers().size(); ++ layer_id)
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-areas-%d-%lf.svg", iRun, object.layers()[layer_id]->print_z), debug_out_path("support-areas-%d-%lf.svg", iRun, object.layers()[layer_id]->print_z),
union_ex(layer_support_areas[layer_id], false)); union_ex(layer_support_areas[layer_id]));
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
BOOST_LOG_TRIVIAL(info) << "Support generator - Creating intermediate layers - indices"; BOOST_LOG_TRIVIAL(info) << "Support generator - Creating intermediate layers - indices";
@ -466,7 +467,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
for (const MyLayer *layer : top_contacts) for (const MyLayer *layer : top_contacts)
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z), debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z),
union_ex(layer->polygons, false)); union_ex(layer->polygons));
#endif #endif
BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers"; BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers";
@ -478,7 +479,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it)
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-base-layers-%d-%lf.svg", iRun, (*it)->print_z), debug_out_path("support-base-layers-%d-%lf.svg", iRun, (*it)->print_z),
union_ex((*it)->polygons, false)); union_ex((*it)->polygons));
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts"; BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts";
@ -507,11 +508,11 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
for (const MyLayer *l : interface_layers) for (const MyLayer *l : interface_layers)
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-interface-layers-%d-%lf.svg", iRun, l->print_z), debug_out_path("support-interface-layers-%d-%lf.svg", iRun, l->print_z),
union_ex(l->polygons, false)); union_ex(l->polygons));
for (const MyLayer *l : base_interface_layers) for (const MyLayer *l : base_interface_layers)
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-base-interface-layers-%d-%lf.svg", iRun, l->print_z), debug_out_path("support-base-interface-layers-%d-%lf.svg", iRun, l->print_z),
union_ex(l->polygons, false)); union_ex(l->polygons));
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
/* /*
@ -1308,9 +1309,9 @@ namespace SupportMaterialInternal {
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
static int iRun = 0; static int iRun = 0;
SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++),
{ { { union_ex(offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), false) }, { "unsupported_bridge_edges", "orange", 0.5f } }, { { { union_ex(offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } },
{ { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } },
{ { union_ex(bridges, false) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
} }
} }
@ -1416,13 +1417,35 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
// Generate overhang / contact_polygons for non-raft layers. // Generate overhang / contact_polygons for non-raft layers.
const Layer &lower_layer = *layer.lower_layer; const Layer &lower_layer = *layer.lower_layer;
const bool has_enforcer = ! annotations.enforcers_layers.empty() && ! annotations.enforcers_layers[layer_id].empty(); const bool has_enforcer = ! annotations.enforcers_layers.empty() && ! annotations.enforcers_layers[layer_id].empty();
// Cache support trimming polygons derived from lower layer polygons, possible merged with "on build plate only" trimming polygons.
auto slices_margin_update =
[&slices_margin, &lower_layer, &lower_layer_polygons, buildplate_only, has_enforcer, &annotations, layer_id]
(float slices_margin_offset, float no_interface_offset) {
if (slices_margin.offset != slices_margin_offset) {
slices_margin.offset = slices_margin_offset;
slices_margin.polygons = (slices_margin_offset == 0.f) ?
lower_layer_polygons :
offset2(lower_layer.lslices, -no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (buildplate_only && !annotations.buildplate_covered[layer_id].empty()) {
if (has_enforcer)
// Make a backup of trimming polygons before enforcing "on build plate only".
slices_margin.all_polygons = slices_margin.polygons;
// Trim the inflated contact surfaces by the top surfaces as well.
slices_margin.polygons = union_(slices_margin.polygons, annotations.buildplate_covered[layer_id]);
}
}
};
float fw = 0; float fw = 0;
float lower_layer_offset = 0;
float no_interface_offset = 0;
for (LayerRegion *layerm : layer.regions()) { for (LayerRegion *layerm : layer.regions()) {
// Extrusion width accounts for the roundings of the extrudates. // Extrusion width accounts for the roundings of the extrudates.
// It is the maximum widh of the extrudate. // It is the maximum widh of the extrudate.
fw = float(layerm->flow(frExternalPerimeter).scaled_width()); fw = float(layerm->flow(frExternalPerimeter).scaled_width());
no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw);
float lower_layer_offset = lower_layer_offset =
(layer_id < (size_t)object_config.support_material_enforce_layers.value) ? (layer_id < (size_t)object_config.support_material_enforce_layers.value) ?
// Enforce a full possible support, ignore the overhang angle. // Enforce a full possible support, ignore the overhang angle.
0.f : 0.f :
@ -1494,7 +1517,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
iRun, layer_id, iRun, layer_id,
std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin()), std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin()),
get_extents(diff_polygons)); get_extents(diff_polygons));
Slic3r::ExPolygons expolys = union_ex(diff_polygons, false); Slic3r::ExPolygons expolys = union_ex(diff_polygons);
svg.draw(expolys); svg.draw(expolys);
} }
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
@ -1512,7 +1535,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
iRun, layer_id, iRun, layer_id,
std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin(), std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin(),
layer.print_z), layer.print_z),
union_ex(diff_polygons, false)); union_ex(diff_polygons));
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
//FIXME the overhang_polygons are used to construct the support towers as well. //FIXME the overhang_polygons are used to construct the support towers as well.
@ -1529,20 +1552,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
//FIXME one should trim with the layer span colliding with the support layer, this layer //FIXME one should trim with the layer span colliding with the support layer, this layer
// may be lower than lower_layer, so the support area needed may need to be actually bigger! // may be lower than lower_layer, so the support area needed may need to be actually bigger!
// For the same reason, the non-bridging support area may be smaller than the bridging support area! // For the same reason, the non-bridging support area may be smaller than the bridging support area!
float slices_margin_offset = std::min(lower_layer_offset, float(scale_(gap_xy))); slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset);
if (slices_margin.offset != slices_margin_offset) {
slices_margin.offset = slices_margin_offset;
slices_margin.polygons = (slices_margin_offset == 0.f) ?
lower_layer_polygons :
offset2(lower_layer.lslices, - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) {
if (has_enforcer)
// Make a backup of trimming polygons before enforcing "on build plate only".
slices_margin.all_polygons = slices_margin.polygons;
// Trim the inflated contact surfaces by the top surfaces as well.
slices_margin.polygons = union_(slices_margin.polygons, annotations.buildplate_covered[layer_id]);
}
}
// Offset the contact polygons outside. // Offset the contact polygons outside.
#if 0 #if 0
for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) {
@ -1573,11 +1583,12 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z),
{ { layer.lslices, { "layer.lslices", "gray", 0.2f } }, { { layer.lslices, { "layer.lslices", "gray", 0.2f } },
{ { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "green", 0.5f } }, { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "green", 0.5f } },
{ enforcers_united, { "enforcers", "blue", 0.5f } }, { enforcers_united, { "enforcers", "blue", 0.5f } },
{ { union_ex(enforcer_polygons, true) }, { "new_contacts", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_safety_offset_ex(enforcer_polygons) }, { "new_contacts", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
polygons_append(overhang_polygons, enforcer_polygons); polygons_append(overhang_polygons, enforcer_polygons);
slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset);
polygons_append(contact_polygons, diff(enforcer_polygons, slices_margin.all_polygons.empty() ? slices_margin.polygons : slices_margin.all_polygons)); polygons_append(contact_polygons, diff(enforcer_polygons, slices_margin.all_polygons.empty() ? slices_margin.polygons : slices_margin.all_polygons));
} }
} }
@ -1738,19 +1749,19 @@ static inline void fill_contact_layer(
#endif // SLIC3R_DEBUG #endif // SLIC3R_DEBUG
); );
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), SVG::export_expolygons(debug_out_path("support-top-contacts-final1-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z),
{ { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, { { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "gray", 0.2f } },
{ { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, { { union_ex(*new_layer.contact_polygons) }, { "new_layer.contact_polygons", "yellow", 0.5f } },
{ { union_ex(slices_margin.polygons, false) }, { "slices_margin_cached", "blue", 0.5f } }, { { union_ex(slices_margin.polygons) }, { "slices_margin_cached", "blue", 0.5f } },
{ { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, { { union_ex(dense_interface_polygons) }, { "dense_interface_polygons", "green", 0.5f } },
{ { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
//support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); //support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z));
SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), SVG::export_expolygons(debug_out_path("support-top-contacts-final2-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z),
{ { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, { { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "gray", 0.2f } },
{ { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, { { union_ex(*new_layer.contact_polygons) }, { "new_layer.contact_polygons", "yellow", 0.5f } },
{ { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } },
{ { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, { { union_ex(dense_interface_polygons) }, { "dense_interface_polygons", "green", 0.5f } },
{ { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
} }
} }
@ -1796,11 +1807,11 @@ static inline void fill_contact_layer(
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z),
{ { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, { { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "gray", 0.2f } },
{ { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, { { union_ex(*new_layer.contact_polygons) }, { "new_layer.contact_polygons", "yellow", 0.5f } },
{ { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } },
{ { union_ex(overhang_polygons, false) }, { "overhang_polygons", "green", 0.5f } }, { { union_ex(overhang_polygons) }, { "overhang_polygons", "green", 0.5f } },
{ { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
// Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded.
@ -1964,10 +1975,10 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
Polygons top = collect_region_slices_by_type(layer, stTop); Polygons top = collect_region_slices_by_type(layer, stTop);
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), SVG::export_expolygons(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z),
{ { { union_ex(top, false) }, { "top", "blue", 0.5f } }, { { { union_ex(top) }, { "top", "blue", 0.5f } },
{ { union_ex(supports_projected, true) }, { "overhangs", "magenta", 0.5f } }, { { union_safety_offset_ex(supports_projected) }, { "overhangs", "magenta", 0.5f } },
{ layer.lslices, { "layer.lslices", "green", 0.5f } }, { layer.lslices, { "layer.lslices", "green", 0.5f } },
{ { union_ex(polygons_new, true) }, { "polygons_new", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_safety_offset_ex(polygons_new) }, { "polygons_new", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
// Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any
@ -2037,7 +2048,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z),
union_ex(layer_new.polygons, false)); union_ex(layer_new.polygons));
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
// Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer.
@ -2050,14 +2061,14 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts(
if (! layer_support_areas[layer_id_above].empty()) { if (! layer_support_areas[layer_id_above].empty()) {
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), SVG::export_expolygons(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z),
{ { { union_ex(touching, false) }, { "touching", "blue", 0.5f } }, { { { union_ex(touching) }, { "touching", "blue", 0.5f } },
{ { union_ex(layer_support_areas[layer_id_above], true) }, { "above", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_safety_offset_ex(layer_support_areas[layer_id_above]) }, { "above", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching); layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching);
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z),
union_ex(layer_support_areas[layer_id_above], false)); union_ex(layer_support_areas[layer_id_above]));
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
} }
} }
@ -2080,8 +2091,8 @@ static inline std::pair<Polygons, Polygons> project_support_to_grid(const Layer
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-%d-%lf.svg", debug_name, iRun, layer.print_z), SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-%d-%lf.svg", debug_name, iRun, layer.print_z),
{ { { union_ex(trimming, false) }, { "trimming", "blue", 0.5f } }, { { { union_ex(trimming) }, { "trimming", "blue", 0.5f } },
{ { union_ex(overhangs_projection, true) }, { "overhangs_projection", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_safety_offset_ex(overhangs_projection) }, { "overhangs_projection", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
remove_sticks(overhangs_projection); remove_sticks(overhangs_projection);
@ -2089,8 +2100,8 @@ static inline std::pair<Polygons, Polygons> project_support_to_grid(const Layer
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-cleaned-%d-%lf.svg", debug_name, iRun, layer.print_z), SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-cleaned-%d-%lf.svg", debug_name, iRun, layer.print_z),
{ { { union_ex(trimming, false) }, { "trimming", "blue", 0.5f } }, { { { union_ex(trimming) }, { "trimming", "blue", 0.5f } },
{ { union_ex(overhangs_projection, false) }, { "overhangs_projection", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_ex(overhangs_projection) }, { "overhangs_projection", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
SupportGridPattern support_grid_pattern(&overhangs_projection, &trimming, grid_params); SupportGridPattern support_grid_pattern(&overhangs_projection, &trimming, grid_params);
@ -2113,7 +2124,7 @@ static inline std::pair<Polygons, Polygons> project_support_to_grid(const Layer
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-layer_support_area-gridded-%s-%d-%lf.svg", debug_name, iRun, layer.print_z), debug_out_path("support-layer_support_area-gridded-%s-%d-%lf.svg", debug_name, iRun, layer.print_z),
union_ex(out.first, false)); union_ex(out.first));
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
}); });
@ -2131,13 +2142,13 @@ static inline std::pair<Polygons, Polygons> project_support_to_grid(const Layer
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
Slic3r::SVG::export_expolygons( Slic3r::SVG::export_expolygons(
debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z),
union_ex(out.second, false)); union_ex(out.second));
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
SVG::export_expolygons(debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), SVG::export_expolygons(debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z),
{ { { union_ex(trimming, false) }, { "trimming", "gray", 0.5f } }, { { { union_ex(trimming) }, { "trimming", "gray", 0.5f } },
{ { union_ex(overhangs_projection, true) }, { "overhangs_projection", "blue", 0.5f } }, { { union_safety_offset_ex(overhangs_projection) }, { "overhangs_projection", "blue", 0.5f } },
{ { union_ex(out.second, true) }, { "projection_new", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } }); { { union_safety_offset_ex(out.second) }, { "projection_new", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
}); });
@ -2667,9 +2678,9 @@ void PrintObjectSupportMaterial::generate_base_layers(
BoundingBox bbox = get_extents(polygons_new); BoundingBox bbox = get_extents(polygons_new);
bbox.merge(get_extents(polygons_trimming)); bbox.merge(get_extents(polygons_trimming));
::Slic3r::SVG svg(debug_out_path("support-intermediate-layers-raw-%d-%lf.svg", iRun, layer_intermediate.print_z), bbox); ::Slic3r::SVG svg(debug_out_path("support-intermediate-layers-raw-%d-%lf.svg", iRun, layer_intermediate.print_z), bbox);
svg.draw(union_ex(polygons_new, false), "blue", 0.5f); svg.draw(union_ex(polygons_new), "blue", 0.5f);
svg.draw(to_polylines(polygons_new), "blue"); svg.draw(to_polylines(polygons_new), "blue");
svg.draw(union_ex(polygons_trimming, true), "red", 0.5f); svg.draw(union_safety_offset_ex(polygons_trimming), "red", 0.5f);
svg.draw(to_polylines(polygons_trimming), "red"); svg.draw(to_polylines(polygons_trimming), "red");
} }
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
@ -2706,7 +2717,7 @@ void PrintObjectSupportMaterial::generate_base_layers(
for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it) for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it)
::Slic3r::SVG::export_expolygons( ::Slic3r::SVG::export_expolygons(
debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, (*it)->print_z), debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, (*it)->print_z),
union_ex((*it)->polygons, false)); union_ex((*it)->polygons));
++ iRun; ++ iRun;
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
@ -2802,19 +2813,19 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
const BrimType brim_type = object.config().brim_type; const BrimType brim_type = object.config().brim_type;
const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner;
const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner;
const auto brim_offset = scaled<float>(object.config().brim_offset.value + object.config().brim_width.value); const auto brim_separation = scaled<float>(object.config().brim_separation.value + object.config().brim_width.value);
for (const ExPolygon &ex : object.layers().front()->lslices) { for (const ExPolygon &ex : object.layers().front()->lslices) {
if (brim_outer && brim_inner) if (brim_outer && brim_inner)
polygons_append(brim, offset(ex, brim_offset)); polygons_append(brim, offset(ex, brim_separation));
else { else {
if (brim_outer) if (brim_outer)
polygons_append(brim, offset(ex.contour, brim_offset, ClipperLib::jtRound, float(scale_(0.1)))); polygons_append(brim, offset(ex.contour, brim_separation, ClipperLib::jtRound, float(scale_(0.1))));
else else
brim.emplace_back(ex.contour); brim.emplace_back(ex.contour);
if (brim_inner) { if (brim_inner) {
Polygons holes = ex.holes; Polygons holes = ex.holes;
polygons_reverse(holes); polygons_reverse(holes);
holes = offset(holes, - brim_offset, ClipperLib::jtRound, float(scale_(0.1))); holes = offset(holes, - brim_separation, ClipperLib::jtRound, float(scale_(0.1)));
polygons_reverse(holes); polygons_reverse(holes);
polygons_append(brim, std::move(holes)); polygons_append(brim, std::move(holes));
} else } else

View File

@ -49,6 +49,10 @@
#define ENABLE_SINKING_CONTOURS (1 && ENABLE_2_4_0_ALPHA0) #define ENABLE_SINKING_CONTOURS (1 && ENABLE_2_4_0_ALPHA0)
// Enable implementation of retract acceleration in gcode processor // Enable implementation of retract acceleration in gcode processor
#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA0) #define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA0)
// Enable the fix for exporting and importing to/from 3mf file of mirrored volumes
#define ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT (1 && ENABLE_2_4_0_ALPHA0)
// Enable rendering seams (and other options) in preview using models
#define ENABLE_SEAMS_USING_MODELS (1 && ENABLE_2_4_0_ALPHA0)
// Enable save and save as commands to be enabled also when the plater is empty and allow to load empty projects // Enable save and save as commands to be enabled also when the plater is empty and allow to load empty projects
#define ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED (1 && ENABLE_2_4_0_ALPHA0) #define ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED (1 && ENABLE_2_4_0_ALPHA0)

View File

@ -1275,6 +1275,21 @@ float its_volume(const indexed_triangle_set &its)
return volume; return volume;
} }
float its_average_edge_length(const indexed_triangle_set &its)
{
if (its.indices.empty())
return 0.f;
double edge_length = 0.f;
for (size_t i = 0; i < its.indices.size(); ++ i) {
const its_triangle v = its_triangle_vertices(its, i);
edge_length += (v[1] - v[0]).cast<double>().norm() +
(v[2] - v[0]).cast<double>().norm() +
(v[1] - v[2]).cast<double>().norm();
}
return float(edge_length / (3 * its.indices.size()));
}
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its) std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its)
{ {
return its_split<>(its); return its_split<>(its);

View File

@ -199,6 +199,7 @@ inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its,
} }
float its_volume(const indexed_triangle_set &its); float its_volume(const indexed_triangle_set &its);
float its_average_edge_length(const indexed_triangle_set &its);
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B); void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B);
void its_merge(indexed_triangle_set &A, const std::vector<Vec3f> &triangles); void its_merge(indexed_triangle_set &A, const std::vector<Vec3f> &triangles);

View File

@ -58,6 +58,11 @@ void set_data_dir(const std::string &path);
// Return a full path to the GUI resource files. // Return a full path to the GUI resource files.
const std::string& data_dir(); const std::string& data_dir();
// Format an output path for debugging purposes.
// Writes out the output path prefix to the console for the first time the function is called,
// so the user knows where to search for the debugging output.
std::string debug_out_path(const char *name, ...);
// A special type for strings encoded in the local Windows 8-bit code page. // A special type for strings encoded in the local Windows 8-bit code page.
// This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded. // This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded.
typedef std::string local_encoded_string; typedef std::string local_encoded_string;

View File

@ -65,18 +65,6 @@ static constexpr double EXTERNAL_INFILL_MARGIN = 3.;
#define SCALED_EPSILON scale_(EPSILON) #define SCALED_EPSILON scale_(EPSILON)
#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/"
inline std::string debug_out_path(const char *name, ...)
{
char buffer[2048];
va_list args;
va_start(args, name);
std::vsprintf(buffer, name, args);
va_end(args);
return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer);
}
#ifndef UNUSED #ifndef UNUSED
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
#endif /* UNUSED */ #endif /* UNUSED */

View File

@ -1,6 +1,7 @@
#include "Utils.hpp" #include "Utils.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include <atomic>
#include <locale> #include <locale>
#include <ctime> #include <ctime>
#include <cstdarg> #include <cstdarg>
@ -207,6 +208,23 @@ std::string custom_shapes_dir()
return (boost::filesystem::path(g_data_dir) / "shapes").string(); return (boost::filesystem::path(g_data_dir) / "shapes").string();
} }
static std::atomic<bool> debug_out_path_called(false);
std::string debug_out_path(const char *name, ...)
{
static constexpr const char *SLIC3R_DEBUG_OUT_PATH_PREFIX = "out/";
if (! debug_out_path_called.exchange(true)) {
std::string path = boost::filesystem::system_complete(SLIC3R_DEBUG_OUT_PATH_PREFIX).string();
printf("Debugging output files will be written to %s\n", path.c_str());
}
char buffer[2048];
va_list args;
va_start(args, name);
std::vsprintf(buffer, name, args);
va_end(args);
return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer);
}
#ifdef _WIN32 #ifdef _WIN32
// The following helpers are borrowed from the LLVM project https://github.com/llvm // The following helpers are borrowed from the LLVM project https://github.com/llvm
namespace WindowsSupport namespace WindowsSupport

View File

@ -119,7 +119,7 @@ void Bed3D::Axes::render() const
glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_DEPTH_TEST));
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.0); shader->set_uniform("emission_factor", 0.0f);
// x axis // x axis
const_cast<GLModel*>(&m_arrow)->set_color(-1, { 0.75f, 0.0f, 0.0f, 1.0f }); const_cast<GLModel*>(&m_arrow)->set_color(-1, { 0.75f, 0.0f, 0.0f, 1.0f });
@ -498,7 +498,7 @@ void Bed3D::render_model() const
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.0); shader->set_uniform("emission_factor", 0.0f);
glsafe(::glPushMatrix()); glsafe(::glPushMatrix());
glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z())); glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z()));
model->render(); model->render();

View File

@ -281,7 +281,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field(el, have_skirt); toggle_field(el, have_skirt);
bool have_brim = config->opt_enum<BrimType>("brim_type") != btNoBrim; bool have_brim = config->opt_enum<BrimType>("brim_type") != btNoBrim;
for (auto el : { "brim_width", "brim_offset" }) for (auto el : { "brim_width", "brim_separation" })
toggle_field(el, have_brim); toggle_field(el, have_brim);
// perimeter_extruder uses the same logic as in Print::extruders() // perimeter_extruder uses the same logic as in Print::extruders()
toggle_field("perimeter_extruder", have_perimeters || have_brim); toggle_field("perimeter_extruder", have_perimeters || have_brim);
@ -312,7 +312,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field("support_material_speed", have_support_material || have_brim || have_skirt); toggle_field("support_material_speed", have_support_material || have_brim || have_skirt);
toggle_field("raft_contact_distance", have_raft && !have_support_soluble); toggle_field("raft_contact_distance", have_raft && !have_support_soluble);
toggle_field("raft_expansion", have_raft); for (auto el : { "raft_expansion", "first_layer_acceleration_over_raft", "first_layer_speed_over_raft" })
toggle_field(el, have_raft);
bool has_ironing = config->opt_bool("ironing"); bool has_ironing = config->opt_bool("ironing");
for (auto el : { "ironing_type", "ironing_flowrate", "ironing_spacing", "ironing_speed" }) for (auto el : { "ironing_type", "ironing_flowrate", "ironing_spacing", "ironing_speed" })

View File

@ -22,6 +22,95 @@ namespace Slic3r {
namespace GUI { namespace GUI {
namespace { namespace {
// escaping of path string according to
// https://cgit.freedesktop.org/xdg/xdg-specs/tree/desktop-entry/desktop-entry-spec.xml
std::string escape_string(const std::string& str)
{
// The buffer needs to be bigger if escaping <,>,&
std::vector<char> out(str.size() * 4, 0);
char *outptr = out.data();
for (size_t i = 0; i < str.size(); ++ i) {
char c = str[i];
// must be escaped
if (c == '\"') { //double quote
(*outptr ++) = '\\';
(*outptr ++) = '\"';
} else if (c == '`') { // backtick character
(*outptr ++) = '\\';
(*outptr ++) = '`';
} else if (c == '$') { // dollar sign
(*outptr ++) = '\\';
(*outptr ++) = '$';
} else if (c == '\\') { // backslash character
(*outptr ++) = '\\';
(*outptr ++) = '\\';
(*outptr ++) = '\\';
(*outptr ++) = '\\';
// Reserved characters
// At Ubuntu, all these characters must NOT be escaped for desktop integration to work
/*
} else if (c == ' ') { // space
(*outptr ++) = '\\';
(*outptr ++) = ' ';
} else if (c == '\t') { // tab
(*outptr ++) = '\\';
(*outptr ++) = '\t';
} else if (c == '\n') { // newline
(*outptr ++) = '\\';
(*outptr ++) = '\n';
} else if (c == '\'') { // single quote
(*outptr ++) = '\\';
(*outptr ++) = '\'';
} else if (c == '>') { // greater-than sign
(*outptr ++) = '\\';
(*outptr ++) = '&';
(*outptr ++) = 'g';
(*outptr ++) = 't';
(*outptr ++) = ';';
} else if (c == '<') { //less-than sign
(*outptr ++) = '\\';
(*outptr ++) = '&';
(*outptr ++) = 'l';
(*outptr ++) = 't';
(*outptr ++) = ';';
} else if (c == '~') { // tilde
(*outptr ++) = '\\';
(*outptr ++) = '~';
} else if (c == '|') { // vertical bar
(*outptr ++) = '\\';
(*outptr ++) = '|';
} else if (c == '&') { // ampersand
(*outptr ++) = '\\';
(*outptr ++) = '&';
(*outptr ++) = 'a';
(*outptr ++) = 'm';
(*outptr ++) = 'p';
(*outptr ++) = ';';
} else if (c == ';') { // semicolon
(*outptr ++) = '\\';
(*outptr ++) = ';';
} else if (c == '*') { //asterisk
(*outptr ++) = '\\';
(*outptr ++) = '*';
} else if (c == '?') { // question mark
(*outptr ++) = '\\';
(*outptr ++) = '?';
} else if (c == '#') { // hash mark
(*outptr ++) = '\\';
(*outptr ++) = '#';
} else if (c == '(') { // parenthesis
(*outptr ++) = '\\';
(*outptr ++) = '(';
} else if (c == ')') {
(*outptr ++) = '\\';
(*outptr ++) = ')';
*/
} else
(*outptr ++) = c;
}
return std::string(out.data(), outptr - out.data());
}
// Disects path strings stored in system variable divided by ':' and adds into vector // Disects path strings stored in system variable divided by ':' and adds into vector
void resolve_path_from_var(const std::string& var, std::vector<std::string>& paths) void resolve_path_from_var(const std::string& var, std::vector<std::string>& paths)
{ {
@ -157,7 +246,8 @@ void DesktopIntegrationDialog::perform_desktop_integration()
} }
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path' // Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
boost::replace_all(excutable_path, "'", "'\\''"); //boost::replace_all(excutable_path, "'", "'\\''");
excutable_path = escape_string(excutable_path);
// Find directories icons and applications // Find directories icons and applications
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored. // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
@ -243,14 +333,14 @@ void DesktopIntegrationDialog::perform_desktop_integration()
"Name=PrusaSlicer%1%\n" "Name=PrusaSlicer%1%\n"
"GenericName=3D Printing Software\n" "GenericName=3D Printing Software\n"
"Icon=PrusaSlicer%2%\n" "Icon=PrusaSlicer%2%\n"
"Exec=\'%3%\' %%F\n" "Exec=\"%3%\" %%F\n"
"Terminal=false\n" "Terminal=false\n"
"Type=Application\n" "Type=Application\n"
"MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n" "MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n"
"Categories=Graphics;3DGraphics;Engineering;\n" "Categories=Graphics;3DGraphics;Engineering;\n"
"Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n" "Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n"
"StartupNotify=false\n" "StartupNotify=false\n"
"StartupWMClass=prusa-slicer", name_suffix, version_suffix, excutable_path); "StartupWMClass=prusa-slicer\n", name_suffix, version_suffix, excutable_path);
std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix); std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(path, desktop_file)){ if (create_desktop_file(path, desktop_file)){
@ -292,6 +382,8 @@ void DesktopIntegrationDialog::perform_desktop_integration()
app_config->set("desktop_integration_app_path", GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix)); app_config->set("desktop_integration_app_path", GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix));
// Repeat for Gcode viewer - use same paths as for slicer files // Repeat for Gcode viewer - use same paths as for slicer files
// Do NOT add gcode viewer desktop file on ChromeOS
if (platform_flavor() != PlatformFlavor::LinuxOnChromium) {
// Icon // Icon
if (!target_dir_icons.empty()) if (!target_dir_icons.empty())
{ {
@ -310,13 +402,13 @@ void DesktopIntegrationDialog::perform_desktop_integration()
"Name=Prusa Gcode Viewer%1%\n" "Name=Prusa Gcode Viewer%1%\n"
"GenericName=3D Printing Software\n" "GenericName=3D Printing Software\n"
"Icon=PrusaSlicer-gcodeviewer%2%\n" "Icon=PrusaSlicer-gcodeviewer%2%\n"
"Exec=\'%3%\' --gcodeviwer %%F\n" "Exec=\"%3%\" --gcodeviewer %%F\n"
"Terminal=false\n" "Terminal=false\n"
"Type=Application\n" "Type=Application\n"
"MimeType=text/x.gcode;\n" "MimeType=text/x.gcode;\n"
"Categories=Graphics;3DGraphics;\n" "Categories=Graphics;3DGraphics;\n"
"Keywords=3D;Printing;Slicer;\n" "Keywords=3D;Printing;Slicer;\n"
"StartupNotify=false", name_suffix, version_suffix, excutable_path); "StartupNotify=false\n", name_suffix, version_suffix, excutable_path);
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix); std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(desktop_path, desktop_file)) if (create_desktop_file(desktop_path, desktop_file))
@ -326,6 +418,8 @@ void DesktopIntegrationDialog::perform_desktop_integration()
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create Gcodeviewer desktop file"; BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create Gcodeviewer desktop file";
show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. PrusaSlicer desktop file was probably created successfully.")); show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. PrusaSlicer desktop file was probably created successfully."));
} }
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess); wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
} }
void DesktopIntegrationDialog::undo_desktop_intgration() void DesktopIntegrationDialog::undo_desktop_intgration()
@ -343,7 +437,9 @@ void DesktopIntegrationDialog::undo_desktop_intgration()
BOOST_LOG_TRIVIAL(debug) << "removing " << path; BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str()); std::remove(path.c_str());
} }
// gcode viwer .desktop // No gcode viewer at ChromeOS
if (platform_flavor() != PlatformFlavor::LinuxOnChromium) {
// gcode viewer .desktop
path = std::string(app_config->get("desktop_integration_app_viewer_path")); path = std::string(app_config->get("desktop_integration_app_viewer_path"));
if (!path.empty()) { if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path; BOOST_LOG_TRIVIAL(debug) << "removing " << path;
@ -355,6 +451,7 @@ void DesktopIntegrationDialog::undo_desktop_intgration()
BOOST_LOG_TRIVIAL(debug) << "removing " << path; BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str()); std::remove(path.c_str());
} }
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess); wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
} }

File diff suppressed because it is too large Load Diff

View File

@ -22,17 +22,22 @@ namespace GUI {
class GCodeViewer class GCodeViewer
{ {
using IBufferType = unsigned short; using IBufferType = unsigned short;
using Color = std::array<float, 3>; using Color = std::array<float, 4>;
using VertexBuffer = std::vector<float>; using VertexBuffer = std::vector<float>;
using MultiVertexBuffer = std::vector<VertexBuffer>; using MultiVertexBuffer = std::vector<VertexBuffer>;
using IndexBuffer = std::vector<IBufferType>; using IndexBuffer = std::vector<IBufferType>;
using MultiIndexBuffer = std::vector<IndexBuffer>; using MultiIndexBuffer = std::vector<IndexBuffer>;
#if ENABLE_SEAMS_USING_MODELS
using InstanceBuffer = std::vector<float>;
using InstanceIdBuffer = std::vector<size_t>;
#endif // ENABLE_SEAMS_USING_MODELS
static const std::vector<Color> Extrusion_Role_Colors; static const std::vector<Color> Extrusion_Role_Colors;
static const std::vector<Color> Options_Colors; static const std::vector<Color> Options_Colors;
static const std::vector<Color> Travel_Colors; static const std::vector<Color> Travel_Colors;
static const Color Wipe_Color;
static const std::vector<Color> Range_Colors; static const std::vector<Color> Range_Colors;
static const Color Wipe_Color;
static const Color Neutral_Color;
enum class EOptionsColors : unsigned char enum class EOptionsColors : unsigned char
{ {
@ -80,7 +85,10 @@ class GCodeViewer
size_t position_size_floats() const { return 3; } size_t position_size_floats() const { return 3; }
size_t position_size_bytes() const { return position_size_floats() * sizeof(float); } size_t position_size_bytes() const { return position_size_floats() * sizeof(float); }
size_t normal_offset_floats() const { return position_size_floats(); } size_t normal_offset_floats() const {
assert(format == EFormat::PositionNormal1 || format == EFormat::PositionNormal3);
return position_size_floats();
}
size_t normal_offset_bytes() const { return normal_offset_floats() * sizeof(float); } size_t normal_offset_bytes() const { return normal_offset_floats() * sizeof(float); }
size_t normal_size_floats() const { size_t normal_size_floats() const {
@ -96,6 +104,47 @@ class GCodeViewer
void reset(); void reset();
}; };
#if ENABLE_SEAMS_USING_MODELS
// buffer containing instances data used to render a toolpaths using instanced models
// instance record format: 5 floats -> position.x|position.y|position.z|width|height
// which is sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced()
struct InstanceVBuffer
{
// ranges used to render only subparts of the intances
struct Ranges
{
struct Range
{
// offset in bytes of the 1st instance to render
unsigned int offset;
// count of instances to render
unsigned int count;
// vbo id
unsigned int vbo{ 0 };
// Color to apply to the instances
Color color;
};
std::vector<Range> ranges;
void reset();
};
// cpu-side buffer containing all instances data
InstanceBuffer buffer;
// indices of the moves for all instances
std::vector<size_t> s_ids;
Ranges render_ranges;
size_t data_size_bytes() const { return s_ids.size() * instance_size_bytes(); }
size_t instance_size_floats() const { return 5; }
size_t instance_size_bytes() const { return instance_size_floats() * sizeof(float); }
void reset();
};
#endif // ENABLE_SEAMS_USING_MODELS
// ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type // ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type
struct IBuffer struct IBuffer
{ {
@ -229,13 +278,34 @@ class GCodeViewer
{ {
Point, Point,
Line, Line,
#if ENABLE_SEAMS_USING_MODELS
Triangle,
Model
#else
Triangle Triangle
#endif // ENABLE_SEAMS_USING_MODELS
}; };
ERenderPrimitiveType render_primitive_type; ERenderPrimitiveType render_primitive_type;
// buffers for point, line and triangle primitive types
VBuffer vertices; VBuffer vertices;
std::vector<IBuffer> indices; std::vector<IBuffer> indices;
#if ENABLE_SEAMS_USING_MODELS
struct Model
{
GLModel model;
Color color;
InstanceVBuffer instances;
void reset();
};
// contain the buffer for model primitive types
Model model;
#endif // ENABLE_SEAMS_USING_MODELS
std::string shader; std::string shader;
std::vector<Path> paths; std::vector<Path> paths;
// std::set seems to perform significantly better, at least on Windows. // std::set seems to perform significantly better, at least on Windows.
@ -283,9 +353,24 @@ class GCodeViewer
} }
size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); } size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); }
#if ENABLE_SEAMS_USING_MODELS
bool has_data() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point:
case ERenderPrimitiveType::Line:
case ERenderPrimitiveType::Triangle: {
return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
}
case ERenderPrimitiveType::Model: { return model.model.is_initialized() && !model.instances.buffer.empty(); }
default: { return false; }
}
}
#else
bool has_data() const { bool has_data() const {
return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
} }
#endif // ENABLE_SEAMS_USING_MODELS
}; };
// helper to render shells // helper to render shells
@ -433,18 +518,30 @@ class GCodeViewer
int64_t gl_multi_lines_calls_count{ 0 }; int64_t gl_multi_lines_calls_count{ 0 };
int64_t gl_multi_triangles_calls_count{ 0 }; int64_t gl_multi_triangles_calls_count{ 0 };
int64_t gl_triangles_calls_count{ 0 }; int64_t gl_triangles_calls_count{ 0 };
#if ENABLE_SEAMS_USING_MODELS
int64_t gl_instanced_models_calls_count{ 0 };
#endif // ENABLE_SEAMS_USING_MODELS
// memory // memory
int64_t results_size{ 0 }; int64_t results_size{ 0 };
int64_t total_vertices_gpu_size{ 0 }; int64_t total_vertices_gpu_size{ 0 };
int64_t total_indices_gpu_size{ 0 }; int64_t total_indices_gpu_size{ 0 };
#if ENABLE_SEAMS_USING_MODELS
int64_t total_instances_gpu_size{ 0 };
#endif // ENABLE_SEAMS_USING_MODELS
int64_t max_vbuffer_gpu_size{ 0 }; int64_t max_vbuffer_gpu_size{ 0 };
int64_t max_ibuffer_gpu_size{ 0 }; int64_t max_ibuffer_gpu_size{ 0 };
int64_t paths_size{ 0 }; int64_t paths_size{ 0 };
int64_t render_paths_size{ 0 }; int64_t render_paths_size{ 0 };
#if ENABLE_SEAMS_USING_MODELS
int64_t models_instances_size{ 0 };
#endif // ENABLE_SEAMS_USING_MODELS
// other // other
int64_t travel_segments_count{ 0 }; int64_t travel_segments_count{ 0 };
int64_t wipe_segments_count{ 0 }; int64_t wipe_segments_count{ 0 };
int64_t extrude_segments_count{ 0 }; int64_t extrude_segments_count{ 0 };
#if ENABLE_SEAMS_USING_MODELS
int64_t instances_count{ 0 };
#endif // ENABLE_SEAMS_USING_MODELS
int64_t vbuffers_count{ 0 }; int64_t vbuffers_count{ 0 };
int64_t ibuffers_count{ 0 }; int64_t ibuffers_count{ 0 };
@ -470,22 +567,34 @@ class GCodeViewer
gl_multi_lines_calls_count = 0; gl_multi_lines_calls_count = 0;
gl_multi_triangles_calls_count = 0; gl_multi_triangles_calls_count = 0;
gl_triangles_calls_count = 0; gl_triangles_calls_count = 0;
#if ENABLE_SEAMS_USING_MODELS
gl_instanced_models_calls_count = 0;
#endif // ENABLE_SEAMS_USING_MODELS
} }
void reset_sizes() { void reset_sizes() {
results_size = 0; results_size = 0;
total_vertices_gpu_size = 0; total_vertices_gpu_size = 0;
total_indices_gpu_size = 0; total_indices_gpu_size = 0;
#if ENABLE_SEAMS_USING_MODELS
total_instances_gpu_size = 0;
#endif // ENABLE_SEAMS_USING_MODELS
max_vbuffer_gpu_size = 0; max_vbuffer_gpu_size = 0;
max_ibuffer_gpu_size = 0; max_ibuffer_gpu_size = 0;
paths_size = 0; paths_size = 0;
render_paths_size = 0; render_paths_size = 0;
#if ENABLE_SEAMS_USING_MODELS
models_instances_size = 0;
#endif // ENABLE_SEAMS_USING_MODELS
} }
void reset_others() { void reset_others() {
travel_segments_count = 0; travel_segments_count = 0;
wipe_segments_count = 0; wipe_segments_count = 0;
extrude_segments_count = 0; extrude_segments_count = 0;
#if ENABLE_SEAMS_USING_MODELS
instances_count = 0;
#endif // ENABLE_SEAMS_USING_MODELS
vbuffers_count = 0; vbuffers_count = 0;
ibuffers_count = 0; ibuffers_count = 0;
} }
@ -563,6 +672,9 @@ public:
Endpoints endpoints; Endpoints endpoints;
Endpoints current; Endpoints current;
Endpoints last_current; Endpoints last_current;
#if ENABLE_SEAMS_USING_MODELS
Endpoints global;
#endif // ENABLE_SEAMS_USING_MODELS
Vec3f current_position{ Vec3f::Zero() }; Vec3f current_position{ Vec3f::Zero() };
Marker marker; Marker marker;
GCodeWindow gcode_window; GCodeWindow gcode_window;
@ -632,7 +744,7 @@ public:
void update_shells_color_by_extruder(const DynamicPrintConfig* config); void update_shells_color_by_extruder(const DynamicPrintConfig* config);
void reset(); void reset();
void render() const; void render();
bool has_data() const { return !m_roles.empty(); } bool has_data() const { return !m_roles.empty(); }
bool can_export_toolpaths() const; bool can_export_toolpaths() const;
@ -678,17 +790,18 @@ private:
void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_toolpaths(const GCodeProcessor::Result& gcode_result);
void load_shells(const Print& print, bool initialized); void load_shells(const Print& print, bool initialized);
void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const;
void render_toolpaths() const; void render_toolpaths();
void render_shells() const; void render_shells();
void render_legend(float& legend_height) const; void render_legend(float& legend_height);
#if ENABLE_GCODE_VIEWER_STATISTICS #if ENABLE_GCODE_VIEWER_STATISTICS
void render_statistics() const; void render_statistics();
#endif // ENABLE_GCODE_VIEWER_STATISTICS #endif // ENABLE_GCODE_VIEWER_STATISTICS
bool is_visible(ExtrusionRole role) const { bool is_visible(ExtrusionRole role) const {
return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0; return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0;
} }
bool is_visible(const Path& path) const { return is_visible(path.role); } bool is_visible(const Path& path) const { return is_visible(path.role); }
void log_memory_used(const std::string& label, int64_t additional = 0) const; void log_memory_used(const std::string& label, int64_t additional = 0) const;
Color option_color(EMoveType move_type) const;
}; };
} // namespace GUI } // namespace GUI

View File

@ -4159,7 +4159,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_DEPTH_TEST));
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.0); shader->set_uniform("emission_factor", 0.0f);
for (GLVolume* vol : visible_volumes) { for (GLVolume* vol : visible_volumes) {
shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray); shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray);
@ -5181,7 +5181,7 @@ void GLCanvas3D::_render_objects()
m_camera_clipping_plane = ClippingPlane::ClipsNothing(); m_camera_clipping_plane = ClippingPlane::ClipsNothing();
} }
void GLCanvas3D::_render_gcode() const void GLCanvas3D::_render_gcode()
{ {
m_gcode_viewer.render(); m_gcode_viewer.render();
} }

View File

@ -904,7 +904,7 @@ private:
#else #else
void _render_objects(); void _render_objects();
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING #endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
void _render_gcode() const; void _render_gcode();
void _render_selection() const; void _render_selection() const;
void _render_sequential_clearance(); void _render_sequential_clearance();
#if ENABLE_RENDER_SELECTION_CENTER #if ENABLE_RENDER_SELECTION_CENTER

View File

@ -208,6 +208,85 @@ void GLModel::render() const
} }
} }
#if ENABLE_SEAMS_USING_MODELS
void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) const
{
if (instances_vbo == 0)
return;
GLShaderProgram* shader = wxGetApp().get_current_shader();
assert(shader == nullptr || boost::algorithm::iends_with(shader->get_name(), "_instanced"));
// vertex attributes
GLint position_id = (shader != nullptr) ? shader->get_attrib_location("v_position") : -1;
GLint normal_id = (shader != nullptr) ? shader->get_attrib_location("v_normal") : -1;
assert(position_id != -1 && normal_id != -1);
// instance attributes
GLint offset_id = (shader != nullptr) ? shader->get_attrib_location("i_offset") : -1;
GLint scales_id = (shader != nullptr) ? shader->get_attrib_location("i_scales") : -1;
assert(offset_id != -1 && scales_id != -1);
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, instances_vbo));
if (offset_id != -1) {
glsafe(::glVertexAttribPointer(offset_id, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)0));
glsafe(::glEnableVertexAttribArray(offset_id));
glsafe(::glVertexAttribDivisor(offset_id, 1));
}
if (scales_id != -1) {
glsafe(::glVertexAttribPointer(scales_id, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)(3 * sizeof(float))));
glsafe(::glEnableVertexAttribArray(scales_id));
glsafe(::glVertexAttribDivisor(scales_id, 1));
}
for (const RenderData& data : m_render_data) {
if (data.vbo_id == 0 || data.ibo_id == 0)
continue;
GLenum mode;
switch (data.type)
{
default:
case PrimitiveType::Triangles: { mode = GL_TRIANGLES; break; }
case PrimitiveType::Lines: { mode = GL_LINES; break; }
case PrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; }
case PrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; }
}
if (shader != nullptr)
shader->set_uniform("uniform_color", data.color);
else
glsafe(::glColor4fv(data.color.data()));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, data.vbo_id));
if (position_id != -1) {
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)0));
glsafe(::glEnableVertexAttribArray(position_id));
}
if (normal_id != -1) {
glsafe(::glVertexAttribPointer(normal_id, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)(3 * sizeof(float))));
glsafe(::glEnableVertexAttribArray(normal_id));
}
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.ibo_id));
glsafe(::glDrawElementsInstanced(mode, static_cast<GLsizei>(data.indices_count), GL_UNSIGNED_INT, (const void*)0, instances_count));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
if (normal_id != -1)
glsafe(::glDisableVertexAttribArray(normal_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
}
if (scales_id != -1)
glsafe(::glDisableVertexAttribArray(scales_id));
if (offset_id != -1)
glsafe(::glDisableVertexAttribArray(offset_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
#endif // ENABLE_SEAMS_USING_MODELS
void GLModel::send_to_gpu(RenderData& data, const std::vector<float>& vertices, const std::vector<unsigned int>& indices) void GLModel::send_to_gpu(RenderData& data, const std::vector<float>& vertices, const std::vector<unsigned int>& indices)
{ {
assert(data.vbo_id == 0); assert(data.vbo_id == 0);
@ -598,5 +677,53 @@ GLModel::InitializationData straight_arrow(float tip_width, float tip_height, fl
return data; return data;
} }
GLModel::InitializationData diamond(int resolution)
{
resolution = std::max(4, resolution);
GLModel::InitializationData data;
GLModel::InitializationData::Entity entity;
entity.type = GLModel::PrimitiveType::Triangles;
const float step = 2.0f * float(PI) / float(resolution);
// positions
for (int i = 0; i < resolution; ++i) {
float ii = float(i) * step;
entity.positions.emplace_back(0.5f * ::cos(ii), 0.5f * ::sin(ii), 0.0f);
}
entity.positions.emplace_back(0.0f, 0.0f, 0.5f);
entity.positions.emplace_back(0.0f, 0.0f, -0.5f);
// normals
for (const Vec3f& v : entity.positions) {
entity.normals.emplace_back(v.normalized());
}
// triangles
// top
for (int i = 0; i < resolution; ++i) {
entity.indices.push_back(i + 0);
entity.indices.push_back(i + 1);
entity.indices.push_back(resolution);
}
entity.indices.push_back(resolution - 1);
entity.indices.push_back(0);
entity.indices.push_back(resolution);
// bottom
for (int i = 0; i < resolution; ++i) {
entity.indices.push_back(i + 0);
entity.indices.push_back(resolution + 1);
entity.indices.push_back(i + 1);
}
entity.indices.push_back(resolution - 1);
entity.indices.push_back(resolution + 1);
entity.indices.push_back(0);
data.entities.emplace_back(entity);
return data;
}
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r

View File

@ -72,6 +72,9 @@ namespace GUI {
void reset(); void reset();
void render() const; void render() const;
#if ENABLE_SEAMS_USING_MODELS
void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const;
#endif // ENABLE_SEAMS_USING_MODELS
bool is_initialized() const { return !m_render_data.empty(); } bool is_initialized() const { return !m_render_data.empty(); }
@ -100,6 +103,11 @@ namespace GUI {
// used to render sidebar hints for position and scale // used to render sidebar hints for position and scale
GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness); GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness);
// create a diamond with the given resolution
// the origin of the diamond is in its center
// the diamond is contained into a box with size [1, 1, 1]
GLModel::InitializationData diamond(int resolution);
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r

View File

@ -38,9 +38,17 @@ std::pair<bool, std::string> GLShadersManager::init()
// used to render printbed // used to render printbed
valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" });
// used to render options in gcode preview // used to render options in gcode preview
#if ENABLE_SEAMS_USING_MODELS
if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3))
valid &= append_shader("gouraud_light_instanced", { "gouraud_light_instanced.vs", "gouraud_light_instanced.fs" });
else {
#endif // ENABLE_SEAMS_USING_MODELS
valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" }); valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" });
if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20))
valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" }); valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" });
#if ENABLE_SEAMS_USING_MODELS
}
#endif // ENABLE_SEAMS_USING_MODELS
// used to render extrusion and travel paths as lines in gcode preview // used to render extrusion and travel paths as lines in gcode preview
valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" });
// used to render objects in 3d editor // used to render objects in 3d editor

View File

@ -1049,7 +1049,7 @@ void ObjectList::key_event(wxKeyEvent& event)
|| event.GetKeyCode() == WXK_BACK || event.GetKeyCode() == WXK_BACK
#endif //__WXOSX__ #endif //__WXOSX__
) { ) {
wxGetApp().plater()->remove_selected(); remove();
} }
else if (event.GetKeyCode() == WXK_F5) else if (event.GetKeyCode() == WXK_F5)
wxGetApp().plater()->reload_all_from_disk(); wxGetApp().plater()->reload_all_from_disk();
@ -1702,8 +1702,7 @@ void ObjectList::load_shape_object_from_gallery(const wxArrayString& input_files
snapshot_label += ", " + wxString::FromUTF8(paths[i].filename().string().c_str()); snapshot_label += ", " + wxString::FromUTF8(paths[i].filename().string().c_str());
take_snapshot(snapshot_label); take_snapshot(snapshot_label);
std::vector<size_t> res = wxGetApp().plater()->load_files(paths, true, false); if (! wxGetApp().plater()->load_files(paths, true, false).empty())
if (!res.empty())
wxGetApp().mainframe->update_title(); wxGetApp().mainframe->update_title();
} }
@ -1803,21 +1802,21 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
cnv->get_gizmos_manager().reset_all_states(); cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove paint-on supports")); Plater::TakeSnapshot(plater, _L("Remove paint-on supports"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->supported_facets.clear(); mv->supported_facets.reset();
break; break;
case InfoItemType::CustomSeam: case InfoItemType::CustomSeam:
cnv->get_gizmos_manager().reset_all_states(); cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove paint-on seam")); Plater::TakeSnapshot(plater, _L("Remove paint-on seam"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->seam_facets.clear(); mv->seam_facets.reset();
break; break;
case InfoItemType::MmuSegmentation: case InfoItemType::MmuSegmentation:
cnv->get_gizmos_manager().reset_all_states(); cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove Multi Material painting")); Plater::TakeSnapshot(plater, _L("Remove Multi Material painting"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->mmu_segmentation_facets.clear(); mv->mmu_segmentation_facets.reset();
break; break;
case InfoItemType::Sinking: case InfoItemType::Sinking:
@ -1856,7 +1855,7 @@ void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item)
if (is_layer_settings) if (is_layer_settings)
layer_height = m_config->opt_float("layer_height"); layer_height = m_config->opt_float("layer_height");
m_config->clear(); m_config->reset();
if (extruder >= 0) if (extruder >= 0)
m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
@ -1932,7 +1931,7 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
const auto last_volume = object->volumes[0]; const auto last_volume = object->volumes[0];
if (!last_volume->config.empty()) { if (!last_volume->config.empty()) {
object->config.apply(last_volume->config); object->config.apply(last_volume->config);
last_volume->config.clear(); last_volume->config.reset();
// update extruder color in ObjectList // update extruder color in ObjectList
wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx); wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx);
@ -2637,7 +2636,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
model_object->config.has("extruder") ? model_object->config.extruder() : 0, model_object->config.has("extruder") ? model_object->config.extruder() : 0,
get_mesh_errors_count(obj_idx) > 0); get_mesh_errors_count(obj_idx) > 0);
update_info_items(obj_idx, nullptr, true); update_info_items(obj_idx, nullptr, call_selection_changed);
// add volumes to the object // add volumes to the object
if (model_object->volumes.size() > 1) { if (model_object->volumes.size() > 1) {
@ -3769,7 +3768,7 @@ void ObjectList::last_volume_is_deleted(const int obj_idx)
auto volume = (*m_objects)[obj_idx]->volumes.front(); auto volume = (*m_objects)[obj_idx]->volumes.front();
// clear volume's config values // clear volume's config values
volume->config.clear(); volume->config.reset();
// set a default extruder value, since user can't add it manually // set a default extruder value, since user can't add it manually
volume->config.set_key_value("extruder", new ConfigOptionInt(0)); volume->config.set_key_value("extruder", new ConfigOptionInt(0));

View File

@ -191,7 +191,7 @@ void GLGizmoBase::render_grabbers(float size) const
if (shader == nullptr) if (shader == nullptr)
return; return;
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1); shader->set_uniform("emission_factor", 0.1f);
for (int i = 0; i < (int)m_grabbers.size(); ++i) { for (int i = 0; i < (int)m_grabbers.size(); ++i) {
if (m_grabbers[i].enabled) if (m_grabbers[i].enabled)
m_grabbers[i].render(m_hover_id == i, size); m_grabbers[i].render(m_hover_id == i, size);

View File

@ -136,7 +136,7 @@ void GLGizmoCut::on_render()
if (shader == nullptr) if (shader == nullptr)
return; return;
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1); shader->set_uniform("emission_factor", 0.1f);
m_grabbers[0].color = GrabberColor; m_grabbers[0].color = GrabberColor;
m_grabbers[0].render(m_hover_id == 0, (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0)); m_grabbers[0].render(m_hover_id == 0, (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0));

View File

@ -131,6 +131,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
m_imgui->text(""); m_imgui->text("");
ImGui::Separator(); ImGui::Separator();
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["highlight_by_angle"] + ":"); m_imgui->text(m_desc["highlight_by_angle"] + ":");
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", std::string format_str = std::string("%.f") + I18N::translate_utf8("°",

View File

@ -19,6 +19,9 @@ protected:
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on supports"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on supports"); }
private: private:
bool on_init() override; bool on_init() override;

View File

@ -542,6 +542,7 @@ RENDER_AGAIN:
m_imgui->disabled_begin(! m_enable_hollowing); m_imgui->disabled_begin(! m_enable_hollowing);
float max_tooltip_width = ImGui::GetFontSize() * 20.0f; float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("offset")); m_imgui->text(m_desc.at("offset"));
ImGui::SameLine(settings_sliders_left); ImGui::SameLine(settings_sliders_left);
ImGui::PushItemWidth(window_width - settings_sliders_left); ImGui::PushItemWidth(window_width - settings_sliders_left);
@ -558,6 +559,7 @@ RENDER_AGAIN:
bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider
if (current_mode >= quality_mode) { if (current_mode >= quality_mode) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("quality")); m_imgui->text(m_desc.at("quality"));
ImGui::SameLine(settings_sliders_left); ImGui::SameLine(settings_sliders_left);
m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f"); m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f");
@ -574,6 +576,7 @@ RENDER_AGAIN:
} }
if (current_mode >= closing_d_mode) { if (current_mode >= closing_d_mode) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("closing_distance")); m_imgui->text(m_desc.at("closing_distance"));
ImGui::SameLine(settings_sliders_left); ImGui::SameLine(settings_sliders_left);
m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm");
@ -621,6 +624,7 @@ RENDER_AGAIN:
float diameter_upper_cap = 60.; float diameter_upper_cap = 60.;
if (m_new_hole_radius * 2.f > diameter_upper_cap) if (m_new_hole_radius * 2.f > diameter_upper_cap)
m_new_hole_radius = diameter_upper_cap / 2.f; m_new_hole_radius = diameter_upper_cap / 2.f;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("hole_diameter")); m_imgui->text(m_desc.at("hole_diameter"));
ImGui::SameLine(diameter_slider_left); ImGui::SameLine(diameter_slider_left);
ImGui::PushItemWidth(window_width - diameter_slider_left); ImGui::PushItemWidth(window_width - diameter_slider_left);
@ -636,6 +640,7 @@ RENDER_AGAIN:
bool edited = ImGui::IsItemEdited(); bool edited = ImGui::IsItemEdited();
bool deactivated = ImGui::IsItemDeactivatedAfterEdit(); bool deactivated = ImGui::IsItemDeactivatedAfterEdit();
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["hole_depth"]); m_imgui->text(m_desc["hole_depth"]);
ImGui::SameLine(diameter_slider_left); ImGui::SameLine(diameter_slider_left);
m_imgui->slider_float(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); m_imgui->slider_float(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false);
@ -692,8 +697,10 @@ RENDER_AGAIN:
// Following is rendered in both editing and non-editing mode: // Following is rendered in both editing and non-editing mode:
// m_imgui->text(""); // m_imgui->text("");
ImGui::Separator(); ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view")); m_imgui->text(m_desc.at("clipping_of_view"));
}
else { else {
if (m_imgui->button(m_desc.at("reset_direction"))) { if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){ wxGetApp().CallAfter([this](){

View File

@ -128,6 +128,9 @@ protected:
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
std::string get_gizmo_entering_text() const override { return _u8L("Entering Multimaterial painting"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Multimaterial painting"); }
size_t m_first_selected_extruder_idx = 0; size_t m_first_selected_extruder_idx = 0;
size_t m_second_selected_extruder_idx = 1; size_t m_second_selected_extruder_idx = 1;
std::vector<std::string> m_original_extruders_names; std::vector<std::string> m_original_extruders_names;

View File

@ -141,7 +141,7 @@ void GLGizmoMove3D::on_render()
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1); shader->set_uniform("emission_factor", 0.1f);
// draw grabber // draw grabber
float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0);
m_grabbers[m_hover_id].render(true, mean_size); m_grabbers[m_hover_id].render(true, mean_size);
@ -208,7 +208,7 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box
const_cast<GLModel*>(&m_vbo_cone)->set_color(-1, color); const_cast<GLModel*>(&m_vbo_cone)->set_color(-1, color);
if (!picking) { if (!picking) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1); shader->set_uniform("emission_factor", 0.1f);
} }
glsafe(::glPushMatrix()); glsafe(::glPushMatrix());

View File

@ -47,20 +47,14 @@ void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate)
plater->undo_redo_topmost_string_getter(plater->can_undo(), last_snapshot_name); plater->undo_redo_topmost_string_getter(plater->can_undo(), last_snapshot_name);
if (activate && !m_internal_stack_active) { if (activate && !m_internal_stack_active) {
std::string str = get_painter_type() == PainterGizmoType::FDM_SUPPORTS if (std::string str = this->get_gizmo_entering_text(); last_snapshot_name != str)
? _u8L("Entering Paint-on supports")
: _u8L("Entering Seam painting");
if (last_snapshot_name != str)
Plater::TakeSnapshot(plater, str); Plater::TakeSnapshot(plater, str);
plater->enter_gizmos_stack(); plater->enter_gizmos_stack();
m_internal_stack_active = true; m_internal_stack_active = true;
} }
if (!activate && m_internal_stack_active) { if (!activate && m_internal_stack_active) {
plater->leave_gizmos_stack(); plater->leave_gizmos_stack();
std::string str = get_painter_type() == PainterGizmoType::SEAM if (std::string str = this->get_gizmo_leaving_text(); last_snapshot_name != str)
? _u8L("Leaving Seam painting")
: _u8L("Leaving Paint-on supports");
if (last_snapshot_name != str)
Plater::TakeSnapshot(plater, str); Plater::TakeSnapshot(plater, str);
m_internal_stack_active = false; m_internal_stack_active = false;
} }

View File

@ -173,6 +173,9 @@ protected:
virtual wxString handle_snapshot_action_name(bool shift_down, Button button_down) const = 0; virtual wxString handle_snapshot_action_name(bool shift_down, Button button_down) const = 0;
virtual std::string get_gizmo_entering_text() const = 0;
virtual std::string get_gizmo_leaving_text() const = 0;
friend class ::Slic3r::GUI::GLGizmoMmuSegmentation; friend class ::Slic3r::GUI::GLGizmoMmuSegmentation;
}; };

View File

@ -339,7 +339,7 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick
const_cast<GLModel*>(&m_cone)->set_color(-1, color); const_cast<GLModel*>(&m_cone)->set_color(-1, color);
if (!picking) { if (!picking) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1); shader->set_uniform("emission_factor", 0.1f);
} }
glsafe(::glPushMatrix()); glsafe(::glPushMatrix());

View File

@ -236,7 +236,7 @@ void GLGizmoScale3D::on_render()
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1); shader->set_uniform("emission_factor", 0.1f);
// draw grabbers // draw grabbers
m_grabbers[0].render(true, grabber_mean_size); m_grabbers[0].render(true, grabber_mean_size);
m_grabbers[1].render(true, grabber_mean_size); m_grabbers[1].render(true, grabber_mean_size);
@ -251,7 +251,7 @@ void GLGizmoScale3D::on_render()
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1); shader->set_uniform("emission_factor", 0.1f);
// draw grabbers // draw grabbers
m_grabbers[2].render(true, grabber_mean_size); m_grabbers[2].render(true, grabber_mean_size);
m_grabbers[3].render(true, grabber_mean_size); m_grabbers[3].render(true, grabber_mean_size);
@ -266,7 +266,7 @@ void GLGizmoScale3D::on_render()
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1); shader->set_uniform("emission_factor", 0.1f);
// draw grabbers // draw grabbers
m_grabbers[4].render(true, grabber_mean_size); m_grabbers[4].render(true, grabber_mean_size);
m_grabbers[5].render(true, grabber_mean_size); m_grabbers[5].render(true, grabber_mean_size);
@ -284,7 +284,7 @@ void GLGizmoScale3D::on_render()
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader != nullptr) { if (shader != nullptr) {
shader->start_using(); shader->start_using();
shader->set_uniform("emission_factor", 0.1); shader->set_uniform("emission_factor", 0.1f);
// draw grabbers // draw grabbers
for (int i = 6; i < 10; ++i) { for (int i = 6; i < 10; ++i) {
m_grabbers[i].render(true, grabber_mean_size); m_grabbers[i].render(true, grabber_mean_size);

View File

@ -138,6 +138,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size")); m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(cursor_size_slider_left); ImGui::SameLine(cursor_size_slider_left);
ImGui::PushItemWidth(window_width - cursor_size_slider_left); ImGui::PushItemWidth(window_width - cursor_size_slider_left);
@ -188,8 +189,10 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
ImGui::Separator(); ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view")); m_imgui->text(m_desc.at("clipping_of_view"));
}
else { else {
if (m_imgui->button(m_desc.at("reset_direction"))) { if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){ wxGetApp().CallAfter([this](){

View File

@ -20,6 +20,9 @@ protected:
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
std::string get_gizmo_entering_text() const override { return _u8L("Entering Seam painting"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Seam painting"); }
private: private:
bool on_init() override; bool on_init() override;

View File

@ -199,7 +199,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
ImGui::SameLine(m_gui_cfg->bottom_left_width); ImGui::SameLine(m_gui_cfg->bottom_left_width);
if (m_imgui->button(_L("Preview"))) { if (m_imgui->button(_L("Preview"))) {
m_state = State::preview; m_state = State::preview;
// simplify but not aply on mesh // simplify but not apply on mesh
process(); process();
} }
ImGui::SameLine(); ImGui::SameLine();
@ -207,13 +207,10 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
if (!m_is_valid_result) { if (!m_is_valid_result) {
m_state = State::close_on_end; m_state = State::close_on_end;
process(); process();
} else { } else if (m_exist_preview) {
// use preview and close // use preview and close
if (m_exist_preview) { after_apply();
// fix hollowing, sla support points, modifiers, ... } else { // no changes made
auto plater = wxGetApp().plater();
plater->changed_mesh(m_obj_index);
}
close(); close();
} }
} }
@ -237,18 +234,22 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
m_parent.reload_scene(true); m_parent.reload_scene(true);
// set m_state must be before close() !!! // set m_state must be before close() !!!
m_state = State::settings; m_state = State::settings;
if (close_on_end) { if (close_on_end) after_apply();
// fix hollowing, sla support points, modifiers, ...
auto plater = wxGetApp().plater();
plater->changed_mesh(m_obj_index);
close();
}
// Fix warning icon in object list // Fix warning icon in object list
wxGetApp().obj_list()->update_item_error_icon(m_obj_index, -1); wxGetApp().obj_list()->update_item_error_icon(m_obj_index, -1);
} }
} }
void GLGizmoSimplify::after_apply() {
// set flag to NOT revert changes when switch GLGizmoBase::m_state
m_exist_preview = false;
// fix hollowing, sla support points, modifiers, ...
auto plater = wxGetApp().plater();
plater->changed_mesh(m_obj_index);
close();
}
void GLGizmoSimplify::close() { void GLGizmoSimplify::close() {
// close gizmo == open it again // close gizmo == open it again
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager(); GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
@ -282,11 +283,11 @@ void GLGizmoSimplify::process()
} }
}; };
std::function<void(int)> statusfn = [this](int percent) { int64_t last = 0;
std::function<void(int)> statusfn = [this, &last](int percent) {
m_progress = percent; m_progress = percent;
// check max 4fps // check max 4fps
static int64_t last = 0;
int64_t now = m_parent.timestamp_now(); int64_t now = m_parent.timestamp_now();
if ((now - last) < 250) return; if ((now - last) < 250) return;
last = now; last = now;

View File

@ -32,6 +32,7 @@ protected:
virtual void on_set_state() override; virtual void on_set_state() override;
private: private:
void after_apply();
void close(); void close();
void process(); void process();
void set_its(indexed_triangle_set &its); void set_its(indexed_triangle_set &its);

View File

@ -169,7 +169,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
const_cast<GLModel*>(&m_cone)->set_color(-1, render_color); const_cast<GLModel*>(&m_cone)->set_color(-1, render_color);
const_cast<GLModel*>(&m_sphere)->set_color(-1, render_color); const_cast<GLModel*>(&m_sphere)->set_color(-1, render_color);
if (shader && !picking) if (shader && !picking)
shader->set_uniform("emission_factor", 0.5); shader->set_uniform("emission_factor", 0.5f);
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
glsafe(::glPushMatrix()); glsafe(::glPushMatrix());
@ -224,7 +224,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
render_color[3] = 0.7f; render_color[3] = 0.7f;
const_cast<GLModel*>(&m_cylinder)->set_color(-1, render_color); const_cast<GLModel*>(&m_cylinder)->set_color(-1, render_color);
if (shader) if (shader)
shader->set_uniform("emission_factor", 0.5); shader->set_uniform("emission_factor", 0.5f);
for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) {
if (is_mesh_point_clipped(drain_hole.pos.cast<double>())) if (is_mesh_point_clipped(drain_hole.pos.cast<double>()))
continue; continue;
@ -786,8 +786,7 @@ RENDER_AGAIN:
// Following is rendered in both editing and non-editing mode: // Following is rendered in both editing and non-editing mode:
ImGui::Separator(); ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) if (m_c->object_clipper()->get_position() == 0.f) {
{
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view")); m_imgui->text(m_desc.at("clipping_of_view"));
} }

View File

@ -620,7 +620,7 @@ void MainFrame::update_title()
wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : ""; wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : "";
if (!dirty_marker.empty() || !project.empty()) { if (!dirty_marker.empty() || !project.empty()) {
if (!dirty_marker.empty() && project.empty()) if (!dirty_marker.empty() && project.empty())
project = _("Untitled"); project = _L("Untitled");
title = dirty_marker + project + " - "; title = dirty_marker + project + " - ";
} }
} }
@ -819,7 +819,7 @@ bool MainFrame::is_active_and_shown_tab(Tab* tab)
bool MainFrame::can_start_new_project() const bool MainFrame::can_start_new_project() const
{ {
return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty()); return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || GetTitle().StartsWith('*') || !m_plater->model().objects.empty());
} }
bool MainFrame::can_save() const bool MainFrame::can_save() const

View File

@ -106,7 +106,6 @@ void MeshClipper::recalculate_triangles()
Transform3d tr = Transform3d::Identity(); Transform3d tr = Transform3d::Identity();
tr.rotate(q); tr.rotate(q);
tr = m_trafo.get_matrix() * tr; tr = m_trafo.get_matrix() * tr;
height_mesh += 0.001f; // to avoid z-fighting
if (m_limiting_plane != ClippingPlane::ClipsNothing()) if (m_limiting_plane != ClippingPlane::ClipsNothing())
{ {
@ -165,6 +164,8 @@ void MeshClipper::recalculate_triangles()
m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.); m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.);
tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting
m_vertex_array.release_geometry(); m_vertex_array.release_geometry();
for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up);

View File

@ -112,7 +112,7 @@ public:
// The class references extern TriangleMesh, which must stay alive // The class references extern TriangleMesh, which must stay alive
// during MeshRaycaster existence. // during MeshRaycaster existence.
MeshRaycaster(const TriangleMesh& mesh) MeshRaycaster(const TriangleMesh& mesh)
: m_emesh(mesh) : m_emesh(mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length
{ {
m_normals.reserve(mesh.stl.facet_start.size()); m_normals.reserve(mesh.stl.facet_start.size());
for (const stl_facet& facet : mesh.stl.facet_start) for (const stl_facet& facet : mesh.stl.facet_start)

View File

@ -1369,7 +1369,18 @@ void NotificationManager::push_hint_notification(bool open_next)
} }
NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 300, "" }; NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 300, "" };
// from user - open now
if (!open_next) {
push_notification_data(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler, open_next), 0); push_notification_data(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler, open_next), 0);
stop_delayed_notifications_of_type(NotificationType::DidYouKnowHint);
// at startup - delay for half a second to let other notification pop up, than try every 30 seconds
// show only if no notifications are shown
} else {
auto condition = [this]() {
return this->get_notification_count() == 0;
};
push_delayed_notification(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000);
}
} }
bool NotificationManager::is_hint_notification_open() bool NotificationManager::is_hint_notification_open()
@ -1425,6 +1436,28 @@ bool NotificationManager::push_notification_data(std::unique_ptr<NotificationMan
} }
} }
void NotificationManager::push_delayed_notification(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval)
{
if (initial_delay == 0 && condition_callback()) {
if( push_notification_data(std::move(notification), 0))
return;
}
m_waiting_notifications.emplace_back(std::move(notification), condition_callback, initial_delay == 0 ? delay_interval : initial_delay, delay_interval);
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(initial_delay == 0 ? delay_interval : initial_delay);
}
void NotificationManager::stop_delayed_notifications_of_type(const NotificationType type)
{
for (auto it = m_waiting_notifications.begin(); it != m_waiting_notifications.end();) {
if ((*it).notification->get_type() == type) {
it = m_waiting_notifications.erase(it);
}
else {
++it;
}
}
}
void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay_width) void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay_width)
{ {
sort_notifications(); sort_notifications();
@ -1477,6 +1510,26 @@ bool NotificationManager::update_notifications(GLCanvas3D& canvas)
++it; ++it;
} }
// delayed notifications
for (auto it = m_waiting_notifications.begin(); it != m_waiting_notifications.end();) {
// substract time
if ((*it).remaining_time > 0)
(*it).remaining_time -= time_since_render;
if ((*it).remaining_time <= 0) {
if ((*it).condition_callback()) { // push notification, erase it from waiting list (frame is scheduled by push)
(*it).notification->reset_timer();
if (push_notification_data(std::move((*it).notification), 0)) {
it = m_waiting_notifications.erase(it);
continue;
}
}
// not possible to push, delay for delay_interval
(*it).remaining_time = (*it).delay_interval;
}
next_render = std::min<int64_t>(next_render, (*it).remaining_time);
++it;
}
// request next frame in future // request next frame in future
if (next_render < max) if (next_render < max)
canvas.schedule_extra_frame(int(next_render)); canvas.schedule_extra_frame(int(next_render));
@ -1569,6 +1622,15 @@ void NotificationManager::device_ejected()
notification->close(); notification->close();
} }
} }
size_t NotificationManager::get_notification_count() const
{
size_t ret = 0;
for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_state() != PopNotification::EState::Hidden)
ret++;
}
return ret;
}
}//namespace GUI }//namespace GUI
}//namespace Slic3r }//namespace Slic3r

View File

@ -190,6 +190,8 @@ public:
void set_move_from_overlay(bool move) { m_move_from_overlay = move; } void set_move_from_overlay(bool move) { m_move_from_overlay = move; }
// perform update_state on each notification and ask for more frames if needed, return true for render needed // perform update_state on each notification and ask for more frames if needed, return true for render needed
bool update_notifications(GLCanvas3D& canvas); bool update_notifications(GLCanvas3D& canvas);
// returns number of all notifications shown
size_t get_notification_count() const;
private: private:
// duration 0 means not disapearing // duration 0 means not disapearing
struct NotificationData { struct NotificationData {
@ -262,6 +264,8 @@ private:
EState get_state() const { return m_state; } EState get_state() const { return m_state; }
bool is_hovered() const { return m_state == EState::Hovered; } bool is_hovered() const { return m_state == EState::Hovered; }
void set_hovered() { if (m_state != EState::Finished && m_state != EState::ClosePending && m_state != EState::Hidden && m_state != EState::Unknown) m_state = EState::Hovered; } void set_hovered() { if (m_state != EState::Finished && m_state != EState::ClosePending && m_state != EState::Hidden && m_state != EState::Unknown) m_state = EState::Hovered; }
// set start of notification to now. Used by delayed notifications
void reset_timer() { m_notification_start = GLCanvas3D::timestamp_now(); m_state = EState::Shown; }
protected: protected:
// Call after every size change // Call after every size change
virtual void init(); virtual void init();
@ -515,10 +519,34 @@ private:
// in HintNotification.hpp // in HintNotification.hpp
class HintNotification; class HintNotification;
// Data of waiting notifications
struct DelayedNotification
{
std::unique_ptr<PopNotification> notification;
std::function<bool(void)> condition_callback;
int64_t remaining_time;
int64_t delay_interval;
DelayedNotification(std::unique_ptr<PopNotification> n, std::function<bool(void)> cb, int64_t r, int64_t d)
: notification(std::move(n))
, condition_callback(cb)
, remaining_time(r)
, delay_interval(d)
{}
};
//pushes notification into the queue of notifications that are rendered //pushes notification into the queue of notifications that are rendered
//can be used to create custom notification //can be used to create custom notification
bool push_notification_data(const NotificationData& notification_data, int timestamp); bool push_notification_data(const NotificationData& notification_data, int timestamp);
bool push_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification, int timestamp); bool push_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification, int timestamp);
// Delayed notifications goes first to the m_waiting_notifications vector and only after remaining time is <= 0
// and condition callback is success, notification is regular pushed from update function.
// Otherwise another delay interval waiting. Timestamp is 0.
// Note that notification object is constructed when being added to the waiting list, but there are no updates called on it and its timer is reset at regular push.
// Also note that no control of same notification is done during push_delayed_notification but if waiting notif fails to push, it continues waiting.
void push_delayed_notification(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval);
// Removes all notifications of type from m_waiting_notifications
void stop_delayed_notifications_of_type(const NotificationType type);
//finds older notification of same type and moves it to the end of queue. returns true if found //finds older notification of same type and moves it to the end of queue. returns true if found
bool activate_existing(const NotificationManager::PopNotification* notification); bool activate_existing(const NotificationManager::PopNotification* notification);
// Put the more important notifications to the bottom of the list. // Put the more important notifications to the bottom of the list.
@ -531,6 +559,8 @@ private:
// Cache of IDs to identify and reuse ImGUI windows. // Cache of IDs to identify and reuse ImGUI windows.
NotificationIDProvider m_id_provider; NotificationIDProvider m_id_provider;
std::deque<std::unique_ptr<PopNotification>> m_pop_notifications; std::deque<std::unique_ptr<PopNotification>> m_pop_notifications;
// delayed waiting notifications, first is remaining time
std::deque<DelayedNotification> m_waiting_notifications;
//timestamps used for slicing finished - notification could be gone so it needs to be stored here //timestamps used for slicing finished - notification could be gone so it needs to be stored here
std::unordered_set<int> m_used_timestamps; std::unordered_set<int> m_used_timestamps;
// True if G-code preview is active. False if the Plater is active. // True if G-code preview is active. False if the Plater is active.

View File

@ -1834,7 +1834,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
, main_frame(main_frame) , main_frame(main_frame)
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
"bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
"brim_width", "brim_offset", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material", "brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width",
"extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology", "extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology",
// These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
@ -2221,7 +2221,14 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
std::vector<size_t> obj_idxs; std::vector<size_t> obj_idxs;
for (size_t i = 0; i < input_files.size(); ++i) { for (size_t i = 0; i < input_files.size(); ++i) {
#ifdef _WIN32
auto path = input_files[i];
// On Windows, we swap slashes to back slashes, see GH #6803 as read_from_file() does not understand slashes on Windows thus it assignes full path to names of loaded objects.
path.make_preferred();
#else // _WIN32
// Don't make a copy on Posix. Slash is a path separator, back slashes are not accepted as a substitute.
const auto &path = input_files[i]; const auto &path = input_files[i];
#endif // _WIN32
const auto filename = path.filename(); const auto filename = path.filename();
dlg.Update(static_cast<int>(100.0f * static_cast<float>(i) / static_cast<float>(input_files.size())), _L("Loading file") + ": " + from_path(filename)); dlg.Update(static_cast<int>(100.0f * static_cast<float>(i) / static_cast<float>(input_files.size())), _L("Loading file") + ": " + from_path(filename));
dlg.Fit(); dlg.Fit();
@ -2336,9 +2343,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
else { else {
model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion)); model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion));
for (auto obj : model.objects) for (auto obj : model.objects)
if (obj->name.empty() || if (obj->name.empty())
obj->name.find_first_of("/") != std::string::npos) // When file is imported from Fusion360 the path containes "/" instead of "\\" (see https://github.com/prusa3d/PrusaSlicer/issues/6803)
// But read_from_file doesn't support that direction separator and as a result object name containes full path
obj->name = fs::path(obj->input_file).filename().string(); obj->name = fs::path(obj->input_file).filename().string();
} }
} catch (const ConfigurationError &e) { } catch (const ConfigurationError &e) {
@ -2457,7 +2462,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
if (load_model) { if (load_model) {
wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().string()); wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().make_preferred().string());
// XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames... // XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames...
statusbar()->set_status_text(_L("Loaded")); statusbar()->set_status_text(_L("Loaded"));
} }
@ -2915,6 +2920,7 @@ void Plater::priv::update_print_volume_state()
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(this->config->opt<ConfigOptionPoints>("bed_shape")->values)); BoundingBox bed_box_2D = get_extents(Polygon::new_scale(this->config->opt<ConfigOptionPoints>("bed_shape")->values));
BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(this->config->opt_float("max_print_height")))); BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(this->config->opt_float("max_print_height"))));
// Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced. // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced.
print_volume.offset(BedEpsilon);
print_volume.min(2) = -1e10; print_volume.min(2) = -1e10;
this->q->model().update_print_volume_state(print_volume); this->q->model().update_print_volume_state(print_volume);
} }
@ -4786,10 +4792,7 @@ void Plater::load_project(const wxString& filename)
p->reset(); p->reset();
std::vector<fs::path> input_paths; if (! load_files({ into_path(filename) }).empty()) {
input_paths.push_back(into_path(filename));
if (! load_files(input_paths).empty()) {
// At least one file was loaded. // At least one file was loaded.
p->set_project_filename(filename); p->set_project_filename(filename);
reset_project_dirty_initial_presets(); reset_project_dirty_initial_presets();
@ -4806,7 +4809,7 @@ void Plater::add_model(bool imperial_units/* = false*/)
std::vector<fs::path> paths; std::vector<fs::path> paths;
for (const auto &file : input_files) for (const auto &file : input_files)
paths.push_back(into_path(file)); paths.emplace_back(into_path(file));
wxString snapshot_label; wxString snapshot_label;
assert(! paths.empty()); assert(! paths.empty());
@ -4839,12 +4842,8 @@ void Plater::extract_config_from_project()
wxString input_file; wxString input_file;
wxGetApp().load_project(this, input_file); wxGetApp().load_project(this, input_file);
if (input_file.empty()) if (! input_file.empty())
return; load_files({ into_path(input_file) }, false, true);
std::vector<fs::path> input_paths;
input_paths.push_back(into_path(input_file));
load_files(input_paths, false, true);
} }
void Plater::load_gcode() void Plater::load_gcode()
@ -5075,15 +5074,11 @@ bool Plater::load_files(const wxArrayString& filenames)
} }
case LoadType::LoadGeometry: { case LoadType::LoadGeometry: {
Plater::TakeSnapshot snapshot(this, _L("Import Object")); Plater::TakeSnapshot snapshot(this, _L("Import Object"));
std::vector<fs::path> in_paths; load_files({ *it }, true, false);
in_paths.emplace_back(*it);
load_files(in_paths, true, false);
break; break;
} }
case LoadType::LoadConfig: { case LoadType::LoadConfig: {
std::vector<fs::path> in_paths; load_files({ *it }, false, true);
in_paths.emplace_back(*it);
load_files(in_paths, false, true);
break; break;
} }
case LoadType::Unknown : { case LoadType::Unknown : {
@ -5158,8 +5153,11 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo
void Plater::remove_selected() void Plater::remove_selected()
{ {
if (p->get_selection().is_empty())
return;
Plater::TakeSnapshot snapshot(this, _L("Delete Selected Objects")); Plater::TakeSnapshot snapshot(this, _L("Delete Selected Objects"));
this->p->view3D->delete_selected(); p->view3D->delete_selected();
} }
void Plater::increase_instances(size_t num) void Plater::increase_instances(size_t num)
@ -6221,9 +6219,9 @@ void Plater::clear_before_change_mesh(int obj_idx)
bool paint_removed = false; bool paint_removed = false;
for (ModelVolume* mv : mo->volumes) { for (ModelVolume* mv : mo->volumes) {
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty(); paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty();
mv->supported_facets.clear(); mv->supported_facets.reset();
mv->seam_facets.clear(); mv->seam_facets.reset();
mv->mmu_segmentation_facets.clear(); mv->mmu_segmentation_facets.reset();
} }
if (paint_removed) { if (paint_removed) {
// snapshot_time is captured by copy so the lambda knows where to undo/redo to. // snapshot_time is captured by copy so the lambda knows where to undo/redo to.
@ -6585,6 +6583,11 @@ bool Plater::is_render_statistic_dialog_visible() const
return p->show_render_statistic_dialog; return p->show_render_statistic_dialog;
} }
Plater::TakeSnapshot::TakeSnapshot(Plater *plater, const std::string &snapshot_name)
: TakeSnapshot(plater, from_u8(snapshot_name)) {}
// Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu. // Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu.
bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos) bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos)
{ {

View File

@ -383,10 +383,11 @@ public:
Plater *m_plater; Plater *m_plater;
}; };
// ROII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot. // RAII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot.
class TakeSnapshot class TakeSnapshot
{ {
public: public:
TakeSnapshot(Plater *plater, const std::string &snapshot_name);
TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater) TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater)
{ {
m_plater->take_snapshot(snapshot_name); m_plater->take_snapshot(snapshot_name);

View File

@ -1874,7 +1874,7 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con
const_cast<GLModel*>(&m_arrow)->set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); const_cast<GLModel*>(&m_arrow)->set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis));
GLShaderProgram* shader = wxGetApp().get_current_shader(); GLShaderProgram* shader = wxGetApp().get_current_shader();
if (shader != nullptr) if (shader != nullptr)
shader->set_uniform("emission_factor", 0.0); shader->set_uniform("emission_factor", 0.0f);
glsafe(::glTranslated(0.0, 5.0, 0.0)); glsafe(::glTranslated(0.0, 5.0, 0.0));
m_arrow.render(); m_arrow.render();

View File

@ -1509,7 +1509,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Brim")); optgroup = page->new_optgroup(L("Brim"));
optgroup->append_single_option_line("brim_type", category_path + "brim"); optgroup->append_single_option_line("brim_type", category_path + "brim");
optgroup->append_single_option_line("brim_width", category_path + "brim"); optgroup->append_single_option_line("brim_width", category_path + "brim");
optgroup->append_single_option_line("brim_offset", category_path + "brim"); optgroup->append_single_option_line("brim_separation", category_path + "brim");
page = add_options_page(L("Support material"), "support"); page = add_options_page(L("Support material"), "support");
category_path = "support-material_1698#"; category_path = "support-material_1698#";
@ -1565,12 +1565,14 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Modifiers")); optgroup = page->new_optgroup(L("Modifiers"));
optgroup->append_single_option_line("first_layer_speed"); optgroup->append_single_option_line("first_layer_speed");
optgroup->append_single_option_line("first_layer_speed_over_raft");
optgroup = page->new_optgroup(L("Acceleration control (advanced)")); optgroup = page->new_optgroup(L("Acceleration control (advanced)"));
optgroup->append_single_option_line("perimeter_acceleration"); optgroup->append_single_option_line("perimeter_acceleration");
optgroup->append_single_option_line("infill_acceleration"); optgroup->append_single_option_line("infill_acceleration");
optgroup->append_single_option_line("bridge_acceleration"); optgroup->append_single_option_line("bridge_acceleration");
optgroup->append_single_option_line("first_layer_acceleration"); optgroup->append_single_option_line("first_layer_acceleration");
optgroup->append_single_option_line("first_layer_acceleration_over_raft");
optgroup->append_single_option_line("default_acceleration"); optgroup->append_single_option_line("default_acceleration");
optgroup = page->new_optgroup(L("Autospeed (advanced)")); optgroup = page->new_optgroup(L("Autospeed (advanced)"));

View File

@ -23,12 +23,6 @@ BUILD()
RETVAL = newSVpv(SLIC3R_BUILD_ID, 0); RETVAL = newSVpv(SLIC3R_BUILD_ID, 0);
OUTPUT: RETVAL OUTPUT: RETVAL
SV*
DEBUG_OUT_PATH_PREFIX()
CODE:
RETVAL = newSVpv(SLIC3R_DEBUG_OUT_PATH_PREFIX, 0);
OUTPUT: RETVAL
SV* SV*
FORK_NAME() FORK_NAME()
CODE: CODE: