Merge branch 'master' into lm_drilling_backend_rebased

This commit is contained in:
Lukas Matena 2020-02-03 15:20:16 +01:00
commit a1d4dab999
63 changed files with 3891 additions and 3327 deletions

View file

@ -233,11 +233,14 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
)
if (_is_multi)
list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE} ${OpenVDB_${COMPONENT}_LIBRARY_DEBUG})
list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE})
if (MSVC OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG)
list(APPEND OpenVDB_LIB_COMPONENTS ${OpenVDB_${COMPONENT}_LIBRARY_DEBUG})
endif ()
list(FIND CMAKE_CONFIGURATION_TYPES "Debug" _has_debug)
if(OpenVDB_${COMPONENT}_LIBRARY_RELEASE AND (_has_debug LESS 0 OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG))
if(OpenVDB_${COMPONENT}_LIBRARY_RELEASE AND (NOT MSVC OR _has_debug LESS 0 OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG))
set(OpenVDB_${COMPONENT}_FOUND TRUE)
else()
set(OpenVDB_${COMPONENT}_FOUND FALSE)
@ -518,12 +521,19 @@ list(REMOVE_DUPLICATES OpenVDB_LIBRARY_DIRS)
foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
if(NOT TARGET OpenVDB::${COMPONENT})
if (${COMPONENT} STREQUAL openvdb)
include (${CMAKE_CURRENT_LIST_DIR}/CheckAtomic.cmake)
set(_LINK_LIBS ${_OPENVDB_VISIBLE_DEPENDENCIES} ${CMAKE_REQUIRED_LIBRARIES})
else ()
set(_LINK_LIBS _OPENVDB_VISIBLE_DEPENDENCIES)
endif ()
add_library(OpenVDB::${COMPONENT} UNKNOWN IMPORTED)
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
INTERFACE_COMPILE_OPTIONS "${OpenVDB_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${OpenVDB_INCLUDE_DIR}"
IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps
INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers)
INTERFACE_LINK_LIBRARIES "${_LINK_LIBS}" # visible deps (headers)
INTERFACE_COMPILE_FEATURES cxx_std_11
IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}"
)
@ -531,8 +541,13 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
if (_is_multi)
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}"
IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}"
)
if (MSVC OR OpenVDB_${COMPONENT}_LIBRARY_DEBUG)
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}"
)
endif ()
endif ()
if (OPENVDB_USE_STATIC_LIBS)

View file

@ -0,0 +1,7 @@
min_slic3r_version = 2.2.0-alpha3
0.0.2-alpha0 Print bed textures are now configurable from the Preset Bundle. Requires PrusaSlicer 2.2.0-alpha3 and newer.
# The following line (max_slic3r_version) forces the users of PrusaSlicer 2.2.0-alpha3 and newer to update the profiles to 1.1.1-alpha3 and newer,
# so they will see the print bed.
max_slic3r_version = 2.2.0-alpha2
min_slic3r_version = 2.2.0-alpha0
0.0.1 Initial version

View file

@ -5,7 +5,7 @@
name = Creality
# Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 0.0.1
config_version = 0.0.2-alpha0
# Where to get the updates from?
config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/
# changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1%

View file

@ -635,13 +635,30 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh<
{
using namespace std;
auto opposite_vertex = [](const Index a0, const Index a1) {
// get opposite index of A
int a2=-1;
for(int c=0;c<3;++c)
if(c!=a0 && c!=a1) {
a2 = c;
break;
}
assert(a2 != -1);
return a2;
};
// must be co-planar
if(
A.supporting_plane() != B.supporting_plane() &&
A.supporting_plane() != B.supporting_plane().opposite())
{
Index a2 = opposite_vertex(shared[0].first, shared[1].first);
if (! B.supporting_plane().has_on(A.vertex(a2)))
return false;
}
Index b2 = opposite_vertex(shared[0].second, shared[1].second);
if (int(CGAL::coplanar_orientation(A.vertex(shared[0].first), A.vertex(shared[1].first), A.vertex(a2))) *
int(CGAL::coplanar_orientation(B.vertex(shared[0].second), B.vertex(shared[1].second), B.vertex(b2))) < 0)
// There is certainly no self intersection as the non-shared triangle vertices lie on opposite sides of the shared edge.
return false;
// Since A and B are non-degenerate the intersection must be a polygon
// (triangle). Either
// - the vertex of A (B) opposite the shared edge of lies on B (A), or
@ -650,22 +667,10 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh<
// Determine if the vertex opposite edge (a0,a1) in triangle A lies in
// (intersects) triangle B
const auto & opposite_point_inside = [](
const Triangle_3 & A, const Index a0, const Index a1, const Triangle_3 & B)
const Triangle_3 & A, const Index a2, const Triangle_3 & B)
-> bool
{
// get opposite index
Index a2 = -1;
for(int c = 0;c<3;c++)
{
if(c != a0 && c != a1)
{
a2 = c;
break;
}
}
assert(a2 != -1);
bool ret = CGAL::do_intersect(A.vertex(a2),B);
return ret;
return CGAL::do_intersect(A.vertex(a2),B);
};
// Determine if edge opposite vertex va in triangle A intersects edge
@ -681,8 +686,8 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh<
};
if(
!opposite_point_inside(A,shared[0].first,shared[1].first,B) &&
!opposite_point_inside(B,shared[0].second,shared[1].second,A) &&
!opposite_point_inside(A,a2,B) &&
!opposite_point_inside(B,b2,A) &&
!opposite_edges_intersect(A,shared[0].first,B,shared[1].second) &&
!opposite_edges_intersect(A,shared[1].first,B,shared[0].second))
{
@ -936,4 +941,4 @@ inline void igl::copyleft::cgal::SelfIntersectMesh<
//process_chunk(0, candidate_triangle_pairs.size());
}
#endif
#endif

View file

@ -233,15 +233,30 @@ cmake_policy(SET CMP0011 NEW)
find_package(CGAL REQUIRED)
cmake_policy(POP)
add_library(libslic3r_cgal OBJECT MeshBoolean.cpp MeshBoolean.hpp)
target_include_directories(libslic3r_cgal PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
$<TARGET_PROPERTY:libigl,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_INCLUDE_DIRECTORIES>)
target_compile_definitions(libslic3r_cgal PRIVATE
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_DEFINITIONS>)
target_compile_options(libslic3r_cgal PRIVATE
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_OPTIONS>)
add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp)
target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options
# (-frounding-math) still propagate to dependent libs which is not desired.
get_target_property(_cgal_tgt CGAL::CGAL ALIASED_TARGET)
if (NOT TARGET ${_cgal_tgt})
set (_cgal_tgt CGAL::CGAL)
endif ()
get_target_property(_opts ${_cgal_tgt} INTERFACE_COMPILE_OPTIONS)
if (_opts)
set(_opts_bad "${_opts}")
set(_opts_good "${_opts}")
list(FILTER _opts_bad INCLUDE REGEX frounding-math)
list(FILTER _opts_good EXCLUDE REGEX frounding-math)
set_target_properties(${_cgal_tgt} PROPERTIES INTERFACE_COMPILE_OPTIONS "${_opts_good}")
target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}")
endif()
target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl)
if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround
target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF)
endif ()
encoding_check(libslic3r)
@ -263,7 +278,7 @@ target_link_libraries(libslic3r
qhull
semver
TBB::tbb
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_LINK_LIBRARIES>
libslic3r_cgal
${CMAKE_DL_LIBS}
)
@ -282,5 +297,3 @@ endif()
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
endif ()
target_sources(libslic3r PRIVATE $<TARGET_OBJECTS:libslic3r_cgal>)

View file

@ -958,7 +958,7 @@ namespace DoExport {
skirts.emplace_back(std::move(s));
}
ooze_prevention.enable = true;
ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.));
ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.)));
#if 0
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
@ -1091,7 +1091,7 @@ namespace DoExport {
static inline std::vector<const PrintInstance*> sort_object_instances_by_max_z(const Print &print)
{
std::vector<const PrintObject*> objects(print.objects().begin(), print.objects().end());
std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->size(2) < po2->size(2); });
std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->size()(2) < po2->size()(2); });
std::vector<const PrintInstance*> instances;
instances.reserve(objects.size());
for (const PrintObject *object : objects)

View file

@ -252,46 +252,6 @@ template<class T> struct remove_cvref
template<class T> using remove_cvref_t = typename remove_cvref<T>::type;
template<class T> using DefaultContainer = std::vector<T>;
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
template<class T, class I, template<class> class Container = DefaultContainer>
inline Container<remove_cvref_t<T>> linspace(const T &start,
const T &stop,
const I &n)
{
Container<remove_cvref_t<T>> vals(n, T());
T stride = (stop - start) / n;
size_t i = 0;
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
return start + i++ * stride;
});
return vals;
}
/// A set of equidistant values starting from 'start' (inclusive), ending
/// in the closest multiple of 'stride' less than or equal to 'end' and
/// leaving 'stride' space between each value.
/// Very similar to Matlab [start:stride:end] notation.
template<class T, template<class> class Container = DefaultContainer>
inline Container<remove_cvref_t<T>> grid(const T &start,
const T &stop,
const T &stride)
{
Container<remove_cvref_t<T>>
vals(size_t(std::ceil((stop - start) / stride)), T());
int i = 0;
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
return start + i++ * stride;
});
return vals;
}
// A shorter C++14 style form of the enable_if metafunction
template<bool B, class T>
using enable_if_t = typename std::enable_if<B, T>::type;
@ -392,6 +352,56 @@ inline IntegerOnly<I, std::vector<T, Args...>> reserve_vector(I capacity)
return ret;
}
/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
template<class T, class I>
inline std::vector<T> linspace_vector(const ArithmeticOnly<T> &start,
const T &stop,
const IntegerOnly<I> &n)
{
std::vector<T> vals(n, T());
T stride = (stop - start) / n;
size_t i = 0;
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
return start + i++ * stride;
});
return vals;
}
template<size_t N, class T>
inline std::array<ArithmeticOnly<T>, N> linspace_array(const T &start, const T &stop)
{
std::array<T, N> vals = {T()};
T stride = (stop - start) / N;
size_t i = 0;
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
return start + i++ * stride;
});
return vals;
}
/// A set of equidistant values starting from 'start' (inclusive), ending
/// in the closest multiple of 'stride' less than or equal to 'end' and
/// leaving 'stride' space between each value.
/// Very similar to Matlab [start:stride:end] notation.
template<class T>
inline std::vector<ArithmeticOnly<T>> grid(const T &start,
const T &stop,
const T &stride)
{
std::vector<T> vals(size_t(std::ceil((stop - start) / stride)), T());
int i = 0;
std::generate(vals.begin(), vals.end(), [&i, start, stride] {
return start + i++ * stride;
});
return vals;
}
} // namespace Slic3r
#endif // MTUTILS_HPP

View file

@ -111,7 +111,7 @@ static TriangleMesh cgal_to_triangle_mesh(const _CGALMesh &cgalmesh)
auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face));
int i = 0;
Vec3crd trface;
for (auto v : vtc) trface(i++) = int(v.idx());
for (auto v : vtc) trface(i++) = static_cast<unsigned>(v);
facets.emplace_back(trface);
}

View file

@ -907,10 +907,8 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const
const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true);
for (const ModelVolume *v : this->volumes)
{
if (v->is_model_part())
m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
}
}
return m_raw_bounding_box;
}
@ -1115,7 +1113,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
if (keep_upper) {
upper->set_model(nullptr);
upper->sla_support_points.clear();
lower->sla_drain_holes.clear();
upper->sla_drain_holes.clear();
upper->sla_points_status = sla::PointsStatus::NoPoints;
upper->clear_volumes();
upper->input_file = "";

View file

@ -674,6 +674,7 @@ public:
set_rotation(Z, rotation);
set_offset(X, unscale<double>(offs(X)));
set_offset(Y, unscale<double>(offs(Y)));
this->object->invalidate_bounding_box();
}
protected:

View file

@ -836,7 +836,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Update the ModelObject instance, possibly invalidate the linked PrintObjects.
assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved);
// Check whether a model part volume was added or removed, their transformations or order changed.
// Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked.
// Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked.
bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART);
bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER);
bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER);
@ -899,10 +899,14 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
model_object.instances.emplace_back(new ModelInstance(*model_instance));
model_object.instances.back()->set_model_object(&model_object);
}
} else {
// Just synchronize the content of the instances. This avoids memory allocation and it does not invalidate ModelInstance pointers,
// which may be accessed by G-code export in the meanwhile to deduce sequential print order.
auto new_instance = model_object_new.instances.begin();
} else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(),
[](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable &&
l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) {
// If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid.
// This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only.
model_object.invalidate_bounding_box();
// Synchronize the content of instances.
auto new_instance = model_object_new.instances.begin();
for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) {
(*old_instance)->set_transformation((*new_instance)->get_transformation());
(*old_instance)->print_volume_state = (*new_instance)->print_volume_state;
@ -1197,7 +1201,7 @@ std::string Print::validate() const
{
std::vector<coord_t> object_height;
for (const PrintObject *object : m_objects)
object_height.insert(object_height.end(), object->instances().size(), object->size(2));
object_height.insert(object_height.end(), object->instances().size(), object->size()(2));
std::sort(object_height.begin(), object_height.end());
// Ignore the tallest *copy* (this is why we repeat height for all of them):
// it will be printed as last one so its height doesn't matter.
@ -1429,7 +1433,7 @@ BoundingBox Print::bounding_box() const
for (const PrintObject *object : m_objects)
for (const PrintInstance &instance : object->instances()) {
bb.merge(instance.shift);
bb.merge(instance.shift + to_2d(object->size));
bb.merge(instance.shift + to_2d(object->size()));
}
return bb;
}

View file

@ -120,17 +120,17 @@ public:
// so that next call to make_perimeters() performs a union() before computing loops
bool typed_slices;
Vec3crd size; // XYZ in scaled coordinates
// XYZ in scaled coordinates
const Vec3crd& size() const { return m_size; }
const PrintObjectConfig& config() const { return m_config; }
const LayerPtrs& layers() const { return m_layers; }
const SupportLayerPtrs& support_layers() const { return m_support_layers; }
const Transform3d& trafo() const { return m_trafo; }
const PrintInstances& instances() const { return m_instances; }
const Point instance_center(size_t idx) const { return m_instances[idx].shift + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); }
const Point instance_center(size_t idx) const { return m_instances[idx].shift + m_copies_shift + Point(this->size().x() / 2, this->size().y() / 2); }
// since the object is aligned to origin, bounding box coincides with size
BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); }
BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size())); }
// adds region_id, too, if necessary
void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) {
@ -235,6 +235,8 @@ private:
void combine_infill();
void _generate_support_material();
// XYZ in scaled coordinates
Vec3crd m_size;
PrintObjectConfig m_config;
// Translation in Z + Rotation + Scaling / Mirroring.
Transform3d m_trafo = Transform3d::Identity();

View file

@ -43,7 +43,7 @@ namespace Slic3r {
PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_instances) :
PrintObjectBaseWithState(print, model_object),
typed_slices(false),
size(Vec3crd::Zero())
m_size(Vec3crd::Zero())
{
// Compute the translation to be applied to our meshes so that we work with smaller coordinates
{
@ -56,7 +56,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta
const BoundingBoxf3 modobj_bbox = model_object->raw_bounding_box();
m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1));
// Scale the object size and store it
this->size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast<coord_t>();
this->m_size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast<coord_t>();
}
if (add_instances) {
@ -73,6 +73,8 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta
PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances)
{
for (PrintInstance &i : instances)
i.shift += m_copies_shift;
// Invalidate and set copies.
PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED;
bool equal_length = instances.size() == m_instances.size();
@ -83,7 +85,7 @@ PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances)
if (m_print->invalidate_steps({ psSkirt, psBrim, psGCodeExport }) ||
(! equal_length && m_print->invalidate_step(psWipeTower)))
status = PrintBase::APPLY_STATUS_INVALIDATED;
m_instances = instances;
m_instances = std::move(instances);
for (PrintInstance &i : m_instances)
i.print_object = this;
}
@ -1448,7 +1450,7 @@ void PrintObject::update_slicing_parameters()
{
if (! m_slicing_params.valid)
m_slicing_params = SlicingParameters::create_from_config(
this->print()->config(), m_config, unscale<double>(this->size(2)), this->object_extruders());
this->print()->config(), m_config, unscale<double>(this->size()(2)), this->object_extruders());
}
SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z)

View file

@ -26,6 +26,7 @@
#include <igl/ray_mesh_intersect.h>
#include <igl/point_mesh_squared_distance.h>
#include <igl/remove_duplicate_vertices.h>
#include <igl/collapse_small_triangles.h>
#include <igl/signed_distance.h>
#ifdef _MSC_VER
#pragma warning(pop)
@ -194,17 +195,12 @@ public:
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
};
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
static const double dEPS = 1e-6;
static const constexpr double MESH_EPS = 1e-6;
void to_eigen_mesh(const TriangleMesh &tmesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F)
{
const stl_file& stl = tmesh.stl;
auto&& bb = tmesh.bounding_box();
m_ground_level += bb.min(Z);
Eigen::MatrixXd V;
Eigen::MatrixXi F;
V.resize(3*stl.stats.number_of_facets, 3);
F.resize(stl.stats.number_of_facets, 3);
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
@ -217,9 +213,37 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
F(i, 2) = int(3*i+2);
}
// We will convert this to a proper 3d mesh with no duplicate points.
Eigen::VectorXi SVI, SVJ;
igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F);
if (!tmesh.has_shared_vertices())
{
Eigen::MatrixXd rV;
Eigen::MatrixXi rF;
// We will convert this to a proper 3d mesh with no duplicate points.
Eigen::VectorXi SVI, SVJ;
igl::remove_duplicate_vertices(V, F, MESH_EPS, rV, SVI, SVJ, rF);
V = std::move(rV);
F = std::move(rF);
}
}
void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &out)
{
Pointf3s points(size_t(V.rows()));
std::vector<Vec3crd> facets(size_t(F.rows()));
for (Eigen::Index i = 0; i < V.rows(); ++i)
points[size_t(i)] = V.row(i);
for (Eigen::Index i = 0; i < F.rows(); ++i)
facets[size_t(i)] = F.row(i);
out = {points, facets};
}
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
auto&& bb = tmesh.bounding_box();
m_ground_level += bb.min(Z);
to_eigen_mesh(tmesh, m_V, m_F);
// Build the AABB accelaration tree
m_aabb->init(m_V, m_F);
@ -262,6 +286,10 @@ EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
}
EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default;
EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default;
EigenMesh3D::hit_result
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
{

View file

@ -12,6 +12,9 @@ namespace sla {
struct Contour3D;
void to_eigen_mesh(const TriangleMesh &mesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F);
void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &);
/// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree.
// Implemented in libslic3r/SLA/Common.cpp
@ -30,11 +33,15 @@ class EigenMesh3D {
public:
EigenMesh3D(const TriangleMesh&);
explicit EigenMesh3D(const TriangleMesh&);
explicit EigenMesh3D(const Contour3D &other);
EigenMesh3D(const EigenMesh3D& other);
EigenMesh3D(const Contour3D &other);
EigenMesh3D& operator=(const EigenMesh3D&);
EigenMesh3D(EigenMesh3D &&other);
EigenMesh3D& operator=(EigenMesh3D &&other);
~EigenMesh3D();
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
@ -70,9 +77,6 @@ public:
inline bool is_valid() const { return m_mesh != nullptr; }
inline bool is_hit() const { return !std::isinf(m_t); }
// Hit_result can decay into a double as the hit distance.
inline operator double() const { return distance(); }
inline const Vec3d& normal() const {
assert(is_valid());
return m_normal;

View file

@ -1,3 +1,5 @@
#include <string_view>
#include <libslic3r/SLA/RasterWriter.hpp>
#include "libslic3r/PrintConfig.hpp"
@ -12,14 +14,16 @@
namespace Slic3r { namespace sla {
std::string RasterWriter::createIniContent(const std::string& projectname) const
void RasterWriter::write_ini(const std::map<std::string, std::string> &m, std::string &ini)
{
for (auto &param : m) ini += param.first + " = " + param.second + "\n";
}
std::string RasterWriter::create_ini_content(const std::string& projectname) const
{
std::string out("action = print\njobDir = ");
out += projectname + "\n";
for (auto &param : m_config)
out += param.first + " = " + param.second + "\n";
write_ini(m_config, out);
return out;
}
@ -53,7 +57,12 @@ void RasterWriter::save(Zipper &zipper, const std::string &prjname)
zipper.add_entry("config.ini");
zipper << createIniContent(project);
zipper << create_ini_content(project);
zipper.add_entry("prusaslicer.ini");
std::string prusaslicer_ini;
write_ini(m_slicer_config, prusaslicer_ini);
zipper << prusaslicer_ini;
for(unsigned i = 0; i < m_layers_rst.size(); i++)
{
@ -89,6 +98,29 @@ std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
return ret;
}
void append_full_config(const DynamicPrintConfig &cfg, std::map<std::string, std::string> &keys)
{
using namespace std::literals::string_view_literals;
// Sorted list of config keys, which shall not be stored into the ini.
static constexpr auto banned_keys = {
"compatible_printers"sv,
"compatible_prints"sv,
"print_host"sv,
"printhost_apikey"sv,
"printhost_cafile"sv
};
assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
auto is_banned = [](const std::string &key) {
return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
};
for (const std::string &key : cfg.keys())
if (! is_banned(key) && ! cfg.option(key)->is_nil())
keys[key] = cfg.opt_serialize(key);
}
} // namespace
void RasterWriter::set_config(const DynamicPrintConfig &cfg)
@ -101,9 +133,9 @@ void RasterWriter::set_config(const DynamicPrintConfig &cfg)
m_config["printerVariant"] = get_cfg_value(cfg, "printer_variant");
m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id");
m_config["fileCreationTimestamp"] = Utils::utc_timestamp();
m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID;
append_full_config(cfg, m_slicer_config);
}
void RasterWriter::set_statistics(const PrintStatistics &stats)

View file

@ -66,8 +66,10 @@ private:
double m_gamma;
std::map<std::string, std::string> m_config;
std::map<std::string, std::string> m_slicer_config;
std::string createIniContent(const std::string& projectname) const;
static void write_ini(const std::map<std::string, std::string> &m, std::string &ini);
std::string create_ini_content(const std::string& projectname) const;
public:

View file

@ -166,190 +166,182 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder,
return pc == ABORT;
}
// Give points on a 3D ring with given center, radius and orientation
// method based on:
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
template<size_t N>
class PointRing {
std::array<double, N> m_phis;
// Two vectors that will be perpendicular to each other and to the
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
// placeholder.
// a and b vectors are perpendicular to the ring direction and to each other.
// Together they define the plane where we have to iterate with the
// given angles in the 'm_phis' vector
Vec3d a = {0, 1, 0}, b;
double m_radius = 0.;
static inline bool constexpr is_one(double val)
{
return std::abs(std::abs(val) - 1) < 1e-20;
}
public:
PointRing(const Vec3d &n)
{
m_phis = linspace_array<N>(0., 2 * PI);
// We have to address the case when the direction vector v (same as
// dir) is coincident with one of the world axes. In this case two of
// its components will be completely zero and one is 1.0. Our method
// becomes dangerous here due to division with zero. Instead, vector
// 'a' can be an element-wise rotated version of 'v'
if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
a = {n(Z), n(X), n(Y)};
b = {n(Y), n(Z), n(X)};
}
else {
a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
b = a.cross(n);
}
}
Vec3d get(size_t idx, const Vec3d src, double r) const
{
double phi = m_phis[idx];
double sinphi = std::sin(phi);
double cosphi = std::cos(phi);
double rpscos = r * cosphi;
double rpssin = r * sinphi;
// Point on the sphere
return {src(X) + rpscos * a(X) + rpssin * b(X),
src(Y) + rpscos * a(Y) + rpssin * b(Y),
src(Z) + rpscos * a(Z) + rpssin * b(Z)};
}
};
template<class C, class Hit = EigenMesh3D::hit_result>
static Hit min_hit(const C &hits)
{
auto mit = std::min_element(hits.begin(), hits.end(),
[](const Hit &h1, const Hit &h2) {
return h1.distance() < h2.distance();
});
return *mit;
}
EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width)
{
static const size_t SAMPLES = 8;
// method based on:
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
// Move away slightly from the touching point to avoid raycasting on the
// inner surface of the mesh.
const double& sd = m_cfg.safety_distance_mm;
auto& m = m_mesh;
using HitResult = EigenMesh3D::hit_result;
// Hit results
std::array<HitResult, SAMPLES> hits;
struct Rings {
double rpin;
double rback;
Vec3d spin;
Vec3d sback;
PointRing<SAMPLES> ring;
Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); }
Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); }
} rings {r_pin + sd, r_back + sd, s, s + width * dir, dir};
// We will shoot multiple rays from the head pinpoint in the direction
// of the pinhead robe (side) surface. The result will be the smallest
// hit distance.
// Move away slightly from the touching point to avoid raycasting on the
// inner surface of the mesh.
Vec3d v = dir; // Our direction (axis)
Vec3d c = s + width * dir;
const double& sd = m_cfg.safety_distance_mm;
ccr::enumerate(hits.begin(), hits.end(),
[&m, &rings, sd](HitResult &hit, size_t i) {
// Two vectors that will be perpendicular to each other and to the
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
// placeholder.
Vec3d a(0, 1, 0), b;
// Point on the circle on the pin sphere
Vec3d ps = rings.pinring(i);
// This is the point on the circle on the back sphere
Vec3d p = rings.backring(i);
// Point ps is not on mesh but can be inside or
// outside as well. This would cause many problems
// with ray-casting. To detect the position we will
// use the ray-casting result (which has an is_inside
// predicate).
// The portions of the circle (the head-back circle) for which we will
// shoot rays.
std::array<double, SAMPLES> phis;
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
Vec3d n = (p - ps).normalized();
auto q = m.query_ray_hit(ps + sd * n, n);
auto& m = m_mesh;
using HitResult = EigenMesh3D::hit_result;
if (q.is_inside()) { // the hit is inside the model
if (q.distance() > rings.rpin) {
// If we are inside the model and the hit
// distance is bigger than our pin circle
// diameter, it probably indicates that the
// support point was already inside the
// model, or there is really no space
// around the point. We will assign a zero
// hit distance to these cases which will
// enforce the function return value to be
// an invalid ray with zero hit distance.
// (see min_element at the end)
hit = HitResult(0.0);
} else {
// re-cast the ray from the outside of the
// object. The starting point has an offset
// of 2*safety_distance because the
// original ray has also had an offset
auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n);
hit = q2;
}
} else
hit = q;
});
// Hit results
std::array<HitResult, SAMPLES> hits;
// We have to address the case when the direction vector v (same as
// dir) is coincident with one of the world axes. In this case two of
// its components will be completely zero and one is 1.0. Our method
// becomes dangerous here due to division with zero. Instead, vector
// 'a' can be an element-wise rotated version of 'v'
auto chk1 = [] (double val) {
return std::abs(std::abs(val) - 1) < 1e-20;
};
if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) {
a = {v(Z), v(X), v(Y)};
b = {v(Y), v(Z), v(X)};
}
else {
a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize();
b = a.cross(v);
}
// Now a and b vectors are perpendicular to v and to each other.
// Together they define the plane where we have to iterate with the
// given angles in the 'phis' vector
ccr::enumerate(
phis.begin(), phis.end(),
[&hits, &m, sd, r_pin, r_back, s, a, b, c](double phi, size_t i) {
double sinphi = std::sin(phi);
double cosphi = std::cos(phi);
// Let's have a safety coefficient for the radiuses.
double rpscos = (sd + r_pin) * cosphi;
double rpssin = (sd + r_pin) * sinphi;
double rpbcos = (sd + r_back) * cosphi;
double rpbsin = (sd + r_back) * sinphi;
// Point on the circle on the pin sphere
Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X),
s(Y) + rpscos * a(Y) + rpssin * b(Y),
s(Z) + rpscos * a(Z) + rpssin * b(Z));
// Point ps is not on mesh but can be inside or
// outside as well. This would cause many problems
// with ray-casting. To detect the position we will
// use the ray-casting result (which has an is_inside
// predicate).
// This is the point on the circle on the back sphere
Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X),
c(Y) + rpbcos * a(Y) + rpbsin * b(Y),
c(Z) + rpbcos * a(Z) + rpbsin * b(Z));
Vec3d n = (p - ps).normalized();
auto q = m.query_ray_hit(ps + sd * n, n);
if (q.is_inside()) { // the hit is inside the model
if (q.distance() > r_pin + sd) {
// If we are inside the model and the hit
// distance is bigger than our pin circle
// diameter, it probably indicates that the
// support point was already inside the
// model, or there is really no space
// around the point. We will assign a zero
// hit distance to these cases which will
// enforce the function return value to be
// an invalid ray with zero hit distance.
// (see min_element at the end)
hits[i] = HitResult(0.0);
} else {
// re-cast the ray from the outside of the
// object. The starting point has an offset
// of 2*safety_distance because the
// original ray has also had an offset
auto q2 = m.query_ray_hit(
ps + (q.distance() + 2 * sd) * n, n);
hits[i] = q2;
}
} else
hits[i] = q;
});
auto mit = std::min_element(hits.begin(), hits.end());
return *mit;
return min_hit(hits);
}
EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
const Vec3d &s, const Vec3d &dir, double r, bool ins_check)
const Vec3d &src, const Vec3d &dir, double r, bool ins_check)
{
static const size_t SAMPLES = 8;
PointRing<SAMPLES> ring{dir};
// helper vector calculations
Vec3d a(0, 1, 0), b;
const double& sd = m_cfg.safety_distance_mm;
// INFO: for explanation of the method used here, see the previous
// method's comments.
auto chk1 = [] (double val) {
return std::abs(std::abs(val) - 1) < 1e-20;
};
if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) {
a = {dir(Z), dir(X), dir(Y)};
b = {dir(Y), dir(Z), dir(X)};
}
else {
a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize();
b = a.cross(dir);
}
// circle portions
std::array<double, SAMPLES> phis;
for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size();
auto& m = m_mesh;
using HitResult = EigenMesh3D::hit_result;
using Hit = EigenMesh3D::hit_result;
// Hit results
std::array<HitResult, SAMPLES> hits;
std::array<Hit, SAMPLES> hits;
ccr::enumerate(
phis.begin(), phis.end(),
[&m, a, b, sd, dir, r, s, ins_check, &hits] (double phi, size_t i) {
double sinphi = std::sin(phi);
double cosphi = std::cos(phi);
// Let's have a safety coefficient for the radiuses.
double rcos = (sd + r) * cosphi;
double rsin = (sd + r) * sinphi;
// Point on the circle on the pin sphere
Vec3d p (s(X) + rcos * a(X) + rsin * b(X),
s(Y) + rcos * a(Y) + rsin * b(Y),
s(Z) + rcos * a(Z) + rsin * b(Z));
auto hr = m.query_ray_hit(p + sd*dir, dir);
if(ins_check && hr.is_inside()) {
if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0);
else {
// re-cast the ray from the outside of the object
auto hr2 =
m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir);
hits[i] = hr2;
}
} else hits[i] = hr;
});
ccr::enumerate(hits.begin(), hits.end(),
[this, r, src, ins_check, &ring, dir] (Hit &hit, size_t i) {
const double sd = m_cfg.safety_distance_mm;
// Point on the circle on the pin sphere
Vec3d p = ring.get(i, src, r + sd);
auto hr = m_mesh.query_ray_hit(p + sd * dir, dir);
if(ins_check && hr.is_inside()) {
if(hr.distance() > 2 * r + sd) hit = Hit(0.0);
else {
// re-cast the ray from the outside of the object
hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir);
}
} else hit = hr;
});
auto mit = std::min_element(hits.begin(), hits.end());
return *mit;
return min_hit(hits);
}
bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
@ -419,7 +411,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
// TODO: This is a workaround to not have a faulty last bridge
while(ej(Z) >= eupper(Z) /*endz*/) {
if(bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r) >= bridge_distance)
if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance)
{
m_builder.add_crossbridge(sj, ej, pillar.r);
was_connected = true;
@ -430,7 +422,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
Vec3d sjback(ej(X), ej(Y), sj(Z));
Vec3d ejback(sj(X), sj(Y), ej(Z));
if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) &&
bridge_mesh_intersect(sjback, dirv(sjback, ejback),
bridge_mesh_distance(sjback, dirv(sjback, ejback),
pillar.r) >= bridge_distance) {
// need to check collision for the cross stick
m_builder.add_crossbridge(sjback, ejback, pillar.r);
@ -487,7 +479,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
bridgestart(Z) -= zdiff;
touchjp(Z) = Zdown;
double t = bridge_mesh_intersect(headjp, {0,0,-1}, r);
double t = bridge_mesh_distance(headjp, DOWN, r);
// We can't insert a pillar under the source head to connect
// with the nearby pillar's starting junction
@ -505,8 +497,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm;
if(bridgeend(Z) < minz) return false;
double t = bridge_mesh_intersect(bridgestart,
dirv(bridgestart, bridgeend), r);
double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r);
// Cannot insert the bridge. (further search might not worth the hassle)
if(t < distance(bridgestart, bridgeend)) return false;
@ -633,7 +624,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
};
// We have to check if the bridge is feasible.
if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm())
if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm())
abort_in_shame();
else {
// If the new endpoint is below ground, do not make a pillar
@ -764,7 +755,7 @@ void SupportTreeBuildsteps::filter()
{
auto dir = spheric_to_dir(plr, azm).normalized();
double score = pinhead_mesh_intersect(
double score = pinhead_mesh_distance(
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
return score;
@ -950,11 +941,11 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
{
auto hjp = head.junction_point();
double r = head.r_back_mm;
double t = bridge_mesh_intersect(hjp, dir, head.r_back_mm);
double t = bridge_mesh_distance(hjp, dir, head.r_back_mm);
double d = 0, tdown = 0;
t = std::min(t, m_cfg.max_bridge_length_mm);
while (d < t && !std::isinf(tdown = bridge_mesh_intersect(hjp + d * dir, DOWN, r)))
while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r)))
d += r;
if(!std::isinf(tdown)) return false;
@ -989,7 +980,7 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head)
auto oresult = solver.optimize_max(
[this, hjp, r_back](double plr, double azm) {
Vec3d n = spheric_to_dir(plr, azm).normalized();
return bridge_mesh_intersect(hjp, n, r_back);
return bridge_mesh_distance(hjp, n, r_back);
},
initvals(polar, azimuth), // let's start with what we have
bound(3*PI/4, PI), // Must not exceed the slope limit
@ -1259,9 +1250,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
m_pillar_index.insert(pp.endpoint(), unsigned(pp.id));
m_builder.add_junction(s, pillar().r);
double t = bridge_mesh_intersect(pillarsp,
dirv(pillarsp, s),
pillar().r);
double t = bridge_mesh_distance(pillarsp, dirv(pillarsp, s),
pillar().r);
if (distance(pillarsp, s) < t)
m_builder.add_bridge(pillarsp, s, pillar().r);
@ -1312,8 +1302,8 @@ void SupportTreeBuildsteps::routing_headless()
Vec3d sj = sp + R * n; // stick start point
// This is only for checking
double idist = bridge_mesh_intersect(sph, DOWN, R, true);
double realdist = ray_mesh_intersect(sj, DOWN);
double idist = bridge_mesh_distance(sph, DOWN, R, true);
double realdist = ray_mesh_intersect(sj, DOWN).distance();
double dist = realdist;
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;

View file

@ -206,10 +206,10 @@ class SupportTreeBuildsteps {
// When bridging heads to pillars... TODO: find a cleaner solution
ccr::BlockingMutex m_bridge_mutex;
inline double ray_mesh_intersect(const Vec3d& s,
const Vec3d& dir)
inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s,
const Vec3d& dir)
{
return m_mesh.query_ray_hit(s, dir).distance();
return m_mesh.query_ray_hit(s, dir);
}
// This function will test if a future pinhead would not collide with the
@ -229,6 +229,11 @@ class SupportTreeBuildsteps {
double r_pin,
double r_back,
double width);
template<class...Args>
inline double pinhead_mesh_distance(Args&&...args) {
return pinhead_mesh_intersect(std::forward<Args>(args)...).distance();
}
// Checking bridge (pillar and stick as well) intersection with the model.
// If the function is used for headless sticks, the ins_check parameter
@ -243,6 +248,11 @@ class SupportTreeBuildsteps {
const Vec3d& dir,
double r,
bool ins_check = false);
template<class...Args>
inline double bridge_mesh_distance(Args&&...args) {
return bridge_mesh_intersect(std::forward<Args>(args)...).distance();
}
// Helper function for interconnecting two pillars with zig-zag bridges.
bool interconnect(const Pillar& pillar, const Pillar& nextpillar);

View file

@ -678,7 +678,7 @@ void SLAPrint::process()
// We want to first process all objects...
std::vector<SLAPrintObjectStep> level1_obj_steps = {
slaposHollowing, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad
slaposHollowing, slaposDrillHoles, slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad
};
// and then slice all supports to allow preview to be displayed ASAP
@ -984,10 +984,10 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step)
// propagate to dependent steps
if (step == slaposHollowing) {
invalidated |= this->invalidate_all_steps();
} else if (step == slaposObjectSlice) {
invalidated |= this->invalidate_steps({ slaposDrillHolesIfHollowed, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports });
} else if (step == slaposDrillHoles) {
invalidated |= this->invalidate_steps({ slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports });
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
} else if (step == slaposDrillHolesIfHollowed) {
} else if (step == slaposObjectSlice) {
invalidated |= this->invalidate_steps({ slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports });
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
} else if (step == slaposSupportPoints) {
@ -1095,8 +1095,6 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const
const std::vector<ExPolygons>& v = o == soModel? m_po->get_model_slices() :
m_po->get_support_slices();
if(idx >= v.size()) return EMPTY_SLICE;
return idx >= v.size() ? EMPTY_SLICE : v[idx];
}

View file

@ -20,8 +20,8 @@ enum SLAPrintStep : unsigned int {
enum SLAPrintObjectStep : unsigned int {
slaposHollowing,
slaposDrillHoles,
slaposObjectSlice,
slaposDrillHolesIfHollowed,
slaposSupportPoints,
slaposSupportTree,
slaposPad,
@ -138,9 +138,9 @@ public:
// Returns the current layer height
float layer_height() const { return m_height; }
bool is_valid() const { return ! std::isnan(m_slice_z); }
bool is_valid() const { return m_po && ! std::isnan(m_slice_z); }
const SLAPrintObject* print_obj() const { assert(m_po); return m_po; }
const SLAPrintObject* print_obj() const { return m_po; }
// Methods for setting the indices into the slice vectors.
void set_model_slice_idx(const SLAPrintObject &po, size_t id) {

View file

@ -26,9 +26,9 @@ namespace Slic3r {
namespace {
const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
5, // slaposHollowing,
20, // slaposObjectSlice,
5, // slaposDrillHolesIfHollowed
10, // slaposHollowing,
10, // slaposDrillHolesIfHollowed
10, // slaposObjectSlice,
20, // slaposSupportPoints,
10, // slaposSupportTree,
10, // slaposPad,
@ -38,9 +38,9 @@ const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
std::string OBJ_STEP_LABELS(size_t idx)
{
switch (idx) {
case slaposHollowing: return L("Hollowing and drilling holes");
case slaposHollowing: return L("Hollowing model");
case slaposDrillHoles: return L("Drilling holes into hollowed model.");
case slaposObjectSlice: return L("Slicing model");
case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed model.");
case slaposSupportPoints: return L("Generating support points");
case slaposSupportTree: return L("Generating support tree");
case slaposPad: return L("Generating pad");
@ -80,70 +80,70 @@ SLAPrint::Steps::Steps(SLAPrint *print)
void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
{
po.m_hollowing_data.reset();
bool drilling_needed = ! po.m_model_object->sla_drain_holes.empty();
// If the mesh is broken, stop immediately, even before hollowing.
//if (drilling_needed && po.transformed_mesh().needed_repair())
// throw std::runtime_error(L("The mesh appears to be too broken "
// "to drill holes into it reliably."));
if (! po.m_config.hollowing_enable.getBool())
BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
else {
BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
double thickness = po.m_config.hollowing_min_thickness.getFloat();
double quality = po.m_config.hollowing_quality.getFloat();
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
if (meshptr->empty())
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
else {
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
po.m_hollowing_data->interior = *meshptr;
auto &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
hollowed_mesh = po.transformed_mesh();
hollowed_mesh.merge(po.m_hollowing_data->interior);
hollowed_mesh.require_shared_vertices();
}
if (! po.m_config.hollowing_enable.getBool()) {
BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
return;
}
BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
// Drill holes into the hollowed/original mesh.
if (! drilling_needed)
BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes).";
double thickness = po.m_config.hollowing_min_thickness.getFloat();
double quality = po.m_config.hollowing_quality.getFloat();
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
if (meshptr->empty())
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
else {
BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes.";
sla::DrainHoles drainholes = po.transformed_drainhole_points();
TriangleMesh holes_mesh;
for (const sla::DrainHole &holept : drainholes)
holes_mesh.merge(sla::to_triangle_mesh(holept.to_mesh()));
holes_mesh.require_shared_vertices();
MeshBoolean::self_union(holes_mesh);
// If there is no hollowed mesh yet, copy the original mesh.
if (! po.m_hollowing_data) {
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
po.m_hollowing_data->hollow_mesh_with_holes = po.transformed_mesh();
}
TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
hollowed_mesh = po.get_mesh_to_print();
try {
MeshBoolean::cgal::minus(hollowed_mesh, holes_mesh);
}
catch (const std::runtime_error& ex) {
throw std::runtime_error(L("Drilling holes into the mesh failed. "
"This is usually caused by broken model. Try to fix it first."));
}
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
po.m_hollowing_data->interior = *meshptr;
auto &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
hollowed_mesh = po.transformed_mesh();
hollowed_mesh.merge(po.m_hollowing_data->interior);
hollowed_mesh.require_shared_vertices();
}
}
void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
{
// Drill holes into the hollowed/original mesh.
if (po.m_model_object->sla_drain_holes.empty()) {
BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes).";
return;
}
BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes.";
sla::DrainHoles drainholes = po.transformed_drainhole_points();
TriangleMesh holes_mesh;
for (const sla::DrainHole &holept : drainholes)
holes_mesh.merge(sla::to_triangle_mesh(holept.to_mesh()));
holes_mesh.require_shared_vertices();
MeshBoolean::cgal::self_union(holes_mesh); //FIXME-fix and use the cgal version
// If there is no hollowed mesh yet, copy the original mesh.
if (! po.m_hollowing_data) {
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
po.m_hollowing_data->hollow_mesh_with_holes = po.transformed_mesh();
}
TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
try {
MeshBoolean::cgal::minus(hollowed_mesh, holes_mesh);
} catch (const std::runtime_error &ex) {
throw std::runtime_error(L(
"Drilling holes into the mesh failed. "
"This is usually caused by broken model. Try to fix it first."));
}
hollowed_mesh.require_shared_vertices();
}
// The slicing will be performed on an imaginary 1D grid which starts from
// the bottom of the bounding box created around the supported model. So
// the first layer which is usually thicker will be part of the supports
@ -478,14 +478,16 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo
}
// get polygons for all instances in the object
static ClipperPolygons get_all_polygons(
const ExPolygons & input_polygons,
const std::vector<SLAPrintObject::Instance> &instances,
bool is_lefthanded)
static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o)
{
namespace sl = libnest2d::sl;
if (!record.print_obj()) return {};
ClipperPolygons polygons;
auto &input_polygons = record.get_slice(o);
auto &instances = record.print_obj()->instances();
bool is_lefthanded = record.print_obj()->is_left_handed();
polygons.reserve(input_polygons.size() * instances.size());
for (const ExPolygon& polygon : input_polygons) {
@ -558,6 +560,12 @@ void SLAPrint::Steps::initialize_printer_input()
coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
for(const SliceRecord& slicerecord : o->get_slice_index()) {
if (!slicerecord.is_valid())
throw std::runtime_error(
L("There are unprintable objects. Try to "
"adjust support settings to make the "
"objects printable."));
coord_t lvlid = slicerecord.print_level() - gndlvl;
// Neat trick to round the layer levels to the grid.
@ -660,22 +668,13 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
supports_polygons.reserve(c);
for(const SliceRecord& record : layer.slices()) {
const SLAPrintObject *po = record.print_obj();
const ExPolygons &modelslices = record.get_slice(soModel);
bool is_lefth = record.print_obj()->is_left_handed();
if (!modelslices.empty()) {
ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth);
for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp));
}
const ExPolygons &supportslices = record.get_slice(soSupport);
if (!supportslices.empty()) {
ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth);
for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp));
}
ClipperPolygons modelslices = get_all_polygons(record, soModel);
for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp));
ClipperPolygons supportslices = get_all_polygons(record, soSupport);
for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp));
}
model_polygons = polyunion(model_polygons);
@ -864,8 +863,8 @@ void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj)
{
switch(step) {
case slaposHollowing: hollow_model(obj); break;
case slaposDrillHoles: drill_holes(obj); break;
case slaposObjectSlice: slice_model(obj); break;
case slaposDrillHolesIfHollowed: break;
case slaposSupportPoints: support_points(obj); break;
case slaposSupportTree: support_tree(obj); break;
case slaposPad: generate_pad(obj); break;

View file

@ -44,6 +44,7 @@ public:
Steps(SLAPrint *print);
void hollow_model(SLAPrintObject &po);
void drill_holes (SLAPrintObject &po);
void slice_model(SLAPrintObject& po);
void support_points(SLAPrintObject& po);
void support_tree(SLAPrintObject& po);

View file

@ -21,8 +21,10 @@
#include <array>
#include <type_traits>
#include <algorithm>
#include <cmath>
#ifndef NDEBUG
#include <ostream>
#include <iostream>
#endif
@ -63,7 +65,7 @@ namespace implementation {
template<bool B, class T>
using enable_if_t = typename std::enable_if<B, T>::type;
// Meta predicates for floating, 'scaled coord' and generic arithmetic types
// Meta predicates for floating, integer and generic arithmetic types
template<class T, class O = T>
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
@ -82,41 +84,15 @@ struct remove_cvref {
template< class T >
using remove_cvref_t = typename remove_cvref<T>::type;
struct DOut {
#ifndef NDEBUG
std::ostream& out = std::cout;
#endif
};
template<class T>
inline DOut&& operator<<( DOut&& out, T&& d) {
#ifndef NDEBUG
out.out << d;
#endif
return std::move(out);
}
inline DOut dout() { return DOut(); }
template<class T> FloatingOnly<T, bool> is_approx(T val, T ref) { return std::abs(val - ref) < 1e-8; }
template<class T> IntegerOnly <T, bool> is_approx(T val, T ref) { val == ref; }
template<class T, size_t N = 10> class SymetricMatrix {
template<class T> class SymetricMatrix {
static const constexpr size_t N = 10;
public:
explicit SymetricMatrix(ArithmeticOnly<T> c = T()) { std::fill(m, m + N, c); }
SymetricMatrix(T m11, T m12, T m13, T m14,
T m22, T m23, T m24,
T m33, T m34,
T m44)
{
m[0] = m11; m[1] = m12; m[2] = m13; m[3] = m14;
m[4] = m22; m[5] = m23; m[6] = m24;
m[7] = m33; m[8] = m34;
m[9] = m44;
}
// Make plane
SymetricMatrix(T a, T b, T c, T d)
{
@ -140,21 +116,16 @@ public:
return det;
}
const SymetricMatrix operator+(const SymetricMatrix& n) const
const SymetricMatrix& operator+=(const SymetricMatrix& n)
{
return SymetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3]+n[3],
m[4] + n[4], m[5] + n[5], m[6] + n[6],
m[7] + n[7], m[8] + n[8],
m[9] + n[9]);
for (size_t i = 0; i < N; ++i) m[i] += n[i];
return *this;
}
SymetricMatrix& operator+=(const SymetricMatrix& n)
SymetricMatrix operator+(const SymetricMatrix& n)
{
m[0]+=n[0]; m[1]+=n[1]; m[2]+=n[2]; m[3]+=n[3];
m[4]+=n[4]; m[5]+=n[5]; m[6]+=n[6]; m[7]+=n[7];
m[8]+=n[8]; m[9]+=n[9];
return *this;
SymetricMatrix self = *this;
return self += n;
}
T m[N];
@ -349,10 +320,10 @@ public:
}
void simplify_mesh_lossless();
template<class ProgressFn> void simplify_mesh_lossless(ProgressFn &&fn);
void simplify_mesh_lossless() { simplify_mesh_lossless([](int){}); }
};
template<class Mesh> void SimplifiableMesh<Mesh>::compact_faces()
{
auto it = std::remove_if(m_faceinfo.begin(), m_faceinfo.end(),
@ -604,7 +575,7 @@ bool SimplifiableMesh<Mesh>::flipped(const Vertex & p,
}
template<class Mesh>
void SimplifiableMesh<Mesh>::simplify_mesh_lossless()
template<class Fn> void SimplifiableMesh<Mesh>::simplify_mesh_lossless(Fn &&fn)
{
// init
for (FaceInfo &fi : m_faceinfo) fi.deleted = false;
@ -628,7 +599,7 @@ void SimplifiableMesh<Mesh>::simplify_mesh_lossless()
//
double threshold = std::numeric_limits<double>::epsilon(); //1.0E-3 EPS; // Really? (tm)
dout() << "lossless iteration " << iteration << "\n";
fn(iteration);
for (FaceInfo &fi : m_faceinfo) {
if (fi.err[3] > threshold || fi.deleted || fi.dirty) continue;

View file

@ -93,8 +93,6 @@ namespace PerlUtils {
extern std::string path_to_parent_path(const char *src);
};
std::string string_printf(const char *format, ...);
// Standard "generated by Slic3r version xxx timestamp xxx" header string,
// to be placed at the top of Slic3r generated files.
std::string header_slic3r_generated();

View file

@ -231,16 +231,17 @@ static inline bool is_approx(Number value, Number test_value)
}
template<class...Args>
std::string string_printf(const char *const fmt, Args &&...args)
std::string string_printf(const char *const _fmt, Args &&...args)
{
static const size_t INITIAL_LEN = 1024;
std::vector<char> buffer(INITIAL_LEN, '\0');
int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt, std::forward<Args>(args)...);
auto fmt = std::string("%s") + _fmt;
int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt.c_str(), "", std::forward<Args>(args)...);
if (bufflen >= int(INITIAL_LEN)) {
buffer.resize(size_t(bufflen) + 1);
snprintf(buffer.data(), buffer.size(), fmt, std::forward<Args>(args)...);
snprintf(buffer.data(), buffer.size(), fmt.c_str(), "", std::forward<Args>(args)...);
}
return std::string(buffer.begin(), buffer.begin() + bufflen);

View file

@ -577,24 +577,6 @@ namespace PerlUtils {
std::string path_to_parent_path(const char *src) { return boost::filesystem::path(src).parent_path().string(); }
};
std::string string_printf(const char *format, ...)
{
va_list args1;
va_start(args1, format);
va_list args2;
va_copy(args2, args1);
size_t needed_size = ::vsnprintf(nullptr, 0, format, args1) + 1;
va_end(args1);
std::string res(needed_size, '\0');
::vsnprintf(&res.front(), res.size(), format, args2);
va_end(args2);
return res;
}
std::string header_slic3r_generated()
{
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();

View file

@ -147,6 +147,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Mouse3DController.hpp
GUI/DoubleSlider.cpp
GUI/DoubleSlider.hpp
GUI/ObjectDataViewModel.cpp
GUI/ObjectDataViewModel.hpp
Utils/Http.cpp
Utils/Http.hpp
Utils/FixModelByWin10.cpp

View file

@ -10,7 +10,7 @@
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Slicing.hpp"
#include "libslic3r/GCode/Analyzer.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "slic3r/GUI/BitmapCache.hpp"
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Utils.hpp"
@ -792,14 +792,14 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con
for (unsigned int i = 0; i < colors_count; ++i)
{
const std::string& txt_color = config->opt_string("extruder_colour", i);
if (PresetBundle::parse_color(txt_color, rgb))
if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb))
{
colors[i].set(txt_color, rgb);
}
else
{
const std::string& txt_color = config->opt_string("filament_colour", i);
if (PresetBundle::parse_color(txt_color, rgb))
if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb))
colors[i].set(txt_color, rgb);
}
}

View file

@ -1,6 +1,9 @@
#include "BitmapCache.hpp"
#include "libslic3r/Utils.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "GUI.hpp"
#include <boost/filesystem.hpp>
#if ! defined(WIN32) && ! defined(__APPLE__)
@ -20,6 +23,16 @@
namespace Slic3r { namespace GUI {
BitmapCache::BitmapCache()
{
#ifdef __APPLE__
// Note: win->GetContentScaleFactor() is not used anymore here because it tends to
// return bogus results quite often (such as 1.0 on Retina or even 0.0).
// We're using the max scaling factor across all screens because it's very likely to be good enough.
m_scale = mac_max_scaling_factor();
#endif
}
void BitmapCache::clear()
{
for (std::pair<const std::string, wxBitmap*> &bitmap : m_map)
@ -55,6 +68,14 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_
auto it = m_map.find(bitmap_key);
if (it == m_map.end()) {
bitmap = new wxBitmap(width, height);
#ifdef __APPLE__
// Contrary to intuition, the `scale` argument isn't "please scale this to such and such"
// but rather "the wxImage is sized for backing scale such and such".
// So, We need to let the Mac OS wxBitmap implementation
// know that the image may already be scaled appropriately for Retina,
// and thereby that it's not supposed to upscale it.
bitmap->CreateScaled(width, height, -1, m_scale);
#endif
m_map[bitmap_key] = bitmap;
} else {
bitmap = it->second;
@ -100,8 +121,13 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg
size_t width = 0;
size_t height = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
#ifdef __APPLE__
width += bmp->GetScaledWidth();
height = std::max<size_t>(height, bmp->GetScaledHeight());
#else
width += bmp->GetWidth();
height = std::max<size_t>(height, bmp->GetHeight());
#endif
}
#ifdef BROKEN_ALPHA
@ -167,7 +193,12 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
if (bmp->GetWidth() > 0)
memDC.DrawBitmap(*bmp, x, 0, true);
#ifdef __APPLE__
// we should "move" with step equal to non-scaled width
x += bmp->GetScaledWidth();
#else
x += bmp->GetWidth();
#endif
}
memDC.SelectObject(wxNullBitmap);
return bitmap;
@ -175,7 +206,7 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg
#endif
}
wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, float scale /* = 1.0f */, const bool grayscale/* = false*/)
wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale/* = false*/)
{
wxImage image(width, height);
image.InitAlpha();
@ -192,7 +223,7 @@ wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned w
if (grayscale)
image = image.ConvertToGreyscale(m_gs, m_gs, m_gs);
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image), scale));
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image), m_scale));
}
wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width, unsigned height,
@ -227,12 +258,12 @@ wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width,
}
wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height,
float scale /* = 1.0f */, const bool grayscale/* = false*/, const bool dark_mode/* = false*/)
const bool grayscale/* = false*/, const bool dark_mode/* = false*/)
{
std::string bitmap_key = bitmap_name + ( target_height !=0 ?
"-h" + std::to_string(target_height) :
"-w" + std::to_string(target_width))
+ (scale != 1.0f ? "-s" + std::to_string(scale) : "")
+ (m_scale != 1.0f ? "-s" + std::to_string(m_scale) : "")
+ (grayscale ? "-gs" : "");
/* For the Dark mode of any platform, we should draw icons in respect to OS background
@ -272,7 +303,7 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_
if (image == nullptr)
return nullptr;
target_height != 0 ? target_height *= scale : target_width *= scale;
target_height != 0 ? target_height *= m_scale : target_width *= m_scale;
float svg_scale = target_height != 0 ?
(float)target_height / image->height : target_width != 0 ?
@ -297,11 +328,16 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_
::nsvgDeleteRasterizer(rast);
::nsvgDelete(image);
return this->insert_raw_rgba(bitmap_key, width, height, data.data(), scale, grayscale);
return this->insert_raw_rgba(bitmap_key, width, height, data.data(), grayscale);
}
wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency)
//we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap
wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false*/)
{
double scale = suppress_scaling ? 1.0f : m_scale;
width *= scale;
height *= scale;
wxImage image(width, height);
image.InitAlpha();
unsigned char* imgdata = image.GetData();
@ -312,7 +348,32 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi
*imgdata ++ = b;
*imgalpha ++ = transparency;
}
return wxImage_to_wxBitmap_with_alpha(std::move(image));
return wxImage_to_wxBitmap_with_alpha(std::move(image), scale);
}
static inline int hex_digit_to_int(const char c)
{
return
(c >= '0' && c <= '9') ? int(c - '0') :
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
}
bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out)
{
rgb_out[0] = rgb_out[1] = rgb_out[2] = 0;
if (scolor.size() != 7 || scolor.front() != '#')
return false;
const char* c = scolor.data() + 1;
for (size_t i = 0; i < 3; ++i) {
int digit1 = hex_digit_to_int(*c++);
int digit2 = hex_digit_to_int(*c++);
if (digit1 == -1 || digit2 == -1)
return false;
rgb_out[i] = (unsigned char)(digit1 * 16 + digit2);
}
return true;
}
} // namespace GUI

View file

@ -1,24 +1,22 @@
#ifndef SLIC3R_GUI_BITMAP_CACHE_HPP
#define SLIC3R_GUI_BITMAP_CACHE_HPP
#include <map>
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include "libslic3r/libslic3r.h"
#include "libslic3r/Config.hpp"
#include "GUI.hpp"
namespace Slic3r { namespace GUI {
class BitmapCache
{
public:
BitmapCache() {}
BitmapCache();
~BitmapCache() { clear(); }
void clear();
double scale() { return m_scale; }
wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; }
const wxBitmap* find(const std::string &name) const { return const_cast<BitmapCache*>(this)->find(name); }
@ -29,20 +27,23 @@ public:
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3);
wxBitmap* insert(const std::string &name, const std::vector<wxBitmap> &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); }
wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end);
wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, float scale = 1.0f, const bool grayscale = false);
wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false);
// Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero.
wxBitmap* load_png(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false);
// Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width.
wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, float scale = 1.0f, const bool grayscale = false, const bool dark_mode = false);
wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false);
static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency);
static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); }
static wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); }
/*static */wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false);
/*static */wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3], bool suppress_scaling = false) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); }
/*static */wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); }
static bool parse_color(const std::string& scolor, unsigned char* rgb_out);
private:
std::map<std::string, wxBitmap*> m_map;
double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs)
double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs)
double m_scale = 1.0; // value, used for correct scaling of SVG icons on Retina display
};
} // GUI

View file

@ -14,7 +14,9 @@
#include <GL/glew.h>
#if !ENABLE_6DOF_CAMERA
static const float GIMBALL_LOCK_THETA_MAX = 180.0f;
#endif // !ENABLE_6DOF_CAMERA
// phi / theta angles to orient the camera.
static const float VIEW_DEFAULT[2] = { 45.0f, 45.0f };
@ -66,13 +68,10 @@ std::string Camera::get_type_as_string() const
{
switch (m_type)
{
case Unknown:
return "unknown";
case Perspective:
return "perspective";
case Unknown: return "unknown";
case Perspective: return "perspective";
default:
case Ortho:
return "orthographic";
case Ortho: return "orthographic";
};
}
@ -88,10 +87,7 @@ void Camera::set_type(EType type)
void Camera::set_type(const std::string& type)
{
if (type == "1")
set_type(Perspective);
else
set_type(Ortho);
set_type((type == "1") ? Perspective : Ortho);
}
void Camera::select_next_type()
@ -157,17 +153,17 @@ void Camera::select_view(const std::string& direction)
if (direction == "iso")
set_default_orientation();
else if (direction == "left")
m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ());
look_at(m_target - m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ());
else if (direction == "right")
m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ());
look_at(m_target + m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ());
else if (direction == "top")
m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY());
look_at(m_target + m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY());
else if (direction == "bottom")
m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitZ(), m_target, -Vec3d::UnitY());
look_at(m_target - m_distance * Vec3d::UnitZ(), m_target, -Vec3d::UnitY());
else if (direction == "front")
m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ());
look_at(m_target - m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ());
else if (direction == "rear")
m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ());
look_at(m_target + m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ());
}
#else
bool Camera::select_view(const std::string& direction)
@ -244,17 +240,27 @@ void Camera::apply_view_matrix() const
void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z) const
{
#if !ENABLE_6DOF_CAMERA
set_distance(DefaultDistance);
#endif // !ENABLE_6DOF_CAMERA
double w = 0.0;
double h = 0.0;
#if ENABLE_6DOF_CAMERA
double old_distance = m_distance;
m_frustrum_zs = calc_tight_frustrum_zs_around(box);
if (m_distance != old_distance)
// the camera has been moved re-apply view matrix
apply_view_matrix();
#else
while (true)
{
m_frustrum_zs = calc_tight_frustrum_zs_around(box);
#endif // !ENABLE_6DOF_CAMERA
if (near_z > 0.0)
m_frustrum_zs.first = std::min(m_frustrum_zs.first, near_z);
m_frustrum_zs.first = std::max(std::min(m_frustrum_zs.first, near_z), FrustrumMinNearZ);
if (far_z > 0.0)
m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z);
@ -262,12 +268,9 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa
w = 0.5 * (double)m_viewport[2];
h = 0.5 * (double)m_viewport[3];
if (m_zoom != 0.0)
{
double inv_zoom = 1.0 / m_zoom;
w *= inv_zoom;
h *= inv_zoom;
}
double inv_zoom = get_inv_zoom();
w *= inv_zoom;
h *= inv_zoom;
switch (m_type)
{
@ -288,6 +291,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa
}
}
#if !ENABLE_6DOF_CAMERA
if (m_type == Perspective)
{
double fov_deg = Geometry::rad2deg(2.0 * std::atan(h / m_frustrum_zs.first));
@ -307,6 +311,7 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa
else
break;
}
#endif // !ENABLE_6DOF_CAMERA
glsafe(::glMatrixMode(GL_PROJECTION));
glsafe(::glLoadIdentity());
@ -331,14 +336,22 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa
}
#if ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_6DOF_CAMERA
void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor)
#else
void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor)
#endif // ENABLE_6DOF_CAMERA
#else
void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h)
#endif // ENABLE_THUMBNAIL_GENERATOR
{
// Calculate the zoom factor needed to adjust the view around the given box.
#if ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_6DOF_CAMERA
double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor);
#else
double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h, margin_factor);
#endif // ENABLE_6DOF_CAMERA
#else
double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h);
#endif // ENABLE_THUMBNAIL_GENERATOR
@ -355,10 +368,18 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h)
}
#if ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_6DOF_CAMERA
void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor)
#else
void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor)
#endif // ENABLE_6DOF_CAMERA
{
Vec3d center;
#if ENABLE_6DOF_CAMERA
double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor);
#else
double zoom = calc_zoom_to_volumes_factor(volumes, canvas_w, canvas_h, center, margin_factor);
#endif // ENABLE_6DOF_CAMERA
if (zoom > 0.0)
{
m_zoom = zoom;
@ -396,6 +417,7 @@ void Camera::debug_render() const
float deltaZ = farZ - nearZ;
float zoom = (float)m_zoom;
float fov = (float)get_fov();
std::array<int, 4>viewport = get_viewport();
float gui_scale = (float)get_gui_scale();
ImGui::InputText("Type", type.data(), type.length(), ImGuiInputTextFlags_ReadOnly);
@ -415,6 +437,8 @@ void Camera::debug_render() const
ImGui::InputFloat("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputInt4("Viewport", viewport.data(), ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
imgui.end();
}
@ -434,10 +458,31 @@ void Camera::translate_world(const Vec3d& displacement)
void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad)
{
// FIXME -> The following is a HACK !!!
// When the value of the zenit rotation is large enough, the following call to rotate() shows
// numerical instability introducing some scaling into m_view_matrix (verified by checking
// that the camera space unit vectors are no more unit).
// See also https://dev.prusa3d.com/browse/SPE-1082
// We split the zenit rotation into a set of smaller rotations which are then applied.
static const double MAX_ALLOWED = Geometry::deg2rad(0.1);
unsigned int zenit_steps_count = 1 + (unsigned int)(std::abs(delta_zenit_rad) / MAX_ALLOWED);
double zenit_step = delta_zenit_rad / (double)zenit_steps_count;
Vec3d target = m_target;
translate_world(-target);
m_view_matrix.rotate(Eigen::AngleAxisd(delta_zenit_rad, get_dir_right()));
m_view_matrix.rotate(Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ()));
if (zenit_step != 0.0)
{
Vec3d right = get_dir_right();
for (unsigned int i = 0; i < zenit_steps_count; ++i)
{
m_view_matrix.rotate(Eigen::AngleAxisd(zenit_step, right));
}
}
if (delta_azimut_rad != 0.0)
m_view_matrix.rotate(Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ()));
translate_world(target);
}
@ -457,49 +502,77 @@ void Camera::rotate_local_around_pivot(const Vec3d& rotation_rad, const Vec3d& p
m_view_matrix.rotate(Eigen::AngleAxisd(rotation_rad(2), get_dir_forward()));
translate_world(center);
}
#endif // ENABLE_6DOF_CAMERA
double Camera::min_zoom() const
{
return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box, (int)m_viewport[2], (int)m_viewport[3]);
}
#if ENABLE_6DOF_CAMERA
return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box);
#else
return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box, m_viewport[2], m_viewport[3]);
#endif // ENABLE_6DOF_CAMERA
}
std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const
{
std::pair<double, double> ret;
auto& [near_z, far_z] = ret;
#if !ENABLE_6DOF_CAMERA
while (true)
{
#endif // !ENABLE_6DOF_CAMERA
// box in eye space
BoundingBoxf3 eye_box = box.transformed(m_view_matrix);
ret.first = -eye_box.max(2);
ret.second = -eye_box.min(2);
near_z = -eye_box.max(2);
far_z = -eye_box.min(2);
// apply margin
ret.first -= FrustrumZMargin;
ret.second += FrustrumZMargin;
near_z -= FrustrumZMargin;
far_z += FrustrumZMargin;
// ensure min size
if (ret.second - ret.first < FrustrumMinZRange)
if (far_z - near_z < FrustrumMinZRange)
{
double mid_z = 0.5 * (ret.first + ret.second);
double mid_z = 0.5 * (near_z + far_z);
double half_size = 0.5 * FrustrumMinZRange;
ret.first = mid_z - half_size;
ret.second = mid_z + half_size;
near_z = mid_z - half_size;
far_z = mid_z + half_size;
}
if (ret.first >= FrustrumMinNearZ)
#if ENABLE_6DOF_CAMERA
if (near_z < FrustrumMinNearZ)
{
float delta = FrustrumMinNearZ - near_z;
set_distance(m_distance + delta);
near_z += delta;
far_z += delta;
}
else if ((near_z > 2.0 * FrustrumMinNearZ) && (m_distance > DefaultDistance))
{
float delta = m_distance - DefaultDistance;
set_distance(DefaultDistance);
near_z -= delta;
far_z -= delta;
}
#else
if (near_z >= FrustrumMinNearZ)
break;
// ensure min Near Z
set_distance(m_distance + FrustrumMinNearZ - ret.first);
// ensure min near z
set_distance(m_distance + FrustrumMinNearZ - near_z);
}
#endif // ENABLE_6DOF_CAMERA
return ret;
}
#if ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_6DOF_CAMERA
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor) const
#else
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) const
#endif // ENABLE_6DOF_CAMERA
#else
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const
#endif // ENABLE_THUMBNAIL_GENERATOR
@ -511,8 +584,10 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
// project the box vertices on a plane perpendicular to the camera forward axis
// then calculates the vertices coordinate on this plane along the camera xy axes
#if !ENABLE_6DOF_CAMERA
// ensure that the view matrix is updated
apply_view_matrix();
#endif // !ENABLE_6DOF_CAMERA
Vec3d right = get_dir_right();
Vec3d up = get_dir_up();
@ -569,11 +644,19 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
dx *= margin_factor;
dy *= margin_factor;
#if ENABLE_6DOF_CAMERA
return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy);
#else
return std::min((double)canvas_w / dx, (double)canvas_h / dy);
#endif // ENABLE_6DOF_CAMERA
}
#if ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_6DOF_CAMERA
double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor) const
#else
double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor) const
#endif // ENABLE_6DOF_CAMERA
{
if (volumes.empty())
return -1.0;
@ -581,8 +664,10 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canv
// project the volumes vertices on a plane perpendicular to the camera forward axis
// then calculates the vertices coordinate on this plane along the camera xy axes
#if !ENABLE_6DOF_CAMERA
// ensure that the view matrix is updated
apply_view_matrix();
#endif // !ENABLE_6DOF_CAMERA
Vec3d right = get_dir_right();
Vec3d up = get_dir_up();
@ -634,46 +719,57 @@ double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canv
if ((dx <= 0.0) || (dy <= 0.0))
return -1.0f;
#if ENABLE_6DOF_CAMERA
return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy);
#else
return std::min((double)canvas_w / dx, (double)canvas_h / dy);
#endif // ENABLE_6DOF_CAMERA
}
#endif // ENABLE_THUMBNAIL_GENERATOR
void Camera::set_distance(double distance) const
{
#if ENABLE_6DOF_CAMERA
if (m_distance != distance)
{
m_view_matrix.translate((distance - m_distance) * get_dir_forward());
m_distance = distance;
}
#else
m_distance = distance;
apply_view_matrix();
#endif // ENABLE_6DOF_CAMERA
}
#if ENABLE_6DOF_CAMERA
Transform3d Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) const
void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up)
{
Vec3d unit_z = (position - target).normalized();
Vec3d unit_x = up.cross(unit_z).normalized();
Vec3d unit_y = unit_z.cross(unit_x).normalized();
Transform3d matrix;
m_target = target;
Vec3d new_position = m_target + m_distance * unit_z;
matrix(0, 0) = unit_x(0);
matrix(0, 1) = unit_x(1);
matrix(0, 2) = unit_x(2);
matrix(0, 3) = -unit_x.dot(position);
m_view_matrix(0, 0) = unit_x(0);
m_view_matrix(0, 1) = unit_x(1);
m_view_matrix(0, 2) = unit_x(2);
m_view_matrix(0, 3) = -unit_x.dot(new_position);
matrix(1, 0) = unit_y(0);
matrix(1, 1) = unit_y(1);
matrix(1, 2) = unit_y(2);
matrix(1, 3) = -unit_y.dot(position);
m_view_matrix(1, 0) = unit_y(0);
m_view_matrix(1, 1) = unit_y(1);
m_view_matrix(1, 2) = unit_y(2);
m_view_matrix(1, 3) = -unit_y.dot(new_position);
matrix(2, 0) = unit_z(0);
matrix(2, 1) = unit_z(1);
matrix(2, 2) = unit_z(2);
matrix(2, 3) = -unit_z.dot(position);
m_view_matrix(2, 0) = unit_z(0);
m_view_matrix(2, 1) = unit_z(1);
m_view_matrix(2, 2) = unit_z(2);
m_view_matrix(2, 3) = -unit_z.dot(new_position);
matrix(3, 0) = 0.0;
matrix(3, 1) = 0.0;
matrix(3, 2) = 0.0;
matrix(3, 3) = 1.0;
return matrix;
m_view_matrix(3, 0) = 0.0;
m_view_matrix(3, 1) = 0.0;
m_view_matrix(3, 2) = 0.0;
m_view_matrix(3, 3) = 1.0;
}
void Camera::set_default_orientation()

View file

@ -48,11 +48,7 @@ private:
mutable double m_gui_scale;
mutable std::array<int, 4> m_viewport;
#if ENABLE_6DOF_CAMERA
Transform3d m_view_matrix;
#else
mutable Transform3d m_view_matrix;
#endif // ENABLE_6DOF_CAMERA
mutable Transform3d m_projection_matrix;
mutable std::pair<double, double> m_frustrum_zs;
@ -71,7 +67,11 @@ public:
const Vec3d& get_target() const { return m_target; }
void set_target(const Vec3d& target);
#if ENABLE_6DOF_CAMERA
double get_distance() const { return (get_position() - m_target).norm(); }
#else
double get_distance() const { return m_distance; }
#endif // ENABLE_6DOF_CAMERA
double get_gui_scale() const { return m_gui_scale; }
#if !ENABLE_6DOF_CAMERA
@ -115,8 +115,13 @@ public:
void apply_projection(const BoundingBoxf3& box, double near_z = -1.0, double far_z = -1.0) const;
#if ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_6DOF_CAMERA
void zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor);
void zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor = DefaultZoomToVolumesMarginFactor);
#else
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor);
void zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToVolumesMarginFactor);
#endif // ENABLE_6DOF_CAMERA
#else
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h);
#endif // ENABLE_THUMBNAIL_GENERATOR
@ -141,25 +146,29 @@ public:
// returns true if the camera z axis (forward) is pointing in the negative direction of the world z axis
bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; }
#endif // ENABLE_6DOF_CAMERA
double max_zoom() const { return 100.0; }
double min_zoom() const;
#endif // ENABLE_6DOF_CAMERA
private:
// returns tight values for nearZ and farZ plane around the given bounding box
// the camera MUST be outside of the bounding box in eye coordinate of the given box
std::pair<double, double> calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const;
#if ENABLE_THUMBNAIL_GENERATOR
#if ENABLE_6DOF_CAMERA
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor) const;
double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const;
#else
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor) const;
double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const;
#endif // ENABLE_6DOF_CAMERA
#else
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const;
#endif // ENABLE_THUMBNAIL_GENERATOR
void set_distance(double distance) const;
#if ENABLE_6DOF_CAMERA
Transform3d look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) const;
void look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up);
void set_default_orientation();
Vec3d validate_target(const Vec3d& target) const;
#endif // ENABLE_6DOF_CAMERA

View file

@ -52,28 +52,26 @@ Control::Control( wxWindow *parent,
if (!is_osx)
SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
const float scale_factor = get_svg_scale_factor(this);
m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up"));
m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down"));
m_thumb_size = m_bmp_thumb_lower.bmp().GetSize()*(1.0/scale_factor);
m_thumb_size = m_bmp_thumb_lower.GetBmpSize();
m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add");
m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f");
m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_del");
m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_del_f");
m_tick_icon_dim = int((float)m_bmp_add_tick_on.bmp().GetSize().x / scale_factor);
m_tick_icon_dim = m_bmp_add_tick_on.GetBmpWidth();
m_bmp_one_layer_lock_on = ScalableBitmap(this, "lock_closed");
m_bmp_one_layer_lock_off = ScalableBitmap(this, "lock_closed_f");
m_bmp_one_layer_unlock_on = ScalableBitmap(this, "lock_open");
m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f");
m_lock_icon_dim = int((float)m_bmp_one_layer_lock_on.bmp().GetSize().x / scale_factor);
m_lock_icon_dim = m_bmp_one_layer_lock_on.GetBmpWidth();
m_bmp_revert = ScalableBitmap(this, "undo");
m_revert_icon_dim = int((float)m_bmp_revert.bmp().GetSize().x / scale_factor);
m_revert_icon_dim = m_bmp_revert.GetBmpWidth();
m_bmp_cog = ScalableBitmap(this, "cog");
m_cog_icon_dim = int((float)m_bmp_cog.bmp().GetSize().x / scale_factor);
m_cog_icon_dim = m_bmp_cog.GetBmpWidth();
m_selection = ssUndef;
m_ticks.set_pause_print_msg(_utf8(L("Place bearings in slots and resume")));
@ -554,16 +552,9 @@ void Control::draw_ticks(wxDC& dc)
// Draw icon for "Pause print" or "Custom Gcode"
if (tick.gcode != ColorChangeCode && tick.gcode != ToolChangeCode)
icon = create_scaled_bitmap(this, tick.gcode == PausePrintCode ? "pause_print" : "edit_gcode");
else
{
if ((tick.gcode == ColorChangeCode && (
(m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiExtruder ) ||
(m_ticks.mode == t_mode::MultiExtruder && m_mode == t_mode::SingleExtruder) )) ||
(tick.gcode == ToolChangeCode &&
(m_ticks.mode == t_mode::MultiAsSingle && m_mode != t_mode::MultiAsSingle ) ))
icon = create_scaled_bitmap(this, "error_tick");
}
icon = create_scaled_bitmap(tick.gcode == PausePrintCode ? "pause_print" : "edit_gcode");
else if (m_ticks.is_conflict_tick(tick, m_mode, m_only_extruder, m_values[tick.tick]))
icon = create_scaled_bitmap("error_tick");
if (!icon.IsNull())
{
@ -753,7 +744,7 @@ bool Control::is_point_in_rect(const wxPoint& pt, const wxRect& rect)
rect.GetTop() <= pt.y && pt.y <= rect.GetBottom();
}
int Control::is_point_near_tick(const wxPoint& pt)
int Control::get_tick_near_point(const wxPoint& pt)
{
for (auto tick : m_ticks.ticks) {
const wxCoord pos = get_position_from_value(tick.tick);
@ -833,7 +824,7 @@ void Control::OnLeftDown(wxMouseEvent& event)
detect_selected_slider(pos);
if (!m_selection) {
const int tick_val = is_point_near_tick(pos);
const int tick_val = get_tick_near_point(pos);
/* Set current thumb position to the nearest tick (if it is)
* OR to a value corresponding to the mouse click
* */
@ -896,20 +887,70 @@ wxString Control::get_tooltip(IconFocus icon_focus)
{
const int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
const auto tick_code_it = m_ticks.ticks.find(TickCode{tick});
tooltip = tick_code_it == m_ticks.ticks.end() ? (m_mode == t_mode::MultiAsSingle ?
_(L("For add change extruder use left mouse button click")) :
_(L("For add color change use left mouse button click")) ) + "\n" +
_(L("For add another code use right mouse button click")) :
tick_code_it->gcode == ColorChangeCode ? ( m_mode == t_mode::SingleExtruder ?
_(L("For Delete color change use left mouse button click\n"
"For Edit color use right mouse button click")) :
from_u8((boost::format(_utf8(L("Delete color change for Extruder %1%"))) % tick_code_it->extruder).str()) ):
tick_code_it->gcode == PausePrintCode ?
_(L("Delete pause")) :
tick_code_it->gcode == ToolChangeCode ?
from_u8((boost::format(_utf8(L("Delete extruder change to \"%1%\""))) % tick_code_it->extruder).str()) :
from_u8((boost::format(_utf8(L("For Delete \"%1%\" code use left mouse button click\n"
"For Edit \"%1%\" code use right mouse button click"))) % tick_code_it->gcode ).str());
/* Note: just on OSX!!!
* Right click event causes a little scrolling.
* So, as a workaround we use Ctrl+LeftMouseClick instead of RightMouseClick
* Show this information in tooltip
* */
if (tick_code_it == m_ticks.ticks.end()) // tick doesn't exist
{
// Show mode as a first string of tooltop
tooltip = " " + _(L("Slider(print) mode")) + ": ";
tooltip += (m_mode == t_mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
m_mode == t_mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode :
CustomGCode::MultiExtruderMode );
tooltip += "\n\n";
// Show list of actions with new tick
tooltip += ( m_mode == t_mode::MultiAsSingle ?
_(L("For add change extruder use left mouse button click")) :
_(L("For add color change use left mouse button click")) ) + " " +
_(L("OR pres \"+\" key")) + "\n" + (
is_osx ?
_(L("For add another code use Ctrl + Left mouse button click")) :
_(L("For add another code use right mouse button click")) );
}
else // tick exists
{
// Show custom Gcode as a first string of tooltop
tooltip = " ";
tooltip += tick_code_it->gcode == ColorChangeCode ? (
m_mode == t_mode::SingleExtruder ?
from_u8((boost::format(_utf8(L("Color change (\"%1%\")"))) % tick_code_it->gcode ).str()) :
from_u8((boost::format(_utf8(L("Color change (\"%1%\") for Extruder %2%"))) %
tick_code_it->gcode % tick_code_it->extruder).str()) ) :
tick_code_it->gcode == PausePrintCode ?
from_u8((boost::format(_utf8(L("Pause print (\"%1%\")"))) % tick_code_it->gcode ).str()) :
tick_code_it->gcode == ToolChangeCode ?
from_u8((boost::format(_utf8(L("Extruder(tool) is changed to Extruder \"%1%\""))) % tick_code_it->extruder ).str()) :
from_u8((boost::format(_utf8(L("\"%1%\""))) % tick_code_it->gcode ).str()) ;
// If tick is marked as a conflict (exclamation icon),
// we should to explain why
ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_only_extruder, m_values[tick]);
if (conflict != ctNone)
tooltip += "\n\n" + _(L("Note")) + "! ";
if (conflict == ctModeConflict)
tooltip += _(L("G-code of this tick has a conflict with slider(print) mode.\n"
"Any its editing will cause a changes of DoubleSlider data."));
else if (conflict == ctMeaninglessColorChange)
tooltip += _(L("There is a color change for extruder that wouldn't be used till the end of printing.\n"
"This code wouldn't be processed during GCode generation."));
else if (conflict == ctMeaninglessToolChange)
tooltip += _(L("There is a extruder change to the same extruder.\n"
"This code wouldn't be processed during GCode generation."));
else if (conflict == ctRedundant)
tooltip += _(L("There is a color change for extruder that has not been used before.\n"
"Check your choice to avoid redundant color changes."));
// Show list of actions with existing tick
tooltip += "\n\n" + _(L("For Delete tick use left mouse button click OR pres \"-\" key")) + "\n" + (
is_osx ?
_(L("For Edit tick use Ctrl + Left mouse button click")) :
_(L("For Edit tick use right mouse button click")) );
}
}
return tooltip;
@ -988,7 +1029,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current
_(L("Change extruder (N/A)"));
wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _(L("Use another extruder")));
change_extruder_menu_item->SetBitmap(create_scaled_bitmap(this, active_extruders[1] > 0 ? "edit_uni" : "change_extruder"));
change_extruder_menu_item->SetBitmap(create_scaled_bitmap(active_extruders[1] > 0 ? "edit_uni" : "change_extruder"));
GUI::wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this, change_extruder_menu_item](wxUpdateUIEvent& evt) {
enable_menu_item(evt, [this]() {return m_mode == t_mode::MultiAsSingle; }, change_extruder_menu_item, this); },
@ -1001,7 +1042,8 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren
const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt();
if (extruders_cnt > 1)
{
std::set<int> used_extruders_for_tick = get_used_extruders_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value);
int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
std::set<int> used_extruders_for_tick = m_ticks.get_used_extruders_for_tick(tick, m_only_extruder, m_values[tick]);
wxMenu* add_color_change_menu = new wxMenu();
@ -1014,14 +1056,14 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren
append_menu_item(add_color_change_menu, wxID_ANY, item_name, "",
[this, i](wxCommandEvent&) { add_code_as_tick(ColorChangeCode, i); }, "", menu,
[is_used_extruder]() { return is_used_extruder; }, GUI::wxGetApp().plater());
[]() { return true; }, GUI::wxGetApp().plater());
}
const wxString menu_name = switch_current_code ?
from_u8((boost::format(_utf8(L("Switch code to Color change (%1%) for:"))) % ColorChangeCode).str()) :
from_u8((boost::format(_utf8(L("Add color change (%1%) for:"))) % ColorChangeCode).str());
wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, "");
add_color_change_menu_item->SetBitmap(create_scaled_bitmap(this, "colorchange_add_m"));
add_color_change_menu_item->SetBitmap(create_scaled_bitmap("colorchange_add_m"));
}
}
@ -1220,7 +1262,7 @@ std::array<int, 2> Control::get_active_extruders_for_tick(int tick) const
auto it = m_ticks.ticks.lower_bound(TickCode{tick});
if (it->tick == tick) // current tick exists
if (it != m_ticks.ticks.end() && it->tick == tick) // current tick exists
extruders[1] = it->extruder;
while (it != m_ticks.ticks.begin()) {
@ -1235,10 +1277,10 @@ std::array<int, 2> Control::get_active_extruders_for_tick(int tick) const
}
// Get used extruders for tick.
// Means all extruders(toools) will be used during printing from current tick to the end
std::set<int> Control::get_used_extruders_for_tick(int tick) const
// Means all extruders(tools) which will be used during printing from current tick to the end
std::set<int> TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z) const
{
if (m_mode == t_mode::MultiExtruder)
if (mode == t_mode::MultiExtruder)
{
// #ys_FIXME: get tool ordering from _correct_ place
const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering();
@ -1248,7 +1290,7 @@ std::set<int> Control::get_used_extruders_for_tick(int tick) const
std::set<int> used_extruders;
auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(m_values[tick]));
auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(print_z));
for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools)
{
const std::vector<unsigned>& extruders = it_layer_tools->extruders;
@ -1259,12 +1301,11 @@ std::set<int> Control::get_used_extruders_for_tick(int tick) const
return used_extruders;
}
const int default_initial_extruder = m_mode == t_mode::MultiAsSingle ? std::max(m_only_extruder, 1) : 1;
if (m_ticks.empty())
const int default_initial_extruder = mode == t_mode::MultiAsSingle ? std::max(only_extruder, 1) : 1;
if (ticks.empty())
return {default_initial_extruder};
std::set<int> used_extruders;
const std::set<TickCode>& ticks = m_ticks.ticks;
auto it_start = ticks.lower_bound(TickCode{tick});
auto it = it_start;
@ -1341,7 +1382,7 @@ void Control::OnRightUp(wxMouseEvent& event)
append_menu_item(&menu, wxID_ANY, it->gcode == ColorChangeCode ? _(L("Edit color")) :
it->gcode == PausePrintCode ? _(L("Edit pause print message")) :
_(L("Edit custom G-code")), "",
[this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu);
[this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu, []() {return true; }, this);
if (it->gcode == ColorChangeCode && m_mode == t_mode::MultiAsSingle)
append_change_extruder_menu_item(&menu, true);
@ -1350,7 +1391,7 @@ void Control::OnRightUp(wxMouseEvent& event)
it->gcode == ToolChangeCode ? _(L("Delete tool change")) :
it->gcode == PausePrintCode ? _(L("Delete pause print")) :
_(L("Delete custom G-code")), "",
[this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu);
[this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu, []() {return true; }, this);
GUI::wxGetApp().plater()->PopupMenu(&menu);
@ -1379,6 +1420,28 @@ static std::string get_new_color(const std::string& color)
return "";
}
// To avoid get an empty string from wxTextEntryDialog
// Let disable OK button, if TextCtrl is empty
static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg)
{
// detect TextCtrl and OK button
wxTextCtrl* textctrl {nullptr};
wxWindowList& dlg_items = dlg->GetChildren();
for (auto item : dlg_items) {
textctrl = dynamic_cast<wxTextCtrl*>(item);
if (textctrl)
break;
}
if (!textctrl)
return;
wxButton* btn_OK = static_cast<wxButton*>(dlg->FindWindowById(wxID_OK));
btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) {
evt.Enable(!textctrl->IsEmpty());
}, btn_OK->GetId());
}
static std::string get_custom_code(const std::string& code_in, double height)
{
wxString msg_text = from_u8(_utf8(L("Enter custom G-code used on current layer"))) + ":";
@ -1387,7 +1450,9 @@ static std::string get_custom_code(const std::string& code_in, double height)
// get custom gcode
wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in,
wxTextEntryDialogStyle | wxTE_MULTILINE);
if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty())
upgrade_text_entry_dialog(&dlg);
if (dlg.ShowModal() != wxID_OK)
return "";
return dlg.GetValue().ToStdString();
@ -1401,6 +1466,8 @@ static std::string get_pause_print_msg(const std::string& msg_in, double height)
// get custom gcode
wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in),
wxTextEntryDialogStyle);
upgrade_text_entry_dialog(&dlg);
if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty())
return "";
@ -1759,6 +1826,61 @@ bool TickCodeInfo::has_tick_with_code(const std::string& gcode)
return false;
}
ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z)
{
if ((tick.gcode == ColorChangeCode && (
(mode == t_mode::SingleExtruder && out_mode == t_mode::MultiExtruder ) ||
(mode == t_mode::MultiExtruder && out_mode == t_mode::SingleExtruder) )) ||
(tick.gcode == ToolChangeCode &&
(mode == t_mode::MultiAsSingle && out_mode != t_mode::MultiAsSingle)) )
return ctModeConflict;
// check ColorChange tick
if (tick.gcode == ColorChangeCode)
{
// We should mark a tick as a "MeaninglessColorChange",
// if it has a ColorChange for unused extruder from current print to end of the print
std::set<int> used_extruders_for_tick = get_used_extruders_for_tick(tick.tick, only_extruder, print_z);
if (used_extruders_for_tick.find(tick.extruder) == used_extruders_for_tick.end())
return ctMeaninglessColorChange;
// We should mark a tick as a "Redundant",
// if it has a ColorChange for extruder that has not been used before
if (mode == t_mode::MultiAsSingle && tick.extruder != std::max<int>(only_extruder, 1) )
{
auto it = ticks.lower_bound( tick );
if (it == ticks.begin() && it->gcode == ToolChangeCode && tick.extruder == it->extruder)
return ctNone;
while (it != ticks.begin()) {
--it;
if (it->gcode == ToolChangeCode && tick.extruder == it->extruder)
return ctNone;
}
return ctRedundant;
}
}
// check ToolChange tick
if (mode == t_mode::MultiAsSingle && tick.gcode == ToolChangeCode)
{
// We should mark a tick as a "MeaninglessToolChange",
// if it has a ToolChange to the same extruder
auto it = ticks.find(tick);
if (it == ticks.begin())
return tick.extruder == std::max<int>(only_extruder, 1) ? ctMeaninglessToolChange : ctNone;
--it;
if (it->gcode == ToolChangeCode && tick.extruder == it->extruder)
return ctMeaninglessToolChange;
}
return ctNone;
}
} // DoubleSlider
} // Slic3r

View file

@ -39,6 +39,15 @@ enum IconFocus {
ifCog
};
enum ConflictType
{
ctNone,
ctModeConflict,
ctMeaninglessColorChange,
ctMeaninglessToolChange,
ctRedundant
};
using t_mode = CustomGCode::Mode;
struct TickCode
@ -73,7 +82,13 @@ public:
void switch_code(const std::string& code_from, const std::string& code_to);
bool switch_code_for_tick(std::set<TickCode>::iterator it, const std::string& code_to, const int extruder);
void erase_all_ticks_with_code(const std::string& gcode);
bool has_tick_with_code(const std::string& gcode);
bool has_tick_with_code(const std::string& gcode);
ConflictType is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z);
// Get used extruders for tick.
// Means all extruders(tools) which will be used during printing from current tick to the end
std::set<int> get_used_extruders_for_tick(int tick, int only_extruder, double print_z) const;
void suppress_plus (bool suppress) { m_suppress_plus = suppress; }
void suppress_minus(bool suppress) { m_suppress_minus = suppress; }
@ -230,7 +245,7 @@ protected:
private:
bool is_point_in_rect(const wxPoint& pt, const wxRect& rect);
int is_point_near_tick(const wxPoint& pt);
int get_tick_near_point(const wxPoint& pt);
double get_scroll_step();
wxString get_label(const SelectedSlider& selection) const;
@ -251,10 +266,6 @@ private:
// Use those values to disable selection of active extruders
std::array<int, 2> get_active_extruders_for_tick(int tick) const;
// Get used extruders for tick.
// Means all extruders(toools) will be used during printing from current tick to the end
std::set<int> get_used_extruders_for_tick(int tick) const;
void post_ticks_changed_event(const std::string& gcode = "");
bool check_ticks_changed_event(const std::string& gcode);

View file

@ -1256,6 +1256,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
#if ENABLE_THUMBNAIL_GENERATOR
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
@ -1710,6 +1711,13 @@ void GLCanvas3D::render()
}
const Size& cnv_size = get_canvas_size();
#if ENABLE_6DOF_CAMERA
// Probably due to different order of events on Linux/GTK2, when one switched from 3D scene
// to preview, this was called before canvas had its final size. It reported zero width
// and the viewport was set incorrectly, leading to tripping glAsserts further down
// the road (in apply_projection). That's why the minimum size is forced to 10.
m_camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height()));
#endif // ENABLE_6DOF_CAMERA
if (m_camera.requires_zoom_to_bed)
{
@ -2647,6 +2655,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE));
break;
case WXK_ESCAPE: { deselect_all(); break; }
case WXK_F5: { post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK)); break; }
case '0': { select_view("iso"); break; }
case '1': { select_view("top"); break; }
case '2': { select_view("bottom"); break; }
@ -3839,8 +3848,13 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
#if ENABLE_6DOF_CAMERA
camera.set_scene_box(scene_bounding_box());
#endif // ENABLE_6DOF_CAMERA
#if ENABLE_6DOF_CAMERA
camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height);
camera.zoom_to_volumes(visible_volumes);
#else
camera.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height);
camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height);
#endif // ENABLE_6DOF_CAMERA
camera.apply_view_matrix();
double near_z = -1.0;
@ -4431,8 +4445,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
// ensures that this canvas is current
_set_current();
#if !ENABLE_6DOF_CAMERA
// updates camera
m_camera.apply_viewport(0, 0, w, h);
#endif // !ENABLE_6DOF_CAMERA
}
BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const
@ -4456,8 +4472,12 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be
#if ENABLE_THUMBNAIL_GENERATOR
void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor)
{
#if ENABLE_6DOF_CAMERA
m_camera.zoom_to_box(box, margin_factor);
#else
const Size& cnv_size = get_canvas_size();
m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height(), margin_factor);
#endif // ENABLE_6DOF_CAMERA
m_dirty = true;
}
#else

View file

@ -108,6 +108,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
class GLCanvas3D
{

View file

@ -10,6 +10,7 @@
#include <wx/glcanvas.h>
#include <wx/timer.h>
#include <wx/msgdlg.h>
#include <vector>
#include <string>

View file

@ -960,8 +960,11 @@ void GUI_App::load_current_presets()
this->plater()->set_printer_technology(printer_technology);
for (Tab *tab : tabs_list)
if (tab->supports_printer_technology(printer_technology)) {
if (tab->type() == Preset::TYPE_PRINTER)
if (tab->type() == Preset::TYPE_PRINTER) {
static_cast<TabPrinter*>(tab)->update_pages();
// Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change().
this->plater()->force_print_bed_update();
}
tab->load_current_preset();
}
}

View file

@ -90,20 +90,20 @@ ObjectList::ObjectList(wxWindow* parent) :
// see note in PresetBundle::load_compatible_bitmaps()
// ptFFF
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers");
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill");
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support");
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time");
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel");
CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel");
CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel");
// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim");
// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time");
CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench");
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers");
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill");
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support");
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time");
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel");
CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel");
CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel");
// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim");
// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time");
CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench");
// ptSLA
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/);
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad");
CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap(nullptr, "hollowing");
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/);
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad");
CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap("hollowing");
}
// create control
@ -230,9 +230,9 @@ ObjectList::ObjectList(wxWindow* parent) :
// So the postponed EnsureVisible() call is planned for an item, which may not exist at the Idle processing time, if this wxEVT_SIZE
// event is succeeded by a delete of the currently active item. We are trying our luck by postponing the wxEVT_SIZE triggered EnsureVisible(),
// which seems to be working as of now.
this->CallAfter([this](){ this->EnsureVisible(this->GetCurrentItem()); });
this->CallAfter([this](){ ensure_current_item_visible(); });
#else
this->EnsureVisible(this->GetCurrentItem());
ensure_current_item_visible();
#endif
e.Skip();
}));
@ -265,7 +265,7 @@ void ObjectList::create_objects_ctrl()
// column ItemName(Icon+Text) of the view control:
// And Icon can be consisting of several bitmaps
AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(),
AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(this),
colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE));
// column PrintableProperty (Icon) of the view control:
@ -559,10 +559,10 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const
void ObjectList::init_icons()
{
m_bmp_solidmesh = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART) ].second);
m_bmp_modifiermesh = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::PARAMETER_MODIFIER)].second);
m_bmp_support_enforcer = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER) ].second);
m_bmp_support_blocker = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER) ].second);
m_bmp_solidmesh = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART) ].second);
m_bmp_modifiermesh = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::PARAMETER_MODIFIER)].second);
m_bmp_support_enforcer = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_ENFORCER) ].second);
m_bmp_support_blocker = ScalableBitmap(this, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::SUPPORT_BLOCKER) ].second);
m_bmp_vector.reserve(4); // bitmaps for different types of parts
m_bmp_vector.push_back(&m_bmp_solidmesh.bmp());
@ -575,12 +575,12 @@ void ObjectList::init_icons()
m_objects_model->SetVolumeBitmaps(m_bmp_vector);
// init icon for manifold warning
m_bmp_manifold_warning = ScalableBitmap(nullptr, "exclamation");
m_bmp_manifold_warning = ScalableBitmap(this, "exclamation");
// Set warning bitmap for the model
m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp());
// init bitmap for "Add Settings" context menu
m_bmp_cog = ScalableBitmap(nullptr, "cog");
m_bmp_cog = ScalableBitmap(this, "cog");
}
void ObjectList::msw_rescale_icons()
@ -607,23 +607,20 @@ void ObjectList::msw_rescale_icons()
// Update CATEGORY_ICON according to new scale
{
// Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget,
// see note in PresetBundle::load_compatible_bitmaps()
// ptFFF
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers");
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill");
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support");
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time");
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel");
CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel");
CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel");
// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim");
// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time");
CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench");
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers");
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill");
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support");
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time");
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel");
CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel");
CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel");
// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim");
// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time");
CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench");
// ptSLA
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/);
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad");
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/);
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad");
}
}
@ -1003,14 +1000,13 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event)
const bool mult_sel = multiple_selection();
if ((mult_sel && !selected_instances_of_same_object()) ||
(!mult_sel && (GetSelection() != item)) ||
m_objects_model->GetParent(item) == wxDataViewItem(nullptr) ) {
(!mult_sel && (GetSelection() != item)) ) {
event.Veto();
return;
}
const ItemType& type = m_objects_model->GetItemType(item);
if (!(type & (itVolume | itInstance))) {
if (!(type & (itVolume | itObject | itInstance))) {
event.Veto();
return;
}
@ -1024,11 +1020,13 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event)
for (auto sel : sels )
sub_obj_idxs.insert(m_objects_model->GetInstanceIdByItem(sel));
}
else
else if (type & itObject)
m_dragged_data.init(m_objects_model->GetIdByItem(item), type);
else
m_dragged_data.init(m_objects_model->GetObjectIdByItem(item),
type&itVolume ? m_objects_model->GetVolumeIdByItem(item) :
type&itVolume ? m_objects_model->GetVolumeIdByItem(item) :
m_objects_model->GetInstanceIdByItem(item),
type);
type);
/* Under MSW or OSX, DnD moves an item to the place of another selected item
* But under GTK, DnD moves an item between another two items.
@ -1049,10 +1047,20 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event)
bool ObjectList::can_drop(const wxDataViewItem& item) const
{
return (m_dragged_data.type() == itInstance && !item.IsOk()) ||
(m_dragged_data.type() == itVolume && item.IsOk() &&
m_objects_model->GetItemType(item) == itVolume &&
m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item));
// move instance(s) or object on "empty place" of ObjectList
if ( (m_dragged_data.type() & (itInstance | itObject)) && !item.IsOk() )
return true;
// type of moved item should be the same as a "destination" item
if (!item.IsOk() || !(m_dragged_data.type() & (itVolume|itObject)) ||
m_objects_model->GetItemType(item) != m_dragged_data.type() )
return false;
// move volumes inside one object only
if (m_dragged_data.type() & itVolume)
return m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item);
return true;
}
void ObjectList::OnDropPossible(wxDataViewEvent &event)
@ -1082,9 +1090,6 @@ void ObjectList::OnDrop(wxDataViewEvent &event)
return;
}
const int from_volume_id = m_dragged_data.sub_obj_idx();
int to_volume_id = m_objects_model->GetVolumeIdByItem(item);
// It looks like a fixed in current version of the wxWidgets
// #ifdef __WXGTK__
// /* Under GTK, DnD moves an item between another two items.
@ -1096,14 +1101,33 @@ void ObjectList::OnDrop(wxDataViewEvent &event)
take_snapshot(_((m_dragged_data.type() == itVolume) ? L("Volumes in Object reordered") : L("Object reordered")));
auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes;
auto delta = to_volume_id < from_volume_id ? -1 : 1;
int cnt = 0;
for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++)
std::swap(volumes[id], volumes[id + delta]);
if (m_dragged_data.type() & itVolume)
{
int from_volume_id = m_dragged_data.sub_obj_idx();
int to_volume_id = m_objects_model->GetVolumeIdByItem(item);
int delta = to_volume_id < from_volume_id ? -1 : 1;
select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id,
m_objects_model->GetParent(item)));
auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes;
int cnt = 0;
for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++)
std::swap(volumes[id], volumes[id + delta]);
select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, m_objects_model->GetParent(item)));
}
else if (m_dragged_data.type() & itObject)
{
int from_obj_id = m_dragged_data.obj_idx();
int to_obj_id = item.IsOk() ? m_objects_model->GetIdByItem(item) : ((int)m_objects->size()-1);
int delta = to_obj_id < from_obj_id ? -1 : 1;
int cnt = 0;
for (int id = from_obj_id; cnt < abs(from_obj_id - to_obj_id); id += delta, cnt++)
std::swap((*m_objects)[id], (*m_objects)[id + delta]);
select_item(m_objects_model->ReorganizeObjects(from_obj_id, to_obj_id));
}
changed_object(m_dragged_data.obj_idx());
@ -1741,7 +1765,8 @@ void ObjectList::create_instance_popupmenu(wxMenu*menu)
void ObjectList::create_default_popupmenu(wxMenu*menu)
{
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID);
append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part");
append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part",
[](){return true;}, this);
}
wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
@ -3162,7 +3187,7 @@ void ObjectList::update_selections()
select_items(sels);
// Scroll selected Item in the middle of an object list
this->EnsureVisible(this->GetCurrentItem());
ensure_current_item_visible();
}
void ObjectList::update_selections_on_canvas()

View file

@ -3,6 +3,7 @@
#include <map>
#include <vector>
#include <set>
#include <wx/bitmap.h>
#include <wx/dataview.h>
@ -10,6 +11,7 @@
#include "Event.hpp"
#include "wxExtensions.hpp"
#include "ObjectDataViewModel.hpp"
class wxBoxSizer;
class wxBitmapComboBox;
@ -171,6 +173,12 @@ private:
SettingsBundle m_freq_settings_sla;
#endif
inline void ensure_current_item_visible()
{
if (const auto &item = this->GetCurrentItem())
this->EnsureVisible(item);
}
public:
ObjectList(wxWindow* parent);
~ObjectList();

View file

@ -9,6 +9,8 @@
#include <wx/stattext.h>
#include <wx/sizer.h>
#include <algorithm>
#include "slic3r/GUI/GUI_App.hpp"
@ -189,7 +191,7 @@ void GLGizmoCut::update_max_z(const Selection& selection) const
void GLGizmoCut::set_cut_z(double cut_z) const
{
// Clamp the plane to the object's bounding box
m_cut_z = std::max(0.0, std::min(m_max_z, cut_z));
m_cut_z = std::clamp(cut_z, 0.0, m_max_z);
}
void GLGizmoCut::perform_cut(const Selection& selection)

View file

@ -25,6 +25,9 @@ class GLGizmoCut : public GLGizmoBase
public:
GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
double get_cut_z() const { return m_cut_z; }
void set_cut_z(double cut_z) const;
protected:
virtual bool on_init();
virtual void on_load(cereal::BinaryInputArchive& ar) { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
@ -40,7 +43,6 @@ protected:
private:
void update_max_z(const Selection& selection) const;
void set_cut_z(double cut_z) const;
void perform_cut(const Selection& selection);
double calc_projection(const Linef3& mouse_ray) const;
};

View file

@ -500,7 +500,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true;
}
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow))
// don't allow dragging objects with the Sla gizmo on
// don't allow dragging objects with the Sla gizmo on
processed = true;
else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
{
@ -557,12 +557,9 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
else if (evt.LeftUp() && is_dragging())
{
switch (m_current) {
case Move : m_parent.do_move(L("Gizmo-Move"));
break;
case Scale : m_parent.do_scale(L("Gizmo-Scale"));
break;
case Rotate : m_parent.do_rotate(L("Gizmo-Rotate"));
break;
case Move : m_parent.do_move(L("Gizmo-Move")); break;
case Scale : m_parent.do_scale(L("Gizmo-Scale")); break;
case Rotate : m_parent.do_rotate(L("Gizmo-Rotate")); break;
default : break;
}
@ -779,6 +776,64 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
processed = true;
}
}
else if (m_current == Move)
{
auto do_move = [this, &processed](const Vec3d& displacement) {
Selection& selection = m_parent.get_selection();
selection.start_dragging();
selection.translate(displacement);
wxGetApp().obj_manipul()->set_dirty();
m_parent.do_move(L("Gizmo-Move"));
m_parent.set_as_dirty();
processed = true;
};
switch (keyCode)
{
case WXK_NUMPAD_LEFT: case WXK_LEFT: { do_move(-Vec3d::UnitX()); break; }
case WXK_NUMPAD_RIGHT: case WXK_RIGHT: { do_move(Vec3d::UnitX()); break; }
case WXK_NUMPAD_UP: case WXK_UP: { do_move(Vec3d::UnitY()); break; }
case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-Vec3d::UnitY()); break; }
default: { break; }
}
}
else if (m_current == Rotate)
{
auto do_rotate = [this, &processed](const Vec3d& rotation) {
Selection& selection = m_parent.get_selection();
selection.start_dragging();
selection.rotate(rotation, TransformationType(TransformationType::World_Relative_Joint));
wxGetApp().obj_manipul()->set_dirty();
m_parent.do_rotate(L("Gizmo-Rotate"));
m_parent.set_as_dirty();
processed = true;
};
switch (keyCode)
{
case WXK_NUMPAD_LEFT: case WXK_LEFT: { do_rotate(Vec3d(0.0, 0.0, 0.5 * M_PI)); break; }
case WXK_NUMPAD_RIGHT: case WXK_RIGHT: { do_rotate(-Vec3d(0.0, 0.0, 0.5 * M_PI)); break; }
case WXK_NUMPAD_UP: case WXK_UP: { do_rotate(Vec3d(0.0, 0.0, 0.25 * M_PI)); break; }
case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_rotate(-Vec3d(0.0, 0.0, 0.25 * M_PI)); break; }
default: { break; }
}
}
else if (m_current == Cut)
{
auto do_move = [this, &processed](double delta_z) {
GLGizmoCut* cut = dynamic_cast<GLGizmoCut*>(get_current());
cut->set_cut_z(delta_z + cut->get_cut_z());
m_parent.set_as_dirty();
processed = true;
};
switch (keyCode)
{
case WXK_NUMPAD_UP: case WXK_UP: { do_move(1.0); break; }
case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; }
default: { break; }
}
}
// if (processed)
// m_parent.set_cursor(GLCanvas3D::Standard);

View file

@ -68,7 +68,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
/* Load default preset bitmaps before a tabpanel initialization,
* but after filling of an em_unit value
*/
wxGetApp().preset_bundle->load_default_preset_bitmaps(this);
wxGetApp().preset_bundle->load_default_preset_bitmaps();
// initialize tabpanel and menubar
init_tabpanel();
@ -345,7 +345,7 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect)
/* Load default preset bitmaps before a tabpanel initialization,
* but after filling of an em_unit value
*/
wxGetApp().preset_bundle->load_default_preset_bitmaps(this);
wxGetApp().preset_bundle->load_default_preset_bitmaps();
// update Plater
wxGetApp().plater()->msw_rescale();
@ -578,6 +578,11 @@ void MainFrame::init_menubar()
append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V",
_(L("Paste clipboard")), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); },
"paste_menu", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this);
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _(L("Re&load from disk")) + sep + "F5",
_(L("Reload the plater from disk")), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); },
"", nullptr, [this]() {return !m_plater->model().objects.empty(); }, this);
}
// Window menu
@ -728,7 +733,7 @@ void MainFrame::update_menubar()
m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _(L("S&end G-code")) : _(L("S&end to print"))) + dots + "\tCtrl+Shift+G");
m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _(L("&Filament Settings Tab")) : _(L("Mate&rial Settings Tab"))) + "\tCtrl+3");
m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_scaled_bitmap(this, is_fff ? "spool": "resin"));
m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "spool": "resin"));
}
// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".

View file

@ -53,7 +53,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
rightsizer->Add(btn_sizer, 0, wxALIGN_RIGHT);
if (! bitmap.IsOk()) {
bitmap = create_scaled_bitmap(this, "PrusaSlicer_192px.png", 192);
bitmap = create_scaled_bitmap("PrusaSlicer_192px.png", this, 192);
}
logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
@ -99,7 +99,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg)
btn_ok->SetFocus();
btn_sizer->Add(btn_ok, 0, wxRIGHT, HORIZ_SPACING);
logo->SetBitmap(create_scaled_bitmap(this, "PrusaSlicer_192px_grayscale.png", 192));
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 192));
SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit()));
Fit();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,516 @@
#ifndef slic3r_GUI_ObjectDataViewModel_hpp_
#define slic3r_GUI_ObjectDataViewModel_hpp_
#include <wx/dataview.h>
#include <vector>
namespace Slic3r {
enum class ModelVolumeType : int;
namespace GUI {
typedef double coordf_t;
typedef std::pair<coordf_t, coordf_t> t_layer_height_range;
// ----------------------------------------------------------------------------
// DataViewBitmapText: helper class used by BitmapTextRenderer
// ----------------------------------------------------------------------------
class DataViewBitmapText : public wxObject
{
public:
DataViewBitmapText( const wxString &text = wxEmptyString,
const wxBitmap& bmp = wxNullBitmap) :
m_text(text),
m_bmp(bmp)
{ }
DataViewBitmapText(const DataViewBitmapText &other)
: wxObject(),
m_text(other.m_text),
m_bmp(other.m_bmp)
{ }
void SetText(const wxString &text) { m_text = text; }
wxString GetText() const { return m_text; }
void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; }
const wxBitmap &GetBitmap() const { return m_bmp; }
bool IsSameAs(const DataViewBitmapText& other) const {
return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
}
bool operator==(const DataViewBitmapText& other) const {
return IsSameAs(other);
}
bool operator!=(const DataViewBitmapText& other) const {
return !IsSameAs(other);
}
private:
wxString m_text;
wxBitmap m_bmp;
wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText);
};
DECLARE_VARIANT_OBJECT(DataViewBitmapText)
// ----------------------------------------------------------------------------
// BitmapTextRenderer
// ----------------------------------------------------------------------------
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
class BitmapTextRenderer : public wxDataViewRenderer
#else
class BitmapTextRenderer : public wxDataViewCustomRenderer
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
{
public:
BitmapTextRenderer(wxWindow* parent,
wxDataViewCellMode mode =
#ifdef __WXOSX__
wxDATAVIEW_CELL_INERT
#else
wxDATAVIEW_CELL_EDITABLE
#endif
, int align = wxDVR_DEFAULT_ALIGNMENT
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
);
#else
) :
wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align),
m_parent(parent)
{}
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
bool SetValue(const wxVariant& value);
bool GetValue(wxVariant& value) const;
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY
virtual wxString GetAccessibleDescription() const override;
#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
virtual wxSize GetSize() const override;
bool HasEditorCtrl() const override
{
#ifdef __WXOSX__
return false;
#else
return true;
#endif
}
wxWindow* CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) override;
bool GetValueFromEditorCtrl(wxWindow* ctrl,
wxVariant& value) override;
bool WasCanceled() const { return m_was_unusable_symbol; }
private:
DataViewBitmapText m_value;
bool m_was_unusable_symbol{ false };
wxWindow* m_parent{ nullptr };
};
// ----------------------------------------------------------------------------
// BitmapChoiceRenderer
// ----------------------------------------------------------------------------
class BitmapChoiceRenderer : public wxDataViewCustomRenderer
{
public:
BitmapChoiceRenderer(wxDataViewCellMode mode =
#ifdef __WXOSX__
wxDATAVIEW_CELL_INERT
#else
wxDATAVIEW_CELL_EDITABLE
#endif
, int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {}
bool SetValue(const wxVariant& value);
bool GetValue(wxVariant& value) const;
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
virtual wxSize GetSize() const override;
bool HasEditorCtrl() const override { return true; }
wxWindow* CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) override;
bool GetValueFromEditorCtrl(wxWindow* ctrl,
wxVariant& value) override;
private:
DataViewBitmapText m_value;
};
// ----------------------------------------------------------------------------
// ObjectDataViewModelNode: a node inside ObjectDataViewModel
// ----------------------------------------------------------------------------
enum ItemType {
itUndef = 0,
itObject = 1,
itVolume = 2,
itInstanceRoot = 4,
itInstance = 8,
itSettings = 16,
itLayerRoot = 32,
itLayer = 64,
};
enum ColumnNumber
{
colName = 0, // item name
colPrint , // printable property
colExtruder , // extruder selection
colEditing , // item editing
};
enum PrintIndicator
{
piUndef = 0, // no print indicator
piPrintable , // printable
piUnprintable , // unprintable
};
class ObjectDataViewModelNode;
WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray);
class ObjectDataViewModelNode
{
ObjectDataViewModelNode* m_parent;
MyObjectTreeModelNodePtrArray m_children;
wxBitmap m_empty_bmp;
size_t m_volumes_cnt = 0;
std::vector< std::string > m_opt_categories;
t_layer_height_range m_layer_range = { 0.0f, 0.0f };
wxString m_name;
wxBitmap& m_bmp = m_empty_bmp;
ItemType m_type;
int m_idx = -1;
bool m_container = false;
wxString m_extruder = "default";
wxBitmap m_extruder_bmp;
wxBitmap m_action_icon;
PrintIndicator m_printable {piUndef};
wxBitmap m_printable_icon;
std::string m_action_icon_name = "";
ModelVolumeType m_volume_type;
public:
ObjectDataViewModelNode(const wxString& name,
const wxString& extruder):
m_parent(NULL),
m_name(name),
m_type(itObject),
m_extruder(extruder)
{
set_action_and_extruder_icons();
init_container();
}
ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
const wxString& sub_obj_name,
const wxBitmap& bmp,
const wxString& extruder,
const int idx = -1 ) :
m_parent (parent),
m_name (sub_obj_name),
m_type (itVolume),
m_idx (idx),
m_extruder (extruder)
{
m_bmp = bmp;
set_action_and_extruder_icons();
init_container();
}
ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
const t_layer_height_range& layer_range,
const int idx = -1,
const wxString& extruder = wxEmptyString );
ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type);
~ObjectDataViewModelNode()
{
// free all our children nodes
size_t count = m_children.GetCount();
for (size_t i = 0; i < count; i++)
{
ObjectDataViewModelNode *child = m_children[i];
delete child;
}
#ifndef NDEBUG
// Indicate that the object was deleted.
m_idx = -2;
#endif /* NDEBUG */
}
void init_container();
bool IsContainer() const
{
return m_container;
}
ObjectDataViewModelNode* GetParent()
{
assert(m_parent == nullptr || m_parent->valid());
return m_parent;
}
MyObjectTreeModelNodePtrArray& GetChildren()
{
return m_children;
}
ObjectDataViewModelNode* GetNthChild(unsigned int n)
{
return m_children.Item(n);
}
void Insert(ObjectDataViewModelNode* child, unsigned int n)
{
if (!m_container)
m_container = true;
m_children.Insert(child, n);
}
void Append(ObjectDataViewModelNode* child)
{
if (!m_container)
m_container = true;
m_children.Add(child);
}
void RemoveAllChildren()
{
if (GetChildCount() == 0)
return;
for (int id = int(GetChildCount()) - 1; id >= 0; --id)
{
if (m_children.Item(id)->GetChildCount() > 0)
m_children[id]->RemoveAllChildren();
auto node = m_children[id];
m_children.RemoveAt(id);
delete node;
}
}
size_t GetChildCount() const
{
return m_children.GetCount();
}
bool SetValue(const wxVariant &variant, unsigned int col);
void SetBitmap(const wxBitmap &icon) { m_bmp = icon; }
const wxBitmap& GetBitmap() const { return m_bmp; }
const wxString& GetName() const { return m_name; }
ItemType GetType() const { return m_type; }
void SetIdx(const int& idx);
int GetIdx() const { return m_idx; }
t_layer_height_range GetLayerRange() const { return m_layer_range; }
PrintIndicator IsPrintable() const { return m_printable; }
// use this function only for childrens
void AssignAllVal(ObjectDataViewModelNode& from_node)
{
// ! Don't overwrite other values because of equality of this values for all children --
m_name = from_node.m_name;
m_bmp = from_node.m_bmp;
m_idx = from_node.m_idx;
m_extruder = from_node.m_extruder;
m_type = from_node.m_type;
}
bool SwapChildrens(int frst_id, int scnd_id) {
if (GetChildCount() < 2 ||
frst_id < 0 || (size_t)frst_id >= GetChildCount() ||
scnd_id < 0 || (size_t)scnd_id >= GetChildCount())
return false;
ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id);
ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id);
new_scnd.m_idx = m_children.Item(scnd_id)->m_idx;
new_frst.m_idx = m_children.Item(frst_id)->m_idx;
m_children.Item(frst_id)->AssignAllVal(new_frst);
m_children.Item(scnd_id)->AssignAllVal(new_scnd);
return true;
}
// Set action icons for node
void set_action_and_extruder_icons();
// Set printable icon for node
void set_printable_icon(PrintIndicator printable);
void update_settings_digest_bitmaps();
bool update_settings_digest(const std::vector<std::string>& categories);
int volume_type() const { return int(m_volume_type); }
void msw_rescale();
#ifndef NDEBUG
bool valid();
#endif /* NDEBUG */
bool invalid() const { return m_idx < -1; }
private:
friend class ObjectDataViewModel;
};
// ----------------------------------------------------------------------------
// ObjectDataViewModel
// ----------------------------------------------------------------------------
// custom message the model sends to associated control to notify a last volume deleted from the object:
wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent);
class ObjectDataViewModel :public wxDataViewModel
{
std::vector<ObjectDataViewModelNode*> m_objects;
std::vector<wxBitmap*> m_volume_bmps;
wxBitmap* m_warning_bmp { nullptr };
wxDataViewCtrl* m_ctrl { nullptr };
public:
ObjectDataViewModel();
~ObjectDataViewModel();
wxDataViewItem Add( const wxString &name,
const int extruder,
const bool has_errors = false);
wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item,
const wxString &name,
const Slic3r::ModelVolumeType volume_type,
const bool has_errors = false,
const int extruder = 0,
const bool create_frst_child = true);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num);
wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector<bool>& print_indicator);
wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item);
wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item,
const t_layer_height_range& layer_range,
const int extruder = 0,
const int index = -1);
wxDataViewItem Delete(const wxDataViewItem &item);
wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num);
void DeleteAll();
void DeleteChildren(wxDataViewItem& parent);
void DeleteVolumeChildren(wxDataViewItem& parent);
void DeleteSettings(const wxDataViewItem& parent);
wxDataViewItem GetItemById(int obj_idx);
wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type);
wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx);
wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx);
wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range);
int GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range);
int GetIdByItem(const wxDataViewItem& item) const;
int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const;
int GetObjectIdByItem(const wxDataViewItem& item) const;
int GetVolumeIdByItem(const wxDataViewItem& item) const;
int GetInstanceIdByItem(const wxDataViewItem& item) const;
int GetLayerIdByItem(const wxDataViewItem& item) const;
void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx);
int GetRowByItem(const wxDataViewItem& item) const;
bool IsEmpty() { return m_objects.empty(); }
bool InvalidItem(const wxDataViewItem& item);
// helper method for wxLog
wxString GetName(const wxDataViewItem &item) const;
wxBitmap& GetBitmap(const wxDataViewItem &item) const;
wxString GetExtruder(const wxDataViewItem &item) const;
int GetExtruderNumber(const wxDataViewItem &item) const;
// helper methods to change the model
virtual unsigned int GetColumnCount() const override { return 3;}
virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); }
virtual void GetValue( wxVariant &variant,
const wxDataViewItem &item,
unsigned int col) const override;
virtual bool SetValue( const wxVariant &variant,
const wxDataViewItem &item,
unsigned int col) override;
bool SetValue( const wxVariant &variant,
const int item_idx,
unsigned int col);
void SetExtruder(const wxString& extruder, wxDataViewItem item);
// For parent move child from cur_volume_id place to new_volume_id
// Remaining items will moved up/down accordingly
wxDataViewItem ReorganizeChildren( const int cur_volume_id,
const int new_volume_id,
const wxDataViewItem &parent);
wxDataViewItem ReorganizeObjects( int current_id, int new_id);
virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override;
virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override;
// get object item
wxDataViewItem GetTopParent(const wxDataViewItem &item) const;
virtual bool IsContainer(const wxDataViewItem &item) const override;
virtual unsigned int GetChildren(const wxDataViewItem &parent,
wxDataViewItemArray &array) const override;
void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const;
// Is the container just a header or an item with all columns
// In our case it is an item with all columns
virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
ItemType GetItemType(const wxDataViewItem &item) const ;
wxDataViewItem GetItemByType( const wxDataViewItem &parent_item,
ItemType type) const;
wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const;
wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const;
wxDataViewItem GetLayerRootItem(const wxDataViewItem &item) const;
bool IsSettingsItem(const wxDataViewItem &item) const;
void UpdateSettingsDigest( const wxDataViewItem &item,
const std::vector<std::string>& categories);
bool IsPrintable(const wxDataViewItem &item) const;
void UpdateObjectPrintable(wxDataViewItem parent_item);
void UpdateInstancesPrintable(wxDataViewItem parent_item);
void SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; }
void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; }
void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type);
wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx,
int subobj_idx = -1,
ItemType subobj_type = itInstance);
wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item);
void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; }
// Rescale bitmaps for existing Items
void Rescale();
wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,
const bool is_marked = false);
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const;
bool UpdateColumValues(unsigned col);
void UpdateExtruderBitmap(wxDataViewItem item);
private:
wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type);
wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item);
};
}
}
#endif // slic3r_GUI_ObjectDataViewModel_hpp_

View file

@ -169,7 +169,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) :
info_manifold_text->SetFont(wxGetApp().small_font());
info_manifold = new wxStaticText(parent, wxID_ANY, "");
info_manifold->SetFont(wxGetApp().small_font());
manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(parent, "exclamation"));
manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap("exclamation"));
auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL);
sizer_manifold->Add(info_manifold_text, 0);
sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2);
@ -188,7 +188,7 @@ void ObjectInfo::show_sizer(bool show)
void ObjectInfo::msw_rescale()
{
manifold_warning_icon->SetBitmap(create_scaled_bitmap(nullptr, "exclamation"));
manifold_warning_icon->SetBitmap(create_scaled_bitmap("exclamation"));
}
enum SlicedInfoIdx
@ -258,7 +258,7 @@ void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const w
}
PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) :
wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), 0, nullptr, wxCB_READONLY),
PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)),
preset_type(preset_type),
last_selected(wxNOT_FOUND),
m_em_unit(wxGetApp().em_unit())
@ -1875,6 +1875,7 @@ struct Plater::priv
}
void export_gcode(fs::path output_path, PrintHostJob upload_job);
void reload_from_disk();
void reload_all_from_disk();
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
void set_current_panel(wxPanel* panel);
@ -2075,6 +2076,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); });
view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event<float>& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); });
view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); });
view3D_canvas->Bind(EVT_GLCANVAS_RELOAD_FROM_DISK, [this](SimpleEvent&) { if (!this->model.objects.empty()) this->reload_all_from_disk(); });
// 3DScene/Toolbar:
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
@ -3447,6 +3449,24 @@ void Plater::priv::reload_from_disk()
}
}
void Plater::priv::reload_all_from_disk()
{
Plater::TakeSnapshot snapshot(q, _(L("Reload all from disk")));
Plater::SuppressSnapshots suppress(q);
Selection& selection = get_selection();
Selection::IndicesList curr_idxs = selection.get_volume_idxs();
// reload from disk uses selection
select_all();
reload_from_disk();
// restore previous selection
selection.clear();
for (unsigned int idx : curr_idxs)
{
selection.add(idx, false);
}
}
void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/)
{
if (obj_idx < 0)
@ -5034,6 +5054,11 @@ void Plater::reload_from_disk()
p->reload_from_disk();
}
void Plater::reload_all_from_disk()
{
p->reload_all_from_disk();
}
bool Plater::has_toolpaths_to_export() const
{
return p->preview->get_canvas3d()->has_toolpaths_to_export();
@ -5376,6 +5401,13 @@ void Plater::force_filament_colors_update()
this->p->schedule_background_process();
}
void Plater::force_print_bed_update()
{
// Fill in the printer model key with something which cannot possibly be valid, so that Plater::on_config_change() will update the print bed
// once a new Printer profile config is loaded.
p->config->opt_string("printer_model", true) = "\x01\x00\x01";
}
void Plater::on_activate()
{
#ifdef __linux__
@ -5392,11 +5424,6 @@ void Plater::on_activate()
this->p->show_delayed_error_message();
}
const DynamicPrintConfig* Plater::get_plater_config() const
{
return p->config;
}
// Get vector of extruder colors considering filament color, if extruder color is undefined.
std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const
{

View file

@ -12,6 +12,7 @@
#include "3DScene.hpp"
#include "GLTexture.hpp"
#include "wxExtensions.hpp"
class wxButton;
class ScalableButton;
@ -49,7 +50,7 @@ using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
class Plater;
enum class ActionButtonType : int;
class PresetComboBox : public wxBitmapComboBox
class PresetComboBox : public PresetBitmapComboBox
{
public:
PresetComboBox(wxWindow *parent, Preset::Type preset_type);
@ -193,6 +194,7 @@ public:
void export_amf();
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
void reload_from_disk();
void reload_all_from_disk();
bool has_toolpaths_to_export() const;
void export_toolpaths_to_obj() const;
void hollow();
@ -227,9 +229,9 @@ public:
void on_extruders_change(size_t extruders_count);
void on_config_change(const DynamicPrintConfig &config);
void force_filament_colors_update();
void force_print_bed_update();
// On activating the parent window.
void on_activate();
const DynamicPrintConfig* get_plater_config() const;
std::vector<std::string> get_extruder_colors_from_plater_config() const;
std::vector<std::string> get_colors_for_color_print() const;

View file

@ -550,7 +550,6 @@ const std::vector<std::string>& Preset::sla_printer_options()
s_opts = {
"printer_technology",
"bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height",
"bed_shape", "max_print_height",
"display_width", "display_height", "display_pixels_x", "display_pixels_y",
"display_mirror_x", "display_mirror_y",
"display_orientation",
@ -874,18 +873,14 @@ bool PresetCollection::delete_preset(const std::string& name)
return true;
}
void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name)
void PresetCollection::load_bitmap_default(const std::string &file_name)
{
// XXX: See note in PresetBundle::load_compatible_bitmaps()
(void)window;
*m_bitmap_main_frame = create_scaled_bitmap(nullptr, file_name);
*m_bitmap_main_frame = create_scaled_bitmap(file_name);
}
void PresetCollection::load_bitmap_add(wxWindow *window, const std::string &file_name)
void PresetCollection::load_bitmap_add(const std::string &file_name)
{
// XXX: See note in PresetBundle::load_compatible_bitmaps()
(void)window;
*m_bitmap_add = create_scaled_bitmap(nullptr, file_name);
*m_bitmap_add = create_scaled_bitmap(file_name);
}
const Preset* PresetCollection::get_selected_preset_parent() const

View file

@ -313,10 +313,10 @@ public:
bool delete_preset(const std::string& name);
// Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame.
void load_bitmap_default(wxWindow *window, const std::string &file_name);
void load_bitmap_default(const std::string &file_name);
// Load "add new printer" bitmap to be placed at the wxBitmapComboBox of a MainFrame.
void load_bitmap_add(wxWindow *window, const std::string &file_name);
void load_bitmap_add(const std::string &file_name);
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; }

View file

@ -480,19 +480,12 @@ void PresetBundle::export_selections(AppConfig &config)
config.set("presets", "printer", printers.get_selected_preset_name());
}
void PresetBundle::load_compatible_bitmaps(wxWindow *window)
void PresetBundle::load_compatible_bitmaps()
{
// We don't actually pass the window pointer here and instead generate
// a low DPI bitmap, because the wxBitmapComboBox and wxDataViewCtrl don't support
// high DPI bitmaps very well, they compute their dimensions wrong.
// TODO: Update this when fixed in wxWidgets
// See also PresetCollection::load_bitmap_default() and PresetCollection::load_bitmap_add()
(void)window;
*m_bitmapCompatible = create_scaled_bitmap(nullptr, "flag_green");
*m_bitmapIncompatible = create_scaled_bitmap(nullptr, "flag_red");
*m_bitmapLock = create_scaled_bitmap(nullptr, "lock_closed");
*m_bitmapLockOpen = create_scaled_bitmap(nullptr, "lock_open");
*m_bitmapCompatible = create_scaled_bitmap("flag_green");
*m_bitmapIncompatible = create_scaled_bitmap("flag_red");
*m_bitmapLock = create_scaled_bitmap("lock_closed");
*m_bitmapLockOpen = create_scaled_bitmap("lock_open");
prints .set_bitmap_compatible(m_bitmapCompatible);
filaments .set_bitmap_compatible(m_bitmapCompatible);
@ -1536,31 +1529,7 @@ void PresetBundle::set_filament_preset(size_t idx, const std::string &name)
filament_presets[idx] = Preset::remove_suffix_modified(name);
}
static inline int hex_digit_to_int(const char c)
{
return
(c >= '0' && c <= '9') ? int(c - '0') :
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
}
bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out)
{
rgb_out[0] = rgb_out[1] = rgb_out[2] = 0;
if (scolor.size() != 7 || scolor.front() != '#')
return false;
const char *c = scolor.data() + 1;
for (size_t i = 0; i < 3; ++ i) {
int digit1 = hex_digit_to_int(*c ++);
int digit2 = hex_digit_to_int(*c ++);
if (digit1 == -1 || digit2 == -1)
return false;
rgb_out[i] = (unsigned char)(digit1 * 16 + digit2);
}
return true;
}
void PresetBundle::load_default_preset_bitmaps(wxWindow *window)
void PresetBundle::load_default_preset_bitmaps()
{
// Clear bitmap cache, before load new scaled default preset bitmaps
m_bitmapCache->clear();
@ -1570,13 +1539,13 @@ void PresetBundle::load_default_preset_bitmaps(wxWindow *window)
this->sla_materials.clear_bitmap_cache();
this->printers.clear_bitmap_cache();
this->prints.load_bitmap_default(window, "cog");
this->sla_prints.load_bitmap_default(window, "cog");
this->filaments.load_bitmap_default(window, "spool.png");
this->sla_materials.load_bitmap_default(window, "resin");
this->printers.load_bitmap_default(window, "printer");
this->printers.load_bitmap_add(window, "add.png");
this->load_compatible_bitmaps(window);
this->prints.load_bitmap_default("cog");
this->sla_prints.load_bitmap_default("cog");
this->filaments.load_bitmap_default("spool.png");
this->sla_materials.load_bitmap_default("resin");
this->printers.load_bitmap_default("printer");
this->printers.load_bitmap_add("add.png");
this->load_compatible_bitmaps();
}
void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui)
@ -1587,7 +1556,7 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre
unsigned char rgb[3];
std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder);
if (! parse_color(extruder_color, rgb))
if (!m_bitmapCache->parse_color(extruder_color, rgb))
// Extruder color is not defined.
extruder_color.clear();
@ -1623,7 +1592,12 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre
// To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so
// set a bitmap height to m_bitmapLock->GetHeight()
const int icon_height = m_bitmapLock->GetHeight();//2 * icon_unit; //16 * scale_f + 0.5f;
// Note, under OSX we should use a ScaledHeight because of Retina scale
#ifdef __APPLE__
const int icon_height = m_bitmapLock->GetScaledHeight();
#else
const int icon_height = m_bitmapLock->GetHeight();
#endif
wxString tooltip = "";
@ -1652,10 +1626,10 @@ void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::Pre
// Paint a red flag for incompatible presets.
bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(normal_icon_width, icon_height) : *m_bitmapIncompatible);
// Paint the color bars.
parse_color(filament_rgb, rgb);
m_bitmapCache->parse_color(filament_rgb, rgb);
bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? wide_icon_width : normal_icon_width, icon_height, rgb));
if (! single_bar) {
parse_color(extruder_rgb, rgb);
m_bitmapCache->parse_color(extruder_rgb, rgb);
bmps.emplace_back(m_bitmapCache->mksolid(thin_icon_width, icon_height, rgb));
}
// Paint a lock at the system presets.

View file

@ -54,8 +54,7 @@ public:
// There will be an entry for each system profile loaded,
// and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors.
// std::set<VendorProfile> vendors;
VendorMap vendors;
VendorMap vendors;
struct ObsoletePresets {
std::vector<std::string> prints;
@ -130,9 +129,7 @@ public:
// preset if the current print or filament preset is not compatible.
void update_compatible(bool select_other_if_incompatible);
static bool parse_color(const std::string &scolor, unsigned char *rgb_out);
void load_default_preset_bitmaps(wxWindow *window);
void load_default_preset_bitmaps();
// Set the is_visible flag for printer vendors, printer models and printer variants
// based on the user configuration.
@ -161,7 +158,7 @@ private:
// If it is not an external config, then the config will be stored into the user profile directory.
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
void load_compatible_bitmaps(wxWindow *window);
void load_compatible_bitmaps();
DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const;

View file

@ -114,7 +114,7 @@ void Tab::create_preset_tab()
#endif //__WXOSX__
// preset chooser
m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(35 * m_em_unit, -1), 0, 0, wxCB_READONLY);
m_presets_choice = new PresetBitmapComboBox(panel, wxSize(35 * m_em_unit, -1));
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
@ -1690,7 +1690,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup)
auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) {
auto btn = new wxButton(parent, wxID_ANY, " " + _(L("Browse"))+" " +dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
btn->SetBitmap(create_scaled_bitmap(this, "browse"));
btn->SetBitmap(create_scaled_bitmap("browse"));
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);

View file

@ -120,7 +120,7 @@ protected:
Preset::Type m_type;
std::string m_name;
const wxString m_title;
wxBitmapComboBox* m_presets_choice;
PresetBitmapComboBox* m_presets_choice;
ScalableButton* m_btn_save_preset;
ScalableButton* m_btn_delete_preset;
ScalableButton* m_btn_hide_incompatible_presets;

View file

@ -149,7 +149,7 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, w
MsgDialog(nullptr, wxString::Format(_(L("%s incompatibility")), SLIC3R_APP_NAME),
wxString::Format(_(L("%s configuration is incompatible")), SLIC3R_APP_NAME), wxID_NONE)
{
logo->SetBitmap(create_scaled_bitmap(this, "PrusaSlicer_192px_grayscale.png", 192));
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 192));
auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L(
"This version of %s is not compatible with currently installed configuration bundles.\n"

View file

@ -1,7 +1,7 @@
#include <algorithm>
#include <sstream>
#include "WipeTowerDialog.hpp"
#include "PresetBundle.hpp"
#include "BitmapCache.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
@ -191,7 +191,7 @@ WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, con
for (const std::string& color : extruder_colours) {
unsigned char rgb[3];
Slic3r::PresetBundle::parse_color(color, rgb);
Slic3r::GUI::BitmapCache::parse_color(color, rgb);
m_colours.push_back(wxColor(rgb[0], rgb[1], rgb[2]));
}

File diff suppressed because it is too large Load diff

View file

@ -4,23 +4,14 @@
#include <wx/checklst.h>
#include <wx/combo.h>
#include <wx/dataview.h>
#include <wx/dc.h>
#include <wx/wupdlock.h>
#include <wx/button.h>
#include <wx/sizer.h>
#include <wx/menu.h>
#include <wx/wx.h>
#include <wx/bmpcbox.h>
#include <vector>
#include <set>
#include <functional>
namespace Slic3r {
enum class ModelVolumeType : int;
};
typedef double coordf_t;
typedef std::pair<coordf_t, coordf_t> t_layer_height_range;
#ifdef __WXMSW__
void msw_rescale_menu(wxMenu* menu);
@ -48,15 +39,13 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string,
void enable_menu_item(wxUpdateUIEvent& evt, std::function<bool()> const cb_condition, wxMenuItem* item, wxWindow* win);
class wxDialog;
class wxBitmapComboBox;
void edit_tooltip(wxString& tooltip);
void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector<int>& btn_ids);
int em_unit(wxWindow* win);
float get_svg_scale_factor(wxWindow* win);
wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name,
const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false);
wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr,
const int px_cnt = 16, const bool grayscale = false);
std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon = false);
void apply_extruder_selector(wxBitmapComboBox** ctrl,
@ -102,6 +91,37 @@ public:
void OnListBoxSelection(wxCommandEvent& evt);
};
namespace Slic3r {
namespace GUI {
// *** PresetBitmapComboBox ***
// BitmapComboBox used to presets list on Sidebar and Tabs
class PresetBitmapComboBox: public wxBitmapComboBox
{
public:
PresetBitmapComboBox(wxWindow* parent, const wxSize& size = wxDefaultSize);
~PresetBitmapComboBox() {}
#ifdef __APPLE__
protected:
/* For PresetBitmapComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina
* (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean
* "please scale this to such and such" but rather
* "the wxImage is already sized for backing scale such and such". )
* Unfortunately, the constructor changes the size of wxBitmap too.
* Thus We need to use unscaled size value for bitmaps that we use
* to avoid scaled size of control items.
* For this purpose control drawing methods and
* control size calculation methods (virtual) are overridden.
**/
virtual bool OnAddBitmap(const wxBitmap& bitmap) override;
virtual void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override;
#endif
};
}
}
// *** wxDataViewTreeCtrlComboBox ***
@ -127,587 +147,6 @@ public:
};
// ----------------------------------------------------------------------------
// DataViewBitmapText: helper class used by PrusaBitmapTextRenderer
// ----------------------------------------------------------------------------
class DataViewBitmapText : public wxObject
{
public:
DataViewBitmapText( const wxString &text = wxEmptyString,
const wxBitmap& bmp = wxNullBitmap) :
m_text(text),
m_bmp(bmp)
{ }
DataViewBitmapText(const DataViewBitmapText &other)
: wxObject(),
m_text(other.m_text),
m_bmp(other.m_bmp)
{ }
void SetText(const wxString &text) { m_text = text; }
wxString GetText() const { return m_text; }
void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; }
const wxBitmap &GetBitmap() const { return m_bmp; }
bool IsSameAs(const DataViewBitmapText& other) const {
return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
}
bool operator==(const DataViewBitmapText& other) const {
return IsSameAs(other);
}
bool operator!=(const DataViewBitmapText& other) const {
return !IsSameAs(other);
}
private:
wxString m_text;
wxBitmap m_bmp;
wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText);
};
DECLARE_VARIANT_OBJECT(DataViewBitmapText)
// ----------------------------------------------------------------------------
// ObjectDataViewModelNode: a node inside ObjectDataViewModel
// ----------------------------------------------------------------------------
enum ItemType {
itUndef = 0,
itObject = 1,
itVolume = 2,
itInstanceRoot = 4,
itInstance = 8,
itSettings = 16,
itLayerRoot = 32,
itLayer = 64,
};
enum ColumnNumber
{
colName = 0, // item name
colPrint , // printable property
colExtruder , // extruder selection
colEditing , // item editing
};
enum PrintIndicator
{
piUndef = 0, // no print indicator
piPrintable , // printable
piUnprintable , // unprintable
};
class ObjectDataViewModelNode;
WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray);
class ObjectDataViewModelNode
{
ObjectDataViewModelNode* m_parent;
MyObjectTreeModelNodePtrArray m_children;
wxBitmap m_empty_bmp;
size_t m_volumes_cnt = 0;
std::vector< std::string > m_opt_categories;
t_layer_height_range m_layer_range = { 0.0f, 0.0f };
wxString m_name;
wxBitmap& m_bmp = m_empty_bmp;
ItemType m_type;
int m_idx = -1;
bool m_container = false;
wxString m_extruder = "default";
wxBitmap m_extruder_bmp;
wxBitmap m_action_icon;
PrintIndicator m_printable {piUndef};
wxBitmap m_printable_icon;
std::string m_action_icon_name = "";
Slic3r::ModelVolumeType m_volume_type;
public:
ObjectDataViewModelNode(const wxString &name,
const wxString& extruder):
m_parent(NULL),
m_name(name),
m_type(itObject),
m_extruder(extruder)
{
set_action_and_extruder_icons();
init_container();
}
ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
const wxString& sub_obj_name,
const wxBitmap& bmp,
const wxString& extruder,
const int idx = -1 ) :
m_parent (parent),
m_name (sub_obj_name),
m_type (itVolume),
m_idx (idx),
m_extruder (extruder)
{
m_bmp = bmp;
set_action_and_extruder_icons();
init_container();
}
ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
const t_layer_height_range& layer_range,
const int idx = -1,
const wxString& extruder = wxEmptyString );
ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type);
~ObjectDataViewModelNode()
{
// free all our children nodes
size_t count = m_children.GetCount();
for (size_t i = 0; i < count; i++)
{
ObjectDataViewModelNode *child = m_children[i];
delete child;
}
#ifndef NDEBUG
// Indicate that the object was deleted.
m_idx = -2;
#endif /* NDEBUG */
}
void init_container();
bool IsContainer() const
{
return m_container;
}
ObjectDataViewModelNode* GetParent()
{
assert(m_parent == nullptr || m_parent->valid());
return m_parent;
}
MyObjectTreeModelNodePtrArray& GetChildren()
{
return m_children;
}
ObjectDataViewModelNode* GetNthChild(unsigned int n)
{
return m_children.Item(n);
}
void Insert(ObjectDataViewModelNode* child, unsigned int n)
{
if (!m_container)
m_container = true;
m_children.Insert(child, n);
}
void Append(ObjectDataViewModelNode* child)
{
if (!m_container)
m_container = true;
m_children.Add(child);
}
void RemoveAllChildren()
{
if (GetChildCount() == 0)
return;
for (int id = int(GetChildCount()) - 1; id >= 0; --id)
{
if (m_children.Item(id)->GetChildCount() > 0)
m_children[id]->RemoveAllChildren();
auto node = m_children[id];
m_children.RemoveAt(id);
delete node;
}
}
size_t GetChildCount() const
{
return m_children.GetCount();
}
bool SetValue(const wxVariant &variant, unsigned int col);
void SetBitmap(const wxBitmap &icon) { m_bmp = icon; }
const wxBitmap& GetBitmap() const { return m_bmp; }
const wxString& GetName() const { return m_name; }
ItemType GetType() const { return m_type; }
void SetIdx(const int& idx);
int GetIdx() const { return m_idx; }
t_layer_height_range GetLayerRange() const { return m_layer_range; }
PrintIndicator IsPrintable() const { return m_printable; }
// use this function only for childrens
void AssignAllVal(ObjectDataViewModelNode& from_node)
{
// ! Don't overwrite other values because of equality of this values for all children --
m_name = from_node.m_name;
m_bmp = from_node.m_bmp;
m_idx = from_node.m_idx;
m_extruder = from_node.m_extruder;
m_type = from_node.m_type;
}
bool SwapChildrens(int frst_id, int scnd_id) {
if (GetChildCount() < 2 ||
frst_id < 0 || (size_t)frst_id >= GetChildCount() ||
scnd_id < 0 || (size_t)scnd_id >= GetChildCount())
return false;
ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id);
ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id);
new_scnd.m_idx = m_children.Item(scnd_id)->m_idx;
new_frst.m_idx = m_children.Item(frst_id)->m_idx;
m_children.Item(frst_id)->AssignAllVal(new_frst);
m_children.Item(scnd_id)->AssignAllVal(new_scnd);
return true;
}
// Set action icons for node
void set_action_and_extruder_icons();
// Set printable icon for node
void set_printable_icon(PrintIndicator printable);
void update_settings_digest_bitmaps();
bool update_settings_digest(const std::vector<std::string>& categories);
int volume_type() const { return int(m_volume_type); }
void msw_rescale();
#ifndef NDEBUG
bool valid();
#endif /* NDEBUG */
bool invalid() const { return m_idx < -1; }
private:
friend class ObjectDataViewModel;
};
// ----------------------------------------------------------------------------
// ObjectDataViewModel
// ----------------------------------------------------------------------------
// custom message the model sends to associated control to notify a last volume deleted from the object:
wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent);
class ObjectDataViewModel :public wxDataViewModel
{
std::vector<ObjectDataViewModelNode*> m_objects;
std::vector<wxBitmap*> m_volume_bmps;
wxBitmap* m_warning_bmp { nullptr };
wxDataViewCtrl* m_ctrl { nullptr };
public:
ObjectDataViewModel();
~ObjectDataViewModel();
wxDataViewItem Add( const wxString &name,
const int extruder,
const bool has_errors = false);
wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item,
const wxString &name,
const Slic3r::ModelVolumeType volume_type,
const bool has_errors = false,
const int extruder = 0,
const bool create_frst_child = true);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num);
wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector<bool>& print_indicator);
wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item);
wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item,
const t_layer_height_range& layer_range,
const int extruder = 0,
const int index = -1);
wxDataViewItem Delete(const wxDataViewItem &item);
wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num);
void DeleteAll();
void DeleteChildren(wxDataViewItem& parent);
void DeleteVolumeChildren(wxDataViewItem& parent);
void DeleteSettings(const wxDataViewItem& parent);
wxDataViewItem GetItemById(int obj_idx);
wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type);
wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx);
wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx);
wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range);
int GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range);
int GetIdByItem(const wxDataViewItem& item) const;
int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const;
int GetObjectIdByItem(const wxDataViewItem& item) const;
int GetVolumeIdByItem(const wxDataViewItem& item) const;
int GetInstanceIdByItem(const wxDataViewItem& item) const;
int GetLayerIdByItem(const wxDataViewItem& item) const;
void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx);
int GetRowByItem(const wxDataViewItem& item) const;
bool IsEmpty() { return m_objects.empty(); }
bool InvalidItem(const wxDataViewItem& item);
// helper method for wxLog
wxString GetName(const wxDataViewItem &item) const;
wxBitmap& GetBitmap(const wxDataViewItem &item) const;
wxString GetExtruder(const wxDataViewItem &item) const;
int GetExtruderNumber(const wxDataViewItem &item) const;
// helper methods to change the model
virtual unsigned int GetColumnCount() const override { return 3;}
virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); }
virtual void GetValue( wxVariant &variant,
const wxDataViewItem &item,
unsigned int col) const override;
virtual bool SetValue( const wxVariant &variant,
const wxDataViewItem &item,
unsigned int col) override;
bool SetValue( const wxVariant &variant,
const int item_idx,
unsigned int col);
void SetExtruder(const wxString& extruder, wxDataViewItem item);
// For parent move child from cur_volume_id place to new_volume_id
// Remaining items will moved up/down accordingly
wxDataViewItem ReorganizeChildren( const int cur_volume_id,
const int new_volume_id,
const wxDataViewItem &parent);
virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override;
virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override;
// get object item
wxDataViewItem GetTopParent(const wxDataViewItem &item) const;
virtual bool IsContainer(const wxDataViewItem &item) const override;
virtual unsigned int GetChildren(const wxDataViewItem &parent,
wxDataViewItemArray &array) const override;
void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const;
// Is the container just a header or an item with all columns
// In our case it is an item with all columns
virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
ItemType GetItemType(const wxDataViewItem &item) const ;
wxDataViewItem GetItemByType( const wxDataViewItem &parent_item,
ItemType type) const;
wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const;
wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const;
wxDataViewItem GetLayerRootItem(const wxDataViewItem &item) const;
bool IsSettingsItem(const wxDataViewItem &item) const;
void UpdateSettingsDigest( const wxDataViewItem &item,
const std::vector<std::string>& categories);
bool IsPrintable(const wxDataViewItem &item) const;
void UpdateObjectPrintable(wxDataViewItem parent_item);
void UpdateInstancesPrintable(wxDataViewItem parent_item);
void SetVolumeBitmaps(const std::vector<wxBitmap*>& volume_bmps) { m_volume_bmps = volume_bmps; }
void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; }
void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type);
wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx,
int subobj_idx = -1,
ItemType subobj_type = itInstance);
wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item);
void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; }
// Rescale bitmaps for existing Items
void Rescale();
wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,
const bool is_marked = false);
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const;
bool UpdateColumValues(unsigned col);
void UpdateExtruderBitmap(wxDataViewItem item);
private:
wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type);
wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item);
};
// ----------------------------------------------------------------------------
// BitmapTextRenderer
// ----------------------------------------------------------------------------
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
class BitmapTextRenderer : public wxDataViewRenderer
#else
class BitmapTextRenderer : public wxDataViewCustomRenderer
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
{
public:
BitmapTextRenderer(wxDataViewCellMode mode =
#ifdef __WXOSX__
wxDATAVIEW_CELL_INERT
#else
wxDATAVIEW_CELL_EDITABLE
#endif
,int align = wxDVR_DEFAULT_ALIGNMENT
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
);
#else
) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {}
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
bool SetValue(const wxVariant &value);
bool GetValue(wxVariant &value) const;
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY
virtual wxString GetAccessibleDescription() const override;
#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
virtual bool Render(wxRect cell, wxDC *dc, int state);
virtual wxSize GetSize() const;
bool HasEditorCtrl() const override
{
#ifdef __WXOSX__
return false;
#else
return true;
#endif
}
wxWindow* CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) override;
bool GetValueFromEditorCtrl( wxWindow* ctrl,
wxVariant& value) override;
bool WasCanceled() const { return m_was_unusable_symbol; }
private:
DataViewBitmapText m_value;
bool m_was_unusable_symbol {false};
};
// ----------------------------------------------------------------------------
// BitmapChoiceRenderer
// ----------------------------------------------------------------------------
class BitmapChoiceRenderer : public wxDataViewCustomRenderer
{
public:
BitmapChoiceRenderer(wxDataViewCellMode mode =
#ifdef __WXOSX__
wxDATAVIEW_CELL_INERT
#else
wxDATAVIEW_CELL_EDITABLE
#endif
,int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {}
bool SetValue(const wxVariant& value);
bool GetValue(wxVariant& value) const;
virtual bool Render(wxRect cell, wxDC* dc, int state);
virtual wxSize GetSize() const;
bool HasEditorCtrl() const override { return true; }
wxWindow* CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) override;
bool GetValueFromEditorCtrl( wxWindow* ctrl,
wxVariant& value) override;
private:
DataViewBitmapText m_value;
};
// ----------------------------------------------------------------------------
// MyCustomRenderer
// ----------------------------------------------------------------------------
class MyCustomRenderer : public wxDataViewCustomRenderer
{
public:
// This renderer can be either activatable or editable, for demonstration
// purposes. In real programs, you should select whether the user should be
// able to activate or edit the cell and it doesn't make sense to switch
// between the two -- but this is just an example, so it doesn't stop us.
explicit MyCustomRenderer(wxDataViewCellMode mode)
: wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER)
{ }
virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/
{
dc->SetBrush(*wxLIGHT_GREY_BRUSH);
dc->SetPen(*wxTRANSPARENT_PEN);
rect.Deflate(2);
dc->DrawRoundedRectangle(rect, 5);
RenderText(m_value,
0, // no offset
wxRect(dc->GetTextExtent(m_value)).CentreIn(rect),
dc,
state);
return true;
}
virtual bool ActivateCell(const wxRect& WXUNUSED(cell),
wxDataViewModel *WXUNUSED(model),
const wxDataViewItem &WXUNUSED(item),
unsigned int WXUNUSED(col),
const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/
{
wxString position;
if (mouseEvent)
position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y);
else
position = "from keyboard";
// wxLogMessage("MyCustomRenderer ActivateCell() %s", position);
return false;
}
virtual wxSize GetSize() const override/*wxOVERRIDE*/
{
return wxSize(60, 20);
}
virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/
{
m_value = value.GetString();
return true;
}
virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; }
virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; }
virtual wxWindow*
CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) override/*wxOVERRIDE*/
{
wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value,
labelRect.GetPosition(),
labelRect.GetSize(),
wxTE_PROCESS_ENTER);
text->SetInsertionPointEnd();
return text;
}
virtual bool
GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/
{
wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl);
if (!text)
return false;
value = text->GetValue();
return true;
}
private:
wxString m_value;
};
// ----------------------------------------------------------------------------
// ScalableBitmap
// ----------------------------------------------------------------------------
@ -718,11 +157,14 @@ public:
ScalableBitmap() {};
ScalableBitmap( wxWindow *parent,
const std::string& icon_name = "",
const int px_cnt = 16,
const bool is_horizontal = false);
const int px_cnt = 16);
~ScalableBitmap() {}
wxSize GetBmpSize() const;
int GetBmpWidth() const;
int GetBmpHeight() const;
void msw_rescale();
const wxBitmap& bmp() const { return m_bmp; }
@ -730,14 +172,12 @@ public:
const std::string& name() const{ return m_icon_name; }
int px_cnt()const {return m_px_cnt;}
bool is_horizontal()const {return m_is_horizontal;}
private:
wxWindow* m_parent{ nullptr };
wxBitmap m_bmp = wxBitmap();
std::string m_icon_name = "";
int m_px_cnt {16};
bool m_is_horizontal {false};
};
@ -821,7 +261,6 @@ private:
// bitmap dimensions
int m_px_cnt{ 16 };
bool m_is_horizontal{ false };
};

View file

@ -26,58 +26,6 @@ const char *const SUPPORT_TEST_MODELS[] = {
} // namespace
// Test pair hash for 'nums' random number pairs.
template <class I, class II> void test_pairhash()
{
const constexpr size_t nums = 1000;
I A[nums] = {0}, B[nums] = {0};
std::unordered_set<I> CH;
std::unordered_map<II, std::pair<I, I>> ints;
std::random_device rd;
std::mt19937 gen(rd());
const I Ibits = int(sizeof(I) * CHAR_BIT);
const II IIbits = int(sizeof(II) * CHAR_BIT);
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
if (std::is_signed<I>::value) bits -= 1;
const I Imin = 0;
const I Imax = I(std::pow(2., bits) - 1);
std::uniform_int_distribution<I> dis(Imin, Imax);
for (size_t i = 0; i < nums;) {
I a = dis(gen);
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
}
for (size_t i = 0; i < nums;) {
I b = dis(gen);
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
}
for (size_t i = 0; i < nums; ++i) {
I a = A[i], b = B[i];
REQUIRE(a != b);
II hash_ab = sla::pairhash<I, II>(a, b);
II hash_ba = sla::pairhash<I, II>(b, a);
REQUIRE(hash_ab == hash_ba);
auto it = ints.find(hash_ab);
if (it != ints.end()) {
REQUIRE((
(it->second.first == a && it->second.second == b) ||
(it->second.first == b && it->second.second == a)
));
} else
ints[hash_ab] = std::make_pair(a, b);
}
}
TEST_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") {
test_pairhash<int, int>();
test_pairhash<int, long>();
@ -225,69 +173,6 @@ TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") {
REQUIRE(raster.pixel_dimensions().h_mm == Approx(pixdim.h_mm));
}
using TPixel = uint8_t;
static constexpr const TPixel FullWhite = 255;
static constexpr const TPixel FullBlack = 0;
template <class A, int N> constexpr int arraysize(const A (&)[N]) { return N; }
static void check_raster_transformations(sla::Raster::Orientation o,
sla::Raster::TMirroring mirroring)
{
double disp_w = 120., disp_h = 68.;
sla::Raster::Resolution res{2560, 1440};
sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
sla::Raster::Trafo trafo{o, mirroring};
trafo.origin_x = bb.center().x();
trafo.origin_y = bb.center().y();
sla::Raster raster{res, pixdim, trafo};
// create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors)
coord_t pw = 32 * coord_t(std::ceil(scaled<double>(pixdim.w_mm)));
coord_t ph = 32 * coord_t(std::ceil(scaled<double>(pixdim.h_mm)));
ExPolygon box;
box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}};
double tr_x = scaled<double>(20.), tr_y = tr_x;
box.translate(tr_x, tr_y);
ExPolygon expected_box = box;
// Now calculate the position of the translated box according to output
// trafo.
if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.);
if (mirroring[X])
for (auto &p : expected_box.contour.points) p.x() = -p.x();
if (mirroring[Y])
for (auto &p : expected_box.contour.points) p.y() = -p.y();
raster.draw(box);
Point expected_coords = expected_box.contour.bounding_box().center();
double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm;
double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm;
auto w = size_t(std::floor(rx));
auto h = res.height_px - size_t(std::floor(ry));
REQUIRE((w < res.width_px && h < res.height_px));
auto px = raster.read_pixel(w, h);
if (px != FullWhite) {
sla::PNGImage img;
std::fstream outf("out.png", std::ios::out);
outf << img.serialize(raster);
}
REQUIRE(px == FullWhite);
}
TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") {
sla::Raster::TMirroring mirrorings[] = {sla::Raster::NoMirror,
sla::Raster::MirrorX,
@ -301,54 +186,6 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") {
check_raster_transformations(orientation, mirror);
}
static ExPolygon square_with_hole(double v)
{
ExPolygon poly;
coord_t V = scaled(v / 2.);
poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}};
poly.holes.emplace_back();
V = V / 2;
poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}};
return poly;
}
static double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim)
{
return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack);
}
static double raster_white_area(const sla::Raster &raster)
{
if (raster.empty()) return std::nan("");
auto res = raster.resolution();
double a = 0;
for (size_t x = 0; x < res.width_px; ++x)
for (size_t y = 0; y < res.height_px; ++y) {
auto px = raster.read_pixel(x, y);
a += pixel_area(px, raster.pixel_dimensions());
}
return a;
}
static double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd)
{
auto lines = p.lines();
double pix_err = pixel_area(FullWhite, pd) / 2.;
// Worst case is when a line is parallel to the shorter axis of one pixel,
// when the line will be composed of the max number of pixels
double pix_l = std::min(pd.h_mm, pd.w_mm);
double error = 0.;
for (auto &l : lines)
error += (unscaled(l.length()) / pix_l) * pix_err;
return error;
}
TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") {
double disp_w = 120., disp_h = 68.;
@ -388,8 +225,4 @@ TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]")
std::fstream infile{"extruder_idler_quads.obj", std::ios::in};
cntr.from_obj(infile);
}
}

View file

@ -292,3 +292,103 @@ void check_validity(const TriangleMesh &input_mesh, int flags)
REQUIRE(mesh.is_manifold());
}
}
void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirroring mirroring)
{
double disp_w = 120., disp_h = 68.;
sla::Raster::Resolution res{2560, 1440};
sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px};
auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)});
sla::Raster::Trafo trafo{o, mirroring};
trafo.origin_x = bb.center().x();
trafo.origin_y = bb.center().y();
sla::Raster raster{res, pixdim, trafo};
// create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors)
coord_t pw = 32 * coord_t(std::ceil(scaled<double>(pixdim.w_mm)));
coord_t ph = 32 * coord_t(std::ceil(scaled<double>(pixdim.h_mm)));
ExPolygon box;
box.contour.points = {{-pw, -ph}, {pw, -ph}, {pw, ph}, {-pw, ph}};
double tr_x = scaled<double>(20.), tr_y = tr_x;
box.translate(tr_x, tr_y);
ExPolygon expected_box = box;
// Now calculate the position of the translated box according to output
// trafo.
if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.);
if (mirroring[X])
for (auto &p : expected_box.contour.points) p.x() = -p.x();
if (mirroring[Y])
for (auto &p : expected_box.contour.points) p.y() = -p.y();
raster.draw(box);
Point expected_coords = expected_box.contour.bounding_box().center();
double rx = unscaled(expected_coords.x() + bb.center().x()) / pixdim.w_mm;
double ry = unscaled(expected_coords.y() + bb.center().y()) / pixdim.h_mm;
auto w = size_t(std::floor(rx));
auto h = res.height_px - size_t(std::floor(ry));
REQUIRE((w < res.width_px && h < res.height_px));
auto px = raster.read_pixel(w, h);
if (px != FullWhite) {
sla::PNGImage img;
std::fstream outf("out.png", std::ios::out);
outf << img.serialize(raster);
}
REQUIRE(px == FullWhite);
}
ExPolygon square_with_hole(double v)
{
ExPolygon poly;
coord_t V = scaled(v / 2.);
poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}};
poly.holes.emplace_back();
V = V / 2;
poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}};
return poly;
}
double raster_white_area(const sla::Raster &raster)
{
if (raster.empty()) return std::nan("");
auto res = raster.resolution();
double a = 0;
for (size_t x = 0; x < res.width_px; ++x)
for (size_t y = 0; y < res.height_px; ++y) {
auto px = raster.read_pixel(x, y);
a += pixel_area(px, raster.pixel_dimensions());
}
return a;
}
double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd)
{
auto lines = p.lines();
double pix_err = pixel_area(FullWhite, pd) / 2.;
// Worst case is when a line is parallel to the shorter axis of one pixel,
// when the line will be composed of the max number of pixels
double pix_l = std::min(pd.h_mm, pd.w_mm);
double error = 0.;
for (auto &l : lines)
error += (unscaled(l.length()) / pix_l) * pix_err;
return error;
}

View file

@ -6,6 +6,7 @@
// Debug
#include <fstream>
#include <unordered_set>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Format/OBJ.hpp"
@ -109,4 +110,78 @@ inline void test_support_model_collision(
test_support_model_collision(obj_filename, input_supportcfg, hcfg, {});
}
// Test pair hash for 'nums' random number pairs.
template <class I, class II> void test_pairhash()
{
const constexpr size_t nums = 1000;
I A[nums] = {0}, B[nums] = {0};
std::unordered_set<I> CH;
std::unordered_map<II, std::pair<I, I>> ints;
std::random_device rd;
std::mt19937 gen(rd());
const I Ibits = int(sizeof(I) * CHAR_BIT);
const II IIbits = int(sizeof(II) * CHAR_BIT);
int bits = IIbits / 2 < Ibits ? Ibits / 2 : Ibits;
if (std::is_signed<I>::value) bits -= 1;
const I Imin = 0;
const I Imax = I(std::pow(2., bits) - 1);
std::uniform_int_distribution<I> dis(Imin, Imax);
for (size_t i = 0; i < nums;) {
I a = dis(gen);
if (CH.find(a) == CH.end()) { CH.insert(a); A[i] = a; ++i; }
}
for (size_t i = 0; i < nums;) {
I b = dis(gen);
if (CH.find(b) == CH.end()) { CH.insert(b); B[i] = b; ++i; }
}
for (size_t i = 0; i < nums; ++i) {
I a = A[i], b = B[i];
REQUIRE(a != b);
II hash_ab = sla::pairhash<I, II>(a, b);
II hash_ba = sla::pairhash<I, II>(b, a);
REQUIRE(hash_ab == hash_ba);
auto it = ints.find(hash_ab);
if (it != ints.end()) {
REQUIRE((
(it->second.first == a && it->second.second == b) ||
(it->second.first == b && it->second.second == a)
));
} else
ints[hash_ab] = std::make_pair(a, b);
}
}
// SLA Raster test utils:
using TPixel = uint8_t;
static constexpr const TPixel FullWhite = 255;
static constexpr const TPixel FullBlack = 0;
template <class A, int N> constexpr int arraysize(const A (&)[N]) { return N; }
void check_raster_transformations(sla::Raster::Orientation o,
sla::Raster::TMirroring mirroring);
ExPolygon square_with_hole(double v);
inline double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim)
{
return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack);
}
double raster_white_area(const sla::Raster &raster);
double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd);
#endif // SLA_TEST_UTILS_HPP